@@ -266,141 +266,73 @@ export class GitHubDeploymentHandler {
266266 temp_dir : tempDir
267267 } , 'Installing dependencies with npm install' ) ;
268268
269- return new Promise ( ( resolve , reject ) => {
270- const npmInstall = spawn ( 'npm' , [ 'install' ] , {
271- cwd : tempDir ,
272- stdio : 'pipe'
273- } ) ;
274-
275- let stderr = '' ;
276-
277- // Capture and emit stdout to backend
278- npmInstall . stdout . on ( 'data' , ( data ) => {
279- const output = data . toString ( ) . trim ( ) ;
280- if ( output ) {
281- this . logBuffer . add ( {
282- installation_id : installationId ,
283- team_id : teamId ,
284- user_id : userId ,
285- level : 'info' ,
286- message : `[npm install] ${ output } ` ,
287- timestamp : new Date ( ) . toISOString ( )
288- } ) ;
289- }
290- } ) ;
291-
292- npmInstall . stderr . on ( 'data' , ( data ) => {
293- const output = data . toString ( ) . trim ( ) ;
294- stderr += output ;
295-
296- // Also emit stderr as warn logs
297- if ( output ) {
298- this . logBuffer . add ( {
299- installation_id : installationId ,
300- team_id : teamId ,
301- user_id : userId ,
302- level : 'warn' ,
303- message : `[npm install] ${ output } ` ,
304- timestamp : new Date ( ) . toISOString ( )
305- } ) ;
306- }
307- } ) ;
308-
309- npmInstall . on ( 'exit' , ( code ) => {
310- if ( code === 0 ) {
311- this . logger . info ( {
312- operation : 'npm_install_success' ,
313- temp_dir : tempDir
314- } , 'Dependencies installed successfully' ) ;
315- resolve ( ) ;
316- } else {
317- this . logger . error ( {
318- operation : 'npm_install_failed' ,
319- temp_dir : tempDir ,
320- exit_code : code ,
321- stderr : stderr . substring ( 0 , 500 ) // Limit stderr output
322- } , `npm install failed with code ${ code } ` ) ;
323-
324- reject ( new Error ( `npm install failed with code ${ code } : ${ stderr . substring ( 0 , 200 ) } ` ) ) ;
269+ // Use nsjail in production, direct spawn in development
270+ if ( this . processSpawner . shouldUseNsjail ( ) ) {
271+ const result = await this . processSpawner . spawnBuildCommandWithNsjail (
272+ 'npm' ,
273+ [ 'install' ] ,
274+ tempDir ,
275+ {
276+ allowNetwork : true , // npm needs network for package downloads
277+ timeoutMs : 120000 , // 2 minutes
278+ runtime : 'node'
325279 }
326- } ) ;
327-
328- npmInstall . on ( 'error' , ( error ) => {
329- this . logger . error ( {
330- operation : 'npm_install_error' ,
331- temp_dir : tempDir ,
332- error : error . message
333- } , 'npm install process error' ) ;
334-
335- reject ( new Error ( `npm install process error: ${ error . message } ` ) ) ;
336- } ) ;
337- } ) ;
338- }
339-
340- /**
341- * Build package if build script exists
342- */
343- async buildPackage (
344- tempDir : string ,
345- installationId : string ,
346- teamId : string ,
347- userId ?: string
348- ) : Promise < void > {
349- try {
350- // Read package.json to check for build script
351- const packageJsonPath = path . join ( tempDir , 'package.json' ) ;
352- const packageJsonContent = await fs . promises . readFile ( packageJsonPath , 'utf8' ) ;
353- const packageJson = JSON . parse ( packageJsonContent ) ;
354-
355- // Check if there's a build script
356- if ( ! packageJson . scripts ?. build ) {
357- this . logger . debug ( {
358- operation : 'npm_build_skip' ,
359- temp_dir : tempDir
360- } , 'No build script found, skipping build' ) ;
280+ ) ;
361281
362- // Emit log to backend so users know build was skipped
282+ // Emit logs to backend
283+ if ( result . stdout ) {
363284 this . logBuffer . add ( {
364285 installation_id : installationId ,
365286 team_id : teamId ,
366287 user_id : userId ,
367288 level : 'info' ,
368- message : ' [npm build] No build script found, skipping build' ,
289+ message : ` [npm install] ${ result . stdout . substring ( 0 , 1000 ) } ` ,
369290 timestamp : new Date ( ) . toISOString ( )
370291 } ) ;
292+ }
371293
372- return ;
294+ if ( result . code !== 0 ) {
295+ this . logBuffer . add ( {
296+ installation_id : installationId ,
297+ team_id : teamId ,
298+ user_id : userId ,
299+ level : 'error' ,
300+ message : `[npm install] ${ result . stderr . substring ( 0 , 500 ) } ` ,
301+ timestamp : new Date ( ) . toISOString ( )
302+ } ) ;
303+ throw new Error ( `npm install failed with code ${ result . code } : ${ result . stderr . substring ( 0 , 200 ) } ` ) ;
373304 }
374305
375- this . logger . debug ( {
376- operation : 'npm_build_start ' ,
306+ this . logger . info ( {
307+ operation : 'npm_install_success ' ,
377308 temp_dir : tempDir
378- } , 'Building package with npm run build' ) ;
379-
309+ } , 'Dependencies installed successfully' ) ;
310+ } else {
311+ // Development mode - direct spawn
380312 return new Promise ( ( resolve , reject ) => {
381- const npmBuild = spawn ( 'npm' , [ 'run' , 'build '] , {
313+ const npmInstall = spawn ( 'npm' , [ 'install ' ] , {
382314 cwd : tempDir ,
383315 stdio : 'pipe'
384316 } ) ;
385317
386318 let stderr = '' ;
387319
388320 // Capture and emit stdout to backend
389- npmBuild . stdout . on ( 'data' , ( data ) => {
321+ npmInstall . stdout . on ( 'data' , ( data ) => {
390322 const output = data . toString ( ) . trim ( ) ;
391323 if ( output ) {
392324 this . logBuffer . add ( {
393325 installation_id : installationId ,
394326 team_id : teamId ,
395327 user_id : userId ,
396328 level : 'info' ,
397- message : `[npm build ] ${ output } ` ,
329+ message : `[npm install ] ${ output } ` ,
398330 timestamp : new Date ( ) . toISOString ( )
399331 } ) ;
400332 }
401333 } ) ;
402334
403- npmBuild . stderr . on ( 'data' , ( data ) => {
335+ npmInstall . stderr . on ( 'data' , ( data ) => {
404336 const output = data . toString ( ) . trim ( ) ;
405337 stderr += output ;
406338
@@ -411,41 +343,197 @@ export class GitHubDeploymentHandler {
411343 team_id : teamId ,
412344 user_id : userId ,
413345 level : 'warn' ,
414- message : `[npm build ] ${ output } ` ,
346+ message : `[npm install ] ${ output } ` ,
415347 timestamp : new Date ( ) . toISOString ( )
416348 } ) ;
417349 }
418350 } ) ;
419351
420- npmBuild . on ( 'exit' , ( code ) => {
352+ npmInstall . on ( 'exit' , ( code ) => {
421353 if ( code === 0 ) {
422354 this . logger . info ( {
423- operation : 'npm_build_success ' ,
355+ operation : 'npm_install_success ' ,
424356 temp_dir : tempDir
425- } , 'Package built successfully' ) ;
357+ } , 'Dependencies installed successfully' ) ;
426358 resolve ( ) ;
427359 } else {
428360 this . logger . error ( {
429- operation : 'npm_build_failed ' ,
361+ operation : 'npm_install_failed ' ,
430362 temp_dir : tempDir ,
431363 exit_code : code ,
432- stderr : stderr . substring ( 0 , 500 )
433- } , `npm run build failed with code ${ code } ` ) ;
364+ stderr : stderr . substring ( 0 , 500 ) // Limit stderr output
365+ } , `npm install failed with code ${ code } ` ) ;
434366
435- reject ( new Error ( `npm run build failed with code ${ code } : ${ stderr . substring ( 0 , 200 ) } ` ) ) ;
367+ reject ( new Error ( `npm install failed with code ${ code } : ${ stderr . substring ( 0 , 200 ) } ` ) ) ;
436368 }
437369 } ) ;
438370
439- npmBuild . on ( 'error' , ( error ) => {
371+ npmInstall . on ( 'error' , ( error ) => {
440372 this . logger . error ( {
441- operation : 'npm_build_error ' ,
373+ operation : 'npm_install_error ' ,
442374 temp_dir : tempDir ,
443375 error : error . message
444- } , 'npm run build process error' ) ;
376+ } , 'npm install process error' ) ;
445377
446- reject ( new Error ( `npm run build process error: ${ error . message } ` ) ) ;
378+ reject ( new Error ( `npm install process error: ${ error . message } ` ) ) ;
447379 } ) ;
448380 } ) ;
381+ }
382+ }
383+
384+ /**
385+ * Build package if build script exists
386+ */
387+ async buildPackage (
388+ tempDir : string ,
389+ installationId : string ,
390+ teamId : string ,
391+ userId ?: string
392+ ) : Promise < void > {
393+ try {
394+ // Read package.json to check for build script
395+ const packageJsonPath = path . join ( tempDir , 'package.json' ) ;
396+ const packageJsonContent = await fs . promises . readFile ( packageJsonPath , 'utf8' ) ;
397+ const packageJson = JSON . parse ( packageJsonContent ) ;
398+
399+ // Check if there's a build script
400+ if ( ! packageJson . scripts ?. build ) {
401+ this . logger . debug ( {
402+ operation : 'npm_build_skip' ,
403+ temp_dir : tempDir
404+ } , 'No build script found, skipping build' ) ;
405+
406+ // Emit log to backend so users know build was skipped
407+ this . logBuffer . add ( {
408+ installation_id : installationId ,
409+ team_id : teamId ,
410+ user_id : userId ,
411+ level : 'info' ,
412+ message : '[npm build] No build script found, skipping build' ,
413+ timestamp : new Date ( ) . toISOString ( )
414+ } ) ;
415+
416+ return ;
417+ }
418+
419+ this . logger . debug ( {
420+ operation : 'npm_build_start' ,
421+ temp_dir : tempDir
422+ } , 'Building package with npm run build' ) ;
423+
424+ // Use nsjail in production, direct spawn in development
425+ if ( this . processSpawner . shouldUseNsjail ( ) ) {
426+ const result = await this . processSpawner . spawnBuildCommandWithNsjail (
427+ 'npm' ,
428+ [ 'run' , 'build' ] ,
429+ tempDir ,
430+ {
431+ allowNetwork : false , // Build should not need network
432+ timeoutMs : 120000 , // 2 minutes
433+ runtime : 'node'
434+ }
435+ ) ;
436+
437+ // Emit logs to backend
438+ if ( result . stdout ) {
439+ this . logBuffer . add ( {
440+ installation_id : installationId ,
441+ team_id : teamId ,
442+ user_id : userId ,
443+ level : 'info' ,
444+ message : `[npm build] ${ result . stdout . substring ( 0 , 1000 ) } ` ,
445+ timestamp : new Date ( ) . toISOString ( )
446+ } ) ;
447+ }
448+
449+ if ( result . code !== 0 ) {
450+ this . logBuffer . add ( {
451+ installation_id : installationId ,
452+ team_id : teamId ,
453+ user_id : userId ,
454+ level : 'error' ,
455+ message : `[npm build] ${ result . stderr . substring ( 0 , 500 ) } ` ,
456+ timestamp : new Date ( ) . toISOString ( )
457+ } ) ;
458+ throw new Error ( `npm run build failed with code ${ result . code } : ${ result . stderr . substring ( 0 , 200 ) } ` ) ;
459+ }
460+
461+ this . logger . info ( {
462+ operation : 'npm_build_success' ,
463+ temp_dir : tempDir
464+ } , 'Package built successfully' ) ;
465+ } else {
466+ // Development mode - direct spawn
467+ return new Promise ( ( resolve , reject ) => {
468+ const npmBuild = spawn ( 'npm' , [ 'run' , 'build' ] , {
469+ cwd : tempDir ,
470+ stdio : 'pipe'
471+ } ) ;
472+
473+ let stderr = '' ;
474+
475+ // Capture and emit stdout to backend
476+ npmBuild . stdout . on ( 'data' , ( data ) => {
477+ const output = data . toString ( ) . trim ( ) ;
478+ if ( output ) {
479+ this . logBuffer . add ( {
480+ installation_id : installationId ,
481+ team_id : teamId ,
482+ user_id : userId ,
483+ level : 'info' ,
484+ message : `[npm build] ${ output } ` ,
485+ timestamp : new Date ( ) . toISOString ( )
486+ } ) ;
487+ }
488+ } ) ;
489+
490+ npmBuild . stderr . on ( 'data' , ( data ) => {
491+ const output = data . toString ( ) . trim ( ) ;
492+ stderr += output ;
493+
494+ // Also emit stderr as warn logs
495+ if ( output ) {
496+ this . logBuffer . add ( {
497+ installation_id : installationId ,
498+ team_id : teamId ,
499+ user_id : userId ,
500+ level : 'warn' ,
501+ message : `[npm build] ${ output } ` ,
502+ timestamp : new Date ( ) . toISOString ( )
503+ } ) ;
504+ }
505+ } ) ;
506+
507+ npmBuild . on ( 'exit' , ( code ) => {
508+ if ( code === 0 ) {
509+ this . logger . info ( {
510+ operation : 'npm_build_success' ,
511+ temp_dir : tempDir
512+ } , 'Package built successfully' ) ;
513+ resolve ( ) ;
514+ } else {
515+ this . logger . error ( {
516+ operation : 'npm_build_failed' ,
517+ temp_dir : tempDir ,
518+ exit_code : code ,
519+ stderr : stderr . substring ( 0 , 500 )
520+ } , `npm run build failed with code ${ code } ` ) ;
521+
522+ reject ( new Error ( `npm run build failed with code ${ code } : ${ stderr . substring ( 0 , 200 ) } ` ) ) ;
523+ }
524+ } ) ;
525+
526+ npmBuild . on ( 'error' , ( error ) => {
527+ this . logger . error ( {
528+ operation : 'npm_build_error' ,
529+ temp_dir : tempDir ,
530+ error : error . message
531+ } , 'npm run build process error' ) ;
532+
533+ reject ( new Error ( `npm run build process error: ${ error . message } ` ) ) ;
534+ } ) ;
535+ } ) ;
536+ }
449537
450538 } catch ( error ) {
451539 const errorMessage = error instanceof Error ? error . message : String ( error ) ;
0 commit comments