Skip to content

Commit 7e6a437

Browse files
committed
refactor(satellite): extract GitHub deployment utilities for maintainability
1 parent 92278a2 commit 7e6a437

File tree

2 files changed

+224
-147
lines changed

2 files changed

+224
-147
lines changed

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

Lines changed: 11 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,15 @@ import {
1818
parsePyprojectDependencies,
1919
resolvePythonEntryPoint
2020
} from '../utils/python-helpers';
21+
import {
22+
parseGitHubUrl,
23+
resolvePackageEntry,
24+
detectRuntime,
25+
type GitHubInfo
26+
} from '../utils/node-helpers';
2127

22-
/**
23-
* Parsed GitHub repository information
24-
*/
25-
export interface GitHubInfo {
26-
owner: string;
27-
repo: string;
28-
ref: string;
29-
}
28+
// Re-export GitHubInfo for backward compatibility
29+
export type { GitHubInfo };
3030

3131
/**
3232
* Helper to check if a file exists
@@ -58,52 +58,6 @@ export class GitHubDeploymentHandler {
5858
this.tmpfsManager = new TmpfsManager(logger);
5959
}
6060

61-
/**
62-
* Parse GitHub URL from package manager arguments
63-
* Supports:
64-
* - github:owner/repo#ref (for npx)
65-
* - git+https://github.com/owner/repo.git@ref (for uvx/pip)
66-
*/
67-
parseGitHubUrl(command: string, args: string[]): GitHubInfo | null {
68-
// Check if this is a supported package manager command
69-
const supportedCommands = ['npx', 'uvx'];
70-
if (!supportedCommands.includes(command)) {
71-
return null;
72-
}
73-
74-
// Try npm/npx format: github:owner/repo#ref
75-
const githubArg = args.find(arg => arg.startsWith('github:'));
76-
if (githubArg) {
77-
const match = githubArg.match(/^github:([^/]+)\/([^#]+)#(.+)$/);
78-
if (match) {
79-
return {
80-
owner: match[1],
81-
repo: match[2],
82-
ref: match[3]
83-
};
84-
}
85-
}
86-
87-
// Try Python/uvx format: git+https://github.com/owner/repo.git@ref
88-
const gitPlusArg = args.find(arg => arg.startsWith('git+https://github.com/'));
89-
if (gitPlusArg) {
90-
const match = gitPlusArg.match(/^git\+https:\/\/github\.com\/([^/]+)\/([^.]+)\.git@(.+)$/);
91-
if (match) {
92-
return {
93-
owner: match[1],
94-
repo: match[2],
95-
ref: match[3]
96-
};
97-
}
98-
}
99-
100-
this.logger.warn({
101-
operation: 'github_url_parse_failed',
102-
args,
103-
command
104-
}, `Failed to parse GitHub URL from ${command} arguments`);
105-
return null;
106-
}
10761

10862
/**
10963
* Check if a config requires GitHub deployment
@@ -554,79 +508,6 @@ export class GitHubDeploymentHandler {
554508
}
555509
}
556510

557-
/**
558-
* Resolve package entry point from package.json
559-
*/
560-
async resolvePackageEntry(tempDir: string): Promise<string> {
561-
this.logger.debug({
562-
operation: 'package_entry_resolve_start',
563-
temp_dir: tempDir
564-
}, 'Resolving package entry point from package.json');
565-
566-
try {
567-
const packageJsonPath = path.join(tempDir, 'package.json');
568-
const packageJsonContent = await fs.promises.readFile(packageJsonPath, 'utf8');
569-
const packageJson = JSON.parse(packageJsonContent);
570-
571-
// Check for bin field (can be string or object)
572-
if (packageJson.bin) {
573-
let entryPoint: string;
574-
575-
if (typeof packageJson.bin === 'string') {
576-
// bin: "dist/index.js"
577-
entryPoint = packageJson.bin;
578-
} else if (typeof packageJson.bin === 'object') {
579-
// bin: { "server-name": "dist/index.js" }
580-
// Use the first entry
581-
const binEntries = Object.values(packageJson.bin);
582-
if (binEntries.length === 0) {
583-
throw new Error('bin field is empty object');
584-
}
585-
entryPoint = binEntries[0] as string;
586-
} else {
587-
throw new Error(`Invalid bin field type: ${typeof packageJson.bin}`);
588-
}
589-
590-
const absolutePath = path.join(tempDir, entryPoint);
591-
592-
this.logger.info({
593-
operation: 'package_entry_resolved',
594-
temp_dir: tempDir,
595-
entry_point: entryPoint,
596-
absolute_path: absolutePath
597-
}, `Resolved package entry point: ${entryPoint}`);
598-
599-
return absolutePath;
600-
}
601-
602-
// Fallback to main field
603-
if (packageJson.main) {
604-
const absolutePath = path.join(tempDir, packageJson.main);
605-
606-
this.logger.info({
607-
operation: 'package_entry_resolved_main',
608-
temp_dir: tempDir,
609-
main: packageJson.main,
610-
absolute_path: absolutePath
611-
}, `Using main field as entry point: ${packageJson.main}`);
612-
613-
return absolutePath;
614-
}
615-
616-
throw new Error('No bin or main field found in package.json');
617-
618-
} catch (error) {
619-
const errorMessage = error instanceof Error ? error.message : String(error);
620-
621-
this.logger.error({
622-
operation: 'package_entry_resolve_failed',
623-
temp_dir: tempDir,
624-
error: errorMessage
625-
}, 'Failed to resolve package entry point');
626-
627-
throw new Error(`Failed to resolve package entry point: ${errorMessage}`);
628-
}
629-
}
630511

631512

632513
/**
@@ -1054,23 +935,6 @@ export class GitHubDeploymentHandler {
1054935
return result;
1055936
}
1056937

1057-
/**
1058-
* Detect runtime from extracted repository files
1059-
*/
1060-
async detectRuntime(tempDir: string): Promise<'node' | 'python' | 'unknown'> {
1061-
// Check for Node.js
1062-
if (await fileExists(path.join(tempDir, 'package.json'))) {
1063-
return 'node';
1064-
}
1065-
1066-
// Check for Python
1067-
if (await fileExists(path.join(tempDir, 'pyproject.toml')) ||
1068-
await fileExists(path.join(tempDir, 'requirements.txt'))) {
1069-
return 'python';
1070-
}
1071-
1072-
return 'unknown';
1073-
}
1074938

1075939
/**
1076940
* Prepare a GitHub deployment - downloads, extracts, builds, and updates config
@@ -1095,7 +959,7 @@ export class GitHubDeploymentHandler {
1095959
}, `GitHub deployment detected (${config.runtime || 'unknown'} runtime), downloading repository via Octokit`);
1096960

1097961
// Parse GitHub URL from package manager arguments
1098-
const githubInfo = this.parseGitHubUrl(config.command, config.args || []);
962+
const githubInfo = parseGitHubUrl(config.command, config.args || [], this.logger);
1099963
if (!githubInfo) {
1100964
throw new Error('Failed to parse GitHub URL from package manager arguments');
1101965
}
@@ -1178,7 +1042,7 @@ export class GitHubDeploymentHandler {
11781042
await this.extractTarball(tarballBuffer, deploymentDir);
11791043

11801044
// Detect runtime from extracted files or use config runtime
1181-
const runtime = config.runtime || await this.detectRuntime(deploymentDir);
1045+
const runtime = config.runtime || await detectRuntime(deploymentDir);
11821046

11831047
this.logger.info({
11841048
operation: 'github_deployment_runtime_detected',
@@ -1214,7 +1078,7 @@ export class GitHubDeploymentHandler {
12141078
await this.buildPackage(deploymentDir, config.installation_id, config.team_id, config.user_id);
12151079

12161080
// Resolve Node.js package entry point
1217-
const entryPoint = await this.resolvePackageEntry(deploymentDir);
1081+
const entryPoint = await resolvePackageEntry(deploymentDir, this.logger);
12181082

12191083
// Make entry point relative to deployment dir for nsjail mounting
12201084
const relativeEntryPoint = path.relative(deploymentDir, entryPoint);

0 commit comments

Comments
 (0)