From 222487114fb03cfe9a99ff2969faf1e5f889d4da Mon Sep 17 00:00:00 2001 From: Christopher Sundersingh <83315412+sundersc@users.noreply.github.com> Date: Tue, 3 Aug 2021 13:20:38 -0700 Subject: [PATCH] Revert "feat: create new amplify-prompts package to handle all terminal interactions (#7774)" This reverts commit 39b326202283f402f82d7e38a830acdc3845a8d7. --- .gitignore | 1 - packages/amplify-cli-core/src/index.ts | 39 --- .../amplify-helpers/confirm-prompt.ts | 3 - .../amplify-helpers/input-validation.ts | 1 - .../src/extensions/amplify-helpers/print.ts | 18 -- packages/amplify-prompts/jest.config.js | 7 - packages/amplify-prompts/package.json | 35 --- .../src/__tests__/formatter.test.ts | 17 -- .../src/__tests__/printer.test.ts | 112 ------- .../src/__tests__/prompter.test.ts | 130 --------- .../src/__tests__/validators.test.ts | 107 ------- packages/amplify-prompts/src/demo/demo.ts | 138 --------- packages/amplify-prompts/src/flags.ts | 14 - packages/amplify-prompts/src/formatter.ts | 15 - packages/amplify-prompts/src/index.ts | 4 - packages/amplify-prompts/src/printer.ts | 62 ---- packages/amplify-prompts/src/prompter.ts | 275 ------------------ packages/amplify-prompts/src/validators.ts | 61 ---- packages/amplify-prompts/tsconfig.json | 8 - yarn.lock | 10 +- 20 files changed, 1 insertion(+), 1056 deletions(-) delete mode 100644 packages/amplify-prompts/jest.config.js delete mode 100644 packages/amplify-prompts/package.json delete mode 100644 packages/amplify-prompts/src/__tests__/formatter.test.ts delete mode 100644 packages/amplify-prompts/src/__tests__/printer.test.ts delete mode 100644 packages/amplify-prompts/src/__tests__/prompter.test.ts delete mode 100644 packages/amplify-prompts/src/__tests__/validators.test.ts delete mode 100644 packages/amplify-prompts/src/demo/demo.ts delete mode 100644 packages/amplify-prompts/src/flags.ts delete mode 100644 packages/amplify-prompts/src/formatter.ts delete mode 100644 packages/amplify-prompts/src/index.ts delete mode 100644 packages/amplify-prompts/src/printer.ts delete mode 100644 packages/amplify-prompts/src/prompter.ts delete mode 100644 packages/amplify-prompts/src/validators.ts delete mode 100644 packages/amplify-prompts/tsconfig.json diff --git a/.gitignore b/.gitignore index cf495d96c71..7ef57f62877 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,6 @@ packages/amplify-migration-tests/amplify-migration-reports packages/amplify-migration-tests/lib packages/amplify-headless-interface/lib packages/amplify-provider-awscloudformation/lib -packages/amplify-prompts/lib packages/amplify-util-import/lib packages/amplify-util-headless-input/lib packages/*/node_modules diff --git a/packages/amplify-cli-core/src/index.ts b/packages/amplify-cli-core/src/index.ts index 97a6fa83839..351c80ca04e 100644 --- a/packages/amplify-cli-core/src/index.ts +++ b/packages/amplify-cli-core/src/index.ts @@ -30,9 +30,6 @@ export type $TSAny = any; // Use it for all CLI Context class references, it enables a quick way to see what we have on the context export type $TSContext = { amplify: AmplifyToolkit; - /** - * @deprecated Use printer from package amplify-prompts instead - */ print: IContextPrint; migrationInfo: $TSAny; projectHasMobileHubResources: boolean; @@ -52,49 +49,16 @@ export type CategoryName = string; export type ResourceName = string; export type IContextPrint = { - /** - * @deprecated Use printer.info from amplify-prommpts instead - */ info: (message: string) => void; - /** - * @deprecated Why are you using this? If you really need it, implement it in amplify-prompts printer.ts - */ fancy: (message?: string) => void; - /** - * @deprecated Use printer.warn from amplify-prompts instead - */ warning: (message: string) => void; - /** - * @deprecated Use printer.error from amplify-prompts instead - */ error: (message: string) => void; - /** - * @deprecated Use printer.success from amplify-prommpts instead - */ success: (message: string) => void; - /** - * @deprecated The next time we refactor code that uses this function, refactor the table function into formatter.ts from amplify-prompts and use that instead - */ table: (data: string[][], options?: { format?: 'markdown' | 'lean' }) => void; - /** - * @deprecated Use printer.debug from amplify-prompts instead - */ debug: (message: string) => void; - /** - * @deprecated Use printer.info from amplify-prompts and specify color - */ green: (message: string) => void; - /** - * @deprecated Use printer.info from amplify-prompts and specify color - */ yellow: (message: string) => void; - /** - * @deprecated Use printer.info from amplify-prompts and specify color - */ red: (message: string) => void; - /** - * @deprecated Use printer.info from amplify-prompts and specify color - */ blue: (message: string) => void; }; @@ -224,9 +188,6 @@ interface AmplifyToolkit { getResourceStatus: (category?: $TSAny, resourceName?: $TSAny, providerName?: $TSAny, filteredResources?: $TSAny) => $TSAny; getResourceOutputs: () => $TSAny; getWhen: () => $TSAny; - /** - * @deprecated Use validators from amplify-prompts or add a new validator in that module - */ inputValidation: (input: $TSAny) => (value: $TSAny) => boolean | string; listCategories: () => $TSAny; makeId: (n?: number) => string; diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/confirm-prompt.ts b/packages/amplify-cli/src/extensions/amplify-helpers/confirm-prompt.ts index a4f95b02b05..8c22fc9a0d5 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/confirm-prompt.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/confirm-prompt.ts @@ -1,8 +1,5 @@ import * as inquirer from 'inquirer'; -/** - * @deprecated Use confirmContinue from ammplify-prompts instead - */ export async function confirmPrompt(message: string, defaultValue = true) { const ans = await inquirer.prompt({ name: 'yesno', diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/input-validation.ts b/packages/amplify-cli/src/extensions/amplify-helpers/input-validation.ts index 9cf0e7c4cd1..e7f8e8d8416 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/input-validation.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/input-validation.ts @@ -1,5 +1,4 @@ /** - * @deprecated Use validators from amplify-prompts or add a new validator to that module * question is either of the legacy form: * { * validation: { diff --git a/packages/amplify-cli/src/extensions/amplify-helpers/print.ts b/packages/amplify-cli/src/extensions/amplify-helpers/print.ts index 307340a1459..aaafbd387aa 100644 --- a/packages/amplify-cli/src/extensions/amplify-helpers/print.ts +++ b/packages/amplify-cli/src/extensions/amplify-helpers/print.ts @@ -21,37 +21,22 @@ type CLIPrintColors = typeof importedColors & { const colors = importedColors as CLIPrintColors; -/** - * @deprecated Use printer.info from amplify-prompts instead - */ function info(message) { console.log(colors.info(message)); } -/** - * @deprecated Use printer.warn from amplify-prompts instead - */ function warning(message) { console.log(colors.warning(message)); } -/** - * @deprecated Use printer.error from amplify-prompts instead - */ function error(message) { console.log(colors.error(message)); } -/** - * @deprecated Use printer.success from amplify-prompts instead - */ function success(message) { console.log(colors.success(message)); } -/** - * @deprecated Use printer.debug from amplify-prompts instead - */ function debug(message, title = 'DEBUG') { const topLine = `vvv -----[ ${title} ]----- vvv`; const botLine = `^^^ -----[ ${title} ]----- ^^^`; @@ -61,9 +46,6 @@ function debug(message, title = 'DEBUG') { console.log(colors.rainbow(botLine)); } -/** - * @deprecated The next time we refactor code that uses this function, refactor the table function into formatter.ts from amplify-prompts and use that instead - */ function table(data, options: any = {}) { let t: CLITable.Table; switch (options.format) { diff --git a/packages/amplify-prompts/jest.config.js b/packages/amplify-prompts/jest.config.js deleted file mode 100644 index 5e50958850b..00000000000 --- a/packages/amplify-prompts/jest.config.js +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - roots: ['/src'], - testMatch: ['**/__tests__/**/*.+(ts|tsx|js)', '**/?(*.)+(spec|test).+(ts|tsx|js)'], - transform: { - '^.+\\.(ts|tsx)$': 'ts-jest', - }, -}; diff --git a/packages/amplify-prompts/package.json b/packages/amplify-prompts/package.json deleted file mode 100644 index 4f9eb26444b..00000000000 --- a/packages/amplify-prompts/package.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "name": "amplify-prompts", - "version": "1.0.0", - "description": "Utility functions for Amplify CLI terminal I/O", - "main": "lib/index.js", - "scripts": { - "build": "tsc", - "clean": "rimraf lib tsconfig.tsbuildinfo", - "demo": "yarn build && node lib/demo/demo.js", - "test": "jest", - "watch": "tsc -w" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/aws-amplify/amplify-cli.git" - }, - "keywords": [ - "amplify", - "cli", - "prompts" - ], - "author": "Amazon Web Services", - "license": "Apache-2.0", - "bugs": { - "url": "https://github.com/aws-amplify/amplify-cli/issues" - }, - "homepage": "https://github.com/aws-amplify/amplify-cli#readme", - "dependencies": { - "chalk": "^4.1.1", - "enquirer": "^2.3.6" - }, - "devDependencies": { - "rimraf": "^3.0.2" - } -} diff --git a/packages/amplify-prompts/src/__tests__/formatter.test.ts b/packages/amplify-prompts/src/__tests__/formatter.test.ts deleted file mode 100644 index d65c37d5492..00000000000 --- a/packages/amplify-prompts/src/__tests__/formatter.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { printer } from '../printer'; -import { formatter } from '../formatter'; - -jest.mock('../printer'); - -const printer_mock = printer as jest.Mocked; - -describe('list', () => { - beforeEach(jest.clearAllMocks); - it('prints list items at info level', () => { - const items = ['item1', 'item2']; - formatter.list(items); - expect(printer_mock.info.mock.calls.length).toBe(2); - expect(printer_mock.info.mock.calls[0][0]).toMatchInlineSnapshot(`"- item1"`); - expect(printer_mock.info.mock.calls[1][0]).toMatchInlineSnapshot(`"- item2"`); - }); -}); diff --git a/packages/amplify-prompts/src/__tests__/printer.test.ts b/packages/amplify-prompts/src/__tests__/printer.test.ts deleted file mode 100644 index 40844a08e37..00000000000 --- a/packages/amplify-prompts/src/__tests__/printer.test.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { AmplifyPrinter } from '../printer'; -import os from 'os'; -import * as flags from '../flags'; -const writeStream_stub = ({ - write: jest.fn(), -} as unknown) as jest.Mocked; - -jest.mock('../flags'); -jest.mock('os'); - -type Writeable = { -readonly [P in keyof T]: T[P] }; - -const flags_mock = flags as jest.Mocked>; - -const testInput = 'this is a test line'; - -const printer = new AmplifyPrinter(writeStream_stub); - -const os_mock = os as jest.Mocked>; - -os_mock.EOL = '\n'; - -beforeEach(() => { - jest.clearAllMocks(); - flags_mock.isDebug = false; - flags_mock.isSilent = false; - flags_mock.isYes = false; -}); - -it('prints debug lines when debug flag is set', () => { - flags_mock.isDebug = true; - printer.debug(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "this is a test line - " - `); -}); - -it('does not print debug lines by default', () => { - printer.debug(testInput); - expect(writeStream_stub.write.mock.calls.length).toBe(0); -}); - -it('prints info line by default', () => { - printer.info(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "this is a test line - " - `); -}); - -it('prints info line in specified color', () => { - printer.info(testInput, 'blue'); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "this is a test line - " - `); -}); - -it('does not print info line when silent flag is set', () => { - flags_mock.isSilent = true; - printer.info(testInput); - expect(writeStream_stub.write.mock.calls.length).toBe(0); -}); - -it('prints success line by default', () => { - printer.success(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "✅ this is a test line - " - `); -}); - -it('does not print success line when silent flag is set', () => { - flags_mock.isSilent = true; - printer.success(testInput); - expect(writeStream_stub.write.mock.calls.length).toBe(0); -}); - -it('prints warn line by default', () => { - printer.warn(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "⚠️ this is a test line - " - `); -}); - -it('prints warn line when silent flag is set', () => { - flags_mock.isSilent = true; - printer.warn(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "⚠️ this is a test line - " - `); -}); - -it('prints error line by default', () => { - printer.error(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "🛑 this is a test line - " - `); -}); - -it('prints error line when silent flag is set', () => { - flags_mock.isSilent = true; - printer.error(testInput); - expect(writeStream_stub.write.mock.calls[0][0]).toMatchInlineSnapshot(` - "🛑 this is a test line - " - `); -}); diff --git a/packages/amplify-prompts/src/__tests__/prompter.test.ts b/packages/amplify-prompts/src/__tests__/prompter.test.ts deleted file mode 100644 index 47a89d83f26..00000000000 --- a/packages/amplify-prompts/src/__tests__/prompter.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { prompter } from '../prompter'; -import { prompt } from 'enquirer'; -import * as flags from '../flags'; - -jest.mock('../flags'); - -type Writeable = { -readonly [P in keyof T]: T[P] }; - -const flags_mock = flags as jest.Mocked>; - -jest.mock('enquirer', () => ({ - prompt: jest.fn(), -})); - -const prompt_mock = prompt as jest.MockedFunction; - -beforeEach(() => { - jest.clearAllMocks(); - flags_mock.isYes = false; -}); - -describe('confirmContinue', () => { - it('returns true if yes flag set', async () => { - flags_mock.isYes = true; - expect(await prompter.confirmContinue()).toBe(true); - expect(prompt_mock.mock.calls.length).toBe(0); - }); - - it('returns prompt response by default', async () => { - prompt_mock.mockResolvedValueOnce({ result: true }); - expect(await prompter.confirmContinue()).toBe(true); - - prompt_mock.mockResolvedValueOnce({ result: false }); - expect(await prompter.confirmContinue()).toBe(false); - }); -}); - -describe('yesOrNo', () => { - it('returns default value if yes flag set', async () => { - flags_mock.isYes = true; - expect(await prompter.yesOrNo('test message', false)).toBe(false); - expect(await prompter.yesOrNo('test message', true)).toBe(true); - }); - - it('returns prompt response by default', async () => { - prompt_mock.mockResolvedValueOnce({ result: true }); - expect(await prompter.yesOrNo('test message', false)).toBe(true); - }); -}); - -describe('input', () => { - it('throws if yes flag set and no initial value', async () => { - flags_mock.isYes = true; - expect(() => prompter.input('test message')).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot prompt for [test message] when '--yes' flag is set"`, - ); - }); - - it('returns initial value without prompt if yes flag set', async () => { - flags_mock.isYes = true; - expect(await prompter.input('test message', { initial: 'initial value' })).toEqual('initial value'); - expect(prompt_mock.mock.calls.length).toBe(0); - }); - - it('returns prompt response if no transformer present', async () => { - const result = 'this is the result'; - prompt_mock.mockResolvedValueOnce({ result }); - expect(await prompter.input('test message')).toEqual(result); - }); - - it('returns transformed response if transformer present', async () => { - const promptResponse = 'this is the result'; - const transformedValue = 'transformed value'; - prompt_mock.mockResolvedValueOnce({ result: promptResponse }); - expect(await prompter.input('test message', { transform: input => transformedValue })).toEqual(transformedValue); - }); - - it('transforms each input part separately when "many" specified', async () => { - prompt_mock.mockResolvedValueOnce({ result: ['10', '20'] }); - expect( - await prompter.input<'many'>('test message', { returnSize: 'many', transform: input => `${input}suffix` }), - ).toEqual(['10suffix', '20suffix']); - }); -}); - -describe('pick', () => { - it('throws if yes flag set and multiple options provided', async () => { - flags_mock.isYes = true; - expect(() => prompter.pick('test message', ['opt1', 'opt2'])).rejects.toThrowErrorMatchingInlineSnapshot( - `"Cannot prompt for [test message] when '--yes' flag is set"`, - ); - }); - - it('returns single option when yes flag set if only one option is provided', async () => { - flags_mock.isYes = true; - expect(await prompter.pick('test message', ['opt1'])).toEqual('opt1'); - expect(prompt_mock.mock.calls.length).toBe(0); - }); - - it('returns initial selection if specified when yes flag is set', async () => { - flags_mock.isYes = true; - const result = await prompter.pick<'many'>('test message', ['opt1', 'opt2', 'opt3'], { returnSize: 'many', initial: [1, 2] }); - expect(result).toEqual(['opt2', 'opt3']); - expect(prompt_mock.mock.calls.length).toBe(0); - }); - - it('throws if no choices provided', async () => { - expect(() => prompter.pick('test message', [])).rejects.toThrowErrorMatchingInlineSnapshot( - `"No choices provided for prompt [test message]"`, - ); - }); - - it('returns single option without prompting if only one option provided', async () => { - expect(await prompter.pick('test message', ['only option'])).toEqual('only option'); - expect(prompt_mock.mock.calls.length).toBe(0); - }); - - it('returns selected item', async () => { - prompt_mock.mockResolvedValueOnce({ result: 'first opt' }); - expect(await prompter.pick('test message', ['first opt', 'second opt'])).toEqual('first opt'); - }); - - it('returns selected items when multiSelect', async () => { - const mockResult = ['val1', 'val3']; - prompt_mock.mockResolvedValueOnce({ result: mockResult }); - expect( - await prompter.pick<'many'>('test message', ['val1', 'val2', 'val3'], { returnSize: 'many' }), - ).toEqual(mockResult); - }); -}); diff --git a/packages/amplify-prompts/src/__tests__/validators.test.ts b/packages/amplify-prompts/src/__tests__/validators.test.ts deleted file mode 100644 index c7c40abd47b..00000000000 --- a/packages/amplify-prompts/src/__tests__/validators.test.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { alphanumeric, and, integer, maxLength, minLength, not, or } from '../validators'; - -describe('alphanumeric', () => { - it('returns true for alphanumeric strings', () => { - expect(alphanumeric()('thisisatest')).toBe(true); - }); - - it('returns default error message for non-alphanumeric strings', () => { - expect(alphanumeric()('!@#$')).toMatchInlineSnapshot(`"Input must be alphanumeric"`); - }); - - it('returns specified error message for non-alphanumeric string', () => { - expect(alphanumeric('this is the error message')('!@#$')).toMatchInlineSnapshot(`"this is the error message"`); - }); -}); - -describe('integer', () => { - it('returns true for integers', () => { - expect(integer()('10')).toBe(true); - }); - - it('returns default error message for non-integers', () => { - expect(integer()('non an integer')).toMatchInlineSnapshot(`"Input must be a number"`); - }); - - it('returns specified error message for non-integers', () => { - expect(integer('custom error message')('not an integer')).toMatchInlineSnapshot(`"custom error message"`); - }); -}); - -describe('maxLength', () => { - it('returns true for input of max length', () => { - expect(maxLength(3)('abc')).toBe(true); - }); - - it('returns true for input of max length - 1', () => { - expect(maxLength(3)('ab')).toBe(true); - }); - - it('returns default error message for input of max length + 1', () => { - expect(maxLength(3)('abcd')).toMatchInlineSnapshot(`"Input must be less than 3 characters long"`); - }); - - it('returns specified error message for input of max length + 1', () => { - expect(maxLength(3, 'custom error message')('abcd')).toMatchInlineSnapshot(`"custom error message"`); - }); -}); - -describe('minLength', () => { - it('returns true for input of min length', () => { - expect(minLength(3)('abc')).toBe(true); - }); - - it('returns true for input of mmin length + 1', () => { - expect(minLength(3)('abcd')).toBe(true); - }); - - it('returns default error message for input of min length - 1', () => { - expect(minLength(3)('ab')).toMatchInlineSnapshot(`"Input must be more than 3 characters long"`); - }); - - it('returns specified error message for input of min length - 1', () => { - expect(minLength(3, 'custom error message')('ab')).toMatchInlineSnapshot(`"custom error message"`); - }); -}); - -describe('and', () => { - it('returns true if all validators return true', async () => { - expect(await and([input => true, input => true])('anything')).toBe(true); - }); - - it('returns first error message', async () => { - expect(await and([input => true, input => 'first error', input => 'second error'])('anything')).toMatchInlineSnapshot(`"first error"`); - }); - - it('returns override message if any validators return error message', async () => { - expect( - await and([input => true, input => 'first error', input => 'second error'], 'custom error message')('anything'), - ).toMatchInlineSnapshot(`"custom error message"`); - }); -}); - -describe('or', () => { - it('returns true if one validator returns true', async () => { - expect(await or([input => 'first error', input => true])('anything')).toBe(true); - }); - - it('returns last error message if all validators return error', async () => { - expect(await or([input => 'first error', input => 'second error'])('anything')).toMatchInlineSnapshot(`"second error"`); - }); - - it('returns override error mmessage if all validators return error', async () => { - expect(await or([input => 'first error', input => 'second error'], 'custom message')('anything')).toMatchInlineSnapshot( - `"custom message"`, - ); - }); -}); - -describe('not', () => { - it('returns error message if validator returns true', async () => { - expect(await not(input => true, 'custom error message')('anything')).toMatchInlineSnapshot(`"custom error message"`); - }); - - it('returns true when validator returns error message', async () => { - expect(await not(input => 'error message', 'other message')('anything')).toBe(true); - }); -}); diff --git a/packages/amplify-prompts/src/demo/demo.ts b/packages/amplify-prompts/src/demo/demo.ts deleted file mode 100644 index 74b36ae1aa3..00000000000 --- a/packages/amplify-prompts/src/demo/demo.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { printer } from '../printer'; -import { prompter } from '../prompter'; -import { alphanumeric, and, integer, minLength } from '../validators'; - -const printResult = (result: any) => console.log(`Prommpt result was [${result}]`); -const printTypeofResult = (result: any) => console.log(`Response type was [${typeof result}]`); - -/** - * The following is meant to be a runnable example of functionality offered by amplify-prommpts - * Run `yarn demo` to see it in action - */ -const demo = async () => { - printResult(await prompter.input('this is a prompt', { initial: 'that has an initial value' })); - printResult(await prompter.pick('these options have a default selected', ['opt1', 'opt2', 'opt3'], { initial: 1 })); - printResult( - await prompter.pick<'many'>('this is a multiselect with a default', ['opt1', 'opt2', 'opt3'], { initial: [1, 2], returnSize: 'many' }), - ); - printResult(await prompter.pick('theres only one option here', ['opt1'])); - // confirmContinue - printer.info( - 'confirmContine is intended to be used anywhere the CLI is doing a potentially dangerous or destructive action and we want the customer to confirm their understanding.', - ); - printResult(await prompter.confirmContinue()); - printer.info('A custom prompt can also be used'); - printResult(await prompter.confirmContinue('This will melt your laptop. Proceed?')); - - // yesOrNo - printer.blankLine(); - printer.info( - 'yesOrNo is similar to confirmContinue but it should be used when we simply want to know whether or not to perform an optional task.', - ); - printer.info('A message must be specified for this prompt'); - printResult(await prompter.yesOrNo('Do you want to wait for GME to go to the moon?', false)); - - printer.warn( - 'The main difference between yesOrNo and confirmContinue is confirmContinue will always return true when the --yes flag is set but yesOrNo will return the default value', - ); - - // input - printer.blankLine(); - printer.info('To collect free-form input fromm the customer, use prompter.input'); - printer.info('The simplest case is asking for a string input'); - printResult(await prompter.input("What's your favorite color of Skittle?")); - - printer.info('To get an input type besides a string, specify a transform function'); - const result1 = await prompter.input('How mmany Skittles do you want?', { transform: input => Number.parseInt(input, 10) }); - printResult(result1); - printTypeofResult(result1); - - printer.info('In the above case, you may want to validate the input before the value is returned'); - printer.info('A validate function can accomplish this'); - printer.info('Try entering a value that is not a number this time'); - printResult( - await prompter.input<'one', number>('How many Skittles do you want', { - transform: input => Number.parseInt(input, 10), - validate: integer(), - }), - ); - // note the use of the integer() validator. You can write your own validators or use some common ones found in ../validators.ts - - printer.info('Validators can also be combined using boolean utility functions'); - printResult( - await prompter.input('This input must be alphanumeric and at least 3 characters', { - validate: and([alphanumeric(), minLength(3)]), - }), - ); - - printer.info('An initial value can be specified to a prompt'); - printResult(await prompter.input("What's your favorite Skittle color?", { initial: 'yellow' })); - - printer.info('To enter passwords and other sensitive information, text can be hidden'); - printResult(await prompter.input('Enter your super secret value', { hidden: true })); - printer.info("Note that the result is printed for demo purposes only. Don't ever actually print sensitive info to the console"); - - printer.info('To enter a list of values and have it returned as an array of values, specify a returnSize of "many"'); - const resultInputMany = await prompter.input<'many'>('Enter a list of names for each bag of Skittles', { returnSize: 'many' }); - printResult(resultInputMany); - printTypeofResult(resultInputMany); - printer.info( - 'Note that when using a "many" input, the transform and validate functions will be applied to each part of the input, rather than the whole input', - ); - - // pick - printer.blankLine(); - printer.info('prommpter.pick is used to select one or more items fromm a selection set'); - printer.info('It supports autocomplete of choices automatically'); - const choices1 = ['red', 'yellow', 'green', 'orange', 'purple']; - printResult(await prompter.pick('Pick your favorite Skittle color', choices1)); - - printer.info('To pick a value that is different than the display value, a list of name value pairs can be specified'); - const choices2 = [ - { - name: 'red', - value: 1, - }, - { - name: 'yellow', - value: 2, - }, - { - name: 'green', - value: 3, - }, - { - name: 'orange', - value: 4, - }, - { - name: 'purple', - value: 5, - }, - ]; - // Note: without specifying pick type parameters, TS can infer that the result is either number or number[]. - // Type parameters should be specified to tell TS that the result will be a single number. - const result2 = await prompter.pick<'one', number>('Pick your favorite Skittle color again', choices2); - printResult(result2); - printTypeofResult(result2); - - printer.info('A default selection can be specified by providing the index of the option'); - printResult( - await prompter.pick<'one', number>('Pick it again, this time with a default value', choices2, { initial: 2 }), - ); - - printer.info('Multiple choices can be selected by specifying multiSelect true'); - printer.info('When multiSelect is on, an array of initial indexes can be specified'); - printResult( - await prompter.pick<'many', number>('Pick your favorite colors', choices2, { returnSize: 'many', initial: [1, 2] }), - ); - - printer.info('Individual choices can be disabled or have hint text next to them'); - (choices2[1] as any).hint = 'definitely the best'; - (choices2[2] as any).disabled = true; - printResult( - await prompter.pick<'many', number>('Pick your favorite Skittle color', choices2, { returnSize: 'many' }), - ); -}; - -demo(); diff --git a/packages/amplify-prompts/src/flags.ts b/packages/amplify-prompts/src/flags.ts deleted file mode 100644 index 3b386e2daea..00000000000 --- a/packages/amplify-prompts/src/flags.ts +++ /dev/null @@ -1,14 +0,0 @@ -/** - * If this flag is set, debug messages will be printed. - */ -export const isDebug = process.argv.includes('--debug'); - -/** - * If this flag is set, only warn and error messages will be printed. - */ -export const isSilent = process.argv.includes('--silent'); - -/** - * If this flag is set, all prompts are suppressed. - */ -export const isYes = !!['--yes', '-y'].find(yesFlag => process.argv.includes(yesFlag)); diff --git a/packages/amplify-prompts/src/formatter.ts b/packages/amplify-prompts/src/formatter.ts deleted file mode 100644 index f41073788c3..00000000000 --- a/packages/amplify-prompts/src/formatter.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Printer, printer as defaultPrinter } from './printer'; - -/** - * Provides methods for writing formmatted multi-line output to a printer - */ -class AmplifyPrintFormater implements Formatter { - constructor(private readonly printer: Printer = defaultPrinter) {} - list = (items: string[]) => items.forEach(item => this.printer.info(`- ${item}`)); -} - -export const formatter: Formatter = new AmplifyPrintFormater(); - -export type Formatter = { - list: (items: string[]) => void; -}; diff --git a/packages/amplify-prompts/src/index.ts b/packages/amplify-prompts/src/index.ts deleted file mode 100644 index a20be9f7658..00000000000 --- a/packages/amplify-prompts/src/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './formatter'; -export * from './printer'; -export * from './prompter'; -export * from './validators'; diff --git a/packages/amplify-prompts/src/printer.ts b/packages/amplify-prompts/src/printer.ts deleted file mode 100644 index 5d8787897b7..00000000000 --- a/packages/amplify-prompts/src/printer.ts +++ /dev/null @@ -1,62 +0,0 @@ -import chalk from 'chalk'; -import os from 'os'; -import { isDebug, isSilent } from './flags'; - -/** - * Provides methods for printing lines to a writeable stream (stdout by default) - */ -export class AmplifyPrinter implements Printer { - constructor(private readonly outputStream: NodeJS.WritableStream = process.stdout) {} - - debug = (line: string): void => { - if (isDebug) { - this.writeSilenceableLine(line); - } - }; - - info = (line: string, color: Color = 'reset'): void => { - this.writeSilenceableLine(chalk[color](line)); - }; - - blankLine = (): void => { - this.writeSilenceableLine(); - }; - - success = (line: string): void => { - this.writeSilenceableLine(`✅ ${chalk.green(line)}`); - }; - - warn = (line: string): void => { - this.writeLine(`⚠️ ${chalk.yellow(line)}`); - }; - - error = (line: string): void => { - this.writeLine(`🛑 ${chalk.red(line)}`); - }; - - private writeSilenceableLine = (line?: string): void => { - if (!isSilent) { - this.writeLine(line); - } - }; - - private writeLine = (line: string = ''): void => { - this.outputStream.write(`${line}${os.EOL}`); - }; -} - -/** - * Convenience export that predefines a default printer - */ -export const printer: Printer = new AmplifyPrinter(); - -export type Printer = { - debug: (line: string) => void; - info: (line: string, color?: Color) => void; - blankLine: () => void; - success: (line: string) => void; - warn: (line: string) => void; - error: (line: string) => void; -}; - -type Color = 'green' | 'blue' | 'yellow' | 'red' | 'reset'; diff --git a/packages/amplify-prompts/src/prompter.ts b/packages/amplify-prompts/src/prompter.ts deleted file mode 100644 index 83ab4da6865..00000000000 --- a/packages/amplify-prompts/src/prompter.ts +++ /dev/null @@ -1,275 +0,0 @@ -import { prompt } from 'enquirer'; -// enquirer actions are not part of the TS types, but they are the recommended way to override enquirer behavior -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -import * as actions from 'enquirer/lib/combos'; -import { isYes } from './flags'; -import { Validator } from './validators'; -import { printer } from './printer'; - -/** - * Provides methods for collecting interactive customer responses from the shell - */ -class AmplifyPrompter implements Prompter { - constructor(private readonly prompter: typeof prompt = prompt, private readonly print: typeof printer = printer) {} - - /** - * Asks a continue prompt. - * Similar to yesOrNo, but 'false' is always the default and if the --yes flag is set, the prompt is skipped and 'true' is returned - */ - confirmContinue = async (message: string = 'Do you want to continue?') => { - if (isYes) { - return true; - } - return this.yesOrNoCommon(message, false); - }; - - /** - * Asks a yes or no question. - * If the --yes flag is set, the prompt is skipped and the initial value is returned - */ - yesOrNo = async (message: string, initial: boolean = true) => { - if (isYes) { - return initial; - } - return this.yesOrNoCommon(message, initial); - }; - - private yesOrNoCommon = async (message: string, initial: boolean) => { - let submitted = false; - const { result } = await this.prompter<{ result: boolean }>({ - type: 'confirm', - name: 'result', - message, - format: value => (submitted ? (value ? 'yes' : 'no') : ''), - onSubmit: () => (submitted = true), - initial, - }); - return result; - }; - - /** - * Prompt for an input. - * By default the input is a string, but can be any type. - * If the type is not a string, the transform function is required to map the prompt response (which is always a string) to the expected return type - * - * If a ReturnSize of 'many' is specified, then the input is treated as a comma-delimited list and returned as an array. - * The validate and transform functions will be applied to each element in the list individually - * - * If the yes flag is set, the initial value is returned. If no initial value is specified, an error is thrown - * @param message The prompt message - * @param options Prompt options. options.transform is required if T !== string - * @returns The prompt response - */ - input = async (message: string, ...options: MaybeOptionalInputOptions) => { - const opts = options?.[0]; - if (isYes) { - if (opts?.initial) { - return opts.initial as PromptReturn; - } else { - throw new Error(`Cannot prompt for [${message}] when '--yes' flag is set`); - } - } - - const validator = (opts?.returnSize === 'many' ? validateEachWith(opts?.validate) : opts?.validate) as ValidatorCast; - - const { result } = await this.prompter<{ result: RS extends 'many' ? string[] : string }>({ - type: (opts as any)?.hidden ? 'invisible' : opts?.returnSize === 'many' ? 'list' : 'input', - name: 'result', - message, - validate: validator, - initial: opts?.initial, - // footer is not part of the TS interface but it's part of the JS API - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - footer: opts?.returnSize === 'many' ? 'Enter a comma-delimited list of values' : undefined, - }); - - if (typeof opts?.transform === 'function') { - if (Array.isArray(result)) { - return (await Promise.all(result.map(async part => (opts.transform as Function)(part) as T))) as PromptReturn; - } - return (opts.transform(result as string) as unknown) as PromptReturn; - } else { - return (result as unknown) as PromptReturn; - } - }; - - /** - * Pick item(s) from a selection set. - * - * If only one choice is provided in the choices list, that choice is returned without a prompt - * If the yes flag is set, the initial selection is returned. If no initial selection is specified, an error is thrown - * @param message The prompt message - * @param choices The selection set to choose from - * @param options Control prompt settings. options.multiSelect = true is required if PickType = 'many' - * @returns The item(s) selected. If PickType = 'one' this is a single value. If PickType = 'many', this is an array - */ - pick = async ( - message: string, - choices: Choices, - ...options: MaybeOptionalPickOptions - ): Promise> => { - // some choices must be provided - if (choices?.length === 0) { - throw new Error(`No choices provided for prompt [${message}]`); - } - - const opts = options?.[0]; - - // map string[] choices into GenericChoice[] - const genericChoices: GenericChoice[] = - typeof choices[0] === 'string' - ? (((choices as string[]).map(choice => ({ name: choice, value: choice })) as unknown) as GenericChoice[]) // this assertion is safe because the choice array can only be a string[] if the generic type is a string - : (choices as GenericChoice[]); - - // enquirer requires all choice values be strings, so set up a mapping of string => T - // and format choices to conform to enquirer's interface - const choiceValueMap = new Map(); - const enquirerChoices = genericChoices.map(choice => { - choiceValueMap.set(choice.name, choice.value); - return { name: choice.name, disabled: choice.disabled, hint: choice.hint }; - }); - - actions.ctrl.a = 'a'; - - let result = genericChoices[0].name as string | string[]; - - if (choices?.length === 1) { - this.print.info(`Only one option for [${message}]. Selecting [${result}].`); - } else if (isYes) { - if (opts?.initial === undefined || (Array.isArray(opts?.initial) && opts?.initial.length === 0)) { - throw new Error(`Cannot prompt for [${message}] when '--yes' flag is set`); - } - if (typeof opts?.initial === 'number') { - result = genericChoices[opts?.initial].name; - } else { - result = opts?.initial.map(idx => genericChoices[idx].name); - } - } else { - ({ result } = await this.prompter<{ result: RS extends 'many' ? string[] : string }>({ - // actions is not part of the TS interface but it's part of the JS API - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - actions, - // footer is not part of the TS interface but it's part of the JS API - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - footer: opts?.returnSize === 'many' ? '(Use to select, to toggle all)' : undefined, - type: 'autocomplete', - name: 'result', - message, - initial: opts?.initial, - // there is a typo in the .d.ts file for this field -- muliple -> multiple - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - multiple: opts?.returnSize === 'many', - choices: enquirerChoices, - })); - } - - if (Array.isArray(result)) { - return result.map(item => choiceValueMap.get(item) as T) as PromptReturn; - } else { - // result is a string - return choiceValueMap.get(result as string) as PromptReturn; - } - }; -} - -export const prompter: Prompter = new AmplifyPrompter(); - -const validateEachWith = (validator?: Validator) => async (input: string[]) => { - if (!validator) { - return true; - } - const validationList = await Promise.all(input.map(part => part.trim()).map(async part => ({ part, result: await validator(part) }))); - const firstInvalid = validationList.find(v => typeof v.result === 'string'); - if (firstInvalid) { - return `${firstInvalid.part} did not satisfy requirement ${firstInvalid.result}`; - } - return true; -}; - -type Prompter = { - confirmContinue: (message?: string) => Promise; - yesOrNo: (message: string, initial?: boolean) => Promise; - // options is typed using spread because it's the only way to make it optional if RS is 'one' and T is a string but required otherwise - input: ( - message: string, - ...options: MaybeOptionalInputOptions - ) => Promise>; - pick: ( - message: string, - choices: Choices, - // options is typed using spread because it's the only way to make it required if RS is 'many' but optional if RS is 'one' - ...options: MaybeOptionalPickOptions - ) => Promise>; -}; - -// the following types are the building blocks of the method input types - -// Hidden cannot be specified if ReturnSize is 'many' -type MaybeAvailableHiddenInputOption = RS extends 'many' - ? {} - : { - hidden?: boolean; - }; - -type InitialSelectionOption = { - initial?: RS extends 'one' ? number : number[]; -}; - -type InitialValueOption = { - initial?: T; -}; - -type ValidateValueOption = { - validate?: Validator; -}; - -type ValidatorCast = (input: string | string[]) => string | true | Promise | Promise; - -type TransformOption = { - transform: (value: string) => T | Promise; -}; - -type MaybeOptionalTransformOption = T extends string ? Partial> : TransformOption; - -type ReturnSizeOption = RS extends 'many' - ? { - returnSize: 'many'; - } - : { - returnSize?: 'one'; - }; - -type Choices = T extends string ? GenericChoice[] | string[] : GenericChoice[]; - -type GenericChoice = { - name: string; - value: T; - hint?: string; - disabled?: boolean; -}; - -type ReturnSize = 'many' | 'one'; - -type MaybeOptionalInputOptions = RS extends 'many' - ? [InputOptions] - : T extends string - ? [InputOptions?] - : [InputOptions]; - -type MaybeOptionalPickOptions = RS extends 'many' ? [PickOptions] : [PickOptions?]; - -type PromptReturn = RS extends 'many' ? T[] : T; - -// the following types are the method input types -type PickOptions = ReturnSizeOption & InitialSelectionOption; - -type InputOptions = ReturnSizeOption & - ValidateValueOption & - InitialValueOption & - MaybeOptionalTransformOption & - MaybeAvailableHiddenInputOption; diff --git a/packages/amplify-prompts/src/validators.ts b/packages/amplify-prompts/src/validators.ts deleted file mode 100644 index d917ca606c9..00000000000 --- a/packages/amplify-prompts/src/validators.ts +++ /dev/null @@ -1,61 +0,0 @@ -export type Validator = (value: string) => true | string | Promise; - -/* - In the functions below, "message" should be a declarative phrase describing the allowed input. - It should describe what the input CAN be, not what it CANNOT be. - Validators can be combined using and(), or() and not() utility functions. - - For example: - and([alphanumeric(), minLength(10)]) will allow alphanumeric inputs with length >= 10 - - Over time, we can build up a library of common validators here to reuse across the codebase. - Each function here should be a factory function that takes in some parameters and returns a Validator -*/ - -export const alphanumeric = (message: string = 'Input must be alphanumeric'): Validator => (input: string) => - /^[a-zA-Z0-9]+$/.test(input) ? true : message; - -export const integer = (message: string = 'Input must be a number'): Validator => (input: string) => - /^[0-9]+$/.test(input) ? true : message; - -export const maxLength = (maxLen: number, message?: string): Validator => (input: string) => - input.length > maxLen ? message || `Input must be less than ${maxLen} characters long` : true; - -export const minLength = (minLen: number, message?: string): Validator => (input: string) => - input.length < minLen ? message || `Input must be more than ${minLen} characters long` : true; - -/** - * Logically "and"s several validators - * If a validator returns an error message, that message is returned by this function, unless an override message is specified - */ -export const and = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => { - for (const validator of validators) { - const result = await validator(input); - if (typeof result === 'string') { - return message ?? result; - } - } - return true; -}; - -/** - * Logically "or" several validators - * If all validators return an error message, the LAST error message is returned by this function, unless an override message is specified - */ -export const or = (validators: [Validator, Validator, ...Validator[]], message?: string): Validator => async (input: string) => { - let result: string | true = true; - for (const validator of validators) { - result = await validator(input); - if (result === true) { - return true; - } - } - return message ?? result; -}; - -/** - * Logical not operator on a validator - * If validator returns true, it returns message. If validator returns an error message, it returns true. - */ -export const not = (validator: Validator, message: string): Validator => async (input: string) => - typeof (await validator(input)) === 'string' ? true : message; diff --git a/packages/amplify-prompts/tsconfig.json b/packages/amplify-prompts/tsconfig.json deleted file mode 100644 index f4f8ec85e30..00000000000 --- a/packages/amplify-prompts/tsconfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./lib", - "rootDir": "./src", - "strict": true - } -} diff --git a/yarn.lock b/yarn.lock index 030790cef7d..6c51944fafb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9892,14 +9892,6 @@ chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.1.1: - version "4.1.1" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" - integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - change-case@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/change-case/-/change-case-3.1.0.tgz#0e611b7edc9952df2e8513b27b42de72647dd17e" @@ -12144,7 +12136,7 @@ enhanced-resolve@^4.3.0: enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" - resolved "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" + resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== dependencies: ansi-colors "^4.1.1"