11import { spawn } from 'child_process' ;
2- import { mkdir } from 'fs/promises' ;
32import * as path from 'path' ;
43import * as fs from 'fs' ;
54import { Logger } from 'pino' ;
@@ -26,6 +25,18 @@ import {
2625 downloadRepository ,
2726 extractTarball
2827} from '../utils/tarball-operations' ;
28+ import {
29+ emitBuildResult ,
30+ type BuildCommandMetadata
31+ } from '../utils/build-logging' ;
32+ import {
33+ readPackageJson ,
34+ hasBuildScript
35+ } from '../utils/package-json-reader' ;
36+ import {
37+ createDeploymentDirectory ,
38+ type DeploymentDirConfig
39+ } from '../utils/deployment-directory' ;
2940
3041// Re-export GitHubInfo for backward compatibility
3142export type { GitHubInfo } ;
@@ -102,26 +113,10 @@ export class GitHubDeploymentHandler {
102113 ) ;
103114
104115 // Emit logs to backend
105- if ( result . stdout ) {
106- this . logBuffer . add ( {
107- installation_id : installationId ,
108- team_id : teamId ,
109- user_id : userId ,
110- level : 'info' ,
111- message : `[npm install] ${ result . stdout . substring ( 0 , 1000 ) } ` ,
112- timestamp : new Date ( ) . toISOString ( )
113- } ) ;
114- }
116+ const metadata : BuildCommandMetadata = { installation_id : installationId , team_id : teamId , user_id : userId } ;
117+ emitBuildResult ( this . logBuffer , metadata , 'npm install' , result ) ;
115118
116119 if ( result . code !== 0 ) {
117- this . logBuffer . add ( {
118- installation_id : installationId ,
119- team_id : teamId ,
120- user_id : userId ,
121- level : 'error' ,
122- message : `[npm install] ${ result . stderr . substring ( 0 , 500 ) } ` ,
123- timestamp : new Date ( ) . toISOString ( )
124- } ) ;
125120 throw new Error ( `npm install failed with code ${ result . code } : ${ result . stderr . substring ( 0 , 200 ) } ` ) ;
126121 }
127122
@@ -213,13 +208,8 @@ export class GitHubDeploymentHandler {
213208 userId ?: string
214209 ) : Promise < void > {
215210 try {
216- // Read package.json to check for build script
217- const packageJsonPath = path . join ( tempDir , 'package.json' ) ;
218- const packageJsonContent = await fs . promises . readFile ( packageJsonPath , 'utf8' ) ;
219- const packageJson = JSON . parse ( packageJsonContent ) ;
220-
221211 // Check if there's a build script
222- if ( ! packageJson . scripts ?. build ) {
212+ if ( ! ( await hasBuildScript ( tempDir ) ) ) {
223213 this . logger . debug ( {
224214 operation : 'npm_build_skip' ,
225215 temp_dir : tempDir
@@ -257,26 +247,10 @@ export class GitHubDeploymentHandler {
257247 ) ;
258248
259249 // Emit logs to backend
260- if ( result . stdout ) {
261- this . logBuffer . add ( {
262- installation_id : installationId ,
263- team_id : teamId ,
264- user_id : userId ,
265- level : 'info' ,
266- message : `[npm build] ${ result . stdout . substring ( 0 , 1000 ) } ` ,
267- timestamp : new Date ( ) . toISOString ( )
268- } ) ;
269- }
250+ const metadata : BuildCommandMetadata = { installation_id : installationId , team_id : teamId , user_id : userId } ;
251+ emitBuildResult ( this . logBuffer , metadata , 'npm build' , result ) ;
270252
271253 if ( result . code !== 0 ) {
272- this . logBuffer . add ( {
273- installation_id : installationId ,
274- team_id : teamId ,
275- user_id : userId ,
276- level : 'error' ,
277- message : `[npm build] ${ result . stderr . substring ( 0 , 500 ) } ` ,
278- timestamp : new Date ( ) . toISOString ( )
279- } ) ;
280254 throw new Error ( `npm run build failed with code ${ result . code } : ${ result . stderr . substring ( 0 , 200 ) } ` ) ;
281255 }
282256
@@ -603,26 +577,10 @@ export class GitHubDeploymentHandler {
603577 }
604578 ) ;
605579
606- if ( depsResult . stdout ) {
607- this . logBuffer . add ( {
608- installation_id : installationId ,
609- team_id : teamId ,
610- user_id : userId ,
611- level : 'info' ,
612- message : `[uv pip] ${ depsResult . stdout . substring ( 0 , 1000 ) } ` ,
613- timestamp : new Date ( ) . toISOString ( )
614- } ) ;
615- }
580+ const metadata : BuildCommandMetadata = { installation_id : installationId , team_id : teamId , user_id : userId } ;
581+ emitBuildResult ( this . logBuffer , metadata , 'uv pip' , depsResult ) ;
616582
617583 if ( depsResult . code !== 0 ) {
618- this . logBuffer . add ( {
619- installation_id : installationId ,
620- team_id : teamId ,
621- user_id : userId ,
622- level : 'error' ,
623- message : `[uv pip] ${ depsResult . stderr . substring ( 0 , 500 ) } ` ,
624- timestamp : new Date ( ) . toISOString ( )
625- } ) ;
626584 throw new Error ( `uv pip install failed with code ${ depsResult . code } : ${ depsResult . stderr . substring ( 0 , 200 ) } ` ) ;
627585 }
628586 }
@@ -855,51 +813,19 @@ export class GitHubDeploymentHandler {
855813 ) ;
856814
857815 // Create deployment directory
858- let deploymentDir : string ;
859-
860- if ( useTmpfs ) {
861- // Production: Use tmpfs with 300MB quota
862- deploymentDir = `${ githubDeploymentBaseDir } /${ config . team_id } /${ config . installation_id } ` ;
863-
864- this . logger . debug ( {
865- operation : 'deployment_tmpfs_create_start' ,
866- deployment_dir : deploymentDir ,
867- tmpfs_size : nsjailConfig . deploymentTmpfsSize
868- } , `Creating tmpfs with ${ nsjailConfig . deploymentTmpfsSize } quota` ) ;
869-
870- try {
871- await this . tmpfsManager . createTmpfs ( deploymentDir , {
872- size : nsjailConfig . deploymentTmpfsSize ,
873- mode : '0755'
874- } ) ;
875-
876- this . logger . info ( {
877- operation : 'deployment_tmpfs_created' ,
878- deployment_dir : deploymentDir ,
879- size : nsjailConfig . deploymentTmpfsSize
880- } , `tmpfs created with kernel-enforced ${ nsjailConfig . deploymentTmpfsSize } quota` ) ;
881- } catch ( error ) {
882- const errorMessage = error instanceof Error ? error . message : String ( error ) ;
883- this . logger . error ( {
884- operation : 'deployment_tmpfs_failed' ,
885- deployment_dir : deploymentDir ,
886- error : errorMessage
887- } , 'Failed to create tmpfs for deployment' ) ;
888-
889- throw new Error ( `Failed to create deployment tmpfs: ${ errorMessage } ` ) ;
890- }
891- } else {
892- // Development: Use regular /tmp directory
893- const { v4 : uuidv4 } = await import ( 'uuid' ) ;
894- deploymentDir = `/tmp/mcp-${ uuidv4 ( ) } ` ;
895-
896- this . logger . debug ( {
897- operation : 'deployment_dir_create_dev' ,
898- deployment_dir : deploymentDir
899- } , 'Creating deployment directory (development mode, no tmpfs)' ) ;
900-
901- await mkdir ( deploymentDir , { recursive : true } ) ;
902- }
816+ const deploymentDirConfig : DeploymentDirConfig = {
817+ teamId : config . team_id ,
818+ installationId : config . installation_id ,
819+ useTmpfs,
820+ tmpfsSize : nsjailConfig . deploymentTmpfsSize ,
821+ baseDir : githubDeploymentBaseDir
822+ } ;
823+
824+ const deploymentDir = await createDeploymentDirectory (
825+ deploymentDirConfig ,
826+ this . tmpfsManager ,
827+ this . logger
828+ ) ;
903829
904830 // Extract tarball to deployment directory
905831 await extractTarball ( tarballBuffer , deploymentDir , this . logger , this . tmpfsManager ) ;
@@ -918,8 +844,7 @@ export class GitHubDeploymentHandler {
918844 // Defense-in-depth: Re-validate build scripts before execution
919845 const packageJsonPath = path . join ( deploymentDir , 'package.json' ) ;
920846 if ( await fileExists ( packageJsonPath ) ) {
921- const packageJsonContent = await fs . promises . readFile ( packageJsonPath , 'utf8' ) ;
922- const packageJson = JSON . parse ( packageJsonContent ) ;
847+ const packageJson = await readPackageJson ( deploymentDir ) ;
923848
924849 if ( packageJson . scripts ) {
925850 const validation = validateBuildScripts ( packageJson . scripts ) ;
0 commit comments