Skip to content

Commit 3022764

Browse files
committed
refactor(satellite): extract build logging and directory utilities
1 parent 3b9776d commit 3022764

4 files changed

Lines changed: 323 additions & 108 deletions

File tree

services/satellite/src/process/github-deployment.ts

Lines changed: 33 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { spawn } from 'child_process';
2-
import { mkdir } from 'fs/promises';
32
import * as path from 'path';
43
import * as fs from 'fs';
54
import { 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
3142
export 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);
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/**
2+
* Build command logging utilities
3+
*
4+
* Extracted from github-deployment.ts to reduce code duplication.
5+
* Provides helpers for emitting build command output to LogBuffer.
6+
*/
7+
8+
/**
9+
* LogBuffer interface for emitting logs to backend
10+
*/
11+
interface LogBuffer {
12+
add(entry: BufferedLogEntry): void;
13+
}
14+
15+
/**
16+
* Buffered log entry structure
17+
*/
18+
interface BufferedLogEntry {
19+
installation_id: string;
20+
team_id: string;
21+
user_id?: string;
22+
level: 'info' | 'warn' | 'error';
23+
message: string;
24+
timestamp: string;
25+
}
26+
27+
/**
28+
* Build command metadata
29+
*/
30+
export interface BuildCommandMetadata {
31+
installation_id: string;
32+
team_id: string;
33+
user_id?: string;
34+
}
35+
36+
/**
37+
* Emit stdout from build command to log buffer
38+
*
39+
* @param logBuffer - LogBuffer instance for emitting logs
40+
* @param metadata - Installation metadata (installation_id, team_id, user_id)
41+
* @param commandName - Command name for log prefix (e.g., "npm install", "uv sync")
42+
* @param stdout - Command stdout output
43+
* @param maxLength - Maximum message length (default: 1000)
44+
*/
45+
export function emitStdout(
46+
logBuffer: LogBuffer,
47+
metadata: BuildCommandMetadata,
48+
commandName: string,
49+
stdout: string,
50+
maxLength = 1000
51+
): void {
52+
if (!stdout) return;
53+
54+
logBuffer.add({
55+
installation_id: metadata.installation_id,
56+
team_id: metadata.team_id,
57+
user_id: metadata.user_id,
58+
level: 'info',
59+
message: `[${commandName}] ${stdout.substring(0, maxLength)}`,
60+
timestamp: new Date().toISOString()
61+
});
62+
}
63+
64+
/**
65+
* Emit stderr from build command to log buffer
66+
*
67+
* @param logBuffer - LogBuffer instance for emitting logs
68+
* @param metadata - Installation metadata (installation_id, team_id, user_id)
69+
* @param commandName - Command name for log prefix (e.g., "npm install", "uv sync")
70+
* @param stderr - Command stderr output
71+
* @param maxLength - Maximum message length (default: 500)
72+
*/
73+
export function emitStderr(
74+
logBuffer: LogBuffer,
75+
metadata: BuildCommandMetadata,
76+
commandName: string,
77+
stderr: string,
78+
maxLength = 500
79+
): void {
80+
if (!stderr) return;
81+
82+
logBuffer.add({
83+
installation_id: metadata.installation_id,
84+
team_id: metadata.team_id,
85+
user_id: metadata.user_id,
86+
level: 'error',
87+
message: `[${commandName}] ${stderr.substring(0, maxLength)}`,
88+
timestamp: new Date().toISOString()
89+
});
90+
}
91+
92+
/**
93+
* Emit build command result (stdout + stderr if failed)
94+
*
95+
* Convenience function that emits both stdout and stderr based on exit code.
96+
*
97+
* @param logBuffer - LogBuffer instance for emitting logs
98+
* @param metadata - Installation metadata
99+
* @param commandName - Command name for log prefix
100+
* @param result - Command execution result
101+
*/
102+
export function emitBuildResult(
103+
logBuffer: LogBuffer,
104+
metadata: BuildCommandMetadata,
105+
commandName: string,
106+
result: { code: number; stdout: string; stderr: string }
107+
): void {
108+
// Emit stdout if available
109+
if (result.stdout) {
110+
emitStdout(logBuffer, metadata, commandName, result.stdout);
111+
}
112+
113+
// Emit stderr as error if command failed
114+
if (result.code !== 0 && result.stderr) {
115+
emitStderr(logBuffer, metadata, commandName, result.stderr);
116+
}
117+
}

0 commit comments

Comments
 (0)