33 * - test different package managers
44 * - think about npm < 6
55 * - get notified if any framework files change
6- * - exit properly anytime
76 */
87
98import { spawn } from 'child_process'
109import prompts from 'prompts'
1110import path from 'path'
11+ import { promisify } from 'util'
12+ import { exec } from 'child_process'
1213import fs_ , { promises as fs } from 'fs'
1314import fsExtra from 'fs-extra'
1415import { fileURLToPath } from 'url'
15- import { cyan } from 'kolorist'
16+ import {
17+ cyan ,
18+ red ,
19+ green ,
20+ } from 'kolorist'
1621import minimist from 'minimist'
1722
23+ const execAsync = promisify ( exec )
24+
1825// Avoids autoconversion to number of the project name by defining that the args
1926// non associated with an option ( _ ) needs to be parsed as a string. See #4606
2027const argv = minimist ( process . argv . slice ( 2 ) , { string : [ '_' ] } )
@@ -31,7 +38,7 @@ const frameworks = [
3138 { title : 'Nuxt' , value : 'nuxt' , command : 'npx nuxi@latest init %PROJECT_NAME% --packageManager=%PACKAGE_MANAGER% --gitInit=false' } ,
3239 // @todo : remove --template=basics
3340 { title : 'Astro' , value : 'astro' , command : 'npm create astro@latest %PROJECT_NAME% -- --install=yes --template=basics' } ,
34- { title : 'Laravel' , value : 'laravel' , command : 'php /usr/local/bin/composer.phar create-project laravel/laravel %PROJECT_NAME%' } ,
41+ { title : 'Laravel' , value : 'laravel' , command : '%COMPOSER_PATH% create-project laravel/laravel %PROJECT_NAME%' } ,
3542]
3643
3744const themes = [
@@ -84,6 +91,11 @@ async function main() {
8491 }
8592 return true
8693 }
94+ } ,
95+ {
96+ onCancel : ( ) => {
97+ throw new Error ( red ( '✖' ) + ' Operation cancelled' )
98+ } ,
8799 } )
88100
89101 const response = await prompts ( [
@@ -137,7 +149,12 @@ async function main() {
137149 message : 'Select a theme for your project:' ,
138150 choices : themes
139151 }
140- ] )
152+ ] ,
153+ {
154+ onCancel : ( ) => {
155+ throw new Error ( red ( '✖' ) + ' Operation cancelled' )
156+ } ,
157+ } )
141158
142159 const { framework, ts, builder, publicKey } = response
143160
@@ -146,17 +163,24 @@ async function main() {
146163 if ( projectName && framework ) {
147164 const fw = getFramework ( framework )
148165
149- console . log ( `Creating project '${ projectName } ' using ${ fw . title } ...` )
166+ status ( `Creating project '${ projectName } ' using ${ fw . title } ...` )
150167
151168 const template = framework === 'vite' ? `vue${ ts ? '-ts' : '' } ` : ''
152169
170+ let composerPath = ''
171+
172+ if ( framework === 'laravel' ) {
173+ composerPath = await getComposerPath ( )
174+ }
175+
153176 const command = fw . command
154177 . replace ( '%PROJECT_NAME%' , projectName )
155178 . replace ( '%TEMPLATE%' , template )
156179 . replace ( '%PACKAGE_MANAGER%' , packageManager )
180+ . replace ( '%COMPOSER_PATH%' , composerPath )
157181 . split ( ' ' )
158182
159- await runCommand ( command [ 0 ] , command . slice ( 1 ) )
183+ await runCommand ( command [ 0 ] , command . slice ( 1 ) , `create project with ${ fw . title } ` )
160184 } else {
161185 console . error ( 'Project creation canceled.' )
162186 return
@@ -171,13 +195,12 @@ async function main() {
171195 * Enter project folder
172196 */
173197 process . chdir ( projectName )
174- console . log ( `Changed directory to ${ projectName } ` )
175198
176199 /**
177200 * Install base dependencies
178201 */
179- console . log ( 'Running npm install ...')
180- await runCommand ( 'npm' , [ 'install' ] )
202+ status ( 'Installing dependencies ...')
203+ await runCommand ( 'npm' , [ 'install' ] , 'install dependencies' )
181204
182205 /**
183206 * Variables
@@ -195,28 +218,28 @@ async function main() {
195218 * Install Tailwind
196219 */
197220 if ( isTailwind ) {
198- console . log ( 'Installing Tailwind...')
221+ status ( '\nInstalling Tailwind...')
199222 await Promise . all ( tailwind [ framework ] . install . map ( async ( script ) => {
200223 const command = script . split ( ' ' )
201- await runCommand ( command [ 0 ] , command . slice ( 1 ) )
224+ await runCommand ( command [ 0 ] , command . slice ( 1 ) , 'install Tailwind CSS' )
202225 } ) )
203226 }
204227
205228 /**
206229 * Install Bootstrap
207230 */
208231 if ( isBootstrap ) {
209- console . log ( 'Installing Bootstrap...' )
210- await runCommand ( 'npm' , [ 'install' , 'bootstrap' ] )
232+ console . log ( '\nInstalling Bootstrap...' )
233+ await runCommand ( 'npm' , [ 'install' , 'bootstrap' ] , 'install Bootstrap' )
211234 }
212235
213236 /**
214237 * Astro updates
215238 */
216239 if ( isAstro ) {
217240 // Install Vue in Astro
218- console . log ( 'Installing Vue...' )
219- await runCommand ( 'npm' , [ 'install' , 'vue' , '@astrojs/vue' ] )
241+ console . log ( '\nInstalling Vue...' )
242+ await runCommand ( 'npm' , [ 'install' , 'vue' , '@astrojs/vue' ] , 'install Vue in Astro' )
220243
221244 // Extend tsconfig.json
222245 await updateAstroTsConfig ( process . cwd ( ) )
@@ -226,8 +249,8 @@ async function main() {
226249 * Install Vue in Laravel
227250 */
228251 if ( isLaravel ) {
229- console . log ( 'Installing Vue...' )
230- await runCommand ( 'npm' , [ 'install' , '@vitejs/plugin-vue' ] )
252+ console . log ( '\nInstalling Vue...' )
253+ await runCommand ( 'npm' , [ 'install' , '@vitejs/plugin-vue' ] , 'install Vue in Laravel' )
231254 }
232255
233256 /**
@@ -237,13 +260,12 @@ async function main() {
237260 ? framework === 'nuxt' ? '@vueform/builder-nuxt' : '@vueform/vueform @vueform/builder'
238261 : framework === 'nuxt' ? '@vueform/nuxt' : '@vueform/vueform'
239262
240- console . log ( `Installing Vueform${ isBuilder ?' + Vueform Builder' :'' } ...`)
241- await runCommand ( 'npm' , [ 'install' , ...vueformPackage . split ( ' ' ) ] )
263+ status ( `\nInstalling Vueform${ isBuilder ?' Builder' :'' } ...`)
264+ await runCommand ( 'npm' , [ 'install' , ...vueformPackage . split ( ' ' ) ] , `install ${ vueformPackage } ` )
242265
243266 /**
244267 * Copy Vueform files to project directory
245268 */
246- console . log ( `Copying additional files to ${ projectName } ...` )
247269 await copyFilesToProject ( sourcePath , targetPath )
248270
249271 /**
@@ -256,25 +278,24 @@ async function main() {
256278 /**
257279 * Show finish instructions
258280 */
259- console . log ( '' )
281+ console . log ( green ( `\n✔ Installation finished` ) )
282+ console . log ( `\nStart your project with:` )
260283 console . log ( cyan ( `cd ${ projectName } ` ) )
261284 console . log ( cyan ( `npm run dev` ) )
262285
263286 /**
264287 * Run dev server
265288 * @todo : remove
266289 */
267- if ( isLaravel ) {
268- await runCommand ( 'npm' , [ 'run' , 'build' ] )
269- await runCommand ( 'php' , [ 'artisan' , 'serve' ] )
270- } else {
271- await runCommand ( 'npm' , [ 'run' , 'dev' ] )
272- }
273-
274-
275- } catch ( err ) {
276- console . error ( 'An error occurred:' , err )
277- process . exit ( 1 )
290+ // if (isLaravel) {
291+ // await runCommand('npm', ['run', 'build'])
292+ // await runCommand('php', ['artisan', 'serve'])
293+ // } else {
294+ // await runCommand('npm', ['run', 'dev'])
295+ // }
296+ } catch ( cancelled ) {
297+ console . log ( red ( cancelled . message ) )
298+ return
278299 }
279300}
280301
@@ -292,24 +313,30 @@ function pkgFromUserAgent(userAgent) {
292313 }
293314}
294315
295- function runCommand ( command , args ) {
316+ function runCommand ( command , args , name = '' ) {
296317 return new Promise ( ( resolve , reject ) => {
297- const process = spawn ( command , args , { stdio : 'inherit' , shell : true } )
318+ const childProcess = spawn ( command , args , {
319+ stdio : 'inherit' ,
320+ } )
298321
299- process . on ( 'close' , code => {
322+ childProcess . on ( 'close' , code => {
300323 if ( code !== 0 ) {
301324 reject ( new Error ( `${ command } exited with code ${ code } ` ) )
302325 } else {
303326 resolve ( )
304327 }
305328 } )
306329
307- process . on ( 'error' , err => {
308- reject ( new Error ( `Failed to start process: ${ err . message } ` ) )
330+ childProcess . on ( 'error' , err => {
331+ reject ( new Error ( `Failed to ${ name ? name : ' start process' } : ${ err . message } ` ) )
309332 } )
310333 } )
311334}
312335
336+ function status ( msg ) {
337+ return console . log ( cyan ( msg ) )
338+ }
339+
313340async function directoryExists ( path ) {
314341 try {
315342 const stats = await fs . stat ( path )
@@ -336,7 +363,7 @@ async function isTypescript(dir, framework, ts) {
336363
337364 return tsConfig . extends !== 'astro/tsconfigs/base'
338365 } catch ( err ) {
339- console . error ( ' Error reading tsconfig.json:' , err )
366+ throw new Error ( ` Error reading tsconfig.json: ${ err . message } ` )
340367 }
341368 break
342369
@@ -360,9 +387,8 @@ async function updateAstroTsConfig(dir) {
360387 }
361388
362389 await fsExtra . writeJson ( tsConfigPath , tsConfig , { spaces : 2 } )
363- console . log ( 'tsconfig.json has been updated' )
364390 } catch ( err ) {
365- console . error ( ' Error updating tsconfig.json:' , err )
391+ throw new Error ( ` Error updating tsconfig.json: ${ err . message } ` )
366392 }
367393}
368394
@@ -377,11 +403,11 @@ async function addPublicKey(dir, publicKey) {
377403 } else if ( await fsExtra . pathExists ( tsFilePath ) ) {
378404 filePath = tsFilePath
379405 } else {
380- console . error ( ' No vueform.config.js or vueform.config.ts file found.' )
406+ throw new Error ( ` No vueform.config.js or vueform.config.ts file found: ${ err . message } ` )
381407 return
382408 }
383409 } catch ( err ) {
384- console . error ( ' Error checking for config files:' , err )
410+ throw new Error ( ` Error checking for config files: ${ err . message } ` )
385411 return
386412 }
387413
@@ -391,19 +417,57 @@ async function addPublicKey(dir, publicKey) {
391417 fileContent = fileContent . replace ( / Y O U R _ P U B L I C _ K E Y / g, publicKey )
392418
393419 await fsExtra . writeFile ( filePath , fileContent , 'utf8' )
394- console . log ( `Public Key has been inserted into ${ path . basename ( filePath ) } ` )
395420 } catch ( err ) {
396- console . error ( `Error inserting Public Key to ${ path . basename ( filePath ) } :` , err )
397- }
421+ throw new Error ( `Error inserting Public Key to ${ path . basename ( filePath ) } : ${ err . message } ` )
422+ }
398423}
399424
400425async function copyFilesToProject ( sourceDir , targetDir ) {
401426 try {
402427 await fsExtra . copy ( sourceDir , targetDir , { overwrite : true } )
403- console . log ( 'Files copied successfully.' )
404428 } catch ( err ) {
405- console . error ( 'Error copying files:' , err )
429+ throw new Error ( `Error copying files: ${ err . message } ` )
430+ }
431+ }
432+
433+ async function getComposerPath ( ) {
434+ const paths = [
435+ '/usr/local/bin/composer' ,
436+ '/usr/local/bin/composer.phar' ,
437+ '/usr/bin/composer' ,
438+ '/usr/bin/composer.phar' ,
439+ 'C:\\ProgramData\\ComposerSetup\\bin\\composer' ,
440+ 'C:\\ProgramData\\ComposerSetup\\bin\\composer.phar' ,
441+ 'C:\\Program Files\\Composer\\composer.phar' ,
442+ 'C:\\Program Files\\Composer\\composer'
443+ ]
444+
445+ let path = 'composer'
446+
447+ try {
448+ await execAsync ( 'composer --version' )
449+ return path
450+ } catch ( error ) {
451+ path = ''
406452 }
453+
454+ paths . forEach ( ( p ) => {
455+ if ( fsExtra . existsSync ( p ) ) {
456+ path = p
457+ }
458+ } )
459+
460+ if ( path . endsWith ( '.phar' ) ) {
461+ path = `php ${ path } `
462+ }
463+
464+ if ( ! path ) {
465+ console . error ( red ( '\nComposer not found. Please ensure Composer is installed and added to your PATH.' ) )
466+ console . error ( red ( 'Visit https://getcomposer.org/download/ for installation instructions.\n' ) )
467+ throw new Error ( red ( '✖' ) + ' Operation cancelled' )
468+ }
469+
470+ return path
407471}
408472
409473main ( )
0 commit comments