@@ -4,7 +4,7 @@ import * as fs from 'node:fs'
44import * as path from 'node:path'
55
66import { parseArgs } from 'node:util'
7- import { intro , outro , text , confirm , multiselect , select , isCancel } from '@clack/prompts'
7+ import { intro , outro , text , confirm , multiselect , select , isCancel , cancel } from '@clack/prompts'
88import { red , green , cyan , bold , dim } from 'picocolors'
99
1010import ejs from 'ejs'
@@ -192,6 +192,15 @@ async function init() {
192192 const forceOverwrite = argv . force
193193
194194 const language = getLanguage ( )
195+ async function unwrapPrompt < T > ( maybeCancelPromise : Promise < T | symbol > ) : Promise < T > {
196+ const result = await maybeCancelPromise
197+
198+ if ( isCancel ( result ) ) {
199+ cancel ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
200+ process . exit ( 0 )
201+ }
202+ return result
203+ }
195204
196205 let result : {
197206 projectName ?: string
@@ -203,7 +212,7 @@ async function init() {
203212 needsPinia ?: boolean
204213 needsVitest ?: boolean
205214 needsE2eTesting ?: false | 'cypress' | 'nightwatch' | 'playwright'
206- needsEslint ? : false | 'eslintOnly' | 'speedUpWithOxlint'
215+ needsEslint ?: boolean
207216 needsOxlint ?: boolean
208217 needsPrettier ?: boolean
209218 features ?: string [ ]
@@ -217,144 +226,120 @@ async function init() {
217226 )
218227
219228 if ( ! targetDir ) {
220- const projectNameInput = await text ( {
221- message : language . projectName . message ,
222- placeholder : defaultProjectName ,
223- validate : ( value ) => ( value . length > 0 ? undefined : 'Should not be empty' ) ,
224- } )
225-
226- if ( isCancel ( projectNameInput ) ) {
227- throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
228- }
229-
230- targetDir = projectNameInput
229+ targetDir = await unwrapPrompt (
230+ text ( {
231+ message : language . projectName . message ,
232+ placeholder : defaultProjectName ,
233+ validate : ( value ) => ( value . length > 0 ? undefined : 'Should not be empty' ) ,
234+ } ) ,
235+ )
231236 }
232237
233238 if ( ! canSkipEmptying ( targetDir ) && ! forceOverwrite ) {
234- const shouldOverwriteInput = await confirm ( {
235- message : `${
236- targetDir === '.'
237- ? language . shouldOverwrite . dirForPrompts . current
238- : `${ language . shouldOverwrite . dirForPrompts . target } "${ targetDir } "`
239- } ${ language . shouldOverwrite . message } `,
240- } )
239+ result . shouldOverwrite = await unwrapPrompt (
240+ confirm ( {
241+ message : `${
242+ targetDir === '.'
243+ ? language . shouldOverwrite . dirForPrompts . current
244+ : `${ language . shouldOverwrite . dirForPrompts . target } "${ targetDir } "`
245+ } ${ language . shouldOverwrite . message } `,
246+ initialValue : false ,
247+ } ) ,
248+ )
241249
242- if ( isCancel ( shouldOverwriteInput ) || ! shouldOverwriteInput ) {
250+ if ( ! result . shouldOverwrite ) {
243251 throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
244252 }
245-
246- result . shouldOverwrite = shouldOverwriteInput
247253 }
248254
249255 if ( ! isValidPackageName ( targetDir ) ) {
250- const packageNameInput = await text ( {
251- message : language . packageName . message ,
252- initialValue : toValidPackageName ( targetDir ) ,
253- validate : ( value ) =>
254- isValidPackageName ( value ) ? undefined : language . packageName . invalidMessage ,
255- } )
256-
257- if ( isCancel ( packageNameInput ) ) {
258- throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
259- }
260-
261- result . packageName = packageNameInput
256+ result . packageName = await unwrapPrompt (
257+ text ( {
258+ message : language . packageName . message ,
259+ initialValue : toValidPackageName ( targetDir ) ,
260+ validate : ( value ) =>
261+ isValidPackageName ( value ) ? undefined : language . packageName . invalidMessage ,
262+ } ) ,
263+ )
262264 }
263265
264266 if ( ! isFeatureFlagsUsed ) {
265- const features = await multiselect ( {
266- message : `${ language . featureSelection . message } ${ dim ( language . featureSelection . hint ) } ` ,
267- options : [
268- {
269- value : 'typescript' ,
270- label : language . needsTypeScript . message ,
271- hint : language . needsTypeScript . hint ,
272- } ,
273- { value : 'jsx' , label : language . needsJsx . message , hint : language . needsJsx . hint } ,
274- {
275- value : 'router' ,
276- label : language . needsRouter . message ,
277- hint : language . needsRouter . hint ,
278- } ,
279- {
280- value : 'pinia' ,
281- label : language . needsPinia . message ,
282- hint : language . needsPinia . hint ,
283- } ,
284- {
285- value : 'vitest' ,
286- label : language . needsVitest . message ,
287- hint : language . needsVitest . hint ,
288- } ,
289- {
290- value : 'e2e' ,
291- label : language . needsE2eTesting . message ,
292- hint : language . needsE2eTesting . hint ,
293- } ,
294- {
295- value : 'eslint' ,
296- label : language . needsEslint . message ,
297- hint : language . needsEslint . hint ,
298- } ,
299- {
300- value : 'prettier' ,
301- label : language . needsPrettier . message ,
302- hint : language . needsPrettier . hint ,
303- } ,
304- ] ,
305- required : false ,
306- } )
307-
308- if ( isCancel ( features ) ) {
309- throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
310- }
311-
312- result . features = features
313-
314- if ( features . includes ( 'e2e' ) ) {
315- const e2eTestingInput = await select ( {
316- message : `${ language . e2eSelection . message } ${ dim ( language . e2eSelection . hint ) } ` ,
267+ const features = await unwrapPrompt (
268+ multiselect ( {
269+ message : `${ language . featureSelection . message } ${ dim ( language . featureSelection . hint ) } ` ,
317270 options : [
318271 {
319- value : 'playwright' ,
320- label : language . e2eSelection . selectOptions . playwright . title ,
321- hint : language . e2eSelection . selectOptions . playwright . desc ,
272+ value : 'typescript' ,
273+ label : language . needsTypeScript . message ,
274+ } ,
275+ { value : 'jsx' , label : language . needsJsx . message } ,
276+ {
277+ value : 'router' ,
278+ label : language . needsRouter . message ,
279+ } ,
280+ {
281+ value : 'pinia' ,
282+ label : language . needsPinia . message ,
283+ } ,
284+ {
285+ value : 'vitest' ,
286+ label : language . needsVitest . message ,
322287 } ,
323288 {
324- value : 'cypress' ,
325- label : language . e2eSelection . selectOptions . cypress . title ,
326- hint : features . includes ( 'vitest' )
327- ? language . e2eSelection . selectOptions . cypress . desc
328- : language . e2eSelection . selectOptions . cypress . hintOnComponentTesting ! ,
289+ value : 'e2e' ,
290+ label : language . needsE2eTesting . message ,
329291 } ,
330292 {
331- value : 'nightwatch' ,
332- label : language . e2eSelection . selectOptions . nightwatch . title ,
333- hint : features . includes ( 'vitest' )
334- ? language . e2eSelection . selectOptions . nightwatch . desc
335- : language . e2eSelection . selectOptions . nightwatch . hintOnComponentTesting ! ,
293+ value : 'eslint' ,
294+ label : language . needsEslint . message ,
295+ } ,
296+ {
297+ value : 'prettier' ,
298+ label : language . needsPrettier . message ,
336299 } ,
337300 ] ,
338- } )
301+ required : false ,
302+ } ) ,
303+ )
339304
340- if ( isCancel ( e2eTestingInput ) ) {
341- throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
342- }
305+ result . features = features
343306
344- result . needsE2eTesting = e2eTestingInput
307+ if ( features . includes ( 'e2e' ) ) {
308+ result . needsE2eTesting = await unwrapPrompt (
309+ select ( {
310+ message : `${ language . e2eSelection . message } ${ dim ( language . e2eSelection . hint ) } ` ,
311+ options : [
312+ {
313+ value : 'playwright' ,
314+ label : language . e2eSelection . selectOptions . playwright . title ,
315+ hint : language . e2eSelection . selectOptions . playwright . desc ,
316+ } ,
317+ {
318+ value : 'cypress' ,
319+ label : language . e2eSelection . selectOptions . cypress . title ,
320+ hint : features . includes ( 'vitest' )
321+ ? language . e2eSelection . selectOptions . cypress . desc
322+ : language . e2eSelection . selectOptions . cypress . hintOnComponentTesting ! ,
323+ } ,
324+ {
325+ value : 'nightwatch' ,
326+ label : language . e2eSelection . selectOptions . nightwatch . title ,
327+ hint : features . includes ( 'vitest' )
328+ ? language . e2eSelection . selectOptions . nightwatch . desc
329+ : language . e2eSelection . selectOptions . nightwatch . hintOnComponentTesting ! ,
330+ } ,
331+ ] ,
332+ } ) ,
333+ )
345334 }
346335
347336 if ( features . includes ( 'eslint' ) ) {
348- const oxlintInput = await confirm ( {
349- message : language . needsOxlint . message ,
350- initialValue : false ,
351- } )
352-
353- if ( isCancel ( oxlintInput ) ) {
354- throw new Error ( red ( '✖' ) + ` ${ language . errors . operationCancelled } ` )
355- }
356-
357- result . needsOxlint = oxlintInput
337+ result . needsOxlint = await unwrapPrompt (
338+ confirm ( {
339+ message : language . needsOxlint . message ,
340+ initialValue : false ,
341+ } ) ,
342+ )
358343 }
359344 }
360345
@@ -396,9 +381,7 @@ async function init() {
396381 const needsEslint = Boolean (
397382 argv . eslint || argv [ 'eslint-with-oxlint' ] || argv [ 'eslint-with-prettier' ] || result . needsEslint ,
398383 )
399- const needsOxlint = Boolean (
400- argv [ 'eslint-with-oxlint' ] || result . needsEslint === 'speedUpWithOxlint' ,
401- )
384+ const needsOxlint = Boolean ( argv [ 'eslint-with-oxlint' ] || result . needsOxlint )
402385
403386 const { needsE2eTesting } = result
404387 const needsCypress = argv . cypress || argv . tests || needsE2eTesting === 'cypress'
0 commit comments