From 6ec598b81e4934a509b2891fc5551bb11cbe56ce Mon Sep 17 00:00:00 2001 From: Simon Boudrias Date: Sun, 30 Jun 2024 14:38:50 -0400 Subject: [PATCH] Major(inquirer): Migrate to Typescript and dual module package --- integration/cjs/integration.test.cjs | 8 + integration/esm/integration.test.mjs | 20 +- packages/inquirer/README.md | 15 +- .../{bottom-bar.js => bottom-bar.mts} | 2 +- .../examples/{checkbox.js => checkbox.mts} | 2 +- .../examples/{editor.js => editor.mts} | 2 +- .../examples/{expand.js => expand.mts} | 2 +- ...ogress.js => filter-validate-progress.mts} | 2 +- .../{hierarchical.js => hierarchical.mts} | 2 +- .../inquirer/examples/{input.js => input.mts} | 2 +- .../inquirer/examples/{list.js => list.mts} | 2 +- .../examples/{long-list.js => long-list.mts} | 2 +- .../{nested-call.js => nested-call.mts} | 2 +- .../examples/{password.js => password.mts} | 2 +- .../inquirer/examples/{pizza.js => pizza.mts} | 4 +- .../examples/{rawlist.js => rawlist.mts} | 2 +- .../examples/{recursive.js => recursive.mts} | 5 +- ...date-input.js => regex-validate-input.mts} | 2 +- ...vable-array.js => rx-observable-array.mts} | 2 +- ...ble-create.js => rx-observable-create.mts} | 2 +- .../inquirer/examples/{spawn.js => spawn.mts} | 2 +- .../{terminal-link.js => terminal-link.mts} | 2 +- .../inquirer/examples/{when.js => when.mts} | 2 +- packages/inquirer/lib/objects/choice.js | 31 --- packages/inquirer/lib/objects/choices.js | 136 ----------- packages/inquirer/lib/objects/separator.js | 33 --- packages/inquirer/lib/prompts/base.js | 171 -------------- packages/inquirer/package.json | 20 +- .../inquirer/{lib/index.js => src/index.mts} | 39 ++-- packages/inquirer/src/objects/choice.mts | 59 +++++ packages/inquirer/src/objects/choices.mts | 143 ++++++++++++ packages/inquirer/src/objects/separator.mts | 32 +++ packages/inquirer/src/prompts/base.mts | 220 ++++++++++++++++++ .../checkbox.js => src/prompts/checkbox.mts} | 92 ++++---- .../confirm.js => src/prompts/confirm.mts} | 47 ++-- .../editor.js => src/prompts/editor.mts} | 49 ++-- .../expand.js => src/prompts/expand.mts} | 115 +++++---- .../input.js => src/prompts/input.mts} | 22 +- .../prompts/list.js => src/prompts/list.mts} | 57 +++-- .../number.js => src/prompts/number.mts} | 6 +- .../password.js => src/prompts/password.mts} | 36 +-- .../rawlist.js => src/prompts/rawlist.mts} | 83 ++++--- .../{lib/ui/baseUI.js => src/ui/baseUI.mts} | 29 ++- .../bottom-bar.js => src/ui/bottom-bar.mts} | 35 ++- .../{lib/ui/prompt.js => src/ui/prompt.mts} | 114 +++++++-- .../utils/events.js => src/utils/events.mts} | 15 +- .../utils/incrementListIndex.mts} | 6 +- .../paginator.js => src/utils/paginator.mts} | 25 +- .../readline.js => src/utils/readline.mts} | 11 +- .../utils/screen-manager.mts} | 35 +-- .../utils/utils.js => src/utils/utils.mts} | 0 .../inquirer/test/bin/{write.js => write.mjs} | 0 .../test/helpers/{events.js => events.mts} | 0 .../helpers/{fixtures.js => fixtures.mts} | 6 +- .../helpers/{readline.js => readline.mts} | 6 +- .../test/specs/{api.test.js => api.test.mts} | 33 +-- .../{inquirer.test.js => inquirer.test.mts} | 10 +- .../{choice.test.js => choice.test.mts} | 15 +- .../{choices.test.js => choices.test.mts} | 30 ++- .../{separator.test.js => separator.test.mts} | 8 +- .../prompts/{base.test.js => base.test.mts} | 6 +- .../{checkbox.test.js => checkbox.test.mts} | 36 +-- .../{confirm.test.js => confirm.test.mts} | 34 +-- .../{editor.test.js => editor.test.mts} | 8 +- .../{expand.test.js => expand.test.mts} | 31 ++- packages/inquirer/test/specs/prompts/input.js | 116 --------- .../inquirer/test/specs/prompts/input.mts | 124 ++++++++++ .../prompts/{list.test.js => list.test.mts} | 57 +++-- .../{number.test.js => number.test.mts} | 30 +-- .../{password.test.js => password.test.mts} | 8 +- .../{rawlist.test.js => rawlist.test.mts} | 29 ++- .../{paginator.test.js => paginator.test.mts} | 3 +- packages/inquirer/tsconfig.cjs.json | 10 + packages/inquirer/tsconfig.json | 14 ++ yarn.lock | 3 +- 75 files changed, 1335 insertions(+), 1031 deletions(-) rename packages/inquirer/examples/{bottom-bar.js => bottom-bar.mts} (91%) rename packages/inquirer/examples/{checkbox.js => checkbox.mts} (96%) rename packages/inquirer/examples/{editor.js => editor.mts} (93%) rename packages/inquirer/examples/{expand.js => expand.mts} (94%) rename packages/inquirer/examples/{filter-validate-progress.js => filter-validate-progress.mts} (95%) rename packages/inquirer/examples/{hierarchical.js => hierarchical.mts} (98%) rename packages/inquirer/examples/{input.js => input.mts} (95%) rename packages/inquirer/examples/{list.js => list.mts} (94%) rename packages/inquirer/examples/{long-list.js => long-list.mts} (97%) rename packages/inquirer/examples/{nested-call.js => nested-call.mts} (90%) rename packages/inquirer/examples/{password.js => password.mts} (93%) rename packages/inquirer/examples/{pizza.js => pizza.mts} (95%) rename packages/inquirer/examples/{rawlist.js => rawlist.mts} (94%) rename packages/inquirer/examples/{recursive.js => recursive.mts} (89%) rename packages/inquirer/examples/{regex-validate-input.js => regex-validate-input.mts} (91%) rename packages/inquirer/examples/{rx-observable-array.js => rx-observable-array.mts} (95%) rename packages/inquirer/examples/{rx-observable-create.js => rx-observable-create.mts} (95%) rename packages/inquirer/examples/{spawn.js => spawn.mts} (75%) rename packages/inquirer/examples/{terminal-link.js => terminal-link.mts} (95%) rename packages/inquirer/examples/{when.js => when.mts} (95%) delete mode 100644 packages/inquirer/lib/objects/choice.js delete mode 100644 packages/inquirer/lib/objects/choices.js delete mode 100644 packages/inquirer/lib/objects/separator.js delete mode 100644 packages/inquirer/lib/prompts/base.js rename packages/inquirer/{lib/index.js => src/index.mts} (60%) create mode 100644 packages/inquirer/src/objects/choice.mts create mode 100644 packages/inquirer/src/objects/choices.mts create mode 100644 packages/inquirer/src/objects/separator.mts create mode 100644 packages/inquirer/src/prompts/base.mts rename packages/inquirer/{lib/prompts/checkbox.js => src/prompts/checkbox.mts} (71%) rename packages/inquirer/{lib/prompts/confirm.js => src/prompts/confirm.mts} (59%) rename packages/inquirer/{lib/prompts/editor.js => src/prompts/editor.mts} (67%) rename packages/inquirer/{lib/prompts/expand.js => src/prompts/expand.mts} (65%) rename packages/inquirer/{lib/prompts/input.js => src/prompts/input.mts} (76%) rename packages/inquirer/{lib/prompts/list.js => src/prompts/list.mts} (74%) rename packages/inquirer/{lib/prompts/number.js => src/prompts/number.mts} (81%) rename packages/inquirer/{lib/prompts/password.js => src/prompts/password.mts} (73%) rename packages/inquirer/{lib/prompts/rawlist.js => src/prompts/rawlist.mts} (70%) rename packages/inquirer/{lib/ui/baseUI.js => src/ui/baseUI.mts} (80%) rename packages/inquirer/{lib/ui/bottom-bar.js => src/ui/bottom-bar.mts} (72%) rename packages/inquirer/{lib/ui/prompt.js => src/ui/prompt.mts} (59%) rename packages/inquirer/{lib/utils/events.js => src/utils/events.mts} (67%) rename packages/inquirer/{lib/utils/incrementListIndex.js => src/utils/incrementListIndex.mts} (71%) rename packages/inquirer/{lib/utils/paginator.js => src/utils/paginator.mts} (75%) rename packages/inquirer/{lib/utils/readline.js => src/utils/readline.mts} (71%) rename packages/inquirer/{lib/utils/screen-manager.js => src/utils/screen-manager.mts} (81%) rename packages/inquirer/{lib/utils/utils.js => src/utils/utils.mts} (100%) rename packages/inquirer/test/bin/{write.js => write.mjs} (100%) rename packages/inquirer/test/helpers/{events.js => events.mts} (100%) rename packages/inquirer/test/helpers/{fixtures.js => fixtures.mts} (84%) rename packages/inquirer/test/helpers/{readline.js => readline.mts} (82%) rename packages/inquirer/test/specs/{api.test.js => api.test.mts} (91%) rename packages/inquirer/test/specs/{inquirer.test.js => inquirer.test.mts} (98%) rename packages/inquirer/test/specs/objects/{choice.test.js => choice.test.mts} (68%) rename packages/inquirer/test/specs/objects/{choices.test.js => choices.test.mts} (71%) rename packages/inquirer/test/specs/objects/{separator.test.js => separator.test.mts} (80%) rename packages/inquirer/test/specs/prompts/{base.test.js => base.test.mts} (75%) rename packages/inquirer/test/specs/prompts/{checkbox.test.js => checkbox.test.mts} (93%) rename packages/inquirer/test/specs/prompts/{confirm.test.js => confirm.test.mts} (80%) rename packages/inquirer/test/specs/prompts/{editor.test.js => editor.test.mts} (84%) rename packages/inquirer/test/specs/prompts/{expand.test.js => expand.test.mts} (84%) delete mode 100644 packages/inquirer/test/specs/prompts/input.js create mode 100644 packages/inquirer/test/specs/prompts/input.mts rename packages/inquirer/test/specs/prompts/{list.test.js => list.test.mts} (83%) rename packages/inquirer/test/specs/prompts/{number.test.js => number.test.mts} (83%) rename packages/inquirer/test/specs/prompts/{password.test.js => password.test.mts} (92%) rename packages/inquirer/test/specs/prompts/{rawlist.test.js => rawlist.test.mts} (86%) rename packages/inquirer/test/specs/utils/{paginator.test.js => paginator.test.mts} (97%) create mode 100644 packages/inquirer/tsconfig.cjs.json create mode 100644 packages/inquirer/tsconfig.json diff --git a/integration/cjs/integration.test.cjs b/integration/cjs/integration.test.cjs index e0ab630cd..f0516e72b 100644 --- a/integration/cjs/integration.test.cjs +++ b/integration/cjs/integration.test.cjs @@ -4,6 +4,8 @@ const assert = require('node:assert'); const { input } = require('@inquirer/prompts'); const { createPrompt } = require('@inquirer/core'); const defaultInput = require('@inquirer/input').default; +const inquirer = require('inquirer').default; +const { createPromptModule } = require('inquirer'); describe('CommonJS Integration', () => { it('@inquirer/prompts should be exported', () => { @@ -17,4 +19,10 @@ describe('CommonJS Integration', () => { it('@inquirer/core should export createPrompt', () => { assert(createPrompt instanceof Function); }); + + it('inquirer should be exported', () => { + assert(inquirer.prompt instanceof Function); + assert(inquirer.createPromptModule instanceof Function); + assert(createPromptModule instanceof Function); + }); }); diff --git a/integration/esm/integration.test.mjs b/integration/esm/integration.test.mjs index 94504ca57..6a62a4e48 100644 --- a/integration/esm/integration.test.mjs +++ b/integration/esm/integration.test.mjs @@ -1,17 +1,10 @@ /* eslint-disable n/no-unsupported-features/node-builtins */ +import { describe, it } from 'node:test'; +import assert from 'node:assert'; import { input } from '@inquirer/prompts'; import defaultInput from '@inquirer/input'; import { createPrompt } from '@inquirer/core'; import inquirer, { createPromptModule } from 'inquirer'; -import { describe, it } from 'node:test'; -import assert from 'node:assert'; - -import Base from 'inquirer/lib/prompts/base.js'; -import Choices from 'inquirer/lib/objects/choices.js'; -import Separator from 'inquirer/lib/objects/separator.js'; -import observe from 'inquirer/lib/utils/events.js'; -import * as utils from 'inquirer/lib/utils/readline.js'; -import Paginator from 'inquirer/lib/utils/paginator.js'; describe('ESM Integration', () => { it('@inquirer/prompts should be exported', () => { @@ -31,13 +24,4 @@ describe('ESM Integration', () => { assert(inquirer.createPromptModule instanceof Function); assert(createPromptModule instanceof Function); }); - - it('inquirer custom prompts util paths are stable', () => { - assert(Base != null); - assert(Choices != null); - assert(Separator != null); - assert(observe != null); - assert(utils != null); - assert(Paginator != null); - }); }); diff --git a/packages/inquirer/README.md b/packages/inquirer/README.md index 6da501fab..7cc7b5b3e 100644 --- a/packages/inquirer/README.md +++ b/packages/inquirer/README.md @@ -94,20 +94,7 @@ inquirer }); ``` -> [!WARNING] -> Inquirer v9 and higher are native esm modules, this mean you cannot use the commonjs syntax `require('inquirer')` anymore. If you want to learn more about using native esm in Node, I'd recommend reading [the following guide](https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c). - -Alternatively, if you require a commonjs module, you should rely on an older version until you're ready to upgrade your environment: - -```sh -npm install --save inquirer@^8.0.0 -``` - -This will then allow import inquirer with the commonjs `require`: - -```js -const inquirer = require('inquirer'); -``` +If you're using Typescript, you'll also want to [add `@types/inquirer`](https://www.npmjs.com/package/@types/inquirer). diff --git a/packages/inquirer/examples/bottom-bar.js b/packages/inquirer/examples/bottom-bar.mts similarity index 91% rename from packages/inquirer/examples/bottom-bar.js rename to packages/inquirer/examples/bottom-bar.mts index 3ce77b7d3..1d44325b2 100644 --- a/packages/inquirer/examples/bottom-bar.js +++ b/packages/inquirer/examples/bottom-bar.mts @@ -1,5 +1,5 @@ import { spawn } from 'node:child_process'; -import BottomBar from '../lib/ui/bottom-bar.js'; +import BottomBar from '../src/ui/bottom-bar.mjs'; const loader = ['/ Installing', '| Installing', String.raw`\ Installing`, '- Installing']; let i = 4; diff --git a/packages/inquirer/examples/checkbox.js b/packages/inquirer/examples/checkbox.mts similarity index 96% rename from packages/inquirer/examples/checkbox.js rename to packages/inquirer/examples/checkbox.mts index dc4208d80..3fcc34cd4 100644 --- a/packages/inquirer/examples/checkbox.js +++ b/packages/inquirer/examples/checkbox.mts @@ -2,7 +2,7 @@ * Checkbox list examples */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt([ diff --git a/packages/inquirer/examples/editor.js b/packages/inquirer/examples/editor.mts similarity index 93% rename from packages/inquirer/examples/editor.js rename to packages/inquirer/examples/editor.mts index 84f622f37..c14942fb7 100644 --- a/packages/inquirer/examples/editor.js +++ b/packages/inquirer/examples/editor.mts @@ -2,7 +2,7 @@ * Editor prompt example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/examples/expand.js b/packages/inquirer/examples/expand.mts similarity index 94% rename from packages/inquirer/examples/expand.js rename to packages/inquirer/examples/expand.mts index 36d6bf955..09d35259a 100644 --- a/packages/inquirer/examples/expand.js +++ b/packages/inquirer/examples/expand.mts @@ -2,7 +2,7 @@ * Expand list examples */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt([ diff --git a/packages/inquirer/examples/filter-validate-progress.js b/packages/inquirer/examples/filter-validate-progress.mts similarity index 95% rename from packages/inquirer/examples/filter-validate-progress.js rename to packages/inquirer/examples/filter-validate-progress.mts index fcb48550d..9a483ef02 100644 --- a/packages/inquirer/examples/filter-validate-progress.js +++ b/packages/inquirer/examples/filter-validate-progress.mts @@ -2,7 +2,7 @@ * Filter and validate progress example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/examples/hierarchical.js b/packages/inquirer/examples/hierarchical.mts similarity index 98% rename from packages/inquirer/examples/hierarchical.js rename to packages/inquirer/examples/hierarchical.mts index 7652ed7ed..c940f069c 100644 --- a/packages/inquirer/examples/hierarchical.js +++ b/packages/inquirer/examples/hierarchical.mts @@ -2,7 +2,7 @@ * Heirarchical conversation example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const directionsPrompt = { type: 'list', diff --git a/packages/inquirer/examples/input.js b/packages/inquirer/examples/input.mts similarity index 95% rename from packages/inquirer/examples/input.js rename to packages/inquirer/examples/input.mts index 8fe89b737..1b58d7060 100644 --- a/packages/inquirer/examples/input.js +++ b/packages/inquirer/examples/input.mts @@ -2,7 +2,7 @@ * Input prompt example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/examples/list.js b/packages/inquirer/examples/list.mts similarity index 94% rename from packages/inquirer/examples/list.js rename to packages/inquirer/examples/list.mts index 63382ae01..e88188710 100644 --- a/packages/inquirer/examples/list.js +++ b/packages/inquirer/examples/list.mts @@ -2,7 +2,7 @@ * List prompt example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt([ diff --git a/packages/inquirer/examples/long-list.js b/packages/inquirer/examples/long-list.mts similarity index 97% rename from packages/inquirer/examples/long-list.js rename to packages/inquirer/examples/long-list.mts index 504a01f50..1a2f357a9 100644 --- a/packages/inquirer/examples/long-list.js +++ b/packages/inquirer/examples/long-list.mts @@ -2,7 +2,7 @@ * Paginated list */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const choices = Array.apply(0, Array.from({ length: 26 })).map((x, y) => String.fromCodePoint(y + 65), diff --git a/packages/inquirer/examples/nested-call.js b/packages/inquirer/examples/nested-call.mts similarity index 90% rename from packages/inquirer/examples/nested-call.js rename to packages/inquirer/examples/nested-call.mts index 6369732b3..117ac9a46 100644 --- a/packages/inquirer/examples/nested-call.js +++ b/packages/inquirer/examples/nested-call.mts @@ -2,7 +2,7 @@ * Nested Inquirer call */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt({ diff --git a/packages/inquirer/examples/password.js b/packages/inquirer/examples/password.mts similarity index 93% rename from packages/inquirer/examples/password.js rename to packages/inquirer/examples/password.mts index 55c199c9c..8139b5106 100644 --- a/packages/inquirer/examples/password.js +++ b/packages/inquirer/examples/password.mts @@ -2,7 +2,7 @@ * Password prompt example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const requireLetterAndNumber = (value) => { if (/\w/.test(value) && /\d/.test(value)) { diff --git a/packages/inquirer/examples/pizza.js b/packages/inquirer/examples/pizza.mts similarity index 95% rename from packages/inquirer/examples/pizza.js rename to packages/inquirer/examples/pizza.mts index 37a730d7a..67661a5f5 100644 --- a/packages/inquirer/examples/pizza.js +++ b/packages/inquirer/examples/pizza.mts @@ -1,9 +1,9 @@ /** * Pizza delivery prompt example - * run example by writing `node pizza.js` in your console + * run example by writing `node pizza.mjs` in your console */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; console.log('Hi, welcome to Node Pizza'); diff --git a/packages/inquirer/examples/rawlist.js b/packages/inquirer/examples/rawlist.mts similarity index 94% rename from packages/inquirer/examples/rawlist.js rename to packages/inquirer/examples/rawlist.mts index ea8d64864..139519989 100644 --- a/packages/inquirer/examples/rawlist.js +++ b/packages/inquirer/examples/rawlist.mts @@ -2,7 +2,7 @@ * Raw List prompt example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt([ diff --git a/packages/inquirer/examples/recursive.js b/packages/inquirer/examples/recursive.mts similarity index 89% rename from packages/inquirer/examples/recursive.js rename to packages/inquirer/examples/recursive.mts index c42888dda..4a5d672b0 100644 --- a/packages/inquirer/examples/recursive.js +++ b/packages/inquirer/examples/recursive.mts @@ -3,8 +3,7 @@ * Allows user to choose when to exit prompt */ -import inquirer from '../lib/index.js'; -const output = []; +import inquirer from '../src/index.mjs'; const questions = [ { @@ -21,6 +20,8 @@ const questions = [ ]; function ask() { + const output: string[] = []; + inquirer.prompt(questions).then((answers) => { output.push(answers.tvShow); if (answers.askAgain) { diff --git a/packages/inquirer/examples/regex-validate-input.js b/packages/inquirer/examples/regex-validate-input.mts similarity index 91% rename from packages/inquirer/examples/regex-validate-input.js rename to packages/inquirer/examples/regex-validate-input.mts index d30349868..5f5177e51 100644 --- a/packages/inquirer/examples/regex-validate-input.js +++ b/packages/inquirer/examples/regex-validate-input.mts @@ -2,7 +2,7 @@ * Filter and validate progress example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/examples/rx-observable-array.js b/packages/inquirer/examples/rx-observable-array.mts similarity index 95% rename from packages/inquirer/examples/rx-observable-array.js rename to packages/inquirer/examples/rx-observable-array.mts index 19123c642..4ebae3664 100644 --- a/packages/inquirer/examples/rx-observable-array.js +++ b/packages/inquirer/examples/rx-observable-array.mts @@ -1,5 +1,5 @@ import { from } from 'rxjs'; -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/examples/rx-observable-create.js b/packages/inquirer/examples/rx-observable-create.mts similarity index 95% rename from packages/inquirer/examples/rx-observable-create.js rename to packages/inquirer/examples/rx-observable-create.mts index 97efb48c3..e73f0c115 100644 --- a/packages/inquirer/examples/rx-observable-create.js +++ b/packages/inquirer/examples/rx-observable-create.mts @@ -1,5 +1,5 @@ import { Observable } from 'rxjs'; -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const observe = new Observable((subscriber) => { subscriber.next({ diff --git a/packages/inquirer/examples/spawn.js b/packages/inquirer/examples/spawn.mts similarity index 75% rename from packages/inquirer/examples/spawn.js rename to packages/inquirer/examples/spawn.mts index fcfe06476..aa1fec982 100644 --- a/packages/inquirer/examples/spawn.js +++ b/packages/inquirer/examples/spawn.mts @@ -1,6 +1,6 @@ import { spawn } from 'node:child_process'; -spawn('node', ['input.js'], { +spawn('node', ['input.mjs'], { cwd: import.meta.dirname, stdio: 'inherit', }); diff --git a/packages/inquirer/examples/terminal-link.js b/packages/inquirer/examples/terminal-link.mts similarity index 95% rename from packages/inquirer/examples/terminal-link.js rename to packages/inquirer/examples/terminal-link.mts index 5b24a9ab9..19839df06 100644 --- a/packages/inquirer/examples/terminal-link.js +++ b/packages/inquirer/examples/terminal-link.mts @@ -5,7 +5,7 @@ */ import terminalLink from 'terminal-link'; -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; inquirer .prompt([ diff --git a/packages/inquirer/examples/when.js b/packages/inquirer/examples/when.mts similarity index 95% rename from packages/inquirer/examples/when.js rename to packages/inquirer/examples/when.mts index 1ab765fca..4d7433918 100644 --- a/packages/inquirer/examples/when.js +++ b/packages/inquirer/examples/when.mts @@ -2,7 +2,7 @@ * When example */ -import inquirer from '../lib/index.js'; +import inquirer from '../src/index.mjs'; const questions = [ { diff --git a/packages/inquirer/lib/objects/choice.js b/packages/inquirer/lib/objects/choice.js deleted file mode 100644 index 5f6d1ecf8..000000000 --- a/packages/inquirer/lib/objects/choice.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Choice object - * Normalize input as choice object - * @constructor - * @param {Number|String|Object} val Choice value. If an object is passed, it should contains - * at least one of `value` or `name` property - */ - -export default class Choice { - constructor(val, answers) { - // Don't process Choice and Separator object - if (val instanceof Choice || val.type === 'separator') { - return val; - } - - if (typeof val === 'string' || typeof val === 'number') { - this.name = String(val); - this.value = val; - this.short = String(val); - } else { - Object.assign(this, val, { - name: val.name || val.value, - value: 'value' in val ? val.value : val.name, - short: val.short || val.name || val.value, - }); - } - - this.disabled = - typeof val.disabled === 'function' ? val.disabled(answers) : val.disabled; - } -} diff --git a/packages/inquirer/lib/objects/choices.js b/packages/inquirer/lib/objects/choices.js deleted file mode 100644 index 3bc305a77..000000000 --- a/packages/inquirer/lib/objects/choices.js +++ /dev/null @@ -1,136 +0,0 @@ -import assert from 'node:assert'; - -import Separator from './separator.js'; -import Choice from './choice.js'; - -/** - * Choices collection - * Collection of multiple `choice` object - */ -export default class Choices { - /** @param {Array} choices All `choice` to keep in the collection */ - constructor(choices, answers) { - this.choices = choices.map((val) => { - if (val.type === 'separator') { - if (!(val instanceof Separator)) { - val = new Separator(val.line); - } - - return val; - } - - return new Choice(val, answers); - }); - - this.realChoices = this.choices - .filter(Separator.exclude) - .filter((item) => !item.disabled); - - Object.defineProperty(this, 'length', { - get() { - return this.choices.length; - }, - set(val) { - this.choices.length = val; - }, - }); - - Object.defineProperty(this, 'realLength', { - get() { - return this.realChoices.length; - }, - set() { - throw new Error('Cannot set `realLength` of a Choices collection'); - }, - }); - } - - [Symbol.iterator]() { - const data = this.choices; - let index = -1; - - return { - next: () => ({ value: data[++index], done: !(index in data) }), - }; - } - - /** - * Get a valid choice from the collection - * @param {Number} selector The selected choice index - * @return {Choice|Undefined} Return the matched choice or undefined - */ - getChoice(selector) { - assert(typeof selector === 'number'); - return this.realChoices[selector]; - } - - /** - * Get a raw element from the collection - * @param {Number} selector The selected index value - * @return {Choice|Undefined} Return the matched choice or undefined - */ - get(selector) { - assert(typeof selector === 'number'); - return this.choices[selector]; - } - - /** - * Match the valid choices against a where clause - * @param {Function|Object} whereClause filter function or key-value object to match against - * @return {Array} Matching choices or empty array - */ - where(whereClause) { - let filterFn; - if (typeof whereClause === 'function') { - filterFn = whereClause; - } else { - const [key, value] = Object.entries(whereClause)[0]; - filterFn = (choice) => choice[key] === value; - } - - return this.realChoices.filter(filterFn); - } - - /** - * Pluck a particular key from the choices - * @param {String} propertyName Property name to select - * @return {Array} Selected properties - */ - pluck(propertyName) { - return this.realChoices.map((choice) => choice[propertyName]); - } - - // Expose usual Array methods - indexOf(...args) { - return this.choices.indexOf(...args); - } - - forEach(...args) { - return this.choices.forEach(...args); - } - - filter(...args) { - return this.choices.filter(...args); - } - - reduce(...args) { - return this.choices.reduce(...args); - } - - find(func) { - return this.choices.find(func); - } - - some(func) { - return this.choices.some(func); - } - - push(...args) { - const objs = args.map((val) => new Choice(val)); - this.choices.push(...objs); - this.realChoices = this.choices - .filter(Separator.exclude) - .filter((item) => !item.disabled); - return this.choices; - } -} diff --git a/packages/inquirer/lib/objects/separator.js b/packages/inquirer/lib/objects/separator.js deleted file mode 100644 index b821b2a40..000000000 --- a/packages/inquirer/lib/objects/separator.js +++ /dev/null @@ -1,33 +0,0 @@ -import colors from 'yoctocolors-cjs'; -import figures from '@inquirer/figures'; - -/** - * Separator object - * Used to space/separate choices group - * @constructor - * @param {String} line Separation line content (facultative) - */ - -export default class Separator { - constructor(line) { - this.type = 'separator'; - this.line = colors.dim(line || Array.from({ length: 15 }).join(figures.line)); - } - - /** - * Helper function returning false if object is a separator - * @param {Object} obj object to test against - * @return {Boolean} `false` if object is a separator - */ - static exclude(obj) { - return obj.type !== 'separator'; - } - - /** - * Stringify separator - * @return {String} the separator display string - */ - toString() { - return this.line; - } -} diff --git a/packages/inquirer/lib/prompts/base.js b/packages/inquirer/lib/prompts/base.js deleted file mode 100644 index 63b3c519b..000000000 --- a/packages/inquirer/lib/prompts/base.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Base prompt implementation - * Should be extended by prompt types. - */ -import colors from 'yoctocolors-cjs'; -import runAsync from 'run-async'; -import { filter, mergeMap, share, take, takeUntil } from 'rxjs'; -import Choices from '../objects/choices.js'; -import ScreenManager from '../utils/screen-manager.js'; - -export default class Prompt { - constructor(question, rl, answers) { - // Setup instance defaults property - Object.assign(this, { - answers, - status: 'pending', - }); - - // Set defaults prompt options - this.opt = { - validate: () => true, - validatingText: '', - filter: (val) => val, - filteringText: '', - when: () => true, - suffix: '', - prefix: colors.green('?'), - transformer: (val) => val, - ...question, - }; - - // Make sure name is present - if (!this.opt.name) { - this.throwParamError('name'); - } - - // Set default message if no message defined - this.opt.message ||= this.opt.name + ':'; - - // Normalize choices - if (Array.isArray(this.opt.choices)) { - this.opt.choices = new Choices(this.opt.choices, answers); - } - - this.rl = rl; - this.screen = new ScreenManager(this.rl); - } - - /** - * Start the Inquiry session and manage output value filtering - * @return {Promise} - */ - - run() { - return new Promise((resolve, reject) => { - this._run( - (value) => resolve(value), - (error) => reject(error), - ); - }); - } - - // Default noop (this one should be overwritten in prompts) - _run(cb) { - cb(); - } - - /** - * Throw an error telling a required parameter is missing - * @param {String} name Name of the missing param - * @return {Throw Error} - */ - - throwParamError(name) { - throw new Error('You must provide a `' + name + '` parameter'); - } - - /** - * Called when the UI closes. Override to do any specific cleanup necessary - */ - close() { - this.screen.releaseCursor(); - } - - /** - * Run the provided validation method each time a submit event occur. - * @param {Rx.Observable} submit - submit event flow - * @return {Object} Object containing two observables: `success` and `error` - */ - handleSubmitEvents(submit) { - const validate = runAsync(this.opt.validate); - const asyncFilter = runAsync(this.opt.filter); - const validation = submit.pipe( - mergeMap((value) => { - this.startSpinner(value, this.opt.filteringText); - return asyncFilter(value, this.answers).then( - (filteredValue) => { - this.startSpinner(filteredValue, this.opt.validatingText); - return validate(filteredValue, this.answers).then( - (isValid) => ({ isValid, value: filteredValue }), - (error_) => ({ isValid: error_, value: filteredValue }), - ); - }, - (error_) => ({ isValid: error_ }), - ); - }), - share(), - ); - - const success = validation.pipe( - filter((state) => state.isValid === true), - take(1), - ); - const error = validation.pipe( - filter((state) => state.isValid !== true), - takeUntil(success), - ); - - return { - success, - error, - }; - } - - startSpinner(value, bottomContent) { - value = this.getSpinningValue(value); - // If the question will spin, cut off the prefix (for layout purposes) - const content = bottomContent - ? this.getQuestion() + value - : this.getQuestion().slice(this.opt.prefix.length + 1) + value; - - this.screen.renderWithSpinner(content, bottomContent); - } - - /** - * Allow override, e.g. for password prompts - * See: https://github.com/SBoudrias/Inquirer.js/issues/1022 - * - * @return {String} value to display while spinning - */ - getSpinningValue(value) { - return value; - } - - /** - * Generate the prompt question string - * @return {String} prompt question string - */ - getQuestion() { - let message = - (this.opt.prefix ? this.opt.prefix + ' ' : '') + - colors.bold(this.opt.message) + - this.opt.suffix + - colors.reset(' '); - - // Append the default if available, and if question isn't touched/answered - if ( - this.opt.default != null && - this.status !== 'touched' && - this.status !== 'answered' - ) { - // If default password is supplied, hide it - message += - this.opt.type === 'password' - ? colors.italic(colors.dim('[hidden] ')) - : colors.dim('(' + this.opt.default + ') '); - } - - return message; - } -} diff --git a/packages/inquirer/package.json b/packages/inquirer/package.json index 33da6ad5d..63cadca72 100644 --- a/packages/inquirer/package.json +++ b/packages/inquirer/package.json @@ -1,13 +1,12 @@ { "name": "inquirer", - "type": "module", "version": "9.3.5", "description": "A collection of common interactive command line user interfaces.", "author": "Simon Boudrias ", + "main": "./dist/cjs/index.js", "files": [ - "lib" + "dist/**/*" ], - "main": "lib/index.js", "keywords": [ "answer", "answers", @@ -60,6 +59,7 @@ "license": "MIT", "dependencies": { "@inquirer/figures": "^1.0.3", + "@inquirer/type": "^1.3.3", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "external-editor": "^3.1.0", @@ -73,5 +73,17 @@ "yoctocolors-cjs": "^2.1.2" }, "homepage": "https://github.com/SBoudrias/Inquirer.js/blob/master/packages/inquirer/README.md", - "sideEffects": false + "sideEffects": false, + "scripts": { + "tsc": "yarn run tsc:esm && yarn run tsc:cjs", + "tsc:esm": "rm -rf dist/esm && tsc -p ./tsconfig.json", + "tsc:cjs": "rm -rf dist/cjs && tsc -p ./tsconfig.cjs.json && node ../../tools/fix-ext.mjs", + "dev": "tsc -p ./tsconfig.json --watch" + }, + "exports": { + ".": { + "import": "./dist/esm/index.mjs", + "require": "./dist/cjs/index.js" + } + } } diff --git a/packages/inquirer/lib/index.js b/packages/inquirer/src/index.mts similarity index 60% rename from packages/inquirer/lib/index.js rename to packages/inquirer/src/index.mts index 16850de9a..be668ad19 100644 --- a/packages/inquirer/lib/index.js +++ b/packages/inquirer/src/index.mts @@ -1,28 +1,32 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * Inquirer.js * A collection of common interactive command line user interfaces. */ -import { default as List } from './prompts/list.js'; -import { default as Input } from './prompts/input.js'; -import { default as Number } from './prompts/number.js'; -import { default as Confirm } from './prompts/confirm.js'; -import { default as RawList } from './prompts/rawlist.js'; -import { default as Expand } from './prompts/expand.js'; -import { default as Checkbox } from './prompts/checkbox.js'; -import { default as Password } from './prompts/password.js'; -import { default as Editor } from './prompts/editor.js'; +import { default as List } from './prompts/list.mjs'; +import { default as Input } from './prompts/input.mjs'; +import { default as Number } from './prompts/number.mjs'; +import { default as Confirm } from './prompts/confirm.mjs'; +import { default as RawList } from './prompts/rawlist.mjs'; +import { default as Expand } from './prompts/expand.mjs'; +import { default as Checkbox } from './prompts/checkbox.mjs'; +import { default as Password } from './prompts/password.mjs'; +import { default as Editor } from './prompts/editor.mjs'; -import { default as BottomBar } from './ui/bottom-bar.js'; -import { default as Prompt } from './ui/prompt.js'; +import { default as BottomBar } from './ui/bottom-bar.mjs'; +import { default as Prompt } from './ui/prompt.mjs'; -import { default as Separator } from './objects/separator.js'; +import { default as Separator } from './objects/separator.mjs'; + +import type { StreamOptions } from './ui/baseUI.mjs'; +import type { PromptCollection, PromptConstructor } from './ui/prompt.mjs'; /** * Create a new self-contained prompt module. */ -export function createPromptModule(opt) { - const promptModule = function (questions, answers) { +export function createPromptModule(opt?: StreamOptions) { + const promptModule = function (questions: any, answers?: Record) { let uiInstance; try { uiInstance = new Prompt(promptModule.prompts, opt); @@ -38,7 +42,7 @@ export function createPromptModule(opt) { return promise; }; - promptModule.prompts = {}; + promptModule.prompts = {} as PromptCollection; /** * Register a prompt type @@ -47,7 +51,7 @@ export function createPromptModule(opt) { * @return {inquirer} */ - promptModule.registerPrompt = function (name, prompt) { + promptModule.registerPrompt = function (name: string, prompt: PromptConstructor) { promptModule.prompts[name] = prompt; return this; }; @@ -79,11 +83,10 @@ export function createPromptModule(opt) { * @param {Function} cb - Callback being passed the user answers * @return {ui.Prompt} */ - const prompt = createPromptModule(); // Expose helper functions on the top level for easiest usage by common users -function registerPrompt(name, newPrompt) { +function registerPrompt(name: string, newPrompt: PromptConstructor) { prompt.registerPrompt(name, newPrompt); } diff --git a/packages/inquirer/src/objects/choice.mts b/packages/inquirer/src/objects/choice.mts new file mode 100644 index 000000000..86eaad187 --- /dev/null +++ b/packages/inquirer/src/objects/choice.mts @@ -0,0 +1,59 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +export type ChoiceConfig = + | { + name: string; + value?: any; + short?: string; + disabled?: string | boolean | ((answers?: Record) => boolean); + [other: string]: any; + } + | { + name?: string; + value: any; + short?: string; + disabled?: string | boolean | ((answers?: Record) => boolean); + [other: string]: any; + }; + +/** + * Choice object + * Normalize input as choice object + * @constructor + * @param {Number|String|Object} val Choice value. If an object is passed, it should contains + * at least one of `value` or `name` property + */ + +export default class Choice { + value: any; + name: string = ''; + short: string = ''; + disabled: boolean = false; + [key: string]: any; + + constructor( + val: string | number | ChoiceConfig | Choice, + answers?: Record, + ) { + // Don't process Choice and Separator object + if (val instanceof Choice) { + return val; + } + + if (typeof val === 'string' || typeof val === 'number') { + this.name = String(val); + this.value = val; + this.short = String(val); + } else { + Object.assign(this, val, { + name: val.name || val.value, + value: 'value' in val ? val.value : val.name, + short: val.short || val.name || val.value, + }); + + if (typeof val.disabled === 'function') { + this.disabled = val.disabled(answers); + } + } + } +} diff --git a/packages/inquirer/src/objects/choices.mts b/packages/inquirer/src/objects/choices.mts new file mode 100644 index 000000000..abb8de9d2 --- /dev/null +++ b/packages/inquirer/src/objects/choices.mts @@ -0,0 +1,143 @@ +import assert from 'node:assert'; + +import Separator from './separator.mjs'; +import Choice, { type ChoiceConfig } from './choice.mjs'; + +/** + * Choices collection + * Collection of multiple `choice` object + */ +export default class Choices { + #choices: Array; + #realChoices: Array; + + constructor( + choices: ReadonlyArray, + answers?: Record, + ) { + this.#choices = choices.map((val) => { + if (Separator.isSeparator(val)) { + return new Separator(val); + } + + return new Choice(val, answers); + }); + + this.#realChoices = this.#choices.filter( + (item): item is Choice => !Separator.isSeparator(item) && !item.disabled, + ); + } + + [Symbol.iterator]() { + const data = this.#choices; + let index = -1; + + return { + next: () => ({ value: data[++index], done: !(index in data) }), + }; + } + + get length() { + return this.#choices.length; + } + + set length(val: number) { + this.#choices.length = val; + } + + get realLength() { + return this.#realChoices.length; + } + + set realLength(_val: number) { + throw new Error('Cannot set `realLength` of a Choices collection'); + } + + /** + * Get a valid choice from the collection + */ + getChoice(selector: number): Choice | undefined { + assert(typeof selector === 'number'); + return this.#realChoices[selector]; + } + + /** + * Get a raw element from the collection + */ + get(selector: number): Choice | Separator | undefined { + assert(typeof selector === 'number'); + return this.#choices[selector]; + } + + /** + * Match the valid choices against a where clause + */ + where(whereClause: (() => boolean) | Record) { + let filterFn; + if (typeof whereClause === 'function') { + filterFn = whereClause; + } else { + const entries = Object.entries(whereClause); + const entry = entries[0]; + if (entries.length !== 1 || !entry) { + throw new Error('[inquirer : Choices#where] Invalid whereClause'); + } + + const [key, value] = entry; + filterFn = (choice) => choice[key] === value; + } + + return this.#realChoices.filter(filterFn); + } + + /** + * Pluck a particular key from the choices + */ + pluck(propertyName: string) { + return this.#realChoices.map((choice) => choice[propertyName]); + } + + // Expose usual Array methods + indexOf(...args: Parameters['indexOf']>) { + return this.#choices.indexOf(...args); + } + + forEach(...args: Parameters['forEach']>) { + return this.#choices.forEach(...args); + } + + filter(...args: Parameters['filter']>) { + return this.#choices.filter(...args); + } + + reduce(...args: Parameters['reduce']>) { + return this.#choices.reduce(...args); + } + + find(...args: Parameters['find']>) { + return this.#choices.find(...args); + } + + findIndex(...args: Parameters['findIndex']>) { + return this.#choices.findIndex(...args); + } + + findChoiceIndex(...args: Parameters['findIndex']>) { + return this.#realChoices.findIndex(...args); + } + + some(...args: Parameters['some']>) { + return this.#choices.some(...args); + } + + push(...args: ReadonlyArray) { + const objs = args.map((val) => + Separator.isSeparator(val) ? new Separator(val) : new Choice(val), + ); + this.#choices.push(...objs); + this.#realChoices = this.#choices.filter( + (item): item is Choice => !Separator.isSeparator(item) && !item.disabled, + ); + return this.#choices; + } +} diff --git a/packages/inquirer/src/objects/separator.mts b/packages/inquirer/src/objects/separator.mts new file mode 100644 index 000000000..58936f264 --- /dev/null +++ b/packages/inquirer/src/objects/separator.mts @@ -0,0 +1,32 @@ +import colors from 'yoctocolors-cjs'; +import figures from '@inquirer/figures'; + +/** + * Separator object + * Used to space/separate choices group + */ + +export default class Separator { + line: string = colors.dim(Array.from({ length: 15 }).join(figures.line)); + type = 'separator'; + + constructor(line?: Separator | string) { + if (Separator.isSeparator(line)) { + return line; + } else if (line) { + this.line = colors.dim(line); + } + } + + static isSeparator(obj: unknown): obj is Separator { + if (typeof obj !== 'object' || obj === null) { + return false; + } + + return obj instanceof Separator || ('type' in obj && obj.type === 'separator'); + } + + toString(): string { + return this.line; + } +} diff --git a/packages/inquirer/src/prompts/base.mts b/packages/inquirer/src/prompts/base.mts new file mode 100644 index 000000000..bf1bb841b --- /dev/null +++ b/packages/inquirer/src/prompts/base.mts @@ -0,0 +1,220 @@ +/** + * Base prompt implementation + * Should be extended by prompt types. + */ +import colors from 'yoctocolors-cjs'; +import runAsync from 'run-async'; +import { Observable, filter, mergeMap, share, take, takeUntil } from 'rxjs'; +import Choices from '../objects/choices.mjs'; +import ScreenManager from '../utils/screen-manager.mjs'; +import type { InquirerReadline } from '@inquirer/type'; +import type { ChoiceConfig } from '../objects/choice.mjs'; +import Separator from '../objects/separator.mjs'; + +type Optionals = T & { + [K in Keys]?: T[K]; +}; + +export type Answers = Record; + +type QuestionDefaults = { + type: string; + name: string; + message: string; + askAnswered: boolean; + when: boolean | ((answers?: Answers) => boolean | Promise); + validate: ( + input: unknown, + answers?: Answers, + ) => boolean | string | Promise; + filter: (input: unknown, answers?: Answers) => unknown | Promise; + transformer: (input: unknown, answers?: Answers) => string; + default: unknown; + suffix: string; + prefix: string; + validatingText: string; + filteringText: string; + choices: Choices; +}; + +export type BaseQuestion = Omit & + Optionals< + QuestionDefaults, + | 'message' + | 'askAnswered' + | 'when' + | 'validate' + | 'filter' + | 'transformer' + | 'default' + | 'suffix' + | 'prefix' + | 'validatingText' + | 'filteringText' + > & { + choices?: ReadonlyArray; + }; + +export default class Base { + opt: Omit & QuestionDefaults; + status: 'pending' | 'expanded' | 'touched' | 'answered' = 'pending'; + answers?: Answers; + screen: ScreenManager; + rl: InquirerReadline; + done: (value: unknown) => void = () => {}; + + constructor( + question: Question, + rl: InquirerReadline, + answers?: Record, + ) { + this.answers = answers; + + // Set defaults prompt options + this.opt = { + ...question, + validate: question.validate ?? (() => true), + validatingText: question.validatingText ?? '', + filter: question.filter ?? ((val) => val), + filteringText: question.filteringText ?? '', + when: question.when ?? (() => true), + suffix: question.suffix ?? '', + prefix: question.prefix ?? colors.green('?'), + transformer: question.transformer ?? ((val) => val), + message: question.message ?? question.name + ':', + choices: new Choices( + Array.isArray(question.choices) ? question.choices : [], + answers, + ), + }; + + // Make sure name is present + if (!this.opt.name) { + this.throwParamError('name'); + } + + this.rl = rl; + this.screen = new ScreenManager(this.rl); + } + + /** + * Start the Inquiry session and manage output value filtering + */ + run() { + return new Promise((resolve, reject) => { + this._run( + (value) => resolve(value), + (error) => reject(error), + ); + }); + } + + /** + * Default noop (this one should be overwritten in prompts) + */ + _run(cb: (value?: unknown) => void, errorHandler: (error: Error) => void): void; + _run() { + throw new Error('`_run` method must be implemented'); + } + + /** + * Throw an error telling a required parameter is missing + */ + throwParamError(name: string) { + throw new Error('You must provide a `' + name + '` parameter'); + } + + /** + * Called when the UI closes. Override to do any specific cleanup necessary + */ + close() { + this.screen.releaseCursor(); + } + + /** + * Run the provided validation method each time a submit event occur. + */ + handleSubmitEvents( + submit: Observable, + ): { + success: Observable<{ isValid: string; value: Value }>; + error: Observable<{ isValid: string; value: Value }>; + } { + const validate = runAsync(this.opt.validate); + const asyncFilter = runAsync(this.opt.filter); + const validation = submit.pipe( + mergeMap((value: Value) => { + this.startSpinner(String(value), this.opt.filteringText); + return asyncFilter(value, this.answers).then( + (filteredValue: string) => { + this.startSpinner(filteredValue, this.opt.validatingText); + return validate(filteredValue, this.answers).then( + (isValid) => ({ isValid, value: filteredValue }), + (error_) => ({ isValid: error_, value: filteredValue }), + ); + }, + (error_) => ({ isValid: error_ }), + ); + }), + share(), + ); + + const success = validation.pipe( + filter((state) => state.isValid === true), + take(1), + ); + const error = validation.pipe( + filter((state) => state.isValid !== true), + takeUntil(success), + ); + + return { + success, + error, + }; + } + + startSpinner(value: string, bottomContent?: string) { + value = this.getSpinningValue(value); + // If the question will spin, cut off the prefix (for layout purposes) + const content = bottomContent + ? this.getQuestion() + value + : this.getQuestion().slice(this.opt.prefix.length + 1) + value; + + this.screen.renderWithSpinner(content, bottomContent); + } + + /** + * Allow override, e.g. for password prompts + * See: https://github.com/SBoudrias/Inquirer.js/issues/1022 + */ + getSpinningValue(value: string): string { + return value; + } + + /** + * Generate the prompt question string + */ + getQuestion(): string { + let message = + (this.opt.prefix ? this.opt.prefix + ' ' : '') + + colors.bold(this.opt.message) + + this.opt.suffix + + colors.reset(' '); + + // Append the default if available, and if question isn't touched/answered + if ( + this.opt.default != null && + this.status !== 'touched' && + this.status !== 'answered' + ) { + // If default password is supplied, hide it + message += + this.opt.type === 'password' + ? colors.italic(colors.dim('[hidden] ')) + : colors.dim('(' + this.opt.default + ') '); + } + + return message; + } +} diff --git a/packages/inquirer/lib/prompts/checkbox.js b/packages/inquirer/src/prompts/checkbox.mts similarity index 71% rename from packages/inquirer/lib/prompts/checkbox.js rename to packages/inquirer/src/prompts/checkbox.mts index 0a7d1b840..62ac70456 100644 --- a/packages/inquirer/lib/prompts/checkbox.js +++ b/packages/inquirer/src/prompts/checkbox.mts @@ -6,31 +6,46 @@ import ansiEscapes from 'ansi-escapes'; import colors from 'yoctocolors-cjs'; import figures from '@inquirer/figures'; import { map, takeUntil } from 'rxjs'; -import observe from '../utils/events.js'; -import Paginator from '../utils/paginator.js'; -import incrementListIndex from '../utils/incrementListIndex.js'; -import Base from './base.js'; - -export default class CheckboxPrompt extends Base { - constructor(questions, rl, answers) { +import observe from '../utils/events.mjs'; +import Paginator from '../utils/paginator.mjs'; +import incrementListIndex from '../utils/incrementListIndex.mjs'; +import Base, { type BaseQuestion, type Answers } from './base.mjs'; +import Separator from '../objects/separator.mjs'; +import Choice from '../objects/choice.mjs'; +import Choices from '../objects/choices.mjs'; +import type { InquirerReadline } from '@inquirer/type'; + +type Question = BaseQuestion & { loop?: boolean; pageSize?: number; default?: unknown[] }; + +export default class CheckboxPrompt extends Base { + paginator: Paginator; + pointer = 0; + firstRender: boolean = false; + dontShowHints: boolean = false; + selection: string[] = []; + + constructor(questions: Question, rl: InquirerReadline, answers?: Answers) { super(questions, rl, answers); - if (!this.opt.choices) { + if (this.opt.choices.realLength === 0) { this.throwParamError('choices'); } if (Array.isArray(this.opt.default)) { for (const choice of this.opt.choices) { - if (this.opt.default.includes(choice.value)) { + if ( + choice && + !Separator.isSeparator(choice) && + this.opt.default.includes(choice.value) + ) { + // @ts-expect-error 2024-06-29 choice.checked = true; } } } - this.pointer = 0; - // Make sure no default is set (so it won't be printed) - this.opt.default = null; + this.opt.default = undefined; const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop; this.paginator = new Paginator(this.screen, { isInfinite: shouldLoop }); @@ -38,20 +53,17 @@ export default class CheckboxPrompt extends Base { /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; const events = observe(this.rl); - const validation = this.handleSubmitEvents( - events.line.pipe(map(this.getCurrentValue.bind(this))), + const validation = this.handleSubmitEvents( + events.line.pipe(map(() => this.getCurrentValue())), ); - validation.success.forEach(this.onEnd.bind(this)); - validation.error.forEach(this.onError.bind(this)); + validation.success.forEach((state) => this.onEnd(state)); + validation.error.forEach((state) => this.onError(state)); events.normalizedUpKey .pipe(takeUntil(validation.success)) @@ -77,10 +89,8 @@ export default class CheckboxPrompt extends Base { /** * Render the prompt to screen - * @return {CheckboxPrompt} self */ - - render(error) { + render(error?: string) { // Render question let message = this.getQuestion(); let bottomContent = ''; @@ -104,28 +114,28 @@ export default class CheckboxPrompt extends Base { } else { const choicesStr = renderChoices(this.opt.choices, this.pointer); const indexPosition = this.opt.choices.indexOf( + // @ts-expect-error 2024-06-29 this.opt.choices.getChoice(this.pointer), ); const realIndexPosition = - this.opt.choices.reduce((acc, value, i) => { - // Dont count lines past the choice we are looking at + // @ts-expect-error 2024-06-29 + this.opt.choices.reduce((acc: number, value, i): number => { + // Don't count lines past the choice we are looking at if (i > indexPosition) { return acc; } // Add line if it's a separator - if (value.type === 'separator') { + if (Separator.isSeparator(value)) { return acc + 1; } - let l = value.name; // Non-strings take up one line - if (typeof l !== 'string') { + if (typeof value.name !== 'string') { return acc + 1; } // Calculate lines taken up by string - l = l.split('\n'); - return acc + l.length; + return acc + value.name.split('\n').length; }, 0) - 1; message += '\n' + this.paginator.paginate(choicesStr, realIndexPosition, this.opt.pageSize); @@ -160,8 +170,9 @@ export default class CheckboxPrompt extends Base { getCurrentValue() { const choices = this.opt.choices.filter( + // @ts-expect-error 2024-06-29 (choice) => Boolean(choice.checked) && !choice.disabled, - ); + ) as Choice[]; this.selection = choices.map((choice) => choice.short); return choices.map((choice) => choice.value); @@ -193,11 +204,13 @@ export default class CheckboxPrompt extends Base { onAllKey() { const shouldBeChecked = this.opt.choices.some( - (choice) => choice.type !== 'separator' && !choice.checked, + // @ts-expect-error 2024-06-29 + (choice) => !Separator.isSeparator(choice) && !choice.checked, ); this.opt.choices.forEach((choice) => { if (choice.type !== 'separator') { + // @ts-expect-error 2024-06-29 choice.checked = shouldBeChecked; } }); @@ -208,6 +221,7 @@ export default class CheckboxPrompt extends Base { onInverseKey() { this.opt.choices.forEach((choice) => { if (choice.type !== 'separator') { + // @ts-expect-error 2024-06-29 choice.checked = !choice.checked; } }); @@ -218,6 +232,7 @@ export default class CheckboxPrompt extends Base { toggleChoice(index) { const item = this.opt.choices.getChoice(index); if (item !== undefined) { + // @ts-expect-error 2024-06-29 this.opt.choices.getChoice(index).checked = !item.checked; } } @@ -225,16 +240,13 @@ export default class CheckboxPrompt extends Base { /** * Function for rendering checkbox choices - * @param {Number} pointer Position of the pointer - * @return {String} Rendered content */ - -function renderChoices(choices, pointer) { +function renderChoices(choices: Choices, pointer: number) { let output = ''; let separatorOffset = 0; choices.forEach((choice, i) => { - if (choice.type === 'separator') { + if (Separator.isSeparator(choice)) { separatorOffset++; output += ' ' + choice + '\n'; return; @@ -247,6 +259,7 @@ function renderChoices(choices, pointer) { typeof choice.disabled === 'string' ? choice.disabled : 'Disabled' })`; } else { + // @ts-expect-error 2024-06-29 const line = getCheckbox(choice.checked) + ' ' + choice.name; output += i - separatorOffset === pointer @@ -262,10 +275,7 @@ function renderChoices(choices, pointer) { /** * Get the checkbox - * @param {Boolean} checked - add a X or not to the checkbox - * @return {String} Composited checkbox string */ - -function getCheckbox(checked) { +function getCheckbox(checked: boolean): string { return checked ? colors.green(figures.radioOn) : figures.radioOff; } diff --git a/packages/inquirer/lib/prompts/confirm.js b/packages/inquirer/src/prompts/confirm.mts similarity index 59% rename from packages/inquirer/lib/prompts/confirm.js rename to packages/inquirer/src/prompts/confirm.mts index 563883e6b..5ab222529 100644 --- a/packages/inquirer/lib/prompts/confirm.js +++ b/packages/inquirer/src/prompts/confirm.mts @@ -4,24 +4,28 @@ import colors from 'yoctocolors-cjs'; import { take, takeUntil } from 'rxjs'; -import observe from '../utils/events.js'; -import Base from './base.js'; +import observe from '../utils/events.mjs'; +import Base, { type Answers, type BaseQuestion } from './base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; -export default class ConfirmPrompt extends Base { - constructor(questions, rl, answers) { +type Question = BaseQuestion; + +export default class ConfirmPrompt extends Base { + filter: (input?: unknown) => boolean; + + constructor(questions: Question, rl: InquirerReadline, answers?: Answers) { super(questions, rl, answers); let rawDefault = true; + this.filter = (input?: unknown): boolean => { + if (typeof input === 'string' && input !== '') { + if (/^y(es)?/i.test(input)) return true; + if (/^n(o)?/i.test(input)) return false; + } + return rawDefault; + }; - Object.assign(this.opt, { - filter(input) { - if (input != null && input !== '') { - if (/^y(es)?/i.test(input)) return true; - if (/^n(o)?/i.test(input)) return false; - } - return rawDefault; - }, - }); + this.opt.filter = this.filter; if (this.opt.default != null) { rawDefault = Boolean(this.opt.default); @@ -32,11 +36,8 @@ export default class ConfirmPrompt extends Base { /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; // Once user confirm (enter key) @@ -53,10 +54,8 @@ export default class ConfirmPrompt extends Base { /** * Render the prompt to screen - * @return {ConfirmPrompt} self */ - - render(answer) { + render(answer?: string | boolean) { let message = this.getQuestion(); if (typeof answer === 'boolean') { @@ -75,13 +74,12 @@ export default class ConfirmPrompt extends Base { /** * When user press `enter` key */ - - onEnd(input) { + onEnd(input: string) { this.status = 'answered'; - let output = this.opt.filter(input); + let output: boolean | string = this.filter(input); if (this.opt.transformer) { - output = this.opt.transformer(output); + output = this.opt.transformer(output, this.answers); } this.render(output); @@ -92,7 +90,6 @@ export default class ConfirmPrompt extends Base { /** * When user press a key */ - onKeypress() { this.render(); } diff --git a/packages/inquirer/lib/prompts/editor.js b/packages/inquirer/src/prompts/editor.mts similarity index 67% rename from packages/inquirer/lib/prompts/editor.js rename to packages/inquirer/src/prompts/editor.mts index cd5ace309..3e35c93d5 100644 --- a/packages/inquirer/lib/prompts/editor.js +++ b/packages/inquirer/src/prompts/editor.mts @@ -4,22 +4,32 @@ import colors from 'yoctocolors-cjs'; import { editAsync } from 'external-editor'; -import { Subject } from 'rxjs'; -import observe from '../utils/events.js'; -import Base from './base.js'; +import { Subject, Subscription } from 'rxjs'; +import observe from '../utils/events.mjs'; +import Base, { type Answers, type BaseQuestion } from './base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; + +type Question = BaseQuestion & { waitUserInput?: boolean; postfix?: string }; + +export default class EditorPrompt extends Base { + editorResult: Subject = new Subject(); + currentText: string; + lineSubscription?: Subscription; + + constructor(questions: BaseQuestion, rl: InquirerReadline, answers?: Answers) { + super(questions, rl, answers); + + // Prevents default from being printed on screen (can look weird with multiple lines) + this.currentText = this.opt.default ? String(this.opt.default) : ''; + this.opt.default = null; + } -export default class EditorPrompt extends Base { /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; - this.editorResult = new Subject(); - // Open Editor on "line" (Enter Key) const events = observe(this.rl); this.lineSubscription = events.line.subscribe(this.startExternalEditor.bind(this)); @@ -28,12 +38,8 @@ export default class EditorPrompt extends Base { // Trigger Validation when editor closes const validation = this.handleSubmitEvents(this.editorResult); - validation.success.forEach(this.onEnd.bind(this)); - validation.error.forEach(this.onError.bind(this)); - - // Prevents default from being printed on screen (can look weird with multiple lines) - this.currentText = this.opt.default; - this.opt.default = null; + validation.success.forEach((state) => this.onEnd(state)); + validation.error.forEach((state) => this.onError(state)); // Init if (waitUserInput) { @@ -47,10 +53,8 @@ export default class EditorPrompt extends Base { /** * Render the prompt to screen - * @return {EditorPrompt} self */ - - render(error) { + render(error?: string) { let bottomContent = ''; let message = this.getQuestion(); @@ -69,7 +73,6 @@ export default class EditorPrompt extends Base { /** * Launch $EDITOR on user press enter */ - startExternalEditor() { // Pause Readline to prevent stdin and stdout from being modified while the editor is showing this.rl.pause(); @@ -89,13 +92,13 @@ export default class EditorPrompt extends Base { onEnd(state) { this.editorResult.unsubscribe(); - this.lineSubscription.unsubscribe(); - this.answer = state.value; + this.lineSubscription?.unsubscribe(); this.status = 'answered'; + // Re-render prompt this.render(); this.screen.done(); - this.done(this.answer); + this.done(state.value); } onError(state) { diff --git a/packages/inquirer/lib/prompts/expand.js b/packages/inquirer/src/prompts/expand.mts similarity index 65% rename from packages/inquirer/lib/prompts/expand.js rename to packages/inquirer/src/prompts/expand.mts index 5e37de575..a38c07b46 100644 --- a/packages/inquirer/lib/prompts/expand.js +++ b/packages/inquirer/src/prompts/expand.mts @@ -4,13 +4,20 @@ import colors from 'yoctocolors-cjs'; import { map, takeUntil } from 'rxjs'; -import Separator from '../objects/separator.js'; -import observe from '../utils/events.js'; -import Paginator from '../utils/paginator.js'; -import Base from './base.js'; - -export default class ExpandPrompt extends Base { - constructor(questions, rl, answers) { +import Separator from '../objects/separator.mjs'; +import observe from '../utils/events.mjs'; +import Paginator from '../utils/paginator.mjs'; +import Base, { type Answers, type BaseQuestion } from './base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; +import Choices from '../objects/choices.mjs'; +import Choice from '../objects/choice.mjs'; + +export default class ExpandPrompt extends Base { + paginator: Paginator; + answer?: string; + selectedKey: string = ''; + + constructor(questions: BaseQuestion, rl: InquirerReadline, answers?: Answers) { super(questions, rl, answers); if (!this.opt.choices) { @@ -35,6 +42,7 @@ export default class ExpandPrompt extends Base { }; // Setup the default string (capitalize the default key) + // @ts-expect-error 2024-06-29 this.opt.default = this.generateChoicesString(this.opt.choices, this.opt.default); this.paginator = new Paginator(this.screen); @@ -42,20 +50,18 @@ export default class ExpandPrompt extends Base { /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; // Save user answer and update prompt to show selected option. const events = observe(this.rl); const validation = this.handleSubmitEvents( - events.line.pipe(map(this.getCurrentValue.bind(this))), + events.line.pipe(map((line) => this.getCurrentValue(line))), ); - validation.success.forEach(this.onSubmit.bind(this)); - validation.error.forEach(this.onError.bind(this)); + validation.success.forEach((state) => this.onSubmit(state)); + validation.error.forEach((state) => this.onError(state)); + // @ts-expect-error 2024-06-29 this.keypressObs = events.keypress .pipe(takeUntil(validation.success)) .forEach(this.onKeypress.bind(this)); @@ -68,17 +74,17 @@ export default class ExpandPrompt extends Base { /** * Render the prompt to screen - * @return {ExpandPrompt} self */ - - render(error, hint) { + render(error?: string, hint?: string) { let message = this.getQuestion(); let bottomContent = ''; if (this.status === 'answered') { + // @ts-expect-error 2024-06-29 message += colors.cyan(this.answer); } else if (this.status === 'expanded') { const choicesStr = renderChoices(this.opt.choices, this.selectedKey); + // @ts-expect-error 2024-06-29 message += this.paginator.paginate(choicesStr, this.selectedKey, this.opt.pageSize); message += '\n Answer: '; } @@ -96,9 +102,11 @@ export default class ExpandPrompt extends Base { this.screen.render(message, bottomContent); } - getCurrentValue(input) { + getCurrentValue(input?: string) { + // @ts-expect-error 2024-06-29 input ||= this.rawDefault; + // @ts-expect-error 2024-06-29 const selected = this.opt.choices.where({ key: input.toLowerCase().trim() })[0]; if (!selected) { return null; @@ -118,12 +126,14 @@ export default class ExpandPrompt extends Base { this.opt.choices.forEach((choice) => { output += '\n '; - if (choice.type === 'separator') { + if (Separator.isSeparator(choice)) { output += ' ' + choice; return; } + // @ts-expect-error 2024-06-29 let choiceStr = choice.key + ') ' + choice.name; + // @ts-expect-error 2024-06-29 if (this.selectedKey === choice.key) { choiceStr = colors.cyan(choiceStr); } @@ -134,7 +144,7 @@ export default class ExpandPrompt extends Base { return output; } - onError(state) { + onError(state: { value: string; isValid: string }) { if (state.value === 'help') { this.selectedKey = ''; this.status = 'expanded'; @@ -151,7 +161,7 @@ export default class ExpandPrompt extends Base { onSubmit(state) { this.status = 'answered'; - const choice = this.opt.choices.where({ value: state.value })[0]; + const choice = this.opt.choices.where({ value: state.value })[0] as Choice; this.answer = choice.short || choice.name; // Re-render prompt @@ -163,39 +173,45 @@ export default class ExpandPrompt extends Base { /** * When user press a key */ - onKeypress() { this.selectedKey = this.rl.line.toLowerCase(); const selected = this.opt.choices.where({ key: this.selectedKey })[0]; if (this.status === 'expanded') { this.render(); } else { - this.render(null, selected ? selected.name : null); + this.render(undefined, selected ? selected.name : undefined); } } /** * Validate the choices - * @param {Array} choices */ - validateChoices(choices) { + validateChoices(choices: Choices) { let formatError; - const errors = []; - const keymap = {}; - choices.filter(Separator.exclude).forEach((choice) => { - if (!choice.key || choice.key.length !== 1) { - formatError = true; - } - - choice.key = String(choice.key).toLowerCase(); - - if (keymap[choice.key]) { - errors.push(choice.key); - } - - keymap[choice.key] = true; - }); + const errors: string[] = []; + const keymap: Record = {}; + + choices + .filter((item) => !Separator.isSeparator(item)) + .forEach((choice) => { + // @ts-expect-error 2024-06-29 + if (!choice.key || choice.key.length !== 1) { + formatError = true; + } + + // @ts-expect-error 2024-06-29 + choice.key = String(choice.key).toLowerCase(); + + // @ts-expect-error 2024-06-29 + if (keymap[choice.key]) { + // @ts-expect-error 2024-06-29 + errors.push(choice.key); + } + + // @ts-expect-error 2024-06-29 + keymap[choice.key] = true; + }); if (formatError) { throw new Error( @@ -203,7 +219,7 @@ export default class ExpandPrompt extends Base { ); } - if (keymap.h) { + if ('h' in keymap) { throw new Error( 'Reserved key error: `key` param cannot be `h` - this value is reserved.', ); @@ -219,20 +235,18 @@ export default class ExpandPrompt extends Base { /** * Generate a string out of the choices keys - * @param {Array} choices - * @param {Number|String} default - the choice index or name to capitalize - * @return {String} The rendered choices key string */ - generateChoicesString(choices, defaultChoice) { + generateChoicesString(choices: Choices, defaultChoice: number | string): string { let defIndex = choices.realLength - 1; if (typeof defaultChoice === 'number' && this.opt.choices.getChoice(defaultChoice)) { defIndex = defaultChoice; } else if (typeof defaultChoice === 'string') { - const index = choices.realChoices.findIndex(({ value }) => value === defaultChoice); + const index = choices.findChoiceIndex(({ value }) => value === defaultChoice); defIndex = index === -1 ? defIndex : index; } const defStr = this.opt.choices.pluck('key'); + // @ts-expect-error 2024-06-29 this.rawDefault = defStr[defIndex]; defStr[defIndex] = String(defStr[defIndex]).toUpperCase(); return defStr.join(''); @@ -241,22 +255,21 @@ export default class ExpandPrompt extends Base { /** * Function for rendering checkbox choices - * @param {String} pointer Selected key - * @return {String} Rendered content */ - -function renderChoices(choices, pointer) { +function renderChoices(choices: Choices, pointer: string): string { let output = ''; choices.forEach((choice) => { output += '\n '; - if (choice.type === 'separator') { + if (Separator.isSeparator(choice)) { output += ' ' + choice; return; } + // @ts-expect-error 2024-06-29 let choiceStr = choice.key + ') ' + choice.name; + // @ts-expect-error 2024-06-29 if (pointer === choice.key) { choiceStr = colors.cyan(choiceStr); } diff --git a/packages/inquirer/lib/prompts/input.js b/packages/inquirer/src/prompts/input.mts similarity index 76% rename from packages/inquirer/lib/prompts/input.js rename to packages/inquirer/src/prompts/input.mts index 7758fa872..6f1e9d53a 100644 --- a/packages/inquirer/lib/prompts/input.js +++ b/packages/inquirer/src/prompts/input.mts @@ -4,17 +4,15 @@ import colors from 'yoctocolors-cjs'; import { map, takeUntil } from 'rxjs'; -import observe from '../utils/events.js'; -import Base from './base.js'; +import observe from '../utils/events.mjs'; +import Base, { type BaseQuestion } from './base.mjs'; -export default class InputPrompt extends Base { +export default class InputPrompt extends Base { /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; // Once user confirm (enter key) @@ -22,8 +20,8 @@ export default class InputPrompt extends Base { const submit = events.line.pipe(map(this.filterInput.bind(this))); const validation = this.handleSubmitEvents(submit); - validation.success.forEach(this.onEnd.bind(this)); - validation.error.forEach(this.onError.bind(this)); + validation.success.forEach((state) => this.onEnd(state)); + validation.error.forEach((state) => this.onError(state)); events.keypress .pipe(takeUntil(validation.success)) @@ -37,19 +35,20 @@ export default class InputPrompt extends Base { /** * Render the prompt to screen - * @return {InputPrompt} self */ - render(error) { + render(error?: string) { let bottomContent = ''; let appendContent = ''; let message = this.getQuestion(); const { transformer } = this.opt; const isFinal = this.status === 'answered'; + // @ts-expect-error 2024-06-29 appendContent = isFinal ? this.answer : this.rl.line; if (transformer) { + // @ts-expect-error 2024-06-29 message += transformer(appendContent, this.answers, { isFinal }); } else { message += isFinal ? colors.cyan(appendContent) : appendContent; @@ -75,6 +74,7 @@ export default class InputPrompt extends Base { } onEnd(state) { + // @ts-expect-error 2024-06-29 this.answer = state.value; this.status = 'answered'; @@ -86,7 +86,9 @@ export default class InputPrompt extends Base { } onError({ value = '', isValid }) { + // @ts-expect-error 2024-06-29 this.rl.line += value; + // @ts-expect-error 2024-06-29 this.rl.cursor += value.length; this.render(isValid); } diff --git a/packages/inquirer/lib/prompts/list.js b/packages/inquirer/src/prompts/list.mts similarity index 74% rename from packages/inquirer/lib/prompts/list.js rename to packages/inquirer/src/prompts/list.mts index 31fd0f0b0..7c66fe36e 100644 --- a/packages/inquirer/lib/prompts/list.js +++ b/packages/inquirer/src/prompts/list.mts @@ -7,35 +7,40 @@ import colors from 'yoctocolors-cjs'; import figures from '@inquirer/figures'; import runAsync from 'run-async'; import { flatMap, map, take, takeUntil } from 'rxjs'; -import observe from '../utils/events.js'; -import Paginator from '../utils/paginator.js'; -import incrementListIndex from '../utils/incrementListIndex.js'; -import Base from './base.js'; - -export default class ListPrompt extends Base { - constructor(questions, rl, answers) { +import observe from '../utils/events.mjs'; +import Paginator from '../utils/paginator.mjs'; +import incrementListIndex from '../utils/incrementListIndex.mjs'; +import Separator from '../objects/separator.mjs'; +import Base, { type Answers, type BaseQuestion } from './base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; +import Choices from '../objects/choices.mjs'; + +export default class ListPrompt extends Base { + paginator: Paginator; + firstRender: boolean = true; + selected: number = 0; + + constructor(questions: BaseQuestion, rl: InquirerReadline, answers?: Answers) { super(questions, rl, answers); - if (!this.opt.choices) { + if (this.opt.choices.realLength === 0) { this.throwParamError('choices'); } - this.firstRender = true; - this.selected = 0; - const def = this.opt.default; // If def is a Number, then use as index. Otherwise, check for value. if (typeof def === 'number' && def >= 0 && def < this.opt.choices.realLength) { this.selected = def; } else if (typeof def !== 'number' && def != null) { - const index = this.opt.choices.realChoices.findIndex(({ value }) => value === def); + const index = this.opt.choices.findChoiceIndex(({ value }) => value === def); this.selected = Math.max(index, 0); } // Make sure no default is set (so it won't be printed) - this.opt.default = null; + this.opt.default = undefined; + // @ts-expect-error 2024-06-29 const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop; this.paginator = new Paginator(this.screen, { isInfinite: shouldLoop }); } @@ -46,7 +51,7 @@ export default class ListPrompt extends Base { * @return {this} */ - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; const events = observe(this.rl); @@ -86,34 +91,37 @@ export default class ListPrompt extends Base { // Render choices or answer depending on the state if (this.status === 'answered') { + // @ts-expect-error 2024-06-29 message += colors.cyan(this.opt.choices.getChoice(this.selected).short); } else { const choicesStr = listRender(this.opt.choices, this.selected); const indexPosition = this.opt.choices.indexOf( + // @ts-expect-error 2024-06-29 this.opt.choices.getChoice(this.selected), ); const realIndexPosition = - this.opt.choices.reduce((acc, value, i) => { - // Dont count lines past the choice we are looking at + // @ts-expect-error 2024-06-29 + this.opt.choices.reduce((acc: number, value, i): number => { + // Don't count lines past the choice we are looking at if (i > indexPosition) { return acc; } + // Add line if it's a separator - if (value.type === 'separator') { + if (Separator.isSeparator(value)) { return acc + 1; } - let l = value.name; // Non-strings take up one line - if (typeof l !== 'string') { + if (typeof value.name !== 'string') { return acc + 1; } // Calculate lines taken up by string - l = l.split('\n'); - return acc + l.length; + return acc + value.name.split('\n').length; }, 0) - 1; message += + // @ts-expect-error 2024-06-29 '\n' + this.paginator.paginate(choicesStr, realIndexPosition, this.opt.pageSize); } @@ -138,6 +146,7 @@ export default class ListPrompt extends Base { } getCurrentValue() { + // @ts-expect-error 2024-06-29 return this.opt.choices.getChoice(this.selected).value; } @@ -165,15 +174,13 @@ export default class ListPrompt extends Base { /** * Function for rendering list choices - * @param {Number} pointer Position of the pointer - * @return {String} Rendered content */ -function listRender(choices, pointer) { +function listRender(choices: Choices, pointer: number): string { let output = ''; let separatorOffset = 0; choices.forEach((choice, i) => { - if (choice.type === 'separator') { + if (Separator.isSeparator(choice)) { separatorOffset++; output += ' ' + choice + '\n'; return; diff --git a/packages/inquirer/lib/prompts/number.js b/packages/inquirer/src/prompts/number.mts similarity index 81% rename from packages/inquirer/lib/prompts/number.js rename to packages/inquirer/src/prompts/number.mts index 34c934d80..c7f3ddbaf 100644 --- a/packages/inquirer/lib/prompts/number.js +++ b/packages/inquirer/src/prompts/number.mts @@ -2,14 +2,14 @@ * `input` type prompt */ -import Input from './input.js'; +import Input from './input.mjs'; /** - * Extention of the Input prompt specifically for use with number inputs. + * Extension of the Input prompt specifically for use with number inputs. */ export default class NumberPrompt extends Input { - filterInput(input) { + override filterInput(input) { if (input && typeof input === 'string') { input = input.trim(); // Match a number in the input diff --git a/packages/inquirer/lib/prompts/password.js b/packages/inquirer/src/prompts/password.mts similarity index 73% rename from packages/inquirer/lib/prompts/password.js rename to packages/inquirer/src/prompts/password.mts index bbc9d09b8..416bac096 100644 --- a/packages/inquirer/lib/prompts/password.js +++ b/packages/inquirer/src/prompts/password.mts @@ -4,8 +4,8 @@ import colors from 'yoctocolors-cjs'; import { map, takeUntil } from 'rxjs'; -import observe from '../utils/events.js'; -import Base from './base.js'; +import observe from '../utils/events.mjs'; +import Base, { type BaseQuestion } from './base.mjs'; function mask(input, maskChar) { input = String(input); @@ -17,14 +17,15 @@ function mask(input, maskChar) { return Array.from({ length: input.length + 1 }).join(maskChar); } -export default class PasswordPrompt extends Base { +type Question = BaseQuestion & { mask?: string }; + +export default class PasswordPrompt extends Base { + answer?: string; + /** * Start the Inquiry session - * @param {Function} cb Callback when prompt is done - * @return {this} */ - - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; const events = observe(this.rl); @@ -33,8 +34,8 @@ export default class PasswordPrompt extends Base { const submit = events.line.pipe(map(this.filterInput.bind(this))); const validation = this.handleSubmitEvents(submit); - validation.success.forEach(this.onEnd.bind(this)); - validation.error.forEach(this.onError.bind(this)); + validation.success.forEach((state) => this.onEnd(state)); + validation.error.forEach((state) => this.onError(state)); events.keypress .pipe(takeUntil(validation.success)) @@ -48,16 +49,15 @@ export default class PasswordPrompt extends Base { /** * Render the prompt to screen - * @return {PasswordPrompt} self */ - render(error) { + render(error?: string) { let message = this.getQuestion(); let bottomContent = ''; message += this.status === 'answered' - ? this.getMaskedValue(this.answer) + ? this.getMaskedValue(this.answer as string) : this.getMaskedValue(this.rl.line || ''); if (error) { @@ -67,12 +67,13 @@ export default class PasswordPrompt extends Base { this.screen.render(message, bottomContent); } - getMaskedValue(value) { + getMaskedValue(value: string) { if (this.status === 'answered') { return this.opt.mask ? colors.cyan(mask(value, this.opt.mask)) : colors.italic(colors.dim('[hidden]')); } + return this.opt.mask ? mask(value, this.opt.mask) : colors.italic(colors.dim('[input is hidden] ')); @@ -81,23 +82,22 @@ export default class PasswordPrompt extends Base { /** * Mask value during async filter/validation. */ - getSpinningValue(value) { + override getSpinningValue(value: string) { return this.getMaskedValue(value); } /** * When user press `enter` key */ - - filterInput(input) { + filterInput(input: string): string { if (!input) { - return this.opt.default == null ? '' : this.opt.default; + return this.opt.default == null ? '' : String(this.opt.default); } return input; } - onEnd(state) { + onEnd(state: { value: string }) { this.status = 'answered'; this.answer = state.value; diff --git a/packages/inquirer/lib/prompts/rawlist.js b/packages/inquirer/src/prompts/rawlist.mts similarity index 70% rename from packages/inquirer/lib/prompts/rawlist.js rename to packages/inquirer/src/prompts/rawlist.mts index de4a856db..13a318927 100644 --- a/packages/inquirer/lib/prompts/rawlist.js +++ b/packages/inquirer/src/prompts/rawlist.mts @@ -4,28 +4,29 @@ import colors from 'yoctocolors-cjs'; import { map, takeUntil } from 'rxjs'; -import Separator from '../objects/separator.js'; -import observe from '../utils/events.js'; -import Paginator from '../utils/paginator.js'; -import incrementListIndex from '../utils/incrementListIndex.js'; -import Base from './base.js'; - -export default class RawListPrompt extends Base { - constructor(questions, rl, answers) { +import Separator from '../objects/separator.mjs'; +import observe from '../utils/events.mjs'; +import Paginator from '../utils/paginator.mjs'; +import incrementListIndex from '../utils/incrementListIndex.mjs'; +import Base, { type Answers, type BaseQuestion } from './base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; +import Choices from '../objects/choices.mjs'; +import Choice from '../objects/choice.mjs'; + +export default class RawListPrompt extends Base { + hiddenLine = ''; + lastKey = ''; + selected = 0; + rawDefault = 0; + paginator: Paginator; + + constructor(questions: BaseQuestion, rl: InquirerReadline, answers?: Answers) { super(questions, rl, answers); - this.hiddenLine = ''; - this.lastKey = ''; - - if (!this.opt.choices) { + if (this.opt.choices.realLength === 0) { this.throwParamError('choices'); } - this.opt.validChoices = this.opt.choices.filter(Separator.exclude); - - this.selected = 0; - this.rawDefault = 0; - Object.assign(this.opt, { validate(val) { return val != null; @@ -37,7 +38,7 @@ export default class RawListPrompt extends Base { this.selected = def; this.rawDefault = def; } else if (typeof def !== 'number' && def != null) { - const index = this.opt.choices.realChoices.findIndex(({ value }) => value === def); + const index = this.opt.choices.findChoiceIndex(({ value }) => value === def); const safeIndex = Math.max(index, 0); this.selected = safeIndex; this.rawDefault = safeIndex; @@ -46,6 +47,7 @@ export default class RawListPrompt extends Base { // Make sure no default is set (so it won't be printed) this.opt.default = null; + // @ts-expect-error 2024-06-29 const shouldLoop = this.opt.loop === undefined ? true : this.opt.loop; this.paginator = new Paginator(undefined, { isInfinite: shouldLoop }); } @@ -56,16 +58,16 @@ export default class RawListPrompt extends Base { * @return {this} */ - _run(cb) { + override _run(cb: (value?: unknown) => void) { this.done = cb; // Once user confirm (enter key) const events = observe(this.rl); - const submit = events.line.pipe(map(this.getCurrentValue.bind(this))); + const submit = events.line.pipe(map((line) => this.getCurrentValue(line))); const validation = this.handleSubmitEvents(submit); - validation.success.forEach(this.onEnd.bind(this)); - validation.error.forEach(this.onError.bind(this)); + validation.success.forEach((state) => this.onEnd(state)); + validation.error.forEach(() => this.onError()); events.normalizedUpKey .pipe(takeUntil(validation.success)) @@ -87,16 +89,17 @@ export default class RawListPrompt extends Base { * @return {RawListPrompt} self */ - render(error) { + render(error?: string) { // Render question let message = this.getQuestion(); let bottomContent = ''; if (this.status === 'answered') { - message += colors.cyan(this.opt.choices.getChoice(this.selected).short); + message += colors.cyan((this.opt.choices.getChoice(this.selected) as Choice).short); } else { const choicesStr = renderChoices(this.opt.choices, this.selected); message += + // @ts-expect-error 2024-06-29 '\n' + this.paginator.paginate(choicesStr, this.selected, this.opt.pageSize); message += '\n Answer: '; } @@ -113,14 +116,15 @@ export default class RawListPrompt extends Base { * When user press `enter` key */ - getCurrentValue(index) { - if (index == null) { + getCurrentValue(line: string) { + let index: number; + if (line == null) { index = this.rawDefault; - } else if (index === '') { + } else if (line === '') { this.selected = this.selected === undefined ? -1 : this.selected; index = this.selected; } else { - index -= 1; + index = Number(line) - 1; } const choice = this.opt.choices.getChoice(index); @@ -129,6 +133,7 @@ export default class RawListPrompt extends Base { onEnd(state) { this.status = 'answered'; + // @ts-expect-error 2024-06-29 this.answer = state.value; // Re-render prompt @@ -145,17 +150,17 @@ export default class RawListPrompt extends Base { /** * When user press a key */ - onKeypress() { - let index; - + let index: number; if (this.lastKey === 'arrow') { index = this.hiddenLine.length > 0 ? Number(this.hiddenLine) - 1 : 0; } else { index = this.rl.line.length > 0 ? Number(this.rl.line) - 1 : 0; } + this.lastKey = ''; + // @ts-expect-error 2024-06-29 this.selected = this.opt.choices.getChoice(index) ? index : undefined; this.render(); } @@ -163,7 +168,6 @@ export default class RawListPrompt extends Base { /** * When user press up key */ - onUpKey() { this.onArrowKey('up'); } @@ -171,19 +175,17 @@ export default class RawListPrompt extends Base { /** * When user press down key */ - onDownKey() { this.onArrowKey('down'); } /** * When user press up or down key - * @param {String} type Arrow type: up or down */ - - onArrowKey(type) { - this.selected = incrementListIndex(this.selected, type, this.opt) || 0; + onArrowKey(type: 'up' | 'down') { + this.selected = incrementListIndex(this.selected, type, this.opt); this.hiddenLine = String(this.selected + 1); + // @ts-expect-error 2024-06-29 this.rl.line = ''; this.lastKey = 'arrow'; } @@ -191,18 +193,15 @@ export default class RawListPrompt extends Base { /** * Function for rendering list choices - * @param {Number} pointer Position of the pointer - * @return {String} Rendered content */ - -function renderChoices(choices, pointer) { +function renderChoices(choices: Choices, pointer?: number): string { let output = ''; let separatorOffset = 0; choices.forEach((choice, i) => { output += output ? '\n ' : ' '; - if (choice.type === 'separator') { + if (Separator.isSeparator(choice)) { separatorOffset++; output += ' ' + choice; return; diff --git a/packages/inquirer/lib/ui/baseUI.js b/packages/inquirer/src/ui/baseUI.mts similarity index 80% rename from packages/inquirer/lib/ui/baseUI.js rename to packages/inquirer/src/ui/baseUI.mts index a74535618..b1b664c2a 100644 --- a/packages/inquirer/lib/ui/baseUI.js +++ b/packages/inquirer/src/ui/baseUI.mts @@ -1,16 +1,30 @@ import readline from 'node:readline'; +import tty from 'node:tty'; import MuteStream from 'mute-stream'; +import { InquirerReadline } from '@inquirer/type'; + +export type StreamOptions = { + input?: tty.ReadStream; + output?: NodeJS.WritableStream; + skipTTYChecks?: boolean; +}; + +class TTYError extends Error { + isTtyError = true; +} /** * Base interface class other can inherits from */ export default class UI { - constructor(opt) { + rl: InquirerReadline; + activePrompt?: { close(): void }; + + constructor(opt?: StreamOptions) { // Instantiate the Readline interface // @Note: Don't reassign if already present (allow test to override the Stream) - this.rl ||= readline.createInterface(setupReadlineOptions(opt)); - + this.rl ||= readline.createInterface(setupReadlineOptions(opt)) as InquirerReadline; this.rl.resume(); this.onForceClose = this.onForceClose.bind(this); @@ -24,10 +38,9 @@ export default class UI { /** * Handle the ^C exit - * @return {null} */ - onForceClose() { + onForceClose(): void { this.close(); process.kill(process.pid, 'SIGINT'); console.log(''); @@ -55,7 +68,7 @@ export default class UI { } } -function setupReadlineOptions(opt = {}) { +function setupReadlineOptions(opt: StreamOptions = {}) { // Inquirer 8.x: // opt.skipTTYChecks = opt.skipTTYChecks === undefined ? opt.input !== undefined : opt.skipTTYChecks; opt.skipTTYChecks = opt.skipTTYChecks === undefined ? true : opt.skipTTYChecks; @@ -66,11 +79,9 @@ function setupReadlineOptions(opt = {}) { // Check if prompt is being called in TTY environment // If it isn't return a failed promise if (!opt.skipTTYChecks && !input.isTTY) { - const nonTtyError = new Error( + throw new TTYError( 'Prompts can not be meaningfully rendered in non-TTY environments', ); - nonTtyError.isTtyError = true; - throw nonTtyError; } // Add mute capabilities to the output diff --git a/packages/inquirer/lib/ui/bottom-bar.js b/packages/inquirer/src/ui/bottom-bar.mts similarity index 72% rename from packages/inquirer/lib/ui/bottom-bar.js rename to packages/inquirer/src/ui/bottom-bar.mts index af4eba991..9777cded4 100644 --- a/packages/inquirer/lib/ui/bottom-bar.js +++ b/packages/inquirer/src/ui/bottom-bar.mts @@ -3,15 +3,19 @@ */ import { Writable } from 'node:stream'; -import * as rlUtils from '../utils/readline.js'; -import Base from './baseUI.js'; +import * as rlUtils from '../utils/readline.mjs'; +import Base, { type StreamOptions } from './baseUI.mjs'; export default class BottomBar extends Base { - constructor(opt = {}) { + bottomBar: string; + log: Writable; + height: number = 0; + + constructor(opt: StreamOptions & { bottomBar?: string } = {}) { super(opt); this.log = new Writable({ - write: (chunk, encoding, cb) => { + write: (chunk, _encoding, cb) => { this.writeLog(chunk); cb(); }, @@ -23,9 +27,7 @@ export default class BottomBar extends Base { /** * Render the prompt to screen - * @return {BottomBar} self */ - render() { this.write(this.bottomBar); return this; @@ -38,11 +40,8 @@ export default class BottomBar extends Base { /** * Update the bottom bar content and rerender - * @param {String} bottomBar Bottom bar content - * @return {BottomBar} self */ - - updateBottomBar(bottomBar) { + updateBottomBar(bottomBar: string) { rlUtils.clearLine(this.rl, 1); this.rl.output.unmute(); this.clean(); @@ -54,11 +53,8 @@ export default class BottomBar extends Base { /** * Write out log data - * @param {String} data - The log data to be output - * @return {BottomBar} self */ - - writeLog(data) { + writeLog(data: string) { this.rl.output.unmute(); this.clean(); this.rl.output.write(this.enforceLF(data.toString())); @@ -69,25 +65,22 @@ export default class BottomBar extends Base { /** * Make sure line end on a line feed - * @param {String} str Input string - * @return {String} The input string with a final line feed */ - - enforceLF(str) { + enforceLF(str: string): string { return /[\n\r]$/.test(str) ? str : str + '\n'; } /** * Helper for writing message in Prompt - * @param {String} message - The message to be output */ - write(message) { + write(message: string) { const msgLines = message.split(/\n/); this.height = msgLines.length; // Write message to screen and setPrompt to control backspace - this.rl.setPrompt(msgLines.at(-1)); + this.rl.setPrompt(msgLines.at(-1) ?? ''); + // @ts-expect-error MuteStream missing rows/columns properties if (this.rl.output.rows === 0 && this.rl.output.columns === 0) { /* When it's a tty through serial port there's no terminal info and the render will malfunction, so we need enforce the cursor to locate to the leftmost position for rendering. */ diff --git a/packages/inquirer/lib/ui/prompt.js b/packages/inquirer/src/ui/prompt.mts similarity index 59% rename from packages/inquirer/lib/ui/prompt.js rename to packages/inquirer/src/ui/prompt.mts index b06ee2583..c2c6916d1 100644 --- a/packages/inquirer/lib/ui/prompt.js +++ b/packages/inquirer/src/ui/prompt.mts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ const _ = { set: (obj, path = '', value) => { let pointer = obj; @@ -13,7 +14,7 @@ const _ = { pointer = pointer[key]; }); }, - get: (obj, path = '', defaultValue) => { + get: (obj, path = '', defaultValue?: unknown) => { const travel = (regexp) => String.prototype.split .call(path, regexp) @@ -34,42 +35,100 @@ import { publish, reduce, isObservable, + Observable, } from 'rxjs'; import runAsync from 'run-async'; -import * as utils from '../utils/utils.js'; -import Base from './baseUI.js'; +import * as utils from '../utils/utils.mjs'; +import Base, { type StreamOptions } from './baseUI.mjs'; +import type { BaseQuestion as Question } from '../prompts/base.mjs'; +import type { InquirerReadline } from '@inquirer/type'; + +export interface PromptBase { + /** + * Runs the prompt. + * + * @returns + * The result of the prompt. + */ + run(): Promise; + + close(): void; +} + +/** + * Provides the functionality to initialize new prompts. + */ +export interface PromptConstructor { + /** + * Initializes a new instance of a prompt. + * + * @param question + * The question to prompt. + * + * @param readLine + * An object for reading from the command-line. + * + * @param answers + * The answers provided by the user. + */ + new ( + question: any, + readLine: InquirerReadline, + answers: Record, + ): PromptBase; +} + +/** + * Provides a set of prompt-constructors. + */ +export type PromptCollection = Record; + +type Answers = Record; + +type QuestionMap = Record; + +function isQuestionMap( + questions: Question | QuestionMap | Question[], +): questions is QuestionMap { + return Object.values(questions).every( + (maybeQuestion) => + typeof maybeQuestion === 'object' && + !Array.isArray(maybeQuestion) && + maybeQuestion != null, + ); +} /** * Base interface class other can inherits from */ export default class PromptUI extends Base { - constructor(prompts, opt) { + prompts: PromptCollection; + answers: Answers = {}; + process?: Observable; + + constructor(prompts: PromptCollection, opt?: StreamOptions) { super(opt); this.prompts = prompts; } - run(questions, answers) { + run( + questions: Question | QuestionMap | Observable | Question[], + answers?: Answers, + ) { // Keep global reference to the answers this.answers = typeof answers === 'object' ? { ...answers } : {}; - let obs; + let obs: Observable; if (Array.isArray(questions)) { obs = from(questions); } else if (isObservable(questions)) { obs = questions; - } else if ( - Object.values(questions).every( - (maybeQuestion) => - typeof maybeQuestion === 'object' && - !Array.isArray(maybeQuestion) && - maybeQuestion != null, - ) - ) { + } else if (isQuestionMap(questions)) { // Case: Called with a set of { name: question } obs = from( Object.entries(questions).map(([name, question]) => ({ - name, ...question, + name, })), ); } else { @@ -82,6 +141,7 @@ export default class PromptUI extends Base { publish(), // Creates a hot Observable. It prevents duplicating prompts. ); + // @ts-expect-error connect() was deprecated in rxjs this.process.connect(); return this.process @@ -104,12 +164,12 @@ export default class PromptUI extends Base { return this.answers; } - onError(error) { + onError(error: Error) { this.close(); return Promise.reject(error); } - processQuestion(question) { + processQuestion(question: Question) { question = { ...question }; return defer(() => { const obs = of(question); @@ -131,15 +191,21 @@ export default class PromptUI extends Base { }); } - fetchAnswer(question) { + fetchAnswer(question: Question) { const Prompt = this.prompts[question.type]; - this.activePrompt = new Prompt(question, this.rl, this.answers); + + if (!Prompt) { + throw new Error(`Prompt for type ${question.type} not found`); + } + + const activePrompt = new Prompt(question, this.rl, this.answers); + this.activePrompt = activePrompt; return defer(() => - from(this.activePrompt.run().then((answer) => ({ name: question.name, answer }))), + from(activePrompt.run().then((answer) => ({ name: question.name, answer }))), ); } - setDefaultType(question) { + setDefaultType(question: Question) { // Default type to input if (!this.prompts[question.type]) { question.type = 'input'; @@ -148,7 +214,7 @@ export default class PromptUI extends Base { return defer(() => of(question)); } - filterIfRunnable(question) { + filterIfRunnable(question: Question) { if ( question.askAnswered !== true && _.get(this.answers, question.name) !== undefined @@ -164,13 +230,13 @@ export default class PromptUI extends Base { return of(question); } - const { answers } = this; return defer(() => from( - runAsync(question.when)(answers).then((shouldRun) => { + runAsync(question.when)(this.answers).then((shouldRun: boolean) => { if (shouldRun) { return question; } + return; }), ).pipe(filter((val) => val != null)), ); diff --git a/packages/inquirer/lib/utils/events.js b/packages/inquirer/src/utils/events.mts similarity index 67% rename from packages/inquirer/lib/utils/events.js rename to packages/inquirer/src/utils/events.mts index 84af5d660..5e3ee9a2b 100644 --- a/packages/inquirer/lib/utils/events.js +++ b/packages/inquirer/src/utils/events.mts @@ -1,17 +1,20 @@ +import type { InquirerReadline } from '@inquirer/type'; import { fromEvent, filter, map, share, takeUntil } from 'rxjs'; -function normalizeKeypressEvents(value, key) { - return { value, key: key || {} }; +type Keypress = { value: string; key: { name: string; ctrl: boolean } }; + +function normalizeKeypressEvents(value: string, key?: Keypress['key']): Keypress { + return { value, key: key || { name: value, ctrl: false } }; } -export default function observe(rl) { - const keypress = fromEvent(rl.input, 'keypress', normalizeKeypressEvents) +export default function observe(rl: InquirerReadline) { + const keypress = fromEvent(rl.input, 'keypress', normalizeKeypressEvents) .pipe(takeUntil(fromEvent(rl, 'close'))) // Ignore `enter` key. On the readline, we only care about the `line` event. .pipe(filter(({ key }) => key.name !== 'enter' && key.name !== 'return')); return { - line: fromEvent(rl, 'line'), + line: fromEvent(rl, 'line'), keypress, normalizedUpKey: keypress.pipe( @@ -31,7 +34,7 @@ export default function observe(rl) { ), numberKey: keypress.pipe( - filter((e) => e.value && '123456789'.includes(e.value)), + filter((e) => Boolean(e.value && '123456789'.includes(e.value))), map((e) => Number(e.value)), share(), ), diff --git a/packages/inquirer/lib/utils/incrementListIndex.js b/packages/inquirer/src/utils/incrementListIndex.mts similarity index 71% rename from packages/inquirer/lib/utils/incrementListIndex.js rename to packages/inquirer/src/utils/incrementListIndex.mts index 797bc3b1a..c9ec146aa 100644 --- a/packages/inquirer/lib/utils/incrementListIndex.js +++ b/packages/inquirer/src/utils/incrementListIndex.mts @@ -1,4 +1,8 @@ -export default function incrementListIndex(current, dir, opt) { +export default function incrementListIndex( + current: number, + dir: 'up' | 'down', + opt: { loop?: boolean; choices: { realLength: number } }, +): number { const len = opt.choices.realLength; const shouldLoop = 'loop' in opt ? Boolean(opt.loop) : true; if (dir === 'up') { diff --git a/packages/inquirer/lib/utils/paginator.js b/packages/inquirer/src/utils/paginator.mts similarity index 75% rename from packages/inquirer/lib/utils/paginator.js rename to packages/inquirer/src/utils/paginator.mts index 9d1ab69fe..2d874b1ed 100644 --- a/packages/inquirer/lib/utils/paginator.js +++ b/packages/inquirer/src/utils/paginator.mts @@ -1,42 +1,49 @@ import colors from 'yoctocolors-cjs'; +import ScreenManager from './screen-manager.mjs'; /** * The paginator returns a subset of the choices if the list is too long. */ export default class Paginator { + lastIndex = 0; + screen?: ScreenManager; + isInfinite: boolean; + pointer?: number; + /** * @param {import("./screen-manager")} [screen] * @param {{isInfinite?: boolean}} [options] */ - constructor(screen, options = {}) { + constructor(screen?: ScreenManager, options: { isInfinite?: boolean } = {}) { const { isInfinite = true } = options; - this.lastIndex = 0; this.screen = screen; this.isInfinite = isInfinite; } - paginate(output, active, pageSize) { + paginate(output: string, active: number, pageSize?: number) { pageSize ||= 7; let lines = output.split('\n'); if (this.screen) { - lines = this.screen.breakLines(lines); - active = lines + const brokenLines = this.screen.breakLines(lines); + active = brokenLines .map((lineParts) => lineParts.length) .splice(0, active) .reduce((a, b) => a + b, 0); - lines = lines.flat(); + lines = brokenLines.flat(); } // Make sure there's enough lines to paginate if (lines.length <= pageSize) { return output; } + const visibleLines = this.isInfinite ? this.getInfiniteLines(lines, active, pageSize) : this.getFiniteLines(lines, active, pageSize); this.lastIndex = active; + return ( visibleLines.join('\n') + '\n' + @@ -44,7 +51,7 @@ export default class Paginator { ); } - getInfiniteLines(lines, active, pageSize) { + getInfiniteLines(lines: readonly string[], active: number, pageSize: number) { if (this.pointer === undefined) { this.pointer = 0; } @@ -65,13 +72,13 @@ export default class Paginator { return infinite.splice(topIndex, pageSize); } - getFiniteLines(lines, active, pageSize) { + getFiniteLines(lines: readonly string[], active: number, pageSize: number) { let topIndex = active - pageSize / 2; if (topIndex < 0) { topIndex = 0; } else if (topIndex + pageSize > lines.length) { topIndex = lines.length - pageSize; } - return lines.splice(topIndex, pageSize); + return [...lines].splice(topIndex, pageSize); } } diff --git a/packages/inquirer/lib/utils/readline.js b/packages/inquirer/src/utils/readline.mts similarity index 71% rename from packages/inquirer/lib/utils/readline.js rename to packages/inquirer/src/utils/readline.mts index a03e3c655..8e5d61331 100644 --- a/packages/inquirer/lib/utils/readline.js +++ b/packages/inquirer/src/utils/readline.mts @@ -1,4 +1,5 @@ import ansiEscapes from 'ansi-escapes'; +import type { InquirerReadline } from '@inquirer/type'; /** * Move cursor left by `x` @@ -6,7 +7,7 @@ import ansiEscapes from 'ansi-escapes'; * @param {Number} x - How far to go left (default to 1) */ -export const left = function (rl, x) { +export const left = function (rl: InquirerReadline, x: number) { rl.output.write(ansiEscapes.cursorBackward(x)); }; @@ -16,7 +17,7 @@ export const left = function (rl, x) { * @param {Number} x - How far to go left (default to 1) */ -export const right = function (rl, x) { +export const right = function (rl: InquirerReadline, x: number) { rl.output.write(ansiEscapes.cursorForward(x)); }; @@ -26,7 +27,7 @@ export const right = function (rl, x) { * @param {Number} x - How far to go up (default to 1) */ -export const up = function (rl, x) { +export const up = function (rl: InquirerReadline, x: number) { rl.output.write(ansiEscapes.cursorUp(x)); }; @@ -36,7 +37,7 @@ export const up = function (rl, x) { * @param {Number} x - How far to go down (default to 1) */ -export const down = function (rl, x) { +export const down = function (rl: InquirerReadline, x: number) { rl.output.write(ansiEscapes.cursorDown(x)); }; @@ -45,6 +46,6 @@ export const down = function (rl, x) { * @param {Readline} rl - Readline instance * @param {Number} len - number of line to delete */ -export const clearLine = function (rl, len) { +export const clearLine = function (rl: InquirerReadline, len: number) { rl.output.write(ansiEscapes.eraseLines(len)); }; diff --git a/packages/inquirer/lib/utils/screen-manager.js b/packages/inquirer/src/utils/screen-manager.mts similarity index 81% rename from packages/inquirer/lib/utils/screen-manager.js rename to packages/inquirer/src/utils/screen-manager.mts index 4fac13848..0c52e2feb 100644 --- a/packages/inquirer/lib/utils/screen-manager.js +++ b/packages/inquirer/src/utils/screen-manager.mts @@ -1,36 +1,40 @@ +import type { InquirerReadline } from '@inquirer/type'; import ansiEscapes from 'ansi-escapes'; import cliWidth from 'cli-width'; import wrapAnsi from 'wrap-ansi'; import stripAnsi from 'strip-ansi'; import stringWidth from 'string-width'; import ora from 'ora'; -import * as util from './readline.js'; +import * as util from './readline.mjs'; -function height(content) { +function height(content: string) { return content.split('\n').length; } /** @param {string} content */ -function lastLine(content) { - return content.split('\n').pop(); +function lastLine(content: string): string { + return content.split('\n').pop() ?? ''; } export default class ScreenManager { - constructor(rl) { - // These variables are keeping information to allow correct prompt re-rendering - this.height = 0; - this.extraLinesUnderPrompt = 0; + // These variables are keeping information to allow correct prompt re-rendering + height: number = 0; + extraLinesUnderPrompt: number = 0; + rl: InquirerReadline; + spinnerId?: NodeJS.Timeout; + + constructor(rl: InquirerReadline) { this.rl = rl; } - renderWithSpinner(content, bottomContent) { + renderWithSpinner(content: string, bottomContent?: string) { if (this.spinnerId) { clearInterval(this.spinnerId); } - let spinner; let contentFunc; + let spinner: ReturnType; let bottomContentFunc; if (bottomContent) { @@ -45,11 +49,12 @@ export default class ScreenManager { this.spinnerId = setInterval( () => this.render(contentFunc(), bottomContentFunc(), true), + // @ts-expect-error Change in new ora API messing up older version usage spinner.interval, ); } - render(content, bottomContent, spinning = false) { + render(content: string, bottomContent?: string, spinning = false) { if (this.spinnerId && !spinning) { clearInterval(this.spinnerId); } @@ -75,7 +80,7 @@ export default class ScreenManager { this.rl.setPrompt(prompt); // SetPrompt will change cursor position, now we can get correct value - const cursorPos = this.rl._getCursorPos(); + const cursorPos = this.rl.getCursorPos(); const width = this.normalizedCliWidth(); content = this.forceLineReturn(content, width); @@ -121,7 +126,7 @@ export default class ScreenManager { this.rl.output.mute(); } - clean(extraLines) { + clean(extraLines: number) { if (extraLines > 0) { util.down(this.rl, extraLines); } @@ -152,7 +157,7 @@ export default class ScreenManager { /** * @param {string[]} lines */ - breakLines(lines, width = this.normalizedCliWidth()) { + breakLines(lines: readonly string[], width = this.normalizedCliWidth()) { // Break lines who're longer than the cli width so we can normalize the natural line // returns behavior across terminals. // re: trim: false; by default, `wrap-ansi` trims whitespace, which @@ -166,7 +171,7 @@ export default class ScreenManager { /** * @param {string} content */ - forceLineReturn(content, width = this.normalizedCliWidth()) { + forceLineReturn(content: string, width = this.normalizedCliWidth()) { return this.breakLines(content.split('\n'), width).flat().join('\n'); } } diff --git a/packages/inquirer/lib/utils/utils.js b/packages/inquirer/src/utils/utils.mts similarity index 100% rename from packages/inquirer/lib/utils/utils.js rename to packages/inquirer/src/utils/utils.mts diff --git a/packages/inquirer/test/bin/write.js b/packages/inquirer/test/bin/write.mjs similarity index 100% rename from packages/inquirer/test/bin/write.js rename to packages/inquirer/test/bin/write.mjs diff --git a/packages/inquirer/test/helpers/events.js b/packages/inquirer/test/helpers/events.mts similarity index 100% rename from packages/inquirer/test/helpers/events.js rename to packages/inquirer/test/helpers/events.mts diff --git a/packages/inquirer/test/helpers/fixtures.js b/packages/inquirer/test/helpers/fixtures.mts similarity index 84% rename from packages/inquirer/test/helpers/fixtures.js rename to packages/inquirer/test/helpers/fixtures.mts index b46d20049..72980db30 100644 --- a/packages/inquirer/test/helpers/fixtures.js +++ b/packages/inquirer/test/helpers/fixtures.mts @@ -1,4 +1,4 @@ -import inquirer from '../../lib/index.js'; +import inquirer from '../../src/index.mjs'; export default { input: { @@ -24,12 +24,14 @@ export default { list: { message: 'message', name: 'name', + // @ts-expect-error 2024-06-29 choices: ['foo', new inquirer.Separator(), 'bar', 'bum'], }, rawlist: { message: 'message', name: 'name', + // @ts-expect-error 2024-06-29 choices: ['foo', 'bar', new inquirer.Separator(), 'bum'], }, @@ -38,6 +40,7 @@ export default { name: 'name', choices: [ { key: 'a', name: 'acab' }, + // @ts-expect-error 2024-06-29 new inquirer.Separator(), { key: 'b', name: 'bar' }, { key: 'c', name: 'chile' }, @@ -48,6 +51,7 @@ export default { checkbox: { message: 'message', name: 'name', + // @ts-expect-error 2024-06-29 choices: ['choice 1', new inquirer.Separator(), 'choice 2', 'choice 3'], }, diff --git a/packages/inquirer/test/helpers/readline.js b/packages/inquirer/test/helpers/readline.mts similarity index 82% rename from packages/inquirer/test/helpers/readline.js rename to packages/inquirer/test/helpers/readline.mts index 85952fccf..f539f54f4 100644 --- a/packages/inquirer/test/helpers/readline.js +++ b/packages/inquirer/test/helpers/readline.mts @@ -11,7 +11,7 @@ Object.assign(stub, { close: vi.fn(() => stub), pause: vi.fn(() => stub), resume: vi.fn(() => stub), - _getCursorPos: vi.fn(() => ({ cols: 0, rows: 0 })), + getCursorPos: vi.fn(() => ({ cols: 0, rows: 0 })), output: { end: vi.fn(), mute: vi.fn(), @@ -23,11 +23,11 @@ Object.assign(stub, { }, }); -const ReadlineStub = function () { +const ReadlineStub = function (...args) { this.line = ''; this.input = new EventEmitter(); - Reflect.apply(EventEmitter, this, arguments); + Reflect.apply(EventEmitter, this, args); }; inherits(ReadlineStub, EventEmitter); diff --git a/packages/inquirer/test/specs/api.test.js b/packages/inquirer/test/specs/api.test.mts similarity index 91% rename from packages/inquirer/test/specs/api.test.js rename to packages/inquirer/test/specs/api.test.mts index b28792abc..0053db2c9 100644 --- a/packages/inquirer/test/specs/api.test.js +++ b/packages/inquirer/test/specs/api.test.mts @@ -3,10 +3,10 @@ */ import { beforeEach, describe, it, expect } from 'vitest'; -import fixtures from '../helpers/fixtures.js'; -import ReadlineStub from '../helpers/readline.js'; -import inquirer from '../../lib/index.js'; -import { autosubmit } from '../helpers/events.js'; +import fixtures from '../helpers/fixtures.mjs'; +import ReadlineStub from '../helpers/readline.mjs'; +import inquirer from '../../src/index.mjs'; +import { autosubmit } from '../helpers/events.mjs'; // Define prompts and their public API const prompts = [ @@ -45,7 +45,7 @@ const tests = { filter(ctx) { describe('filter API', () => { it('should filter the user input', () => - new Promise((done) => { + new Promise((done) => { ctx.fixture.filter = function () { return 'pass'; }; @@ -60,7 +60,7 @@ const tests = { })); it('should allow filter function to be asynchronous', () => - new Promise((resolve) => { + new Promise((done) => { ctx.fixture.filter = function () { const done = this.async(); setTimeout(() => { @@ -71,7 +71,7 @@ const tests = { const prompt = new ctx.Prompt(ctx.fixture, ctx.rl); prompt.run().then((answer) => { expect(answer).toEqual('pass'); - resolve(); + done(); }); ctx.rl.emit('line', ''); @@ -162,6 +162,7 @@ const tests = { called++; // Make sure returning false won't continue if (called === 2) { + // @ts-expect-error 2024-06-29 done(); return; } @@ -184,6 +185,7 @@ const tests = { called++; // Make sure returning false won't continue if (called === 2) { + // @ts-expect-error 2024-06-29 done(); return; } @@ -286,7 +288,7 @@ const tests = { default(ctx) { describe('default API', () => { it('should allow a default value', () => - new Promise((done) => { + new Promise((done) => { ctx.fixture.default = 'pass'; const prompt = new ctx.Prompt(ctx.fixture, ctx.rl); @@ -300,13 +302,13 @@ const tests = { })); it('should allow a falsy default value', () => - new Promise((done) => { + new Promise((done) => { ctx.fixture.default = 0; const prompt = new ctx.Prompt(ctx.fixture, ctx.rl); prompt.run().then((answer) => { expect(ctx.rl.output.__raw__).toContain('(0)'); - expect(answer).toEqual(0); + expect([0, '0']).toContain(answer); done(); }); @@ -345,9 +347,11 @@ const tests = { prompt.run(); - choices.filter(inquirer.Separator.exclude).forEach((choice) => { - expect(ctx.rl.output.__raw__).toContain(choice.name); - }); + choices + .filter((choice) => !inquirer.Separator.isSeparator(choice)) + .forEach((choice) => { + expect(ctx.rl.output.__raw__).toContain(choice.name); + }); }); }); }, @@ -371,8 +375,11 @@ describe('Prompt public APIs', () => { const ctx = {}; beforeEach(() => { + // @ts-expect-error 2024-06-29 ctx.fixture = { ...fixtures[detail.name] }; + // @ts-expect-error 2024-06-29 ctx.Prompt = inquirer.prompt.prompts[detail.name]; + // @ts-expect-error 2024-06-29 ctx.rl = new ReadlineStub(); }); diff --git a/packages/inquirer/test/specs/inquirer.test.js b/packages/inquirer/test/specs/inquirer.test.mts similarity index 98% rename from packages/inquirer/test/specs/inquirer.test.js rename to packages/inquirer/test/specs/inquirer.test.mts index 62af9c4a8..66ce44c32 100644 --- a/packages/inquirer/test/specs/inquirer.test.js +++ b/packages/inquirer/test/specs/inquirer.test.mts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ /** * Inquirer public API test */ @@ -9,13 +10,15 @@ import tty from 'node:tty'; import { vi, expect, beforeEach, afterEach, describe, it } from 'vitest'; import { Observable } from 'rxjs'; -import inquirer from '../../lib/index.js'; +import inquirer from '../../src/index.mjs'; function throwFunc(step) { throw new Error(`askAnswered Error ${step}`); } class StubPrompt { + question: any; + constructor(question) { this.question = question; } @@ -199,6 +202,8 @@ describe('inquirer.prompt', () => { it('should run asynchronous `message`', async () => { const stubMessage = 'Stub message'; class FakePrompt { + question: any; + constructor(question) { this.question = question; expect(question.message).toEqual(stubMessage); @@ -726,6 +731,7 @@ describe('inquirer.prompt', () => { beforeEach(() => { original = process.stdin.isTTY; + // @ts-expect-error monkey patching delete process.stdin.isTTY; }); @@ -791,6 +797,7 @@ describe('inquirer.prompt', () => { it("Don't throw an exception when run in non-tty and custom input is provided async ", async () => { const localPrompt = inquirer.createPromptModule({ + // @ts-expect-error monkey patching input: new stream.Readable({ // We must have a default read implementation // for this to work, if not it will error out @@ -818,6 +825,7 @@ describe('inquirer.prompt', () => { it('Throw an exception when run in non-tty and custom input is provided with skipTTYChecks: false', async () => { const localPrompt = inquirer.createPromptModule({ + // @ts-expect-error monkey patching input: new stream.Readable(), skipTTYChecks: false, }); diff --git a/packages/inquirer/test/specs/objects/choice.test.js b/packages/inquirer/test/specs/objects/choice.test.mts similarity index 68% rename from packages/inquirer/test/specs/objects/choice.test.js rename to packages/inquirer/test/specs/objects/choice.test.mts index edef67c93..417b7a26f 100644 --- a/packages/inquirer/test/specs/objects/choice.test.js +++ b/packages/inquirer/test/specs/objects/choice.test.mts @@ -1,11 +1,11 @@ import { describe, it, expect } from 'vitest'; -import Choice from '../../../lib/objects/choice.js'; -import Separator from '../../../lib/objects/separator.js'; +import Choice from '../../../src/objects/choice.mjs'; describe('Choice object', () => { it('should normalize accept String as value', () => { const choice = new Choice('foo'); + expect(choice.name).toEqual('foo'); expect(choice.value).toEqual('foo'); }); @@ -29,15 +29,4 @@ describe('Choice object', () => { expect(choice.name).toEqual('foo'); expect(choice.value).toEqual('foo'); }); - - it("shouldn't process Separator object", () => { - const sep = new Choice(new Separator()); - expect(sep).toBeInstanceOf(Separator); - }); - - it("shouldn't process object with property type=separator", () => { - const obj = { type: 'separator' }; - const sep = new Choice(obj); - expect(sep).toEqual(obj); - }); }); diff --git a/packages/inquirer/test/specs/objects/choices.test.js b/packages/inquirer/test/specs/objects/choices.test.mts similarity index 71% rename from packages/inquirer/test/specs/objects/choices.test.js rename to packages/inquirer/test/specs/objects/choices.test.mts index e2d8a0590..57a860b83 100644 --- a/packages/inquirer/test/specs/objects/choices.test.js +++ b/packages/inquirer/test/specs/objects/choices.test.mts @@ -1,8 +1,9 @@ import { describe, it, expect } from 'vitest'; -import inquirer from '../../../lib/index.js'; -import Choices from '../../../lib/objects/choices.js'; -import Choice from '../../../lib/objects/choice.js'; +import inquirer from '../../../src/index.mjs'; +import Choices from '../../../src/objects/choices.mjs'; +import Choice from '../../../src/objects/choice.mjs'; +import Separator from '../../../src/objects/separator.mts'; describe('Choices collection', () => { it('should create Choice object from array member', () => { @@ -13,13 +14,13 @@ describe('Choices collection', () => { it('should support for number', () => { const choices = new Choices([1, 2, 3, 4]); - expect(choices.getChoice(0).value).toEqual(1); + expect(choices.getChoice(0)?.value).toEqual(1); }); it('should not process Separator object', () => { const sep = new inquirer.Separator(); const choices = new Choices(['Bar', sep]); - expect(choices.get(0).name).toEqual('Bar'); + expect(choices.get(0)).to.have.property('name', 'Bar'); expect(choices.get(1)).toEqual(sep); }); @@ -55,7 +56,7 @@ describe('Choices collection', () => { value: 'a', short: 'a', key: 'lab', - disabled: undefined, + disabled: false, }, ]); }); @@ -64,13 +65,15 @@ describe('Choices collection', () => { const raw = ['a', 'b', 'c']; const choices = new Choices(raw); choices.forEach((val, i) => { - expect(val.name).toEqual(raw[i]); + expect((val as Choice).name).toEqual(raw[i]); }); }); it('should façade filter', () => { const choices = new Choices(['a', 'b', 'c']); - const filtered = choices.filter((val) => val.name === 'a'); + const filtered = choices.filter( + (val) => !Separator.isSeparator(val) && val.name === 'a', + ) as Choice[]; expect(filtered.length).toEqual(1); expect(filtered[0].name).toEqual('a'); }); @@ -80,9 +83,14 @@ describe('Choices collection', () => { choices.push('b', new inquirer.Separator()); expect(choices.length).toEqual(4); expect(choices.realLength).toEqual(2); - expect(choices.getChoice(0)).toBeInstanceOf(Choice).and.have.property('name', 'a'); - expect(choices.getChoice(1)).toBeInstanceOf(Choice).and.have.property('name', 'b'); - expect(choices.get(1)).toBeInstanceOf(Choice).and.have.property('disabled', true); + + expect(choices.getChoice(0)).toBeInstanceOf(Choice); + expect(choices.getChoice(0)).to.have.property('name', 'a'); + expect(choices.getChoice(1)).toBeInstanceOf(Choice); + expect(choices.getChoice(1)).to.have.property('name', 'b'); + + expect(choices.get(1)).toBeInstanceOf(Choice); + expect(choices.get(1)).to.have.property('disabled', true); expect(choices.get(3)).toBeInstanceOf(inquirer.Separator); }); }); diff --git a/packages/inquirer/test/specs/objects/separator.test.js b/packages/inquirer/test/specs/objects/separator.test.mts similarity index 80% rename from packages/inquirer/test/specs/objects/separator.test.js rename to packages/inquirer/test/specs/objects/separator.test.mts index 5bb3e8311..13c49bbd4 100644 --- a/packages/inquirer/test/specs/objects/separator.test.js +++ b/packages/inquirer/test/specs/objects/separator.test.mts @@ -1,8 +1,8 @@ import { describe, it, expect } from 'vitest'; import stripAnsi from 'strip-ansi'; -import Separator from '../../../lib/objects/separator.js'; -import inquirer from '../../../lib/index.js'; +import Separator from '../../../src/objects/separator.mjs'; +import inquirer from '../../../src/index.mjs'; describe('Separator constructor', () => { it('should set a default', () => { @@ -25,8 +25,8 @@ describe('Separator constructor', () => { }); it('should expose a helper function to check for separator', () => { - expect(Separator.exclude({})).toEqual(true); - expect(Separator.exclude(new Separator())).toEqual(false); + expect(Separator.isSeparator({})).toEqual(false); + expect(Separator.isSeparator(new Separator())).toEqual(true); }); it("give the type 'separator' to its object", () => { diff --git a/packages/inquirer/test/specs/prompts/base.test.js b/packages/inquirer/test/specs/prompts/base.test.mts similarity index 75% rename from packages/inquirer/test/specs/prompts/base.test.js rename to packages/inquirer/test/specs/prompts/base.test.mts index 550363b50..a7381c24a 100644 --- a/packages/inquirer/test/specs/prompts/base.test.js +++ b/packages/inquirer/test/specs/prompts/base.test.mts @@ -1,7 +1,7 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; +import ReadlineStub from '../../helpers/readline.mjs'; -import Base from '../../../lib/prompts/base.js'; +import Base from '../../../src/prompts/base.mjs'; describe('`base` prompt (e.g. prompt helpers)', () => { let rl; @@ -17,7 +17,9 @@ describe('`base` prompt (e.g. prompt helpers)', () => { }; const base = new Base(question, rl); expect(question).not.toEqual(base.opt); + // @ts-expect-error 2024-06-29 expect(question.name).toEqual(base.opt.name); + // @ts-expect-error 2024-06-29 expect(question.message).toEqual(base.opt.message); }); }); diff --git a/packages/inquirer/test/specs/prompts/checkbox.test.js b/packages/inquirer/test/specs/prompts/checkbox.test.mts similarity index 93% rename from packages/inquirer/test/specs/prompts/checkbox.test.js rename to packages/inquirer/test/specs/prompts/checkbox.test.mts index 9fb06d2ea..594f94f3f 100644 --- a/packages/inquirer/test/specs/prompts/checkbox.test.js +++ b/packages/inquirer/test/specs/prompts/checkbox.test.mts @@ -1,8 +1,8 @@ import { vi, expect, beforeEach, describe, it } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import Checkbox from '../../../lib/prompts/checkbox.js'; +import Checkbox from '../../../src/prompts/checkbox.mjs'; describe('`checkbox` prompt', () => { let fixture; @@ -16,7 +16,7 @@ describe('`checkbox` prompt', () => { }); it('should return a single selected choice in an array', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 1'); @@ -27,11 +27,12 @@ describe('`checkbox` prompt', () => { })); it('should return multiples selected choices in an array', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(2); expect(answer[0]).toEqual('choice 1'); expect(answer[1]).toEqual('choice 2'); + done(); }); rl.input.emit('keypress', ' ', { name: 'space' }); @@ -41,7 +42,7 @@ describe('`checkbox` prompt', () => { })); it('should check defaults choices', () => - new Promise((done) => { + new Promise((done) => { fixture.choices = [ { name: '1', checked: true }, { name: '2', checked: false }, @@ -74,7 +75,7 @@ describe('`checkbox` prompt', () => { }); it('should check defaults choices if given as array of values', () => - new Promise((done) => { + new Promise((done) => { fixture.choices = [{ name: '1' }, { name: '2' }, { name: '3' }]; fixture.default = ['1', '3']; checkbox = new Checkbox(fixture, rl); @@ -88,7 +89,7 @@ describe('`checkbox` prompt', () => { })); it('should toggle choice when hitting space', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 1'); @@ -102,7 +103,7 @@ describe('`checkbox` prompt', () => { })); it('should allow for arrow navigation', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 2'); @@ -118,7 +119,7 @@ describe('`checkbox` prompt', () => { })); it('should allow for vi-style navigation', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 2'); @@ -134,7 +135,7 @@ describe('`checkbox` prompt', () => { })); it('should allow for emacs-style navigation', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 2'); @@ -150,7 +151,7 @@ describe('`checkbox` prompt', () => { })); it('should allow 1-9 shortcut key', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer.length).toEqual(1); expect(answer[0]).toEqual('choice 2'); @@ -196,12 +197,13 @@ describe('`checkbox` prompt', () => { }); it('pagination works with multiline choices', () => - new Promise((done) => { + new Promise((done) => { const multilineFixture = { message: 'message', name: 'name', choices: ['a\n\n', 'b\n\n'], }; + // @ts-expect-error 2024-06-29 const list = new Checkbox(multilineFixture, rl); const spy = vi.spyOn(list.paginator, 'paginate'); list.run().then((answer) => { @@ -212,6 +214,7 @@ describe('`checkbox` prompt', () => { expect(realIndexPosition1).toEqual(2); // 'b\n\n': 1st index, but pagination at 5th index position due to 4 extra newlines expect(realIndexPosition2).toEqual(5); + // @ts-expect-error 2024-06-29 expect(answer[0]).toEqual('b\n\n'); done(); }); @@ -245,7 +248,7 @@ describe('`checkbox` prompt', () => { }); it('skip disabled choices', () => - new Promise((done) => { + new Promise((done) => { checkbox.run().then((answer) => { expect(answer[0]).toEqual('choice 1'); done(); @@ -259,7 +262,7 @@ describe('`checkbox` prompt', () => { })); it("uncheck defaults choices who're disabled", () => - new Promise((done) => { + new Promise((done) => { fixture.choices = [{ name: '1', checked: true, disabled: true }, { name: '2' }]; checkbox = new Checkbox(fixture, rl); checkbox.run().then((answer) => { @@ -278,6 +281,9 @@ describe('`checkbox` prompt', () => { return true; }, }, + { + name: 'dis2', + }, ]; checkbox = new Checkbox(fixture, rl, { foo: 'foo' }); const promise = checkbox.run(); diff --git a/packages/inquirer/test/specs/prompts/confirm.test.js b/packages/inquirer/test/specs/prompts/confirm.test.mts similarity index 80% rename from packages/inquirer/test/specs/prompts/confirm.test.js rename to packages/inquirer/test/specs/prompts/confirm.test.mts index ab84dc69c..45c356827 100644 --- a/packages/inquirer/test/specs/prompts/confirm.test.js +++ b/packages/inquirer/test/specs/prompts/confirm.test.mts @@ -1,8 +1,8 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import Confirm from '../../../lib/prompts/confirm.js'; +import Confirm from '../../../src/prompts/confirm.mjs'; describe('`confirm` prompt', () => { let fixture; @@ -12,11 +12,12 @@ describe('`confirm` prompt', () => { beforeEach(() => { fixture = { ...fixtures.confirm }; rl = new ReadlineStub(); + // @ts-expect-error 2024-06-29 confirm = new Confirm(fixture, rl); }); it('should default to true', () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(rl.output.__raw__).toContain('Y/n'); expect(answer).toEqual(true); @@ -27,8 +28,9 @@ describe('`confirm` prompt', () => { })); it('should allow a default `false` value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = false; + // @ts-expect-error 2024-06-29 const falseConfirm = new Confirm(fixture, rl); falseConfirm.run().then((answer) => { @@ -41,8 +43,9 @@ describe('`confirm` prompt', () => { })); it('should allow a default `true` value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = true; + // @ts-expect-error 2024-06-29 const falseConfirm = new Confirm(fixture, rl); falseConfirm.run().then((answer) => { @@ -55,7 +58,7 @@ describe('`confirm` prompt', () => { })); it("should parse 'Y' value to boolean true", () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(answer).toEqual(true); done(); @@ -65,7 +68,7 @@ describe('`confirm` prompt', () => { })); it("should parse 'Yes' value to boolean true", () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(answer).toEqual(true); done(); @@ -75,7 +78,7 @@ describe('`confirm` prompt', () => { })); it("should parse 'N' value to boolean false", () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(answer).toEqual(false); done(); @@ -85,7 +88,7 @@ describe('`confirm` prompt', () => { })); it("should parse 'No' value to boolean false", () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(answer).toEqual(false); done(); @@ -95,7 +98,7 @@ describe('`confirm` prompt', () => { })); it('should parse every other string value to default (unset)', () => - new Promise((done) => { + new Promise((done) => { confirm.run().then((answer) => { expect(answer).toEqual(true); done(); @@ -105,8 +108,9 @@ describe('`confirm` prompt', () => { })); it('should parse every other string value to default (true)', () => - new Promise((done) => { + new Promise((done) => { fixture.default = true; + // @ts-expect-error 2024-06-29 const trueConfirm = new Confirm(fixture, rl); trueConfirm.run().then((answer) => { @@ -118,8 +122,9 @@ describe('`confirm` prompt', () => { })); it('should parse every other string value to default (false)', () => - new Promise((done) => { + new Promise((done) => { fixture.default = false; + // @ts-expect-error 2024-06-29 const falseConfirm = new Confirm(fixture, rl); falseConfirm.run().then((answer) => { @@ -131,8 +136,9 @@ describe('`confirm` prompt', () => { })); it('should tranform the output based on the boolean value', () => - new Promise((done) => { + new Promise((done) => { fixture.transformer = (value) => (value ? '👍' : '👎'); + // @ts-expect-error 2024-06-29 const confirmOutput = new Confirm(fixture, rl); confirmOutput .run() diff --git a/packages/inquirer/test/specs/prompts/editor.test.js b/packages/inquirer/test/specs/prompts/editor.test.mts similarity index 84% rename from packages/inquirer/test/specs/prompts/editor.test.js rename to packages/inquirer/test/specs/prompts/editor.test.mts index bb1590c0b..9f57e8ce1 100644 --- a/packages/inquirer/test/specs/prompts/editor.test.js +++ b/packages/inquirer/test/specs/prompts/editor.test.mts @@ -1,14 +1,14 @@ import { createRequire } from 'node:module'; import { beforeEach, afterEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import Editor from '../../../lib/prompts/editor.js'; +import Editor from '../../../src/prompts/editor.mjs'; const defaultVisual = process.env.VISUAL; const require = createRequire(import.meta.url); -const writeBin = require.resolve('../../bin/write.js'); +const writeBin = require.resolve('../../bin/write.mjs'); describe.each([ { message: 'testing', expectedAnswer: 'testing' }, diff --git a/packages/inquirer/test/specs/prompts/expand.test.js b/packages/inquirer/test/specs/prompts/expand.test.mts similarity index 84% rename from packages/inquirer/test/specs/prompts/expand.test.js rename to packages/inquirer/test/specs/prompts/expand.test.mts index 90425ddda..a12906328 100644 --- a/packages/inquirer/test/specs/prompts/expand.test.js +++ b/packages/inquirer/test/specs/prompts/expand.test.mts @@ -1,8 +1,8 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import Expand from '../../../lib/prompts/expand.js'; +import Expand from '../../../src/prompts/expand.mjs'; describe('`expand` prompt', () => { let fixture; @@ -12,12 +12,14 @@ describe('`expand` prompt', () => { beforeEach(() => { fixture = { ...fixtures.expand }; rl = new ReadlineStub(); + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); }); it('should throw if `key` is missing', () => { expect(() => { fixture.choices = ['a', 'a']; + // @ts-expect-error 2024-06-29 return new Expand(fixture, rl); }).toThrow(/Format error/); }); @@ -28,6 +30,7 @@ describe('`expand` prompt', () => { { key: 'a', name: 'foo' }, { key: 'a', name: 'foo' }, ]; + // @ts-expect-error 2024-06-29 return new Expand(fixture, rl); }).toThrow(/Duplicate key error/); }); @@ -38,6 +41,7 @@ describe('`expand` prompt', () => { { key: 'a', name: 'foo' }, { key: 'A', name: 'foo' }, ]; + // @ts-expect-error 2024-06-29 return new Expand(fixture, rl); }).toThrow(/Duplicate key error/); }); @@ -45,6 +49,7 @@ describe('`expand` prompt', () => { it('should throw if `key` is `h`', () => { expect(() => { fixture.choices = [{ key: 'h', name: 'foo' }]; + // @ts-expect-error 2024-06-29 return new Expand(fixture, rl); }).toThrow(/Reserved key error/); }); @@ -63,6 +68,7 @@ describe('`expand` prompt', () => { { key: 'a', name: 'A Name', value: 'a value', short: 'ShortA' }, { key: 'b', name: 'B Name', value: 'b value', short: 'ShortB' }, ]; + // @ts-expect-error 2024-06-29 const prompt = new Expand(fixture, rl); const promise = prompt.run(); rl.emit('line', 'b'); @@ -74,8 +80,9 @@ describe('`expand` prompt', () => { }); it('should use a string the `default` value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'chile'; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run().then((answer) => { @@ -86,8 +93,9 @@ describe('`expand` prompt', () => { })); it('should use the `default` argument value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 1; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run().then((answer) => { @@ -98,7 +106,7 @@ describe('`expand` prompt', () => { })); it('should return the user input', () => - new Promise((done) => { + new Promise((done) => { expand.run().then((answer) => { expect(answer).toEqual('bar'); done(); @@ -107,7 +115,7 @@ describe('`expand` prompt', () => { })); it('should strip the user input', () => - new Promise((done) => { + new Promise((done) => { expand.run().then((answer) => { expect(answer).toEqual('bar'); done(); @@ -116,7 +124,7 @@ describe('`expand` prompt', () => { })); it('should have help option', () => - new Promise((done) => { + new Promise((done) => { expand.run().then((answer) => { expect(rl.output.__raw__).toMatch(/a\) acab/); expect(rl.output.__raw__).toMatch(/b\) bar/); @@ -139,6 +147,7 @@ describe('`expand` prompt', () => { it('should display and capitalize the default choice `key`', () => { fixture.default = 1; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run(); @@ -147,6 +156,7 @@ describe('`expand` prompt', () => { it('should display and capitalize the default choice by name value', () => { fixture.default = 'chile'; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run(); @@ -155,6 +165,7 @@ describe('`expand` prompt', () => { it('should display and capitalize the default choice H (Help) `key` if no string default matched', () => { fixture.default = 'chile!'; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run(); @@ -163,6 +174,7 @@ describe('`expand` prompt', () => { it('should display and capitalize the default choice H (Help) `key` if none provided', () => { delete fixture.default; + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run(); @@ -170,7 +182,8 @@ describe('`expand` prompt', () => { }); it("should 'autocomplete' the user input", () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 expand = new Expand(fixture, rl); expand.run(); rl.line = 'a'; diff --git a/packages/inquirer/test/specs/prompts/input.js b/packages/inquirer/test/specs/prompts/input.js deleted file mode 100644 index a3b9cf5c1..000000000 --- a/packages/inquirer/test/specs/prompts/input.js +++ /dev/null @@ -1,116 +0,0 @@ -import { describe, it, beforeEach, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; -import Input from '../../../lib/prompts/input.js'; - -describe('`input` prompt', () => { - beforeEach(function () { - this.fixture = { ...fixtures.input }; - this.rl = new ReadlineStub(); - }); - - it('should use raw value from the user', function (done) { - const input = new Input(this.fixture, this.rl); - - input.run().then((answer) => { - expect(answer).toEqual('Inquirer'); - done(); - }); - - this.rl.emit('line', 'Inquirer'); - }); - - it('should output filtered value', function () { - this.fixture.filter = function () { - return 'pass'; - }; - - const prompt = new Input(this.fixture, this.rl); - const promise = prompt.run(); - this.rl.emit('line', ''); - - return promise.then(() => { - expect(this.rl.output.__raw__).toContain('pass'); - }); - }); - - it('should apply the provided transform to the value', function (done) { - this.fixture.transformer = function (value) { - return [...value].reverse().join(''); - }; - - const prompt = new Input(this.fixture, this.rl); - prompt.run(); - - this.rl.line = 'Inquirer'; - this.rl.input.emit('keypress'); - - setTimeout(() => { - expect(this.rl.output.__raw__).toContain('reriuqnI'); - done(); - }, 10); - }); - - it('should use the answers object in the provided transformer', function (done) { - this.fixture.transformer = function (value, answers) { - return answers.capitalize ? value.toUpperCase() : value; - }; - - const answers = { - capitalize: true, - }; - - const prompt = new Input(this.fixture, this.rl, answers); - prompt.run(); - - this.rl.line = 'inquirer'; - this.rl.input.emit('keypress'); - - setTimeout(() => { - expect(this.rl.output.__raw__).toContain('INQUIRER'); - done(); - }, 200); - }); - - it('should use the flags object in the provided transformer', function (done) { - this.fixture.transformer = function (value, answers, flags) { - const text = answers.capitalize ? value.toUpperCase() : value; - if (flags.isFinal) return text + '!'; - return text; - }; - - const answers = { - capitalize: true, - }; - - const prompt = new Input(this.fixture, this.rl, answers); - prompt.run(); - - this.rl.line = 'inquirer'; - this.rl.input.emit('keypress'); - setTimeout(() => { - expect(this.rl.output.__raw__).toContain('INQUIRER'); - done(); - }, 200); - }); - - it('should clear default on input', function (done) { - const defaultValue = 'default-string'; - const input = new Input( - { - ...this.fixture, - default: defaultValue, - }, - this.rl, - ); - - input.run(); - - this.rl.line = 'inquirer'; - this.rl.input.emit('keypress'); - setTimeout(() => { - expect(this.rl.output.__raw__).toContain(defaultValue); - done(); - }, 200); - }); -}); diff --git a/packages/inquirer/test/specs/prompts/input.mts b/packages/inquirer/test/specs/prompts/input.mts new file mode 100644 index 000000000..d71d66c75 --- /dev/null +++ b/packages/inquirer/test/specs/prompts/input.mts @@ -0,0 +1,124 @@ +import { describe, it, beforeEach, expect } from 'vitest'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; +import Input from '../../../src/prompts/input.mjs'; + +describe('`input` prompt', () => { + let fixture; + let rl; + + beforeEach(function () { + fixture = { ...fixtures.input }; + rl = new ReadlineStub(); + }); + + it('should use raw value from the user', () => + new Promise((done) => { + const input = new Input(fixture, rl); + + input.run().then((answer) => { + expect(answer).toEqual('Inquirer'); + done(); + }); + + rl.emit('line', 'Inquirer'); + })); + + it('should output filtered value', function () { + fixture.filter = function () { + return 'pass'; + }; + + const prompt = new Input(fixture, rl); + const promise = prompt.run(); + rl.emit('line', ''); + + return promise.then(() => { + expect(rl.output.__raw__).toContain('pass'); + }); + }); + + it('should apply the provided transform to the value', () => + new Promise((done) => { + fixture.transformer = function (value) { + return [...value].reverse().join(''); + }; + + const prompt = new Input(fixture, rl); + prompt.run(); + + rl.line = 'Inquirer'; + rl.input.emit('keypress'); + + setTimeout(() => { + expect(rl.output.__raw__).toContain('reriuqnI'); + done(); + }, 10); + })); + + it('should use the answers object in the provided transformer', () => + new Promise((done) => { + fixture.transformer = function (value, answers) { + return answers.capitalize ? value.toUpperCase() : value; + }; + + const answers = { + capitalize: true, + }; + + const prompt = new Input(fixture, rl, answers); + prompt.run(); + + rl.line = 'inquirer'; + rl.input.emit('keypress'); + + setTimeout(() => { + expect(rl.output.__raw__).toContain('INQUIRER'); + done(); + }, 200); + })); + + it('should use the flags object in the provided transformer', () => + new Promise((done) => { + fixture.transformer = function (value, answers, flags) { + const text = answers.capitalize ? value.toUpperCase() : value; + if (flags.isFinal) return text + '!'; + return text; + }; + + const answers = { + capitalize: true, + }; + + const prompt = new Input(fixture, rl, answers); + prompt.run(); + + rl.line = 'inquirer'; + rl.input.emit('keypress'); + setTimeout(() => { + expect(rl.output.__raw__).toContain('INQUIRER'); + done(); + }, 200); + })); + + it('should clear default on input', () => + new Promise((done) => { + const defaultValue = 'default-string'; + const input = new Input( + { + ...fixture, + default: defaultValue, + }, + rl, + ); + + input.run(); + + rl.line = 'inquirer'; + rl.input.emit('keypress'); + setTimeout(() => { + expect(rl.output.__raw__).toContain(defaultValue); + done(); + }, 200); + })); +}); diff --git a/packages/inquirer/test/specs/prompts/list.test.js b/packages/inquirer/test/specs/prompts/list.test.mts similarity index 83% rename from packages/inquirer/test/specs/prompts/list.test.js rename to packages/inquirer/test/specs/prompts/list.test.mts index e0977f785..c61067b7e 100644 --- a/packages/inquirer/test/specs/prompts/list.test.js +++ b/packages/inquirer/test/specs/prompts/list.test.mts @@ -1,22 +1,21 @@ import { vi, expect, beforeEach, describe, it } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import List from '../../../lib/prompts/list.js'; +import List from '../../../src/prompts/list.mjs'; describe('`list` prompt', () => { let fixture; let rl; - // let list; beforeEach(() => { fixture = { ...fixtures.list }; rl = new ReadlineStub(); - // list = new List(fixture, rl); }); it('should default to first choice', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -28,7 +27,8 @@ describe('`list` prompt', () => { })); it('should move selected cursor on keypress', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -41,7 +41,8 @@ describe('`list` prompt', () => { })); it('should allow for arrow navigation', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -56,7 +57,8 @@ describe('`list` prompt', () => { })); it('should allow for vi-style navigation', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -71,7 +73,8 @@ describe('`list` prompt', () => { })); it('should allow for emacs-style navigation', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -99,6 +102,7 @@ describe('`list` prompt', () => { describe('when loop undefined / true', () => { it('loops to bottom when too far up', async () => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); const promise = list.run(); pressKey('up', 2); @@ -107,6 +111,7 @@ describe('`list` prompt', () => { }); it('loops to top when too far down', async () => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); const promise = list.run(); pressKey('down', 3); @@ -117,6 +122,7 @@ describe('`list` prompt', () => { describe('when loop: false', () => { it('stays at top when too far up', async () => { + // @ts-expect-error 2024-06-29 const list = new List(Object.assign(fixture, { loop: false }), rl); const promise = list.run(); pressKey('up', 2); @@ -125,6 +131,7 @@ describe('`list` prompt', () => { }); it('stays at bottom when too far down', async () => { + // @ts-expect-error 2024-06-29 const list = new List(Object.assign(fixture, { loop: false }), rl); const promise = list.run(); pressKey('down', 3); @@ -135,12 +142,14 @@ describe('`list` prompt', () => { }); it('should require a choices array', () => { + // @ts-expect-error 2024-06-29 expect(() => new List({ name: 'foo', message: 'bar' })).toThrow(/choices/); }); it('should allow a numeric default', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 1; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -152,8 +161,9 @@ describe('`list` prompt', () => { })); it('should work from a numeric default being the index', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 1; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -166,8 +176,9 @@ describe('`list` prompt', () => { })); it('should allow a string default being the value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'bar'; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -179,8 +190,9 @@ describe('`list` prompt', () => { })); it('should work from a string default', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'bar'; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -193,8 +205,9 @@ describe('`list` prompt', () => { })); it("shouldn't allow an invalid string default to change position", () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'babar'; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -206,8 +219,9 @@ describe('`list` prompt', () => { })); it("shouldn't allow an invalid index as default", () => - new Promise((done) => { + new Promise((done) => { fixture.default = 4; + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { @@ -219,7 +233,8 @@ describe('`list` prompt', () => { })); it('should allow 1-9 shortcut key', () => - new Promise((done) => { + new Promise((done) => { + // @ts-expect-error 2024-06-29 const list = new List(fixture, rl); list.run().then((answer) => { expect(answer).toEqual('bar'); @@ -231,13 +246,15 @@ describe('`list` prompt', () => { })); it('pagination works with multiline choices', () => - new Promise((done) => { + new Promise((done) => { const multilineFixture = { message: 'message', name: 'name', choices: ['a\n\n', 'b\n\n'], }; + // @ts-expect-error 2024-06-29 const list = new List(multilineFixture, rl); + // @ts-expect-error 2024-06-29 const spy = vi.spyOn(list.paginator, 'paginate'); list.run().then((answer) => { const realIndexPosition1 = spy.mock.calls[0][1]; @@ -255,6 +272,7 @@ describe('`list` prompt', () => { })); it('paginator uses non infinite version with loop:false', () => { + // @ts-expect-error 2024-06-29 const list = new List( { name: 'numbers', @@ -263,11 +281,12 @@ describe('`list` prompt', () => { }, rl, ); + // @ts-expect-error 2024-06-29 expect(list.paginator.isInfinite).equal(false); }); it('should provide answers in the "filter" callback option', () => - new Promise((done) => { + new Promise((done) => { const answers = {}; fixture.filter = function () { return true; diff --git a/packages/inquirer/test/specs/prompts/number.test.js b/packages/inquirer/test/specs/prompts/number.test.mts similarity index 83% rename from packages/inquirer/test/specs/prompts/number.test.js rename to packages/inquirer/test/specs/prompts/number.test.mts index d6634836a..df4983fce 100644 --- a/packages/inquirer/test/specs/prompts/number.test.js +++ b/packages/inquirer/test/specs/prompts/number.test.mts @@ -1,8 +1,8 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import NumberPrompt from '../../../lib/prompts/number.js'; +import NumberPrompt from '../../../src/prompts/number.mjs'; const ACCEPTABLE_ERROR = 0.001; @@ -18,7 +18,7 @@ describe('`number` prompt', () => { }); it('should parse the largest number', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toEqual(Number.MAX_SAFE_INTEGER); done(); @@ -28,7 +28,7 @@ describe('`number` prompt', () => { })); it('should parse the smallest number', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toEqual(Number.MIN_SAFE_INTEGER); done(); @@ -38,7 +38,7 @@ describe('`number` prompt', () => { })); it('should parse an integer', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toEqual(42); done(); @@ -48,7 +48,7 @@ describe('`number` prompt', () => { })); it('should parse a negative integer', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toEqual(-363); done(); @@ -58,7 +58,7 @@ describe('`number` prompt', () => { })); it('should parse a positive float', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeCloseTo(4353.43, ACCEPTABLE_ERROR); done(); @@ -68,7 +68,7 @@ describe('`number` prompt', () => { })); it('should parse a negative float', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeCloseTo(-4353.43, ACCEPTABLE_ERROR); done(); @@ -78,7 +78,7 @@ describe('`number` prompt', () => { })); it('should parse a float with no digits before the decimal', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeCloseTo(0.012_64, ACCEPTABLE_ERROR); done(); @@ -88,7 +88,7 @@ describe('`number` prompt', () => { })); it('should parse a float with no digits after the decimal', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeCloseTo(1234, ACCEPTABLE_ERROR); done(); @@ -98,7 +98,7 @@ describe('`number` prompt', () => { })); it('should parse a float with exponents', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeCloseTo(534e12, ACCEPTABLE_ERROR); done(); @@ -108,7 +108,7 @@ describe('`number` prompt', () => { })); it('should parse any other string as NaN', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeNaN(); done(); @@ -118,7 +118,7 @@ describe('`number` prompt', () => { })); it('should parse the empty string as NaN', () => - new Promise((done) => { + new Promise((done) => { number.run().then((answer) => { expect(answer).toBeNaN(); done(); @@ -128,7 +128,7 @@ describe('`number` prompt', () => { })); it('should return default value if it is set on a bad input', () => - new Promise((done) => { + new Promise((done) => { number.opt.default = 11; number.run().then((answer) => { expect(answer).toEqual(11); diff --git a/packages/inquirer/test/specs/prompts/password.test.js b/packages/inquirer/test/specs/prompts/password.test.mts similarity index 92% rename from packages/inquirer/test/specs/prompts/password.test.js rename to packages/inquirer/test/specs/prompts/password.test.mts index 504c23c90..2a0b1d244 100644 --- a/packages/inquirer/test/specs/prompts/password.test.js +++ b/packages/inquirer/test/specs/prompts/password.test.mts @@ -1,9 +1,9 @@ import stripAnsi from 'strip-ansi'; import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; -import Password from '../../../lib/prompts/password.js'; +import Password from '../../../src/prompts/password.mjs'; function testMasking(rl, mask) { return function (answer) { @@ -64,6 +64,7 @@ describe('`password` prompt', () => { fixture.default = 'Inquirer'; const password = new Password(fixture, rl); const promise = password.run().then((answer) => expect(answer).toEqual('')); + // @ts-expect-error 2024-06-29 password.onKeypress({ name: 'backspace' }); rl.emit('line', ''); return promise; @@ -89,6 +90,7 @@ describe('`password` prompt', () => { const input = 'wvAq82yVujm5S9pf'; // Override screen.render to capture all output + // @ts-expect-error 2024-06-29 const { screen } = password; const { render } = screen; screen.render = (...args) => { diff --git a/packages/inquirer/test/specs/prompts/rawlist.test.js b/packages/inquirer/test/specs/prompts/rawlist.test.mts similarity index 86% rename from packages/inquirer/test/specs/prompts/rawlist.test.js rename to packages/inquirer/test/specs/prompts/rawlist.test.mts index 4d8b1a97d..29fdbd103 100644 --- a/packages/inquirer/test/specs/prompts/rawlist.test.js +++ b/packages/inquirer/test/specs/prompts/rawlist.test.mts @@ -1,7 +1,7 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import ReadlineStub from '../../helpers/readline.js'; -import fixtures from '../../helpers/fixtures.js'; -import Rawlist from '../../../lib/prompts/rawlist.js'; +import ReadlineStub from '../../helpers/readline.mjs'; +import fixtures from '../../helpers/fixtures.mjs'; +import Rawlist from '../../../src/prompts/rawlist.mjs'; describe('`rawlist` prompt', () => { let rl; @@ -11,11 +11,12 @@ describe('`rawlist` prompt', () => { beforeEach(() => { rl = new ReadlineStub(); fixture = { ...fixtures.rawlist }; + // @ts-expect-error 2024-06-29 rawlist = new Rawlist(fixture, rl); }); it('should default to first choice', () => - new Promise((done) => { + new Promise((done) => { rawlist.run().then((answer) => { expect(answer).toEqual('foo'); done(); @@ -25,7 +26,7 @@ describe('`rawlist` prompt', () => { })); it('should select given index', () => - new Promise((done) => { + new Promise((done) => { rawlist.run().then((answer) => { expect(answer).toEqual('bar'); done(); @@ -47,6 +48,7 @@ describe('`rawlist` prompt', () => { it('should require a choices array', () => { const mkPrompt = function () { + // @ts-expect-error 2024-06-29 return new Rawlist({ name: 'foo', message: 'bar' }); }; @@ -54,8 +56,9 @@ describe('`rawlist` prompt', () => { }); it('should allow a default index', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 1; + // @ts-expect-error 2024-06-29 const list = new Rawlist(fixture, rl); list.run().then((answer) => { @@ -67,8 +70,9 @@ describe('`rawlist` prompt', () => { })); it("shouldn't allow an invalid index as default", () => - new Promise((done) => { + new Promise((done) => { fixture.default = 4; + // @ts-expect-error 2024-06-29 const list = new Rawlist(fixture, rl); list.run().then((answer) => { @@ -80,8 +84,9 @@ describe('`rawlist` prompt', () => { })); it('should allow string default being the value', () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'bum'; + // @ts-expect-error 2024-06-29 const list = new Rawlist(fixture, rl); list.run().then((answer) => { @@ -93,8 +98,9 @@ describe('`rawlist` prompt', () => { })); it("shouldn't allow an invalid string default to change position", () => - new Promise((done) => { + new Promise((done) => { fixture.default = 'bumby'; + // @ts-expect-error 2024-06-29 const list = new Rawlist(fixture, rl); list.run().then((answer) => { @@ -106,7 +112,7 @@ describe('`rawlist` prompt', () => { })); it('should allow for arrow navigation', () => - new Promise((done) => { + new Promise((done) => { rawlist.run().then((answer) => { expect(answer).toEqual('bar'); done(); @@ -119,7 +125,7 @@ describe('`rawlist` prompt', () => { })); it('should allow for arrow navigation after invalid input', () => - new Promise((done) => { + new Promise((done) => { rawlist .run() .then((answer) => { @@ -165,6 +171,7 @@ describe('`rawlist` prompt', () => { describe('when loop: false', () => { beforeEach(() => { + // @ts-expect-error 2024-06-29 rawlist = new Rawlist(Object.assign(fixture, { loop: false }), rl); }); diff --git a/packages/inquirer/test/specs/utils/paginator.test.js b/packages/inquirer/test/specs/utils/paginator.test.mts similarity index 97% rename from packages/inquirer/test/specs/utils/paginator.test.js rename to packages/inquirer/test/specs/utils/paginator.test.mts index 0d01bf7df..696553c7e 100644 --- a/packages/inquirer/test/specs/utils/paginator.test.js +++ b/packages/inquirer/test/specs/utils/paginator.test.mts @@ -1,5 +1,5 @@ import { beforeEach, describe, it, expect } from 'vitest'; -import Paginator from '../../../lib/utils/paginator.js'; +import Paginator from '../../../src/utils/paginator.mjs'; const output = `\ a @@ -81,6 +81,7 @@ b`); beforeEach(() => { paginator = new Paginator(undefined, { isInfinite: false }); }); + it('shows start for as long as possible', () => { expect(getPage(paginator, 0)).equal(`\ a diff --git a/packages/inquirer/tsconfig.cjs.json b/packages/inquirer/tsconfig.cjs.json new file mode 100644 index 000000000..ec8144327 --- /dev/null +++ b/packages/inquirer/tsconfig.cjs.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["es2023"], + "target": "es6", + "module": "commonjs", + "moduleResolution": "node10", + "outDir": "dist/cjs" + } +} diff --git a/packages/inquirer/tsconfig.json b/packages/inquirer/tsconfig.json new file mode 100644 index 000000000..d599bba87 --- /dev/null +++ b/packages/inquirer/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src"], + "exclude": ["**/*.test.mts"], + "compilerOptions": { + "lib": ["es2023"], + "target": "es2022", + "module": "NodeNext", + "moduleResolution": "nodenext", + "outDir": "dist/esm", + "declaration": false, + "noImplicitAny": false + } +} diff --git a/yarn.lock b/yarn.lock index ad8fa5e31..3a0728ea4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -599,7 +599,7 @@ __metadata: languageName: unknown linkType: soft -"@inquirer/type@npm:^1.4.0, @inquirer/type@workspace:packages/type": +"@inquirer/type@npm:^1.3.3, @inquirer/type@npm:^1.4.0, @inquirer/type@workspace:packages/type": version: 0.0.0-use.local resolution: "@inquirer/type@workspace:packages/type" dependencies: @@ -4441,6 +4441,7 @@ __metadata: resolution: "inquirer@workspace:packages/inquirer" dependencies: "@inquirer/figures": "npm:^1.0.3" + "@inquirer/type": "npm:^1.3.3" ansi-escapes: "npm:^4.3.2" cli-width: "npm:^4.1.0" external-editor: "npm:^3.1.0"