diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 100% rename from .eslintrc.js rename to .eslintrc.cjs diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 0000000..5c27949 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,10 @@ +{ + "require": [ + "ts-node/esm" + ], + "watch-extensions": ["ts"], + "recursive": true, + "reporter": "spec", + "timeout": 15000, + "node-option": ["loader=ts-node/esm", "experimental-specifier-resolution=node"] +} diff --git a/examples/favorites.ts b/examples/favorites.ts index c26ec79..23455cd 100644 --- a/examples/favorites.ts +++ b/examples/favorites.ts @@ -1,6 +1,6 @@ -import {ux} from '@oclif/core' +import {Errors, ux} from '@oclif/core' -import {Command} from '../src' +import {Command} from '../src/index.js' type Favorite = { id: string; @@ -27,4 +27,4 @@ class FavoritesCommand extends Command { } (FavoritesCommand.run([]) as any) - .catch(require('@oclif/core').Errors.handle) + .catch(Errors.handle) diff --git a/examples/login.ts b/examples/login.ts index 897f6f7..6dd7c42 100644 --- a/examples/login.ts +++ b/examples/login.ts @@ -1,4 +1,6 @@ -import {Command, flags} from '../src' +import {Errors} from '@oclif/core' + +import {Command, flags} from '../src/index.js' class LoginCommand extends Command { static flags = { @@ -14,4 +16,4 @@ class LoginCommand extends Command { } (LoginCommand.run(process.argv.slice(2)) as any) - .catch(require('@oclif/core').Errors.handle) + .catch(Errors.handle) diff --git a/examples/logout.ts b/examples/logout.ts index 4e7aa03..efe31a0 100644 --- a/examples/logout.ts +++ b/examples/logout.ts @@ -1,4 +1,6 @@ -import {Command} from '../src' +import {Errors} from '@oclif/core' + +import {Command} from '../src/index.js' class LogoutCommand extends Command { async run() { @@ -8,4 +10,4 @@ class LogoutCommand extends Command { } (LogoutCommand.run([]) as any) - .catch(require('@oclif/core').Errors.handle) + .catch(Errors.handle) diff --git a/examples/run.sh b/examples/run.sh index 26a0708..4c0ede1 100755 --- a/examples/run.sh +++ b/examples/run.sh @@ -13,4 +13,4 @@ if [ ! -f "examples/$COMMAND.ts" ]; then exit 1 fi -./node_modules/.bin/ts-node "examples/$COMMAND.ts" "$@" +NODE_NO_WARNINGS=1 NODE_OPTIONS="--loader ts-node/esm" ./node_modules/.bin/ts-node --esm "examples/$COMMAND.ts" "$@" diff --git a/examples/status.ts b/examples/status.ts index 3a29b5f..c758e79 100644 --- a/examples/status.ts +++ b/examples/status.ts @@ -1,7 +1,7 @@ import {HTTP} from '@heroku/http-call' -import {ux} from '@oclif/core' +import {Errors, ux} from '@oclif/core' -import {Command} from '../src' +import {Command} from '../src/index.js' class StatusCommand extends Command { async run() { @@ -26,4 +26,4 @@ class StatusCommand extends Command { } (StatusCommand.run([]) as any) - .catch(require('@oclif/core').Errors.handle) + .catch(Errors.handle) diff --git a/examples/whoami.ts b/examples/whoami.ts index f3f465a..809afa5 100644 --- a/examples/whoami.ts +++ b/examples/whoami.ts @@ -1,7 +1,7 @@ import * as Heroku from '@heroku-cli/schema' -import {ux} from '@oclif/core' +import {Errors, ux} from '@oclif/core' -import {Command} from '../src' +import {Command} from '../src/index.js' class StatusCommand extends Command { notloggedin() { @@ -22,5 +22,5 @@ class StatusCommand extends Command { } (StatusCommand.run([]) as any) - .catch(require('@oclif/core').Errors.handle) + .catch(Errors.handle) diff --git a/package-lock.json b/package-lock.json index 72f349f..639a25c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,13 +22,13 @@ "devDependencies": { "@heroku-cli/schema": "^1.0.25", "@types/chai": "^5.2.2", + "@types/debug": "^4.1.12", "@types/inquirer": "^8.2.11", "@types/mocha": "^10.0.6", "@types/node": "22.15.21", "@types/proxyquire": "^1.3.31", "@types/sinon": "^17.0.3", "@types/supports-color": "^5.3.0", - "@types/uuid": "^8.3.0", "@types/yargs-parser": "^21.0.3", "@types/yargs-unparser": "^2.0.3", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -39,16 +39,15 @@ "eslint-config-oclif": "^5.2.2", "eslint-config-oclif-typescript": "^3.1.14", "eslint-plugin-import": "^2.31.0", - "fancy-test": "^2.0.42", - "mocha": "^10.7.3", + "fancy-test": "^3.0.16", + "mocha": "^10.8.2", "nock": "^14.0.1", "np": "^10.2.0", "nyc": "^17.1.0", "proxyquire": "^2.1.3", "sinon": "^20.0.0", "stdout-stderr": "^0.1.13", - "ts-node": "^10.9.1", - "tslint": "^6.1.3", + "ts-node": "^10.9.2", "typescript": "^5.7.3" }, "engines": { @@ -1412,6 +1411,13 @@ "node": ">=4" } }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -1469,6 +1475,16 @@ "@types/deep-eql": "*" } }, + "node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/ms": "*" + } + }, "node_modules/@types/deep-eql": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", @@ -1532,6 +1548,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/node": { "version": "22.15.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.21.tgz", @@ -1597,13 +1620,6 @@ "@types/node": "*" } }, - "node_modules/@types/uuid": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", - "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/yargs-parser": { "version": "21.0.3", "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz", @@ -2939,16 +2955,6 @@ "ieee754": "^1.1.13" } }, - "node_modules/builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/builtins": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/builtins/-/builtins-5.1.0.tgz", @@ -3419,13 +3425,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "license": "MIT" - }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -5489,9 +5488,10 @@ } }, "node_modules/fancy-test": { - "version": "2.0.42", - "resolved": "https://registry.npmjs.org/fancy-test/-/fancy-test-2.0.42.tgz", - "integrity": "sha512-TX8YTALYAmExny+f+G24MFxWry3Pk09+9uykwRjfwjibRxJ9ZjJzrnHYVBZK46XQdyli7d+rQc5U/KK7V6uLsw==", + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/fancy-test/-/fancy-test-3.0.16.tgz", + "integrity": "sha512-y1xZFpyYbE2TMiT+agOW2Emv8gr73zvDrKKbcXc8L+gMyIVJFn71cc4ICfzu2zEXjHirpHpdDJN0JBX99wwDXQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", "dev": true, "license": "MIT", "dependencies": { @@ -5501,39 +5501,33 @@ "@types/sinon": "*", "lodash": "^4.17.13", "mock-stdin": "^1.0.0", - "nock": "^13.3.3", + "nock": "^13.5.4", + "sinon": "^16.1.3", "stdout-stderr": "^0.1.9" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fancy-test/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" + "node": ">=18.0.0" } }, - "node_modules/fancy-test/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/fancy-test/node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "ms": "^2.1.1" + "@sinonjs/commons": "^3.0.0" } }, - "node_modules/fancy-test/node_modules/debug/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/fancy-test/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, - "license": "MIT" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, "node_modules/fancy-test/node_modules/ms": { "version": "2.1.2", @@ -5575,31 +5569,23 @@ } } }, - "node_modules/fancy-test/node_modules/stdout-stderr": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/stdout-stderr/-/stdout-stderr-0.1.9.tgz", - "integrity": "sha1-m0juBO/5Ve4Hd24nEl1VJNnQL1c=", + "node_modules/fancy-test/node_modules/sinon": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-16.1.3.tgz", + "integrity": "sha512-mjnWWeyxcAf9nC0bXcPmiDut+oE8HYridTNzBbF98AYVLmWwGRp2ISEpyhYflG1ifILT+eNn3BmKUJPxjXUPlA==", "dev": true, - "license": "MIT", - "dependencies": { - "debug": "^3.1.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/fancy-test/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", "dependencies": { - "ansi-regex": "^3.0.0" + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^10.3.0", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.4", + "supports-color": "^7.2.0" }, - "engines": { - "node": ">=4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, "node_modules/fast-deep-equal": { @@ -7940,6 +7926,13 @@ "json5": "lib/cli.js" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==", + "dev": true, + "license": "MIT" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8680,23 +8673,10 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/mocha": { - "version": "10.7.3", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", - "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "license": "MIT", "dependencies": { @@ -9006,6 +8986,30 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "license": "MIT" }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/@sinonjs/fake-timers": { + "version": "11.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.3.1.tgz", + "integrity": "sha512-EVJO7nW5M/F5Tur0Rf2z/QoMo+1Ia963RiMtapiQrEWvY0iBUvADo8Beegwjpnle5BHkyHuoxSTW3jF43H1XRA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^3.0.1" + } + }, "node_modules/nock": { "version": "14.0.4", "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.4.tgz", @@ -10059,6 +10063,13 @@ "dev": true, "license": "MIT" }, + "node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", + "dev": true, + "license": "MIT" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -11672,142 +11683,6 @@ "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", "license": "Apache-2.0" }, - "node_modules/tslint": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", - "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^4.0.1", - "glob": "^7.1.1", - "js-yaml": "^3.13.1", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.3", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.13.0", - "tsutils": "^2.29.0" - }, - "bin": { - "tslint": "bin/tslint" - }, - "engines": { - "node": ">=4.8.0" - }, - "peerDependencies": { - "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" - } - }, - "node_modules/tslint/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/tslint/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true, - "license": "MIT" - }, - "node_modules/tslint/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/tslint/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tslint/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true, - "license": "0BSD" - }, - "node_modules/tslint/node_modules/tsutils": { - "version": "2.29.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", - "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tslib": "^1.8.1" - }, - "peerDependencies": { - "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" - } - }, - "node_modules/tslint/node_modules/tsutils/node_modules/tslib": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", - "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index febb0c4..8c59033 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,13 @@ "devDependencies": { "@heroku-cli/schema": "^1.0.25", "@types/chai": "^5.2.2", + "@types/debug": "^4.1.12", "@types/inquirer": "^8.2.11", "@types/mocha": "^10.0.6", "@types/node": "22.15.21", "@types/proxyquire": "^1.3.31", "@types/sinon": "^17.0.3", "@types/supports-color": "^5.3.0", - "@types/uuid": "^8.3.0", "@types/yargs-parser": "^21.0.3", "@types/yargs-unparser": "^2.0.3", "@typescript-eslint/eslint-plugin": "^7.0.0", @@ -35,16 +35,15 @@ "eslint-config-oclif": "^5.2.2", "eslint-config-oclif-typescript": "^3.1.14", "eslint-plugin-import": "^2.31.0", - "fancy-test": "^2.0.42", - "mocha": "^10.7.3", + "fancy-test": "^3.0.16", + "mocha": "^10.8.2", "nock": "^14.0.1", "np": "^10.2.0", "nyc": "^17.1.0", "proxyquire": "^2.1.3", "sinon": "^20.0.0", "stdout-stderr": "^0.1.13", - "ts-node": "^10.9.1", - "tslint": "^6.1.3", + "ts-node": "^10.9.2", "typescript": "^5.7.3" }, "engines": { @@ -60,25 +59,16 @@ "license": "ISC", "main": "lib/index.js", "repository": "heroku/heroku-cli-command", - "mocha": { - "require": [ - "test/helpers/init.js", - "ts-node/register", - "source-map-support/register" - ], - "watch-extensions": "ts", - "recursive": true, - "reporter": "spec", - "timeout": 360000 - }, "scripts": { "build": "rm -rf lib && tsc", "lint": "tsc -p test --noEmit && eslint . --ext .ts", "posttest": "npm run lint", "prepublishOnly": "npm run build", + "prepare": "npm run build", "test": "nyc mocha --forbid-only \"test/**/*.test.ts\"", "changelog": "conventional-changelog -p conventionalcommits -i CHANGELOG.md -s", "example": "sh examples/run.sh" }, + "type": "module", "types": "./lib/index.d.ts" } diff --git a/src/api-client.ts b/src/api-client.ts index 7cdc65b..c0587a8 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,17 +1,18 @@ import {HTTP, HTTPError, HTTPRequestOptions} from '@heroku/http-call' import {Errors, Interfaces} from '@oclif/core' +import debug from 'debug' import inquirer from 'inquirer' -import Netrc from 'netrc-parser' +import {Netrc} from 'netrc-parser' import * as url from 'node:url' -import deps from './deps' -import {Login} from './login' -import {Mutex} from './mutex' -import {IDelinquencyConfig, IDelinquencyInfo, ParticleboardClient} from './particleboard-client' -import {RequestId, requestIdHeader} from './request-id' -import {vars} from './vars' +import {Login} from './login.js' +import {Mutex} from './mutex.js' +import {IDelinquencyConfig, IDelinquencyInfo, ParticleboardClient} from './particleboard-client.js' +import {RequestId, requestIdHeader} from './request-id.js' +import {vars} from './vars.js' +import {yubikey} from './yubikey.js' -const debug = require('debug') +const netrc = new Netrc() // eslint-disable-next-line @typescript-eslint/no-namespace export namespace APIClient { @@ -59,13 +60,13 @@ export class APIClient { http: typeof HTTP preauthPromises: { [k: string]: Promise> } private _auth?: string - private readonly _login = new Login(this.config, this) + private readonly _login: Login private _particleboard!: ParticleboardClient private _twoFactorMutex: Mutex | undefined constructor(protected config: Interfaces.Config, public options: IOptions = {}) { this.config = config - + this._login = new Login(this.config, this) if (options.required === undefined) options.required = true options.preauth = options.preauth !== false if (options.debug) debug.enable('http') @@ -86,7 +87,7 @@ export class APIClient { protocol: apiUrl.protocol, } const delinquencyConfig: IDelinquencyConfig = {fetch_delinquency: false, warning_shown: false} - this.http = class APIHTTPClient extends deps.HTTP.HTTP.create(opts) { + this.http = class APIHTTPClient extends HTTP.create(opts) { static configDelinquency(url: string, opts: APIClient.Options): void { if (opts.method?.toUpperCase() !== 'GET' || (opts.hostname && opts.hostname !== apiUrl.hostname)) { delinquencyConfig.fetch_delinquency = false @@ -193,7 +194,7 @@ export class APIClient { this.showWarnings(response) return response } catch (error) { - if (!(error instanceof deps.HTTP.HTTPError)) throw error + if (!(error instanceof HTTPError)) throw error if (retries > 0) { if (opts.retryAuth !== false && error.http.statusCode === 401 && error.body.id === 'unauthorized') { if (process.env.HEROKU_API_KEY) { @@ -258,11 +259,11 @@ export class APIClient { get auth(): string | undefined { if (!this._auth) { - if (process.env.HEROKU_API_TOKEN && !process.env.HEROKU_API_KEY) deps.cli.warn('HEROKU_API_TOKEN is set but you probably meant HEROKU_API_KEY') + if (process.env.HEROKU_API_TOKEN && !process.env.HEROKU_API_KEY) Errors.warn('HEROKU_API_TOKEN is set but you probably meant HEROKU_API_KEY') this._auth = process.env.HEROKU_API_KEY if (!this._auth) { - deps.netrc.loadSync() - this._auth = deps.netrc.machines[vars.apiHost] && deps.netrc.machines[vars.apiHost].password + netrc.loadSync() + this._auth = netrc.machines[vars.apiHost] && netrc.machines[vars.apiHost].password } } @@ -280,13 +281,13 @@ export class APIClient { get particleboard(): ParticleboardClient { if (this._particleboard) return this._particleboard - this._particleboard = new deps.ParticleboardClient(this.config) + this._particleboard = new ParticleboardClient(this.config) return this._particleboard } get twoFactorMutex(): Mutex { if (!this._twoFactorMutex) { - this._twoFactorMutex = new deps.Mutex() + this._twoFactorMutex = new Mutex() } return this._twoFactorMutex @@ -311,9 +312,9 @@ export class APIClient { if (error instanceof Errors.CLIError) Errors.warn(error) } - delete Netrc.machines['api.heroku.com'] - delete Netrc.machines['git.heroku.com'] - await Netrc.save() + delete netrc.machines['api.heroku.com'] + delete netrc.machines['git.heroku.com'] + await netrc.save() } patch(url: string, options: APIClient.Options = {}) { @@ -343,7 +344,7 @@ export class APIClient { } twoFactorPrompt() { - deps.yubikey.enable() + yubikey.enable() return this.twoFactorMutex.synchronize(async () => { try { const {factor} = await inquirer.prompt([{ @@ -352,10 +353,10 @@ export class APIClient { name: 'factor', type: 'password', }]) - deps.yubikey.disable() + yubikey.disable() return factor } catch (error) { - deps.yubikey.disable() + yubikey.disable() throw error } }) diff --git a/src/command.ts b/src/command.ts index b1c2f80..8fed86f 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,22 +1,12 @@ import {Command as Base} from '@oclif/core' -import { - ArgOutput, - FlagOutput, - Input, - ParserOutput, -} from '@oclif/core/lib/interfaces/parser' -import {NonExistentFlagsError} from '@oclif/core/lib/parser/errors' +import {Errors} from '@oclif/core' import parser from 'yargs-parser' import unparser from 'yargs-unparser' -const pjson = require('../package.json') - -import {APIClient, IOptions} from './api-client' -import deps from './deps' +import {APIClient, IOptions} from './api-client.js' export abstract class Command extends Base { allowArbitraryFlags: boolean = false - base = `${pjson.name}@${pjson.version}` _heroku!: APIClient get heroku(): APIClient { @@ -25,16 +15,16 @@ export abstract class Command extends Base { debug: process.env.HEROKU_DEBUG === '1' || process.env.HEROKU_DEBUG?.toUpperCase() === 'TRUE', debugHeaders: process.env.HEROKU_DEBUG_HEADERS === '1' || process.env.HEROKU_DEBUG_HEADERS?.toUpperCase() === 'TRUE', } - this._heroku = new deps.APIClient(this.config, options) + this._heroku = new APIClient(this.config, options) return this._heroku } - protected async parse(options?: Input, argv?: string[]): Promise> { + protected async parse(options?: any, argv?: string[]): Promise { if (this.allowArbitraryFlags) { try { return await super.parse(options, argv) } catch (error) { - const {flags: nonExistentFlags} = error as NonExistentFlagsError + const {flags: nonExistentFlags} = error as {flags: string[]} & Errors.CLIError const parsed = parser(this.argv) const nonExistentFlagsWithValues = {...parsed} diff --git a/src/completions.ts b/src/completions.ts index f053c80..b847845 100644 --- a/src/completions.ts +++ b/src/completions.ts @@ -1,13 +1,14 @@ import {Errors, Interfaces} from '@oclif/core' import * as path from 'node:path' -import deps from './deps' -import {configRemote, getGitRemotes} from './git' +import {APIClient} from './api-client.js' +import {readFile, readdir} from './file.js' +import {configRemote, getGitRemotes} from './git.js' export const oneDay = 60 * 60 * 24 export const herokuGet = async (resource: string, ctx: {config: Interfaces.Config}): Promise => { - const heroku = new deps.APIClient(ctx.config) + const heroku = new APIClient(ctx.config) let {body: resources} = await heroku.get(`/${resource}`) if (typeof resources === 'string') resources = JSON.parse(resources) return resources.map((a: any) => a.name).sort() @@ -71,7 +72,7 @@ export const DynoSizeCompletion = { export const FileCompletion = { async options() { - const files = await deps.file.readdir(process.cwd()) + const files = await readdir(process.cwd()) return files }, @@ -91,7 +92,7 @@ export const ProcessTypeCompletion = { let types: string[] = [] const procfile = path.join(process.cwd(), 'Procfile') try { - const buff = await deps.file.readFile(procfile) + const buff = await readFile(procfile) types = buff .toString() .split('\n') diff --git a/src/deps.ts b/src/deps.ts deleted file mode 100644 index 300e935..0000000 --- a/src/deps.ts +++ /dev/null @@ -1,62 +0,0 @@ -// remote -import apiClient = require('./api-client') -import file = require('./file') -import flags = require('./flags') -import git = require('./git') -import mutex = require('./mutex') -import particleboardClient = require('./particleboard-client') -import yubikey = require('./yubikey') -import HTTP = require('@heroku/http-call') -import oclif = require('@oclif/core') -import netrc = require('netrc-parser') - -const {ux} = oclif - -export const deps = { - get APIClient(): typeof apiClient.APIClient { - return fetch('./api-client').APIClient - }, - get Git(): typeof git.Git { - return fetch('./git').Git - }, - get HTTP(): typeof HTTP { - return fetch('@heroku/http-call') - }, - - // local - get Mutex(): typeof mutex.Mutex { - return fetch('./mutex').Mutex - }, - get ParticleboardClient(): typeof particleboardClient.ParticleboardClient { - return fetch('./particleboard-client').ParticleboardClient - }, - // remote - get cli(): typeof ux { - return fetch('@oclif/core').ux - }, - get file(): typeof file { - return fetch('./file') - }, - get flags(): typeof flags { - return fetch('./flags') - }, - get netrc(): typeof netrc.default { - return fetch('netrc-parser').default - }, - get yubikey(): typeof yubikey.yubikey { - return fetch('./yubikey').yubikey - }, -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const cache: Record = {} - -function fetch(s: string) { - if (!cache[s]) { - cache[s] = require(s) - } - - return cache[s] -} - -export default deps diff --git a/src/file.ts b/src/file.ts index 356c80a..951c345 100644 --- a/src/file.ts +++ b/src/file.ts @@ -1,9 +1,10 @@ +import debugModule from 'debug' import * as fs from 'fs' import {promisify} from 'util' let _debug: any function debug(...args: any[]) { - if (_debug) _debug = require('debug')('@heroku-cli/command:file') + if (!_debug) _debug = debugModule('@heroku-cli/command:file') _debug(...args) } diff --git a/src/flags/app.ts b/src/flags/app.ts index 013a490..13f5f1d 100644 --- a/src/flags/app.ts +++ b/src/flags/app.ts @@ -1,6 +1,6 @@ import {Errors, Flags} from '@oclif/core' -import {IGitRemotes, configRemote, getGitRemotes} from '../git' +import {IGitRemotes, configRemote, getGitRemotes} from '../git.js' class MultipleRemotesError extends Errors.CLIError { constructor(gitRemotes: IGitRemotes[]) { diff --git a/src/flags/index.ts b/src/flags/index.ts index fb99554..d7294f5 100644 --- a/src/flags/index.ts +++ b/src/flags/index.ts @@ -1,9 +1,9 @@ import {Flags} from '@oclif/core' -export {app, remote} from './app' -export {org} from './org' -export {pipeline} from './pipeline' -export {team} from './team' +export {app, remote} from './app.js' +export {org} from './org.js' +export {pipeline} from './pipeline.js' +export {team} from './team.js' // Explicitly export oclif flag types using object destructuring, sorted alphabetically export const {boolean, custom, directory, file, integer, option, string, url} = Flags diff --git a/src/git.ts b/src/git.ts index ee7154f..3a6d47d 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,6 +1,7 @@ import {Errors} from '@oclif/core' +import childProcess from 'node:child_process' -import {vars} from './vars' +import {vars} from './vars.js' export interface IGitRemote { name: string @@ -19,9 +20,8 @@ export class Git { } exec(cmd: string): string { - const {execSync: exec} = require('child_process') try { - return exec(`git ${cmd}`, { + return childProcess.execSync(`git ${cmd}`, { encoding: 'utf8', stdio: [null, 'pipe', null], }) diff --git a/src/index.ts b/src/index.ts index 0dd05a0..bd91523 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,9 @@ - -export {APIClient} from './api-client' -export {Command, Command as default} from './command' -export * as completions from './completions' -export * as flags from './flags' -export {vars} from './vars' +export * from './api-client.js' +export {Command, Command as default} from './command.js' +export * from './completions.js' +export * as flags from './flags/index.js' +export * from './git.js' +export * from './mutex.js' +export * from './particleboard-client.js' +export * from './vars.js' +export * from './yubikey.js' diff --git a/src/login.ts b/src/login.ts index 8611073..1b20f74 100644 --- a/src/login.ts +++ b/src/login.ts @@ -1,19 +1,21 @@ -import HTTP from '@heroku/http-call' -import color from '@heroku-cli/color' +import {HTTP} from '@heroku/http-call' +import {color} from '@heroku-cli/color' import * as Heroku from '@heroku-cli/schema' import {Interfaces, ux} from '@oclif/core' +import debug from 'debug' import inquirer, {QuestionCollection} from 'inquirer' -import Netrc from 'netrc-parser' +import {Netrc} from 'netrc-parser' import * as os from 'node:os' import * as readline from 'node:readline' import open from 'open' -import {APIClient, HerokuAPIError} from './api-client' -import {vars} from './vars' +import {APIClient, HerokuAPIError} from './api-client.js' +import {vars} from './vars.js' -const debug = require('debug')('heroku-cli-command') +const cliDebug = debug('heroku-cli-command') const hostname = os.hostname() const thirtyDays = 60 * 60 * 24 * 30 +const netrc = new Netrc() // eslint-disable-next-line @typescript-eslint/no-namespace export namespace Login { @@ -47,8 +49,8 @@ export class Login { if (process.env.HEROKU_API_KEY) ux.error('Cannot log in with HEROKU_API_KEY set') if (opts.expiresIn && opts.expiresIn > thirtyDays) ux.error('Cannot set an expiration longer than thirty days') - await Netrc.load() - const previousEntry = Netrc.machines['api.heroku.com'] + await netrc.load() + const previousEntry = netrc.machines['api.heroku.com'] let input: string | undefined = opts.method if (!input) { if (opts.expiresIn) { @@ -124,7 +126,7 @@ export class Login { } async logout(token = this.heroku.auth) { - if (!token) return debug('no credentials to logout') + if (!token) return cliDebug('no credentials to logout') const requests: Promise[] = [] // for SSO logins we delete the session since those do not show up in // authorizations because they are created a trusted client @@ -182,7 +184,7 @@ export class Login { urlDisplayed = true } - // ux.warn(`If browser does not open, visit ${color.greenBright(url)}`) + ux.warn(`If browser does not open, visit ${color.greenBright(url)}`) const cp = await open(url, {wait: false, ...(browser ? {app: {name: browser}} : {})}) cp.on('error', err => { ux.warn(err) @@ -301,22 +303,22 @@ export class Login { private async saveToken(entry: NetrcEntry) { const hosts = [vars.apiHost, vars.httpGitHost] hosts.forEach(host => { - if (!Netrc.machines[host]) Netrc.machines[host] = {} - Netrc.machines[host].login = entry.login - Netrc.machines[host].password = entry.password - delete Netrc.machines[host].method - delete Netrc.machines[host].org + if (!netrc.machines[host]) netrc.machines[host] = {} + netrc.machines[host].login = entry.login + netrc.machines[host].password = entry.password + delete netrc.machines[host].method + delete netrc.machines[host].org }) - if (Netrc.machines._tokens) { - (Netrc.machines._tokens as any).forEach((token: any) => { + if (netrc.machines._tokens) { + (netrc.machines._tokens as any).forEach((token: any) => { if (hosts.includes(token.host)) { token.internalWhitespace = '\n ' } }) } - await Netrc.save() + await netrc.save() } private async sso(): Promise { @@ -335,7 +337,7 @@ export class Login { } // TODO: handle browser - debug(`opening browser to ${url}`) + cliDebug(`opening browser to ${url}`) ux.stderr(`Opening browser to:\n${url}\n`) ux.stderr(color.gray( `If the browser fails to open or you're authenticating on a remote diff --git a/src/particleboard-client.ts b/src/particleboard-client.ts index f85681b..f53448e 100644 --- a/src/particleboard-client.ts +++ b/src/particleboard-client.ts @@ -2,9 +2,8 @@ import {HTTP, HTTPRequestOptions} from '@heroku/http-call' import {Interfaces} from '@oclif/core' import * as url from 'node:url' -import deps from './deps' -import {RequestId, requestIdHeader} from './request-id' -import {vars} from './vars' +import {RequestId, requestIdHeader} from './request-id.js' +import {vars} from './vars.js' export interface IDelinquencyInfo { scheduled_deletion_time?: null | string @@ -37,7 +36,7 @@ export class ParticleboardClient { port: particleboardUrl.port, protocol: particleboardUrl.protocol, } - this.http = class ParticleboardHTTPClient extends deps.HTTP.HTTP.create(particleboardOpts) { + this.http = class ParticleboardHTTPClient extends HTTP.create(particleboardOpts) { static async request(url: string, opts: HTTPRequestOptions = {}): Promise> { opts.headers = opts.headers || {} opts.headers[requestIdHeader] = RequestId.create() && RequestId.headerValue diff --git a/src/yubikey.ts b/src/yubikey.ts index 36da296..9f71231 100644 --- a/src/yubikey.ts +++ b/src/yubikey.ts @@ -1,8 +1,9 @@ +import {execSync} from 'node:child_process' + function toggle(onoff: string) { - const cp = require('child_process') if (yubikey.platform !== 'darwin') return try { - cp.execSync( + execSync( `osascript -e 'if application "yubiswitch" is running then tell application "yubiswitch" to ${onoff}'`, {stdio: 'inherit'}, ) diff --git a/test/api-client.test.ts b/test/api-client.test.ts index f530440..b83f9d1 100644 --- a/test/api-client.test.ts +++ b/test/api-client.test.ts @@ -1,39 +1,43 @@ import {Config} from '@oclif/core' -import base, {expect} from 'fancy-test' +import debug from 'debug' +import {expect, fancy} from 'fancy-test' import nock from 'nock' -import {resolve} from 'path' +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' import * as sinon from 'sinon' import {stderr} from 'stdout-stderr' -import {Command as CommandBase} from '../src/command' -import {RequestId, requestIdHeader} from '../src/request-id' +import {Command as CommandBase} from '../src/command.js' +import {RequestId, requestIdHeader} from '../src/request-id.js' +import {restoreNetrcStub, stubNetrc} from './helpers/netrc-stub.js' class Command extends CommandBase { async run() {} } -const netrc = require('netrc-parser').default -netrc.loadSync = function (this: typeof netrc) { - netrc.machines = { - 'api.heroku.com': {password: 'mypass'}, - } -} - const {env} = process -const debug = require('debug') let api: nock.Scope -const test = base.add('config', new Config({root: resolve(__dirname, '../package.json')})) +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) +const test = fancy + .add('config', () => { + const config = new Config({root: resolve(__dirname, '../package.json')}) + return config + }) +// const test = base.add('config', new Config({root: resolve(__dirname, '../package.json')})) describe('api_client', () => { beforeEach(function () { process.env = {} - debug.disable('*') + debug.disable() api = nock('https://api.heroku.com') + stubNetrc() }) afterEach(function () { process.env = env api.done() + restoreNetrcStub() }) test @@ -61,13 +65,23 @@ describe('api_client', () => { }) describe('with HEROKU_HEADERS', () => { + let headersApi: nock.Scope + + beforeEach(() => { + headersApi = nock('https://api.heroku.com') + }) + + afterEach(() => { + headersApi.done() + }) + test .it('makes an HTTP request with HEROKU_HEADERS', async ctx => { process.env.HEROKU_HEADERS = '{"x-foo": "bar"}' - api = nock('https://api.heroku.com', { + headersApi = nock('https://api.heroku.com', { reqheaders: {'x-foo': 'bar'}, }) - api.get('/apps').reply(200, [{name: 'myapp'}]) + headersApi.get('/apps').reply(200, [{name: 'myapp'}]) const cmd = new Command([], ctx.config) const {body} = await cmd.heroku.get('/apps') @@ -433,6 +447,8 @@ describe('api_client', () => { .reply(200, [{name: 'myapp'}]) const cmd = new Command([], ctx.config) + // Mock the twoFactorPrompt method + sinon.stub(cmd.heroku, 'twoFactorPrompt').resolves('123456') const {body} = await cmd.heroku.get('/apps') expect(body).to.deep.equal([{name: 'myapp'}]) @@ -451,6 +467,8 @@ describe('api_client', () => { scope.get('/apps/myapp/dynos').reply(200, {web: 1}) const cmd = new Command([], ctx.config) + // Mock the twoFactorPrompt method + sinon.stub(cmd.heroku, 'twoFactorPrompt').resolves('123456') const info = cmd.heroku.get('/apps/myapp') const anotherapp = cmd.heroku.get('/apps/anotherapp') const _config = cmd.heroku.get('/apps/myapp/config') diff --git a/test/command.test.ts b/test/command.test.ts index 07d0ce3..80daa4a 100644 --- a/test/command.test.ts +++ b/test/command.test.ts @@ -1,12 +1,19 @@ import {Config} from '@oclif/core' -import base, {expect} from 'fancy-test' -import {resolve} from 'path' +import {expect, fancy} from 'fancy-test' +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' -import {Command} from '../src/command' -import * as flags from '../src/flags' +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) -const test = base - .add('config', new Config({root: resolve(__dirname, '../package.json')})) +import {Command} from '../src/command.js' +import * as flags from '../src/flags/index.js' + +const test = fancy + .add('config', () => { + const config = new Config({root: resolve(__dirname, '../package.json')}) + return config + }) class MyCommand extends Command { async run() {} @@ -25,8 +32,9 @@ describe('command', () => { }.run(['--app=myapp'])) test - .it('has heroku clients', async ctx => { + .it('has heroku clients', async (ctx: any) => { const cmd = new MyCommand([], ctx.config) + cmd.config = ctx.config expect(cmd.heroku).to.be.ok }) }) diff --git a/test/flags/app.test.ts b/test/flags/app.test.ts index 7be5220..43ebd51 100644 --- a/test/flags/app.test.ts +++ b/test/flags/app.test.ts @@ -1,9 +1,9 @@ import {expect, fancy} from 'fancy-test' import nock from 'nock' -import {Command as Base} from '../../src' -import * as flags from '../../src/flags' -import {Git} from '../../src/git' +import {Command as Base} from '../../src/command.js' +import * as flags from '../../src/flags/index.js' +import {Git} from '../../src/git.js' let api: nock.Scope const origRemotes = Object.getOwnPropertyDescriptor(Git.prototype, 'remotes') diff --git a/test/flags/org.test.ts b/test/flags/org.test.ts index b09be00..1d1980c 100644 --- a/test/flags/org.test.ts +++ b/test/flags/org.test.ts @@ -1,7 +1,7 @@ import {Command, ux} from '@oclif/core' import {expect, fancy} from 'fancy-test' -import * as flags from '../../src/flags' +import * as flags from '../../src/flags/index.js' describe('required', () => { class OrgCommand extends Command { diff --git a/test/flags/pipeline.test.ts b/test/flags/pipeline.test.ts index 558c91f..74af0d2 100644 --- a/test/flags/pipeline.test.ts +++ b/test/flags/pipeline.test.ts @@ -1,7 +1,7 @@ import {Command} from '@oclif/core' import {expect, fancy} from 'fancy-test' -import * as flags from '../../src/flags' +import * as flags from '../../src/flags/index.js' describe('required', () => { class PipelineCommand extends Command { diff --git a/test/flags/team.test.ts b/test/flags/team.test.ts index 0054923..585569d 100644 --- a/test/flags/team.test.ts +++ b/test/flags/team.test.ts @@ -1,7 +1,7 @@ import {Command, ux} from '@oclif/core' import {expect, fancy} from 'fancy-test' -import * as flags from '../../src/flags' +import * as flags from '../../src/flags/index.js' describe('required', () => { class TeamCommand extends Command { diff --git a/test/git.test.ts b/test/git.test.ts index a7c4397..c39d856 100644 --- a/test/git.test.ts +++ b/test/git.test.ts @@ -1,7 +1,8 @@ -import childProcess from 'child_process' -import {expect, fancy} from 'fancy-test' +import {expect} from 'chai' +import childProcess from 'node:child_process' +import sinon from 'sinon' -import {Git} from '../src/git' +import {Git} from '../src/git.js' describe('git', () => { it('gets the remotes', () => { @@ -17,16 +18,16 @@ heroku\thttps://git.heroku.com/myapp.git (pull) ]) }) - fancy - .stub(childProcess, 'execSync', () => { + it('rethrows other git error', () => { + const stub = sinon.stub(childProcess, 'execSync').callsFake(() => { const err: any = new Error('some other message') - err.code = 'ENOTNOENT' + err.code = 'SOME_OTHER_CODE' throw err }) - .it('rethrows other git error', () => { - const git = new Git() - expect(() => { - git.exec('version') - }).to.throw('some other message') - }) + const git = new Git() + expect(() => { + git.exec('version') + }).to.throw('some other message') + stub.restore() + }) }) diff --git a/test/helpers/netrc-stub.ts b/test/helpers/netrc-stub.ts new file mode 100644 index 0000000..a82697f --- /dev/null +++ b/test/helpers/netrc-stub.ts @@ -0,0 +1,44 @@ +import {Netrc} from 'netrc-parser' +import * as sinon from 'sinon' + +const mockNetrc = { + machines: { + 'api.heroku.com': { + login: 'test@example.com', + password: 'mypass', + }, + }, + saveSync: sinon.stub(), +} + +let loadSyncStub: sinon.SinonStub | undefined +let loadStub: sinon.SinonStub | undefined + +export function stubNetrc() { + // Only stub if not already stubbed + if (typeof (Netrc.prototype.loadSync as any).restore !== 'function') { + loadSyncStub = sinon.stub(Netrc.prototype, 'loadSync').callsFake(function (this: any) { + Object.assign(this, mockNetrc) + return this + }) + } + + if (typeof (Netrc.prototype.load as any).restore !== 'function') { + loadStub = sinon.stub(Netrc.prototype, 'load').callsFake(function (this: any) { + Object.assign(this, mockNetrc) + return Promise.resolve(this) + }) + } +} + +export function restoreNetrcStub() { + if (loadSyncStub) { + loadSyncStub.restore() + loadSyncStub = undefined + } + + if (loadStub) { + loadStub.restore() + loadStub = undefined + } +} diff --git a/test/index.test.ts b/test/index.test.ts index 5cb06a5..b5ba0fc 100644 --- a/test/index.test.ts +++ b/test/index.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai' -import {Command, flags} from '../src' +import {Command, flags} from '../src/index.js' describe('index', () => { it('has flags', async () => { diff --git a/test/login.test.ts b/test/login.test.ts index 763e45a..34f82bd 100644 --- a/test/login.test.ts +++ b/test/login.test.ts @@ -1,12 +1,13 @@ import {Config} from '@oclif/core' -import base, {expect} from 'fancy-test' +import {expect, fancy} from 'fancy-test' import inquirer from 'inquirer' -import Netrc from 'netrc-parser' import nock from 'nock' -import {resolve} from 'path' +import {dirname, resolve} from 'node:path' +import {fileURLToPath} from 'node:url' import * as sinon from 'sinon' -import {Command as CommandBase} from '../src/command' +import {Command as CommandBase} from '../src/command.js' +import {restoreNetrcStub, stubNetrc} from './helpers/netrc-stub.js' class Command extends CommandBase { async run() {} @@ -19,9 +20,7 @@ beforeEach(() => { api.get('/oauth/authorizations').reply(200, []) api.get('/oauth/authorizations/~').reply(200, {}) - // Mock netrc-parser - sinon.stub(Netrc, 'load').resolves() - sinon.stub(Netrc, 'save').resolves() + stubNetrc() // Mock inquirer prompts sinon.stub(inquirer, 'prompt').callsFake(async (questions: any) => { @@ -45,10 +44,17 @@ beforeEach(() => { afterEach(() => { sinon.restore() + restoreNetrcStub() }) -const test = base - .add('config', new Config({root: resolve(__dirname, '../package.json')})) +const __filename = fileURLToPath(import.meta.url) +const __dirname = dirname(__filename) + +const test = fancy + .add('config', () => { + const config = new Config({root: resolve(__dirname, '../package.json')}) + return config + }) describe('login with interactive', () => { test diff --git a/test/mutex.test.ts b/test/mutex.test.ts index bc16cad..996e907 100644 --- a/test/mutex.test.ts +++ b/test/mutex.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai' -import {Mutex} from '../src/mutex' +import {Mutex} from '../src/mutex.js' let output: string[] diff --git a/test/request-id.test.ts b/test/request-id.test.ts index 37a13e1..6d056b1 100644 --- a/test/request-id.test.ts +++ b/test/request-id.test.ts @@ -1,7 +1,7 @@ import {expect} from 'chai' import * as sinon from 'sinon' -import {RequestId} from '../src/request-id' +import {RequestId} from '../src/request-id.js' describe('getRequestId', () => { let generateStub: any diff --git a/test/tsconfig.json b/test/tsconfig.json index 15a4c58..0b5854e 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -1,7 +1,10 @@ { - "extends": "../tsconfig", + "extends": "../tsconfig.json", "compilerOptions": { - "sourceMap": true + "module": "NodeNext", + "moduleResolution": "NodeNext", + "sourceMap": true, + "types": ["mocha", "node"] }, "include": [ "./**/*", diff --git a/test/vars.test.ts b/test/vars.test.ts index 9199a57..548ef20 100644 --- a/test/vars.test.ts +++ b/test/vars.test.ts @@ -1,6 +1,6 @@ import {expect} from 'chai' -import {vars} from '../src/vars' +import {vars} from '../src/vars.js' const {env} = process beforeEach(() => { @@ -10,12 +10,6 @@ afterEach(() => { process.env = env }) -// jest.mock('netrc-parser', () => { -// return class { -// machines = {'api.heroku.com': {password: 'mypass'}} -// } -// }) - describe('vars', () => { it('sets vars by default', () => { expect(vars.host).to.equal('heroku.com') diff --git a/tsconfig.json b/tsconfig.json index f5e77f4..f972b9a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,8 @@ "forceConsistentCasingInFileNames": true, "esModuleInterop": true, "importHelpers": true, - "module": "commonjs", + "module": "NodeNext", + "moduleResolution": "NodeNext", "outDir": "./lib", "pretty": true, "rootDirs": [ @@ -12,7 +13,7 @@ ], "skipLibCheck": true, "strict": true, - "target": "es2017" + "target": "es2022" }, "include": [ "./src/**/*"