Skip to content

Commit 6b73587

Browse files
committed
feat: beatify, graceful exit, composer
1 parent 408dd5a commit 6b73587

File tree

1 file changed

+111
-47
lines changed

1 file changed

+111
-47
lines changed

src/test.js

Lines changed: 111 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,25 @@
33
* - test different package managers
44
* - think about npm < 6
55
* - get notified if any framework files change
6-
* - exit properly anytime
76
*/
87

98
import { spawn } from 'child_process'
109
import prompts from 'prompts'
1110
import path from 'path'
11+
import { promisify } from 'util'
12+
import { exec } from 'child_process'
1213
import fs_, { promises as fs } from 'fs'
1314
import fsExtra from 'fs-extra'
1415
import { fileURLToPath } from 'url'
15-
import { cyan } from 'kolorist'
16+
import {
17+
cyan,
18+
red,
19+
green,
20+
} from 'kolorist'
1621
import 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
2027
const 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

3744
const 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+
313340
async 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(/YOUR_PUBLIC_KEY/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

400425
async 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

409473
main()

0 commit comments

Comments
 (0)