11import type { TemplateName } from '../utils/templates/names'
22
3- import { existsSync , promises as fsp } from 'node:fs'
3+ import { promises as fsp } from 'node:fs'
44import process from 'node:process'
5-
65import { styleText } from 'node:util'
7- import { cancel , intro , outro } from '@clack/prompts'
6+
7+ import { intro , outro } from '@clack/prompts'
88import { defineCommand } from 'citty'
9- import { dirname , extname , resolve } from 'pathe'
9+ import { dirname , resolve } from 'pathe'
1010
1111import { loadKit } from '../utils/kit'
1212import { logger } from '../utils/logger'
@@ -25,9 +25,31 @@ export default defineCommand({
2525 ...logLevelArgs ,
2626 force : {
2727 type : 'boolean' ,
28- description : 'Force override file if it already exists' ,
28+ description : 'Overwrite the file if it already exists' ,
2929 default : false ,
3030 } ,
31+ mode : {
32+ type : 'string' ,
33+ valueHint : 'client|server' ,
34+ description : 'Add a client or server suffix to a component or plugin' ,
35+ } ,
36+ method : {
37+ type : 'string' ,
38+ valueHint : 'connect|delete|get|head|options|patch|post|put|trace' ,
39+ description : 'Add an HTTP method suffix to an API route' ,
40+ } ,
41+ global : {
42+ type : 'boolean' ,
43+ description : 'Create global route middleware' ,
44+ } ,
45+ api : {
46+ type : 'boolean' ,
47+ description : 'Create a server route in the API directory' ,
48+ } ,
49+ pages : {
50+ type : 'boolean' ,
51+ description : 'Include NuxtPage and NuxtLayout in the app template' ,
52+ } ,
3153 template : {
3254 type : 'positional' ,
3355 required : true ,
@@ -47,55 +69,56 @@ export default defineCommand({
4769
4870 const templateName = ctx . args . template as TemplateName
4971
50- // Validate template name
5172 if ( ! templateNames . includes ( templateName ) ) {
52- const templateNames = Object . keys ( templates ) . map ( name => styleText ( 'cyan' , name ) )
53- const lastTemplateName = templateNames . pop ( )
73+ const supported = templateNames . map ( name => styleText ( 'cyan' , name ) )
74+ const last = supported . pop ( )
5475 logger . error ( `Template ${ styleText ( 'cyan' , templateName ) } is not supported.` )
55- logger . info ( `Possible values are ${ templateNames . join ( ', ' ) } or ${ lastTemplateName } .` )
76+ logger . info ( `Possible values are ${ supported . join ( ', ' ) } or ${ last } .` )
5677 process . exit ( 1 )
5778 }
5879
59- // Validate options
60- const ext = extname ( ctx . args . name )
61- const name
62- = ext === '.vue' || ext === '.ts'
63- ? ctx . args . name . replace ( ext , '' )
64- : ctx . args . name
65-
66- if ( ! name ) {
67- cancel ( 'name argument is missing!' )
80+ if ( ctx . args . mode && ctx . args . mode !== 'client' && ctx . args . mode !== 'server' ) {
81+ logger . error ( `Mode must be ${ styleText ( 'cyan' , 'client' ) } or ${ styleText ( 'cyan' , 'server' ) } .` )
82+ process . exit ( 1 )
83+ }
84+ if ( ctx . args . method && ! [ 'connect' , 'delete' , 'get' , 'head' , 'options' , 'patch' , 'post' , 'put' , 'trace' ] . includes ( ctx . args . method ) ) {
85+ logger . error ( `HTTP method ${ styleText ( 'cyan' , ctx . args . method ) } is not supported.` )
6886 process . exit ( 1 )
6987 }
7088
71- // Load config in order to respect srcDir
72- const kit = await loadKit ( cwd )
73- const config = await kit . loadNuxtConfig ( { cwd } )
74-
75- // Resolve template
76- const template = templates [ templateName as keyof typeof templates ]
77-
78- const res = template ( { name, args : ctx . args , nuxtOptions : config } )
89+ const ext = [ '.vue' , '.ts' ] . find ( ext => ctx . args . name . endsWith ( ext ) )
90+ const name = ext
91+ ? ctx . args . name . slice ( 0 , - ext . length )
92+ : ctx . args . name
7993
80- // Ensure not overriding user code
81- if ( ! ctx . args . force && existsSync ( res . path ) ) {
82- logger . error ( `File exists at ${ styleText ( 'cyan' , relativeToProcess ( res . path ) ) } .` )
83- logger . info ( `Use ${ styleText ( 'cyan' , '--force' ) } to override or use a different name.` )
94+ if ( ! name ) {
95+ logger . error ( 'Template name must not be empty.' )
8496 process . exit ( 1 )
8597 }
8698
87- // Ensure parent directory exists
99+ const kit = await loadKit ( cwd )
100+ const config = await kit . loadNuxtConfig ( { cwd } )
101+ const res = templates [ templateName ] ( { name, args : ctx . args , nuxtOptions : config } )
88102 const parentDir = dirname ( res . path )
89- if ( ! existsSync ( parentDir ) ) {
90- logger . step ( `Creating directory ${ styleText ( 'cyan' , relativeToProcess ( parentDir ) ) } .` )
103+ const createdDir = await fsp . mkdir ( parentDir , { recursive : true } )
104+ if ( createdDir ) {
105+ logger . step ( `Created directory ${ styleText ( 'cyan' , relativeToProcess ( parentDir ) ) } .` )
91106 if ( templateName === 'page' ) {
92107 logger . info ( 'This enables vue-router functionality!' )
93108 }
94- await fsp . mkdir ( parentDir , { recursive : true } )
95109 }
96110
97- // Write file
98- await fsp . writeFile ( res . path , `${ res . contents . trim ( ) } \n` )
111+ try {
112+ await fsp . writeFile ( res . path , `${ res . contents . trim ( ) } \n` , { flag : ctx . args . force ? 'w' : 'wx' } )
113+ }
114+ catch ( error ) {
115+ if ( ! ctx . args . force && ( error as NodeJS . ErrnoException ) . code === 'EEXIST' ) {
116+ logger . error ( `File exists at ${ styleText ( 'cyan' , relativeToProcess ( res . path ) ) } .` )
117+ logger . info ( `Use ${ styleText ( 'cyan' , '--force' ) } to overwrite it or use a different name.` )
118+ process . exit ( 1 )
119+ }
120+ throw error
121+ }
99122 logger . success ( `Created ${ styleText ( 'cyan' , relativeToProcess ( res . path ) ) } .` )
100123 outro ( `Generated a new ${ styleText ( 'cyan' , templateName ) } !` )
101124 } ,
0 commit comments