diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f4e27f2b9..4721344d3 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -128,6 +128,7 @@ jobs: - https://github.com/salesforcecli/plugin-org - https://github.com/salesforcecli/plugin-schema - https://github.com/salesforcecli/plugin-user + - https://github.com/salesforcecli/plugin-settings with: packageName: '@oclif/core' externalProjectGitUrl: ${{ matrix.externalProjectGitUrl }} @@ -174,7 +175,7 @@ jobs: with: repo: oclif/plugin-plugins os: ${{ matrix.os }} - command: 'yarn test:integration --retries 3' + command: yarn test:integration --retries 3 # plugin-plugins integration tests depend on sf being installed globally other-setup: npm install -g @salesforce/cli@nightly plugin-update-integration: @@ -187,4 +188,4 @@ jobs: with: repo: oclif/plugin-update os: ${{ matrix.os }} - command: 'yarn test:integration:sf' + command: yarn test:integration:sf --retries 3 diff --git a/CHANGELOG.md b/CHANGELOG.md index fd96154b9..21390f8be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,21 @@ +## [3.19.6](https://github.com/oclif/core/compare/3.19.5...3.19.6) (2024-02-23) + + +### Bug Fixes + +* revert to original prompt implementation ([a96e567](https://github.com/oclif/core/commit/a96e5679c366a300d3149cecd2ef8fd3fb11a954)) + + + +## [3.19.5](https://github.com/oclif/core/compare/3.19.4...3.19.5) (2024-02-22) + + +### Bug Fixes + +* only set timeout for TTY ([8a97f20](https://github.com/oclif/core/commit/8a97f2087d07290474d9bf224057322ae2abe4bc)) + + + ## [3.19.4](https://github.com/oclif/core/compare/3.19.3...3.19.4) (2024-02-21) diff --git a/package.json b/package.json index 48602474e..09123f3b6 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@oclif/core", "description": "base library for oclif CLIs", - "version": "3.19.4", + "version": "3.19.6", "author": "Salesforce", "bugs": "https://github.com/oclif/core/issues", "dependencies": { @@ -64,10 +64,10 @@ "commitlint": "^17.8.1", "cross-env": "^7.0.3", "eslint": "^8.56.0", - "eslint-config-oclif": "^5.0.0", - "eslint-config-oclif-typescript": "^3.0.47", + "eslint-config-oclif": "^5.0.2", + "eslint-config-oclif-typescript": "^3.0.48", "eslint-config-prettier": "^9.1.0", - "fancy-test": "^3.0.1", + "fancy-test": "^3.0.11", "globby": "^11.1.0", "husky": "^8", "lint-staged": "^14.0.1", @@ -78,7 +78,7 @@ "shx": "^0.3.4", "sinon": "^16.1.3", "ts-node": "^10.9.2", - "tsd": "^0.30.4", + "tsd": "^0.30.6", "typescript": "^5" }, "engines": { diff --git a/src/cli-ux/prompt.ts b/src/cli-ux/prompt.ts index 0befdb805..84f25dc21 100644 --- a/src/cli-ux/prompt.ts +++ b/src/cli-ux/prompt.ts @@ -1,5 +1,4 @@ import chalk from 'chalk' -import readline from 'node:readline' import * as Errors from '../errors' import {config} from './config' @@ -27,36 +26,26 @@ interface IPromptConfig { function normal(options: IPromptConfig, retries = 100): Promise { if (retries < 0) throw new Error('no input') - const ac = new AbortController() - const {signal} = ac - return new Promise((resolve, reject) => { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }) - let timeout: NodeJS.Timeout + let timer: NodeJS.Timeout if (options.timeout) { - timeout = setTimeout(() => ac.abort(), options.timeout) - signal.addEventListener( - 'abort', - () => { - rl.close() - clearTimeout(timeout) - reject(new Error('Prompt timeout')) - }, - {once: true}, - ) + timer = setTimeout(() => { + process.stdin.pause() + reject(new Error('Prompt timeout')) + }, options.timeout) + timer.unref() } - rl.question(options.prompt, {signal}, (answer) => { - rl.close() - const data = answer.trim() + process.stdin.setEncoding('utf8') + process.stderr.write(options.prompt) + process.stdin.resume() + process.stdin.once('data', (b) => { + if (timer) clearTimeout(timer) + process.stdin.pause() + const data: string = (typeof b === 'string' ? b : b.toString()).trim() if (!options.default && options.required && data === '') { - clearTimeout(timeout) resolve(normal(options, retries - 1)) } else { - clearTimeout(timeout) resolve(data || (options.default as string)) } }) diff --git a/test/cli-ux/prompt.test.ts b/test/cli-ux/prompt.test.ts index 387be7dca..7f34e83d8 100644 --- a/test/cli-ux/prompt.test.ts +++ b/test/cli-ux/prompt.test.ts @@ -1,65 +1,73 @@ -import {expect} from 'chai' -import readline from 'node:readline' -import {SinonSandbox, createSandbox} from 'sinon' +import * as chai from 'chai' + +const {expect} = chai import {ux} from '../../src/cli-ux' +import {fancy} from './fancy' describe('prompt', () => { - let sandbox: SinonSandbox - - function stubReadline(answers: string[]) { - let callCount = 0 - sandbox.stub(readline, 'createInterface').returns({ - // @ts-expect-error because we're stubbing - async question(_message, opts, cb) { - callCount += 1 - cb(answers[callCount - 1]) - }, - close() {}, + fancy + .stdout() + .stderr() + .end('requires input', async () => { + const promptPromise = ux.prompt('Require input?') + process.stdin.emit('data', '') + process.stdin.emit('data', 'answer') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('answer') }) - } - - beforeEach(() => { - sandbox = createSandbox() - }) - - afterEach(() => { - sandbox.restore() - }) - - it('should require input', async () => { - stubReadline(['', '', 'answer']) - const answer = await ux.prompt('Require input?') - expect(answer).to.equal('answer') - }) - it('should not require input', async () => { - stubReadline(['']) - const answer = await ux.prompt('Require input?', {required: false}) - expect(answer).to.equal('') - }) + fancy + .stdout() + .stderr() + .stdin('y') + .end('confirm', async () => { + const promptPromise = ux.confirm('yes/no?') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal(true) + }) - it('should use default input', async () => { - stubReadline(['']) - const answer = await ux.prompt('Require input?', {default: 'default'}) - expect(answer).to.equal('default') - }) + fancy + .stdout() + .stderr() + .stdin('n') + .end('confirm', async () => { + const promptPromise = ux.confirm('yes/no?') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal(false) + }) - it('should confirm with y', async () => { - stubReadline(['y']) - const answer = await ux.confirm('yes/no?') - expect(answer).to.equal(true) - }) + fancy + .stdout() + .stderr() + .stdin('x') + .end('gets anykey', async () => { + const promptPromise = ux.anykey() + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('x') + }) - it('should confirm with n', async () => { - stubReadline(['n']) - const answer = await ux.confirm('yes/no?') - expect(answer).to.equal(false) - }) + fancy + .stdout() + .stderr() + .end('does not require input', async () => { + const promptPromise = ux.prompt('Require input?', { + required: false, + }) + process.stdin.emit('data', '') + const answer = await promptPromise + await ux.done() + expect(answer).to.equal('') + }) - it('should get anykey', async () => { - stubReadline(['x']) - const answer = await ux.anykey() - expect(answer).to.equal('x') - }) + fancy + .stdout() + .stderr() + .it('timeouts with no input', async () => { + await expect(ux.prompt('Require input?', {timeout: 1})).to.eventually.be.rejectedWith('Prompt timeout') + }) }) diff --git a/yarn.lock b/yarn.lock index 72b8e54c0..68211814c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2681,27 +2681,27 @@ escodegen@^2.0.0: optionalDependencies: source-map "~0.6.1" -eslint-config-oclif-typescript@^3.0.47: - version "3.0.47" - resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-3.0.47.tgz#77c5a1914bf5443bb1603b14929eb11d112304df" - integrity sha512-rTsvSeUwEd4TvH76LTtobUWkTqH1C0ArhpY/5dSngQsXW5oqwGBckSO3I6qfasw6cPIUOa6+3lftsqNUHbovRA== +eslint-config-oclif-typescript@^3.0.48: + version "3.0.48" + resolved "https://registry.yarnpkg.com/eslint-config-oclif-typescript/-/eslint-config-oclif-typescript-3.0.48.tgz#de633bc40756b26a64331c17583114bd6b8ffa14" + integrity sha512-SvsbXkXRdxJYBsg/CZtVCujjsQIMpvTtsjdfbxfWdo/Ocylv+Lte9+GjJuLYQKFpLKff1PU4hsdmce6GhA2NFg== dependencies: "@typescript-eslint/eslint-plugin" "^6.21.0" "@typescript-eslint/parser" "^6.21.0" eslint-config-xo-space "^0.35.0" eslint-import-resolver-typescript "^3.6.1" eslint-plugin-import "^2.29.1" - eslint-plugin-mocha "^10.2.0" + eslint-plugin-mocha "^10.3.0" eslint-plugin-node "^11.1.0" eslint-plugin-perfectionist "^2.5.0" -eslint-config-oclif@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-5.0.0.tgz#69c5cc8a19025e71fc49a10f47475bb8dd18ba24" - integrity sha512-yPxtUzU6eFL+WoW8DbRf7uoHxgiu0B/uY7k7rgHwFHij66WoI3qhBNhKI5R5FS5JeTuBOXKrNqQVDsSH0D/JvA== +eslint-config-oclif@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/eslint-config-oclif/-/eslint-config-oclif-5.0.2.tgz#127fbb803629e6f913353f6975e413f77dfec373" + integrity sha512-5Ut0Rb1UfTJD54KMIA34SiJ7j3uUHfgn73LqkNEZx+mgTAAAL1+6/6uN0RJhmyp+9/HBIlO3v3pCX0pRR5knWQ== dependencies: eslint-config-xo-space "^0.34.0" - eslint-plugin-mocha "^10.1.0" + eslint-plugin-mocha "^10.3.0" eslint-plugin-node "^11.1.0" eslint-plugin-unicorn "^48.0.1" @@ -2812,10 +2812,10 @@ eslint-plugin-import@^2.29.1: semver "^6.3.1" tsconfig-paths "^3.15.0" -eslint-plugin-mocha@^10.1.0, eslint-plugin-mocha@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-10.2.0.tgz#15b05ce5be4b332bb0d76826ec1c5ebf67102ad6" - integrity sha512-ZhdxzSZnd1P9LqDPF0DBcFLpRIGdh1zkF2JHnQklKQOvrQtT73kdP5K9V2mzvbLR+cCAO9OI48NXK/Ax9/ciCQ== +eslint-plugin-mocha@^10.3.0: + version "10.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-10.3.0.tgz#a1cd40737c230f4dc7477a3bef3bbaad7f8d8282" + integrity sha512-IWzbg2K6B1Q7h37Ih4zMyW+nhmw1JvUlHlbCUUUu6PfOOAUGCB0gxmvv7/U+TQQ6e8yHUv+q7KMdIIum4bx+PA== dependencies: eslint-utils "^3.0.0" rambda "^7.4.0" @@ -3036,10 +3036,10 @@ exponential-backoff@^3.1.1: resolved "https://registry.yarnpkg.com/exponential-backoff/-/exponential-backoff-3.1.1.tgz#64ac7526fe341ab18a39016cd22c787d01e00bf6" integrity sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw== -fancy-test@^3.0.1, fancy-test@^3.0.10: - version "3.0.10" - resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.10.tgz#341be69e6eb35fcf1be33c041bed978837502e84" - integrity sha512-6pwRQ7gqW57FKoTyRPPmcZktHLvrX+LS5ZQdXvQTMAq6vOVGCUrtJ4WP2BD4sZisRBnasaT4981L/8Q+qfh3wg== +fancy-test@^3.0.10, fancy-test@^3.0.11: + version "3.0.11" + resolved "https://registry.yarnpkg.com/fancy-test/-/fancy-test-3.0.11.tgz#b2d54ed8c31161ebdddb990bde27cf44e6876a01" + integrity sha512-cyfdbhT7LlkWHtPa2PaR5yWOTDzgmrpBqkTVXC5l7Uzs7og2Wnhj5lQnxA9+nSPr+WfY8jrH0Kf2v8HPsPQFKQ== dependencies: "@types/chai" "*" "@types/lodash" "*" @@ -3047,7 +3047,7 @@ fancy-test@^3.0.1, fancy-test@^3.0.10: "@types/sinon" "*" lodash "^4.17.13" mock-stdin "^1.0.0" - nock "^13.5.1" + nock "^13.5.3" sinon "^16.1.3" stdout-stderr "^0.1.9" @@ -4918,10 +4918,10 @@ nise@^5.1.4: just-extend "^4.0.2" path-to-regexp "^1.7.0" -nock@^13.5.1: - version "13.5.1" - resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.1.tgz#4e40f9877ad0d43b7cdb474261c190f3715dd806" - integrity sha512-+s7b73fzj5KnxbKH4Oaqz07tQ8degcMilU4rrmnKvI//b0JMBU4wEXFQ8zqr+3+L4eWSfU3H/UoIVGUV0tue1Q== +nock@^13.5.3: + version "13.5.3" + resolved "https://registry.yarnpkg.com/nock/-/nock-13.5.3.tgz#9858adf5b840696a410baf98bda720d5fad4f075" + integrity sha512-2NlGmHIK2rTeyy7UaY1ZNg0YZfEJMxghXgZi0b4DBsUyoDNTTxZeCSG1nmirAWF44RkkoV8NnegLVQijgVapNQ== dependencies: debug "^4.1.0" json-stringify-safe "^5.0.1" @@ -6599,10 +6599,10 @@ tsconfig-paths@^3.15.0: minimist "^1.2.6" strip-bom "^3.0.0" -tsd@^0.30.4: - version "0.30.4" - resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.30.4.tgz#bd7ab29befd23fb4d1c827c3db740a930a626ca1" - integrity sha512-ncC4SwAeUk0OTcXt5h8l0/gOLHJSp9ogosvOADT6QYzrl0ITm398B3wkz8YESqefIsEEwvYAU8bvo7/rcN/M0Q== +tsd@^0.30.6: + version "0.30.6" + resolved "https://registry.yarnpkg.com/tsd/-/tsd-0.30.6.tgz#5f857111d923d8b73790510142e1bd8d8fff78c3" + integrity sha512-GfzYfAJwbHTUY0FHC84fgJAX6w2FB6JXM4uvVc2jYyY16bHXVzen5PEXGOjISluMfG7z82otu57n2OzQhnRzwQ== dependencies: "@tsd/typescript" "~5.3.3" eslint-formatter-pretty "^4.1.0"