3
3
* - test different package managers
4
4
* - think about npm < 6
5
5
* - get notified if any framework files change
6
- * - exit properly anytime
7
6
*/
8
7
9
8
import { spawn } from 'child_process'
10
9
import prompts from 'prompts'
11
10
import path from 'path'
11
+ import { promisify } from 'util'
12
+ import { exec } from 'child_process'
12
13
import fs_ , { promises as fs } from 'fs'
13
14
import fsExtra from 'fs-extra'
14
15
import { fileURLToPath } from 'url'
15
- import { cyan } from 'kolorist'
16
+ import {
17
+ cyan ,
18
+ red ,
19
+ green ,
20
+ } from 'kolorist'
16
21
import minimist from 'minimist'
17
22
23
+ const execAsync = promisify ( exec )
24
+
18
25
// Avoids autoconversion to number of the project name by defining that the args
19
26
// non associated with an option ( _ ) needs to be parsed as a string. See #4606
20
27
const argv = minimist ( process . argv . slice ( 2 ) , { string : [ '_' ] } )
@@ -31,7 +38,7 @@ const frameworks = [
31
38
{ title : 'Nuxt' , value : 'nuxt' , command : 'npx nuxi@latest init %PROJECT_NAME% --packageManager=%PACKAGE_MANAGER% --gitInit=false' } ,
32
39
// @todo : remove --template=basics
33
40
{ 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%' } ,
35
42
]
36
43
37
44
const themes = [
@@ -84,6 +91,11 @@ async function main() {
84
91
}
85
92
return true
86
93
}
94
+ } ,
95
+ {
96
+ onCancel : ( ) => {
97
+ throw new Error ( red ( '✖' ) + ' Operation cancelled' )
98
+ } ,
87
99
} )
88
100
89
101
const response = await prompts ( [
@@ -137,7 +149,12 @@ async function main() {
137
149
message : 'Select a theme for your project:' ,
138
150
choices : themes
139
151
}
140
- ] )
152
+ ] ,
153
+ {
154
+ onCancel : ( ) => {
155
+ throw new Error ( red ( '✖' ) + ' Operation cancelled' )
156
+ } ,
157
+ } )
141
158
142
159
const { framework, ts, builder, publicKey } = response
143
160
@@ -146,17 +163,24 @@ async function main() {
146
163
if ( projectName && framework ) {
147
164
const fw = getFramework ( framework )
148
165
149
- console . log ( `Creating project '${ projectName } ' using ${ fw . title } ...` )
166
+ status ( `Creating project '${ projectName } ' using ${ fw . title } ...` )
150
167
151
168
const template = framework === 'vite' ? `vue${ ts ? '-ts' : '' } ` : ''
152
169
170
+ let composerPath = ''
171
+
172
+ if ( framework === 'laravel' ) {
173
+ composerPath = await getComposerPath ( )
174
+ }
175
+
153
176
const command = fw . command
154
177
. replace ( '%PROJECT_NAME%' , projectName )
155
178
. replace ( '%TEMPLATE%' , template )
156
179
. replace ( '%PACKAGE_MANAGER%' , packageManager )
180
+ . replace ( '%COMPOSER_PATH%' , composerPath )
157
181
. split ( ' ' )
158
182
159
- await runCommand ( command [ 0 ] , command . slice ( 1 ) )
183
+ await runCommand ( command [ 0 ] , command . slice ( 1 ) , `create project with ${ fw . title } ` )
160
184
} else {
161
185
console . error ( 'Project creation canceled.' )
162
186
return
@@ -171,13 +195,12 @@ async function main() {
171
195
* Enter project folder
172
196
*/
173
197
process . chdir ( projectName )
174
- console . log ( `Changed directory to ${ projectName } ` )
175
198
176
199
/**
177
200
* Install base dependencies
178
201
*/
179
- console . log ( 'Running npm install ...')
180
- await runCommand ( 'npm' , [ 'install' ] )
202
+ status ( 'Installing dependencies ...')
203
+ await runCommand ( 'npm' , [ 'install' ] , 'install dependencies' )
181
204
182
205
/**
183
206
* Variables
@@ -195,28 +218,28 @@ async function main() {
195
218
* Install Tailwind
196
219
*/
197
220
if ( isTailwind ) {
198
- console . log ( 'Installing Tailwind...')
221
+ status ( '\nInstalling Tailwind...')
199
222
await Promise . all ( tailwind [ framework ] . install . map ( async ( script ) => {
200
223
const command = script . split ( ' ' )
201
- await runCommand ( command [ 0 ] , command . slice ( 1 ) )
224
+ await runCommand ( command [ 0 ] , command . slice ( 1 ) , 'install Tailwind CSS' )
202
225
} ) )
203
226
}
204
227
205
228
/**
206
229
* Install Bootstrap
207
230
*/
208
231
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' )
211
234
}
212
235
213
236
/**
214
237
* Astro updates
215
238
*/
216
239
if ( isAstro ) {
217
240
// 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' )
220
243
221
244
// Extend tsconfig.json
222
245
await updateAstroTsConfig ( process . cwd ( ) )
@@ -226,8 +249,8 @@ async function main() {
226
249
* Install Vue in Laravel
227
250
*/
228
251
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' )
231
254
}
232
255
233
256
/**
@@ -237,13 +260,12 @@ async function main() {
237
260
? framework === 'nuxt' ? '@vueform/builder-nuxt' : '@vueform/vueform @vueform/builder'
238
261
: framework === 'nuxt' ? '@vueform/nuxt' : '@vueform/vueform'
239
262
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 } ` )
242
265
243
266
/**
244
267
* Copy Vueform files to project directory
245
268
*/
246
- console . log ( `Copying additional files to ${ projectName } ...` )
247
269
await copyFilesToProject ( sourcePath , targetPath )
248
270
249
271
/**
@@ -256,25 +278,24 @@ async function main() {
256
278
/**
257
279
* Show finish instructions
258
280
*/
259
- console . log ( '' )
281
+ console . log ( green ( `\n✔ Installation finished` ) )
282
+ console . log ( `\nStart your project with:` )
260
283
console . log ( cyan ( `cd ${ projectName } ` ) )
261
284
console . log ( cyan ( `npm run dev` ) )
262
285
263
286
/**
264
287
* Run dev server
265
288
* @todo : remove
266
289
*/
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
278
299
}
279
300
}
280
301
@@ -292,24 +313,30 @@ function pkgFromUserAgent(userAgent) {
292
313
}
293
314
}
294
315
295
- function runCommand ( command , args ) {
316
+ function runCommand ( command , args , name = '' ) {
296
317
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
+ } )
298
321
299
- process . on ( 'close' , code => {
322
+ childProcess . on ( 'close' , code => {
300
323
if ( code !== 0 ) {
301
324
reject ( new Error ( `${ command } exited with code ${ code } ` ) )
302
325
} else {
303
326
resolve ( )
304
327
}
305
328
} )
306
329
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 } ` ) )
309
332
} )
310
333
} )
311
334
}
312
335
336
+ function status ( msg ) {
337
+ return console . log ( cyan ( msg ) )
338
+ }
339
+
313
340
async function directoryExists ( path ) {
314
341
try {
315
342
const stats = await fs . stat ( path )
@@ -336,7 +363,7 @@ async function isTypescript(dir, framework, ts) {
336
363
337
364
return tsConfig . extends !== 'astro/tsconfigs/base'
338
365
} catch ( err ) {
339
- console . error ( ' Error reading tsconfig.json:' , err )
366
+ throw new Error ( ` Error reading tsconfig.json: ${ err . message } ` )
340
367
}
341
368
break
342
369
@@ -360,9 +387,8 @@ async function updateAstroTsConfig(dir) {
360
387
}
361
388
362
389
await fsExtra . writeJson ( tsConfigPath , tsConfig , { spaces : 2 } )
363
- console . log ( 'tsconfig.json has been updated' )
364
390
} catch ( err ) {
365
- console . error ( ' Error updating tsconfig.json:' , err )
391
+ throw new Error ( ` Error updating tsconfig.json: ${ err . message } ` )
366
392
}
367
393
}
368
394
@@ -377,11 +403,11 @@ async function addPublicKey(dir, publicKey) {
377
403
} else if ( await fsExtra . pathExists ( tsFilePath ) ) {
378
404
filePath = tsFilePath
379
405
} 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 } ` )
381
407
return
382
408
}
383
409
} catch ( err ) {
384
- console . error ( ' Error checking for config files:' , err )
410
+ throw new Error ( ` Error checking for config files: ${ err . message } ` )
385
411
return
386
412
}
387
413
@@ -391,19 +417,57 @@ async function addPublicKey(dir, publicKey) {
391
417
fileContent = fileContent . replace ( / Y O U R _ P U B L I C _ K E Y / g, publicKey )
392
418
393
419
await fsExtra . writeFile ( filePath , fileContent , 'utf8' )
394
- console . log ( `Public Key has been inserted into ${ path . basename ( filePath ) } ` )
395
420
} 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
+ }
398
423
}
399
424
400
425
async function copyFilesToProject ( sourceDir , targetDir ) {
401
426
try {
402
427
await fsExtra . copy ( sourceDir , targetDir , { overwrite : true } )
403
- console . log ( 'Files copied successfully.' )
404
428
} 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 = ''
406
452
}
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
407
471
}
408
472
409
473
main ( )
0 commit comments