11#!/usr/bin/env node
22
3- import { cpSync , existsSync , mkdirSync , readFileSync , readdirSync , writeFileSync } from 'node:fs'
43import { join } from 'node:path'
5- import { spawn } from 'node:child_process'
64import * as p from '@clack/prompts'
5+ import { $ , fs } from 'zx'
76import versions from './versions.json' with { type : 'json' }
87
98const TEMPLATE_DIR = join ( import . meta. dirname , '..' , 'templates' , 'react-spa' )
109
11- const SKIP_PATTERNS = [ 'node_modules' , 'dist' , '.test.js' , '.test.ts' ]
12-
13- function shouldSkip ( name : string ) : boolean {
14- return SKIP_PATTERNS . some ( ( pattern ) => name . includes ( pattern ) )
15- }
16-
17- function copyTemplate ( src : string , dest : string ) : void {
18- const entries = readdirSync ( src , { withFileTypes : true } )
19-
20- for ( const entry of entries ) {
21- if ( shouldSkip ( entry . name ) ) continue
22-
23- const srcPath = join ( src , entry . name )
24- const destPath = join ( dest , entry . name )
25-
26- if ( entry . isDirectory ( ) ) {
27- mkdirSync ( destPath , { recursive : true } )
28- copyTemplate ( srcPath , destPath )
29- } else {
30- cpSync ( srcPath , destPath )
31- }
32- }
33- }
34-
3510function transformPackageJson ( projectName : string , destDir : string ) : void {
3611 const pkgPath = join ( destDir , 'package.json' )
37- const pkg = JSON . parse ( readFileSync ( pkgPath , 'utf-8' ) )
12+ const pkg = fs . readJsonSync ( pkgPath )
3813
39- // Update name
4014 pkg . name = projectName
4115
42- // Replace workspace: and catalog: references with actual versions
4316 for ( const depType of [ 'dependencies' , 'devDependencies' ] as const ) {
4417 if ( ! pkg [ depType ] ) continue
4518 for ( const [ name , version ] of Object . entries ( pkg [ depType ] ) ) {
@@ -55,14 +28,12 @@ function transformPackageJson(projectName: string, destDir: string): void {
5528 }
5629 }
5730
58- // Remove private flag for user projects
5931 delete pkg . private
6032
61- writeFileSync ( pkgPath , JSON . stringify ( pkg , null , 2 ) + '\n' )
33+ fs . writeJsonSync ( pkgPath , pkg , { spaces : 2 } )
6234}
6335
6436function detectPackageManager ( ) : 'pnpm' | 'yarn' | 'bun' | 'npm' {
65- // Check npm_config_user_agent (set when running via package manager)
6637 const userAgent = process . env . npm_config_user_agent
6738 if ( userAgent ) {
6839 if ( userAgent . includes ( 'pnpm' ) ) return 'pnpm'
@@ -72,25 +43,9 @@ function detectPackageManager(): 'pnpm' | 'yarn' | 'bun' | 'npm' {
7243 return 'npm'
7344}
7445
75- async function installDependencies ( pm : string , cwd : string ) : Promise < void > {
76- return new Promise ( ( resolve , reject ) => {
77- const child = spawn ( pm , [ 'install' ] , {
78- cwd,
79- stdio : 'inherit' ,
80- shell : process . platform === 'win32' ,
81- } )
82- child . on ( 'close' , ( code ) => {
83- if ( code === 0 ) resolve ( )
84- else reject ( new Error ( `${ pm } install failed with code ${ code } ` ) )
85- } )
86- child . on ( 'error' , reject )
87- } )
88- }
89-
9046async function main ( ) : Promise < void > {
9147 p. intro ( 'Create Fastify + Vite App' )
9248
93- // Get project name from CLI arg or prompt
9449 let projectName = process . argv [ 2 ]
9550
9651 if ( ! projectName ) {
@@ -100,7 +55,7 @@ async function main(): Promise<void> {
10055 defaultValue : 'my-fastify-app' ,
10156 validate : ( value ) => {
10257 if ( ! value ) return 'Project name is required'
103- if ( existsSync ( value ) ) return `Directory "${ value } " already exists`
58+ if ( fs . existsSync ( value ) ) return `Directory "${ value } " already exists`
10459 } ,
10560 } )
10661
@@ -114,33 +69,33 @@ async function main(): Promise<void> {
11469
11570 const destDir = join ( process . cwd ( ) , projectName )
11671
117- // Check if directory already exists
118- if ( existsSync ( destDir ) ) {
72+ if ( fs . existsSync ( destDir ) ) {
11973 p . cancel ( `Directory "${ projectName } " already exists` )
12074 process . exit ( 1 )
12175 }
12276
123- // Copy template
12477 const copySpinner = p . spinner ( )
12578 copySpinner . start ( 'Copying template files...' )
12679
12780 try {
128- mkdirSync ( destDir , { recursive : true } )
129- copyTemplate ( TEMPLATE_DIR , destDir )
81+ await fs . copy ( TEMPLATE_DIR , destDir , {
82+ filter : ( src ) => ! src . includes ( 'node_modules' ) && ! src . includes ( '.test.' ) ,
83+ } )
13084 transformPackageJson ( projectName , destDir )
13185 copySpinner . stop ( 'Template files copied' )
13286 } catch ( error ) {
13387 copySpinner . stop ( 'Failed to copy template' )
13488 throw error
13589 }
13690
137- // Install dependencies
13891 const pm = detectPackageManager ( )
13992 const installSpinner = p . spinner ( )
14093 installSpinner . start ( `Installing dependencies with ${ pm } ...` )
14194
14295 try {
143- await installDependencies ( pm , destDir )
96+ $ . cwd = destDir
97+ $ . quiet = true
98+ await $ `${ pm } install`
14499 installSpinner . stop ( 'Dependencies installed' )
145100 } catch {
146101 installSpinner . stop ( `Failed to install dependencies. Run "${ pm } install" manually.` )
0 commit comments