33 * CLI command dispatcher for edge-apps-scripts
44 */
55
6- import { execSync , spawn } from 'child_process'
6+ import { execSync , spawn , type ChildProcess } from 'child_process'
77import path from 'path'
88import { fileURLToPath } from 'url'
99
@@ -16,6 +16,10 @@ const commands = {
1616 description : 'Run ESLint with shared configuration' ,
1717 handler : lintCommand ,
1818 } ,
19+ dev : {
20+ description : 'Start Vite development server' ,
21+ handler : devCommand ,
22+ } ,
1923 build : {
2024 description : 'Build application for production' ,
2125 handler : buildCommand ,
@@ -30,20 +34,74 @@ const commands = {
3034 } ,
3135}
3236
37+ /**
38+ * Helper: Get NODE_PATH with library's node_modules included
39+ */
40+ function getNodePath ( ) : string {
41+ const libraryNodeModules = path . resolve ( libraryRoot , 'node_modules' )
42+ const existingNodePath = process . env . NODE_PATH || ''
43+ return existingNodePath
44+ ? `${ libraryNodeModules } ${ path . delimiter } ${ existingNodePath } `
45+ : libraryNodeModules
46+ }
47+
48+ /**
49+ * Helper: Setup signal handlers for spawned processes
50+ */
51+ function setupSignalHandlers ( child : ChildProcess ) : void {
52+ const handleSignal = ( signal : string ) => {
53+ child . kill ( signal as NodeJS . Signals )
54+ child . on ( 'exit' , ( ) => process . exit ( 0 ) )
55+ }
56+ process . on ( 'SIGINT' , ( ) => handleSignal ( 'SIGINT' ) )
57+ process . on ( 'SIGTERM' , ( ) => handleSignal ( 'SIGTERM' ) )
58+ }
59+
60+ /**
61+ * Helper: Spawn a process with common options and signal handling
62+ */
63+ function spawnWithSignalHandling (
64+ command : string ,
65+ args : string [ ] ,
66+ errorMessage : string ,
67+ ) : void {
68+ const child = spawn ( command , args , {
69+ stdio : 'inherit' ,
70+ cwd : process . cwd ( ) ,
71+ shell : process . platform === 'win32' ,
72+ env : {
73+ ...process . env ,
74+ NODE_PATH : getNodePath ( ) ,
75+ } ,
76+ } )
77+
78+ child . on ( 'error' , ( err ) => {
79+ console . error ( errorMessage , err )
80+ process . exit ( 1 )
81+ } )
82+
83+ setupSignalHandlers ( child )
84+ }
85+
86+ /**
87+ * Helper: Get Vite binary and config paths
88+ */
89+ function getVitePaths ( ) : { viteBin : string ; configPath : string } {
90+ return {
91+ viteBin : path . resolve ( libraryRoot , 'node_modules' , '.bin' , 'vite' ) ,
92+ configPath : path . resolve ( libraryRoot , 'vite.config.ts' ) ,
93+ }
94+ }
95+
3396async function lintCommand ( args : string [ ] ) {
3497 try {
35- // Get the caller's directory (the app that invoked this script)
36- const callerDir = process . cwd ( )
37-
38- // Get path to eslint binary in the library's node_modules
3998 const eslintBin = path . resolve (
4099 libraryRoot ,
41100 'node_modules' ,
42101 '.bin' ,
43102 'eslint' ,
44103 )
45104
46- // Build eslint command
47105 const eslintArgs = [
48106 '--config' ,
49107 path . resolve ( libraryRoot , 'eslint.config.ts' ) ,
@@ -55,36 +113,36 @@ async function lintCommand(args: string[]) {
55113 `"${ eslintBin } " ${ eslintArgs . map ( ( arg ) => `"${ arg } "` ) . join ( ' ' ) } ` ,
56114 {
57115 stdio : 'inherit' ,
58- cwd : callerDir ,
116+ cwd : process . cwd ( ) ,
59117 } ,
60118 )
61119 } catch {
62120 process . exit ( 1 )
63121 }
64122}
65123
66- async function buildCommand ( args : string [ ] ) {
124+ async function devCommand ( args : string [ ] ) {
67125 try {
68- const callerDir = process . cwd ( )
126+ const { viteBin, configPath } = getVitePaths ( )
127+ const viteArgs = [ '--config' , configPath , ...args ]
69128
70- // Build for production using Vite
71- const viteBin = path . resolve ( libraryRoot , 'node_modules' , '.bin' , 'vite' )
72- const configPath = path . resolve ( libraryRoot , 'vite.config.ts' )
73- const viteArgs = [ 'build' , '--config' , configPath , ...args ]
129+ spawnWithSignalHandling ( viteBin , viteArgs , 'Failed to start dev server:' )
130+ } catch {
131+ process . exit ( 1 )
132+ }
133+ }
74134
75- // Set NODE_PATH to include library's node_modules so plugins can resolve dependencies
76- const libraryNodeModules = path . resolve ( libraryRoot , 'node_modules' )
77- const existingNodePath = process . env . NODE_PATH || ''
78- const nodePath = existingNodePath
79- ? `${ libraryNodeModules } ${ path . delimiter } ${ existingNodePath } `
80- : libraryNodeModules
135+ async function buildCommand ( args : string [ ] ) {
136+ try {
137+ const { viteBin, configPath } = getVitePaths ( )
138+ const viteArgs = [ 'build' , '--config' , configPath , ...args ]
81139
82140 execSync ( `"${ viteBin } " ${ viteArgs . map ( ( arg ) => `"${ arg } "` ) . join ( ' ' ) } ` , {
83141 stdio : 'inherit' ,
84- cwd : callerDir ,
142+ cwd : process . cwd ( ) ,
85143 env : {
86144 ...process . env ,
87- NODE_PATH : nodePath ,
145+ NODE_PATH : getNodePath ( ) ,
88146 } ,
89147 } )
90148 } catch {
@@ -94,11 +152,7 @@ async function buildCommand(args: string[]) {
94152
95153async function buildDevCommand ( args : string [ ] ) {
96154 try {
97- const callerDir = process . cwd ( )
98-
99- // Build for development with watch mode using Vite
100- const viteBin = path . resolve ( libraryRoot , 'node_modules' , '.bin' , 'vite' )
101- const configPath = path . resolve ( libraryRoot , 'vite.config.ts' )
155+ const { viteBin, configPath } = getVitePaths ( )
102156 const viteArgs = [
103157 'build' ,
104158 '--watch' ,
@@ -108,50 +162,16 @@ async function buildDevCommand(args: string[]) {
108162 ...args ,
109163 ]
110164
111- // Set NODE_PATH to include library's node_modules so plugins can resolve dependencies
112- const libraryNodeModules = path . resolve ( libraryRoot , 'node_modules' )
113- const existingNodePath = process . env . NODE_PATH || ''
114- const nodePath = existingNodePath
115- ? `${ libraryNodeModules } ${ path . delimiter } ${ existingNodePath } `
116- : libraryNodeModules
117-
118- // Use spawn instead of execSync to allow watch mode to run without blocking
119- const child = spawn ( viteBin , viteArgs , {
120- stdio : 'inherit' ,
121- cwd : callerDir ,
122- shell : process . platform === 'win32' ,
123- env : {
124- ...process . env ,
125- NODE_PATH : nodePath ,
126- } ,
127- } )
128-
129- // Attach an error handler
130- child . on ( 'error' , ( err ) => {
131- console . error ( 'Failed to start build process:' , err )
132- process . exit ( 1 )
133- } )
134-
135- // Handle parent process termination to clean up child process
136- const handleSignal = ( signal : string ) => {
137- child . kill ( signal as NodeJS . Signals )
138- child . on ( 'exit' , ( ) => process . exit ( 0 ) )
139- }
140- process . on ( 'SIGINT' , ( ) => handleSignal ( 'SIGINT' ) )
141- process . on ( 'SIGTERM' , ( ) => handleSignal ( 'SIGTERM' ) )
165+ spawnWithSignalHandling ( viteBin , viteArgs , 'Failed to start build process:' )
142166 } catch {
143167 process . exit ( 1 )
144168 }
145169}
146170
147171async function typeCheckCommand ( args : string [ ] ) {
148172 try {
149- const callerDir = process . cwd ( )
150-
151- // Get path to tsc binary in the library's node_modules
152173 const tscBin = path . resolve ( libraryRoot , 'node_modules' , '.bin' , 'tsc' )
153174
154- // Build tsc command
155175 const tscArgs = [
156176 '--noEmit' ,
157177 '--project' ,
@@ -161,7 +181,7 @@ async function typeCheckCommand(args: string[]) {
161181
162182 execSync ( `"${ tscBin } " ${ tscArgs . map ( ( arg ) => `"${ arg } "` ) . join ( ' ' ) } ` , {
163183 stdio : 'inherit' ,
164- cwd : callerDir ,
184+ cwd : process . cwd ( ) ,
165185 } )
166186 } catch {
167187 process . exit ( 1 )
0 commit comments