@@ -57,7 +57,11 @@ function isRootCommand(value: string): value is NonNullable<RootCommand> {
5757 || value === "merge" ;
5858}
5959
60- function parseRootArgs ( args : string [ ] ) : ParsedRootArgs {
60+ function isServeRootOption ( value : string ) : boolean {
61+ return value === "--port" || value === "--debug" || value === "--help" || value === "-h" ;
62+ }
63+
64+ export function parseRootArgs ( args : string [ ] ) : ParsedRootArgs {
6165 let port = parseInt ( process . env . BACKEND_PORT || "5111" , 10 ) ;
6266 let debug = false ;
6367 let command : RootCommand = null ;
@@ -67,7 +71,7 @@ function parseRootArgs(args: string[]): ParsedRootArgs {
6771 const arg = args [ index ] ;
6872 if ( ! arg ) continue ;
6973
70- if ( command ) {
74+ if ( command && ( command !== "serve" || ! isServeRootOption ( arg ) ) ) {
7175 commandArgs . push ( arg ) ;
7276 continue ;
7377 }
@@ -117,40 +121,6 @@ function isWorktreeCommand(command: RootCommand): command is "add" | "list" | "o
117121 || command === "merge" ;
118122}
119123
120- // ── Parse args ───────────────────────────────────────────────────────────────
121-
122- const args = process . argv . slice ( 2 ) ;
123- let parsed : ParsedRootArgs ;
124-
125- try {
126- parsed = parseRootArgs ( args ) ;
127- } catch ( error ) {
128- console . error ( error instanceof Error ? error . message : String ( error ) ) ;
129- process . exit ( 1 ) ;
130- }
131-
132- if ( parsed . command === "init" ) {
133- await import ( "./init.ts" ) ;
134- process . exit ( 0 ) ;
135- }
136-
137- if ( parsed . command === "service" ) {
138- const { default : service } = await import ( "./service.ts" ) ;
139- await service ( parsed . commandArgs ) ;
140- process . exit ( 0 ) ;
141- }
142-
143- if ( parsed . command === "update" ) {
144- console . log ( "Updating webmux to the latest version..." ) ;
145- const proc = Bun . spawn ( [ "bun" , "install" , "--global" , "webmux@latest" ] , {
146- stdin : "inherit" ,
147- stdout : "inherit" ,
148- stderr : "inherit" ,
149- } ) ;
150- const code = await proc . exited ;
151- process . exit ( code ) ;
152- }
153-
154124// ── Load env files from CWD (.env.local overrides .env) ─────────────────────
155125
156126async function loadEnvFile ( path : string ) {
@@ -169,43 +139,6 @@ async function loadEnvFile(path: string) {
169139 }
170140}
171141
172- await loadEnvFile ( resolve ( process . cwd ( ) , ".env.local" ) ) ;
173- await loadEnvFile ( resolve ( process . cwd ( ) , ".env" ) ) ;
174-
175- if ( isWorktreeCommand ( parsed . command ) ) {
176- const { runWorktreeCommand } = await import ( "./worktree-commands.ts" ) ;
177- const exitCode = await runWorktreeCommand ( {
178- command : parsed . command ,
179- args : parsed . commandArgs ,
180- projectDir : process . cwd ( ) ,
181- port : parsed . port ,
182- } ) ;
183- process . exit ( exitCode ) ;
184- }
185-
186- // ── No command → show help ───────────────────────────────────────────────────
187-
188- if ( parsed . command === null ) {
189- usage ( ) ;
190- process . exit ( 0 ) ;
191- }
192-
193- // ── serve: Check for .webmux.yaml ────────────────────────────────────────────
194-
195- if ( ! existsSync ( resolve ( process . cwd ( ) , ".webmux.yaml" ) ) ) {
196- console . error ( "No .webmux.yaml found in this directory.\nRun `webmux init` to set up your project." ) ;
197- process . exit ( 1 ) ;
198- }
199-
200- // ── Shared env for child processes ───────────────────────────────────────────
201-
202- const baseEnv = {
203- ...process . env ,
204- BACKEND_PORT : String ( parsed . port ) ,
205- WEBMUX_PROJECT_DIR : process . cwd ( ) ,
206- ...( parsed . debug ? { WEBMUX_DEBUG : "1" } : { } ) ,
207- } ;
208-
209142// ── Prefixed output ──────────────────────────────────────────────────────────
210143
211144function pipeWithPrefix ( stream : ReadableStream < Uint8Array > , prefix : string ) {
@@ -230,50 +163,113 @@ function pipeWithPrefix(stream: ReadableStream<Uint8Array>, prefix: string) {
230163 } ) ( ) ;
231164}
232165
233- // ── Process management ───────────────────────────────────────────────────────
166+ async function main ( args : string [ ] = process . argv . slice ( 2 ) ) : Promise < void > {
167+ let parsed : ParsedRootArgs ;
168+
169+ try {
170+ parsed = parseRootArgs ( args ) ;
171+ } catch ( error ) {
172+ console . error ( error instanceof Error ? error . message : String ( error ) ) ;
173+ process . exit ( 1 ) ;
174+ }
175+
176+ if ( parsed . command === "init" ) {
177+ await import ( "./init.ts" ) ;
178+ process . exit ( 0 ) ;
179+ }
180+
181+ if ( parsed . command === "service" ) {
182+ const { default : service } = await import ( "./service.ts" ) ;
183+ await service ( parsed . commandArgs ) ;
184+ process . exit ( 0 ) ;
185+ }
234186
235- const children : Subprocess [ ] = [ ] ;
236- let exiting = false ;
187+ if ( parsed . command === "update" ) {
188+ console . log ( "Updating webmux to the latest version..." ) ;
189+ const proc = Bun . spawn ( [ "bun" , "install" , "--global" , "webmux@latest" ] , {
190+ stdin : "inherit" ,
191+ stdout : "inherit" ,
192+ stderr : "inherit" ,
193+ } ) ;
194+ const code = await proc . exited ;
195+ process . exit ( code ) ;
196+ }
197+
198+ await loadEnvFile ( resolve ( process . cwd ( ) , ".env.local" ) ) ;
199+ await loadEnvFile ( resolve ( process . cwd ( ) , ".env" ) ) ;
200+
201+ if ( isWorktreeCommand ( parsed . command ) ) {
202+ const { runWorktreeCommand } = await import ( "./worktree-commands.ts" ) ;
203+ const exitCode = await runWorktreeCommand ( {
204+ command : parsed . command ,
205+ args : parsed . commandArgs ,
206+ projectDir : process . cwd ( ) ,
207+ port : parsed . port ,
208+ } ) ;
209+ process . exit ( exitCode ) ;
210+ }
211+
212+ if ( parsed . command === null ) {
213+ usage ( ) ;
214+ process . exit ( 0 ) ;
215+ }
237216
238- function cleanup ( ) {
239- if ( exiting ) return ;
240- exiting = true ;
241- for ( const child of children ) {
242- try { child . kill ( "SIGTERM" ) ; } catch { }
217+ if ( ! existsSync ( resolve ( process . cwd ( ) , ".webmux.yaml" ) ) ) {
218+ console . error ( "No .webmux.yaml found in this directory.\nRun `webmux init` to set up your project." ) ;
219+ process . exit ( 1 ) ;
243220 }
244- // Force-kill stragglers after 1s, then exit
245- setTimeout ( ( ) => {
221+
222+ const baseEnv = {
223+ ...process . env ,
224+ BACKEND_PORT : String ( parsed . port ) ,
225+ WEBMUX_PROJECT_DIR : process . cwd ( ) ,
226+ ...( parsed . debug ? { WEBMUX_DEBUG : "1" } : { } ) ,
227+ } ;
228+
229+ const children : Subprocess [ ] = [ ] ;
230+ let exiting = false ;
231+
232+ function cleanup ( ) {
233+ if ( exiting ) return ;
234+ exiting = true ;
246235 for ( const child of children ) {
247- try { child . kill ( "SIGKILL " ) ; } catch { }
236+ try { child . kill ( "SIGTERM " ) ; } catch { }
248237 }
249- process . exit ( 0 ) ;
250- } , 1000 ) . unref ( ) ;
251- }
238+ setTimeout ( ( ) => {
239+ for ( const child of children ) {
240+ try { child . kill ( "SIGKILL" ) ; } catch { }
241+ }
242+ process . exit ( 0 ) ;
243+ } , 1000 ) . unref ( ) ;
244+ }
252245
253- process . on ( "SIGINT" , cleanup ) ;
254- process . on ( "SIGTERM" , cleanup ) ;
246+ process . on ( "SIGINT" , cleanup ) ;
247+ process . on ( "SIGTERM" , cleanup ) ;
255248
256- // ── Start ────────────────────────────────────────────────────────────────────
249+ const backendEntry = join ( PKG_ROOT , "backend" , "dist" , "server.js" ) ;
250+ const staticDir = join ( PKG_ROOT , "frontend" , "dist" ) ;
257251
258- const backendEntry = join ( PKG_ROOT , "backend" , "dist" , "server.js" ) ;
259- const staticDir = join ( PKG_ROOT , "frontend" , "dist" ) ;
252+ if ( ! existsSync ( staticDir ) ) {
253+ console . error (
254+ `Error: frontend/dist/ not found. Run 'bun run build' first.` ,
255+ ) ;
256+ process . exit ( 1 ) ;
257+ }
260258
261- if ( ! existsSync ( staticDir ) ) {
262- console . error (
263- `Error: frontend/dist/ not found. Run 'bun run build' first.` ,
264- ) ;
265- process . exit ( 1 ) ;
266- }
259+ console . log ( `Starting webmux on port ${ parsed . port } ...` ) ;
267260
268- console . log ( `Starting webmux on port ${ parsed . port } ...` ) ;
261+ const be = Bun . spawn ( [ "bun" , backendEntry ] , {
262+ env : { ...baseEnv , WEBMUX_STATIC_DIR : staticDir } ,
263+ stdout : "pipe" ,
264+ stderr : "pipe" ,
265+ } ) ;
266+ children . push ( be ) ;
267+ pipeWithPrefix ( be . stdout , "[BE]" ) ;
268+ pipeWithPrefix ( be . stderr , "[BE]" ) ;
269269
270- const be = Bun . spawn ( [ "bun" , backendEntry ] , {
271- env : { ...baseEnv , WEBMUX_STATIC_DIR : staticDir } ,
272- stdout : "pipe" ,
273- stderr : "pipe" ,
274- } ) ;
275- children . push ( be ) ;
276- pipeWithPrefix ( be . stdout , "[BE]" ) ;
277- pipeWithPrefix ( be . stderr , "[BE]" ) ;
270+ await be . exited ;
271+ }
278272
279- await be . exited ;
273+ if ( import . meta. main ) {
274+ await main ( ) ;
275+ }
0 commit comments