-
-
Notifications
You must be signed in to change notification settings - Fork 9.8k
CLI: Add prompts abstraction #31521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
CLI: Add prompts abstraction #31521
Changes from all commits
8536401
ad924e8
a718ece
745682d
16531c5
b7df284
7991203
50e1bd2
6cee77a
3a55010
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,113 @@ | ||||||||||||||||
| import boxen from 'boxen'; | ||||||||||||||||
| import prompts from 'prompts'; | ||||||||||||||||
|
|
||||||||||||||||
| type Option = { | ||||||||||||||||
| value: any; | ||||||||||||||||
| label: string; | ||||||||||||||||
| hint?: string; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| interface BasePromptOptions { | ||||||||||||||||
| message: string; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| interface TextPromptOptions extends BasePromptOptions { | ||||||||||||||||
| placeholder?: string; | ||||||||||||||||
| initialValue?: string; | ||||||||||||||||
| validate?: (value: string) => string | boolean | Promise<string | boolean>; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| interface ConfirmPromptOptions extends BasePromptOptions { | ||||||||||||||||
| initialValue?: boolean; | ||||||||||||||||
| active?: string; | ||||||||||||||||
| inactive?: string; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| interface SelectPromptOptions extends BasePromptOptions { | ||||||||||||||||
| options: Option[]; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| interface PromptOptions { | ||||||||||||||||
| onCancel?: () => void; | ||||||||||||||||
| } | ||||||||||||||||
|
|
||||||||||||||||
| const baseOptions: PromptOptions = { | ||||||||||||||||
| onCancel: () => process.exit(0), | ||||||||||||||||
| }; | ||||||||||||||||
|
Comment on lines
+34
to
+36
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Calling process.exit(0) directly in onCancel may prevent cleanup code from running. Consider allowing custom cancel handlers to be passed in |
||||||||||||||||
|
|
||||||||||||||||
| const text = async (options: TextPromptOptions, promptOptions?: PromptOptions): Promise<string> => { | ||||||||||||||||
| const result = await prompts( | ||||||||||||||||
| { | ||||||||||||||||
| type: 'text', | ||||||||||||||||
| name: 'value', | ||||||||||||||||
| message: options.message, | ||||||||||||||||
| initial: options.initialValue, | ||||||||||||||||
| validate: options.validate, | ||||||||||||||||
| }, | ||||||||||||||||
| { ...baseOptions, ...promptOptions } | ||||||||||||||||
| ); | ||||||||||||||||
|
|
||||||||||||||||
| return result.value; | ||||||||||||||||
| }; | ||||||||||||||||
|
Comment on lines
+50
to
+51
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: No error handling if result.value is undefined (e.g. if user cancels). Should handle this case explicitly
Suggested change
|
||||||||||||||||
|
|
||||||||||||||||
| const confirm = async ( | ||||||||||||||||
| options: ConfirmPromptOptions, | ||||||||||||||||
| promptOptions?: PromptOptions | ||||||||||||||||
| ): Promise<boolean> => { | ||||||||||||||||
| const result = await prompts( | ||||||||||||||||
| { | ||||||||||||||||
| type: 'confirm', | ||||||||||||||||
| name: 'value', | ||||||||||||||||
| message: options.message, | ||||||||||||||||
| initial: options.initialValue, | ||||||||||||||||
| active: options.active, | ||||||||||||||||
| inactive: options.inactive, | ||||||||||||||||
| }, | ||||||||||||||||
| { ...baseOptions, ...promptOptions } | ||||||||||||||||
| ); | ||||||||||||||||
|
|
||||||||||||||||
| return result.value; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| const select = async <T>( | ||||||||||||||||
| options: SelectPromptOptions, | ||||||||||||||||
| promptOptions?: PromptOptions | ||||||||||||||||
| ): Promise<T> => { | ||||||||||||||||
| const result = await prompts( | ||||||||||||||||
| { | ||||||||||||||||
| type: 'select', | ||||||||||||||||
| name: 'value', | ||||||||||||||||
| message: options.message, | ||||||||||||||||
| choices: options.options.map((opt) => ({ | ||||||||||||||||
| title: opt.label, | ||||||||||||||||
| value: opt.value, | ||||||||||||||||
| description: opt.hint, | ||||||||||||||||
| })), | ||||||||||||||||
| }, | ||||||||||||||||
| { ...baseOptions, ...promptOptions } | ||||||||||||||||
| ); | ||||||||||||||||
|
|
||||||||||||||||
| return result.value as T; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| type BoxenOptions = { | ||||||||||||||||
| borderStyle?: 'round' | 'none'; | ||||||||||||||||
| padding?: number; | ||||||||||||||||
| title?: string; | ||||||||||||||||
| titleAlignment?: 'left' | 'center' | 'right'; | ||||||||||||||||
| borderColor?: string; | ||||||||||||||||
| backgroundColor?: string; | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| const logBox = (message: string, style?: BoxenOptions) => { | ||||||||||||||||
| console.log( | ||||||||||||||||
| boxen(message, { borderStyle: 'round', padding: 1, borderColor: '#F1618C', ...style }) | ||||||||||||||||
| ); | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| export const prompt = { | ||||||||||||||||
| confirm, | ||||||||||||||||
| text, | ||||||||||||||||
| select, | ||||||||||||||||
| logBox, | ||||||||||||||||
| }; | ||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,12 +5,12 @@ import { | |||||||||||||||||||||||
| frameworkPackages, | ||||||||||||||||||||||||
| frameworkToRenderer, | ||||||||||||||||||||||||
| getProjectRoot, | ||||||||||||||||||||||||
| prompt, | ||||||||||||||||||||||||
| rendererPackages, | ||||||||||||||||||||||||
| } from 'storybook/internal/common'; | ||||||||||||||||||||||||
| import type { PackageJson } from 'storybook/internal/types'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import picocolors from 'picocolors'; | ||||||||||||||||||||||||
| import prompts from 'prompts'; | ||||||||||||||||||||||||
| import { dedent } from 'ts-dedent'; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| import type { Fix, RunOptions } from '../types'; | ||||||||||||||||||||||||
|
|
@@ -183,12 +183,10 @@ export const rendererToFramework: Fix<MigrationResult> = { | |||||||||||||||||||||||
| async run(options: RunOptions<MigrationResult>) { | ||||||||||||||||||||||||
| const { result, dryRun = false } = options; | ||||||||||||||||||||||||
| const defaultGlob = '**/*.{mjs,cjs,js,jsx,ts,tsx}'; | ||||||||||||||||||||||||
| const { glob } = await prompts({ | ||||||||||||||||||||||||
| type: 'text', | ||||||||||||||||||||||||
| name: 'glob', | ||||||||||||||||||||||||
| const glob = await prompt.text({ | ||||||||||||||||||||||||
| message: | ||||||||||||||||||||||||
| 'Enter a custom glob pattern to scan for story files (or press enter to use default):', | ||||||||||||||||||||||||
| initial: defaultGlob, | ||||||||||||||||||||||||
| initialValue: defaultGlob, | ||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||
|
Comment on lines
+186
to
190
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. logic: Consider adding error handling for the case where prompt.text() fails or returns undefined
Suggested change
|
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| const projectRoot = getProjectRoot(); | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
style: Using
anytype reduces type safety. Consider using a generic type parameter forvalueto maintain type checking