@@ -7,17 +7,22 @@ import spawn from 'cross-spawn'
77import mri from 'mri'
88import colors from 'picocolors'
99
10- const { blue, yellow } = colors
10+ import { DEFAULT_APP_NAME } from './consts.js'
11+ import { renderTitle } from './utils/renderTitle.js'
12+
13+ const { blue, yellow, red, cyan } = colors
1114
1215const argv = mri < {
1316 template ?: string
1417 help ?: boolean
1518 overwrite ?: boolean
1619 immediate ?: boolean
1720 interactive ?: boolean
21+ git ?: boolean
22+ install ?: boolean
1823} > ( process . argv . slice ( 2 ) , {
19- boolean : [ 'help' , 'overwrite' , 'immediate' , 'interactive' ] ,
20- alias : { h : 'help' , t : 'template' , i : 'immediate' } ,
24+ boolean : [ 'help' , 'overwrite' , 'immediate' , 'interactive' , 'git' , 'install' ] ,
25+ alias : { h : 'help' , t : 'template' , i : 'immediate' , g : 'git' } ,
2126 string : [ 'template' ] ,
2227} )
2328const cwd = process . cwd ( )
@@ -26,26 +31,24 @@ const cwd = process.cwd()
2631const helpMessage = `\
2732Usage: create-tona [OPTION]... [DIRECTORY]
2833
29- Create a new Tona theme project in JavaScript or TypeScript.
34+ Create a new Tona theme project with TypeScript.
3035When running in TTY, the CLI will start in interactive mode.
3136
3237Options:
33- -t, --template NAME use a specific template (js or ts )
38+ -t, --template NAME use a specific template (minimal or preact )
3439 -i, --immediate install dependencies and start dev
40+ -g, --git initialize git repository and stage changes
41+ --install install dependencies only
3542 --interactive / --no-interactive force interactive / non-interactive mode
3643
3744Available templates:
38- ${ blue ( 'ts TypeScript' ) }
39- ${ yellow ( 'js JavaScript' ) } `
40-
41- const TEMPLATES = [ 'js' , 'ts' ]
45+ ${ cyan ( 'preact Preact + TypeScript (recommended)' ) }
46+ ${ blue ( 'minimal Minimal TypeScript' ) } `
4247
4348const renameFiles : Record < string , string | undefined > = {
4449 _gitignore : '.gitignore' ,
4550}
4651
47- const defaultTargetDir = 'tona-theme'
48-
4952function run ( [ command , ...args ] : string [ ] , options ?: SpawnOptions ) {
5053 const { status, error } = spawn . sync ( command , args , options )
5154 if ( status != null && status > 0 ) {
@@ -59,15 +62,16 @@ function run([command, ...args]: string[], options?: SpawnOptions) {
5962 }
6063}
6164
62- function install ( root : string , agent : string ) {
65+ function install ( root : string , agent : string , registry ?: string ) {
6366 if ( process . env . _VITE_TEST_CLI ) {
6467 prompts . log . step (
6568 `Installing dependencies with ${ agent } ... (skipped in test)` ,
6669 )
6770 return
6871 }
69- prompts . log . step ( `Installing dependencies with ${ agent } ...` )
70- run ( getInstallCommand ( agent ) , {
72+ const registryInfo = registry ? ` (${ registry } )` : ''
73+ prompts . log . step ( `Installing dependencies with ${ agent } ${ registryInfo } ...` )
74+ run ( getInstallCommand ( agent , registry ) , {
7175 stdio : 'inherit' ,
7276 cwd : root ,
7377 } )
@@ -86,13 +90,17 @@ function start(root: string, agent: string) {
8690}
8791
8892async function init ( ) {
93+ renderTitle ( )
94+
8995 const argTargetDir = argv . _ [ 0 ]
9096 ? formatTargetDir ( String ( argv . _ [ 0 ] ) )
9197 : undefined
9298 const argTemplate = argv . template
9399 const argOverwrite = argv . overwrite
94100 const argImmediate = argv . immediate
95101 const argInteractive = argv . interactive
102+ const argGit = argv . git
103+ const argInstall = argv . install
96104
97105 const help = argv . help
98106 if ( help ) {
@@ -111,9 +119,10 @@ async function init() {
111119 if ( interactive ) {
112120 const projectName = await prompts . text ( {
113121 message : 'Project name:' ,
114- defaultValue : defaultTargetDir ,
115- placeholder : defaultTargetDir ,
122+ defaultValue : DEFAULT_APP_NAME ,
123+ placeholder : DEFAULT_APP_NAME ,
116124 validate : ( value ) => {
125+ if ( ! value ) return 'Invalid project name'
117126 return value . length === 0 || formatTargetDir ( value ) . length > 0
118127 ? undefined
119128 : 'Invalid project name'
@@ -122,7 +131,7 @@ async function init() {
122131 if ( prompts . isCancel ( projectName ) ) return cancel ( )
123132 targetDir = formatTargetDir ( projectName )
124133 } else {
125- targetDir = defaultTargetDir
134+ targetDir = DEFAULT_APP_NAME
126135 }
127136 }
128137
@@ -178,7 +187,7 @@ async function init() {
178187 defaultValue : toValidPackageName ( packageName ) ,
179188 placeholder : toValidPackageName ( packageName ) ,
180189 validate ( dir ) {
181- if ( ! isValidPackageName ( dir ) ) {
190+ if ( ! dir || ! isValidPackageName ( dir ) ) {
182191 return 'Invalid package.json name'
183192 }
184193 } ,
@@ -190,10 +199,32 @@ async function init() {
190199 }
191200 }
192201
193- // 4. Choose template
202+ // 4. Choose language (typescript/javascript) - javascript auto-switches to typescript
203+ if ( interactive ) {
204+ const languageResult = await prompts . select ( {
205+ message : 'Select language:' ,
206+ options : [
207+ {
208+ label : blue ( 'TypeScript (recommended)' ) ,
209+ value : 'ts' ,
210+ } ,
211+ {
212+ label : yellow ( 'JavaScript' ) ,
213+ value : 'js' ,
214+ } ,
215+ ] ,
216+ } )
217+ if ( prompts . isCancel ( languageResult ) ) return cancel ( )
218+
219+ if ( languageResult === 'js' ) {
220+ prompts . log . warn ( red ( 'Wrong answer, using TypeScript instead' ) )
221+ }
222+ }
223+
224+ // 5. Choose template
194225 let template = argTemplate
195226 let hasInvalidArgTemplate = false
196- if ( argTemplate && ! TEMPLATES . includes ( argTemplate ) ) {
227+ if ( argTemplate && ! [ 'minimal' , 'preact' ] . includes ( argTemplate ) ) {
197228 template = undefined
198229 hasInvalidArgTemplate = true
199230 }
@@ -205,35 +236,89 @@ async function init() {
205236 : 'Select a template:' ,
206237 options : [
207238 {
208- label : blue ( 'TypeScript ') ,
209- value : 'ts ' ,
239+ label : cyan ( 'preact (recommended) ') ,
240+ value : 'preact ' ,
210241 } ,
211242 {
212- label : yellow ( 'JavaScript ') ,
213- value : 'js ' ,
243+ label : blue ( 'minimal ') ,
244+ value : 'minimal ' ,
214245 } ,
215246 ] ,
216247 } )
217248 if ( prompts . isCancel ( templateResult ) ) return cancel ( )
218249 template = templateResult
219250 } else {
220- template = 'ts '
251+ template = 'minimal '
221252 }
222253 }
223254
224- const pkgManager = pkgInfo ? pkgInfo . name : 'npm'
255+ let pkgManager = pkgInfo ? pkgInfo . name : 'npm'
256+
257+ const hasPnpm =
258+ spawn . sync ( 'pnpm' , [ '--version' ] , { stdio : 'ignore' } ) . status === 0
259+ if ( hasPnpm ) {
260+ pkgManager = 'pnpm'
261+ }
225262
226- // 5 . Ask about immediate install and package manager
227- let immediate = argImmediate
228- if ( immediate === undefined ) {
263+ // 6 . Ask about git initialization
264+ let initGit = argGit
265+ if ( initGit === undefined ) {
229266 if ( interactive ) {
230- const immediateResult = await prompts . confirm ( {
231- message : `Install with ${ pkgManager } and start now?` ,
267+ const gitResult = await prompts . confirm ( {
268+ message : 'Initialize a Git repository and stage the changes?' ,
232269 } )
233- if ( prompts . isCancel ( immediateResult ) ) return cancel ( )
234- immediate = immediateResult
270+ if ( prompts . isCancel ( gitResult ) ) return cancel ( )
271+ initGit = gitResult
235272 } else {
236- immediate = false
273+ initGit = false
274+ }
275+ }
276+
277+ // 7. Ask about installing dependencies
278+ let registry : string | undefined
279+ let shouldInstall = argInstall || argImmediate
280+ if ( ! shouldInstall ) {
281+ if ( interactive ) {
282+ const installResult = await prompts . select ( {
283+ message : 'Install dependencies?' ,
284+ options : [
285+ {
286+ label : cyan ( 'Yes, via Taobao mirror (recommended)' ) ,
287+ value : 'taobao' ,
288+ } ,
289+ {
290+ label : blue ( 'Yes, via official registry' ) ,
291+ value : 'official' ,
292+ } ,
293+ {
294+ label : 'No' ,
295+ value : 'no' ,
296+ } ,
297+ ] ,
298+ } )
299+ if ( prompts . isCancel ( installResult ) ) return cancel ( )
300+ if ( installResult === 'no' ) {
301+ shouldInstall = false
302+ } else {
303+ shouldInstall = true
304+ registry = installResult === 'taobao' ? 'taobao' : undefined
305+ }
306+ } else {
307+ shouldInstall = false
308+ }
309+ }
310+
311+ // 8. Ask about starting dev server
312+ let shouldStart = argImmediate
313+ if ( shouldStart === undefined && shouldInstall ) {
314+ if ( interactive ) {
315+ const startResult = await prompts . confirm ( {
316+ message : 'Start dev server?' ,
317+ } )
318+ if ( prompts . isCancel ( startResult ) ) return cancel ( )
319+ shouldStart = startResult
320+ } else {
321+ shouldStart = false
237322 }
238323 }
239324
@@ -278,9 +363,25 @@ async function init() {
278363
279364 write ( 'package.json' , JSON . stringify ( pkg , null , 2 ) + '\n' )
280365
281- if ( immediate ) {
282- install ( root , pkgManager )
283- start ( root , pkgManager )
366+ if ( initGit ) {
367+ prompts . log . step ( 'Initializing Git repository...' )
368+ run ( [ 'git' , 'init' , '-b' , 'main' ] , { stdio : 'inherit' , cwd : root } )
369+ run ( [ 'git' , 'add' , '.' ] , { stdio : 'inherit' , cwd : root } )
370+ }
371+
372+ if ( shouldInstall ) {
373+ install ( root , pkgManager , registry )
374+ if ( shouldStart ) {
375+ start ( root , pkgManager )
376+ } else {
377+ const cdProjectName = path . relative ( cwd , root )
378+ let doneMessage = `Done. Now run:\n`
379+ if ( root !== cwd ) {
380+ doneMessage += `\n cd ${ cdProjectName . includes ( ' ' ) ? `"${ cdProjectName } "` : cdProjectName } `
381+ }
382+ doneMessage += `\n ${ getRunCommand ( pkgManager , 'dev' ) . join ( ' ' ) } `
383+ prompts . outro ( doneMessage )
384+ }
284385 } else {
285386 let doneMessage = ''
286387 const cdProjectName = path . relative ( cwd , root )
@@ -365,10 +466,20 @@ function pkgFromUserAgent(userAgent: string | undefined): PkgInfo | undefined {
365466 }
366467}
367468
368- function getInstallCommand ( agent : string ) {
469+ function getInstallCommand ( agent : string , registry ?: string ) {
470+ const registryUrl =
471+ registry === 'taobao' ? 'https://registry.npmmirror.com' : undefined
472+
369473 if ( agent === 'yarn' ) {
474+ if ( registryUrl ) {
475+ return [ agent , '--registry' , registryUrl ]
476+ }
370477 return [ agent ]
371478 }
479+
480+ if ( registryUrl ) {
481+ return [ agent , 'install' , '--registry' , registryUrl ]
482+ }
372483 return [ agent , 'install' ]
373484}
374485
0 commit comments