read launcher build version from package json

This commit is contained in:
2026-07-03 10:45:55 +09:00
parent fa478b45b3
commit dd11e5186a
2 changed files with 71 additions and 16 deletions
+4 -2
View File
@@ -88,11 +88,13 @@ export class CreateLauncherBuildJobDto {
@IsString()
commitSha?: string;
@IsOptional()
@IsString()
version!: string;
version?: string;
@IsOptional()
@IsString()
artifact!: string;
artifact?: string;
@IsOptional()
@IsString()
+67 -14
View File
@@ -3,7 +3,8 @@ import { ConfigService } from '@nestjs/config';
import { exec } from 'node:child_process';
import { createHash } from 'node:crypto';
import { createReadStream } from 'node:fs';
import { copyFile, mkdir, rm, stat } from 'node:fs/promises';
import { copyFile, mkdir, readFile, rm, stat } from 'node:fs/promises';
import { basename } from 'node:path';
import { promisify } from 'node:util';
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
@@ -764,11 +765,12 @@ export class LauncherService {
}
async createBuildJob(body: CreateLauncherBuildJobDto) {
if (!body.version?.trim()) throw new BadRequestException('버전을 입력하세요.');
if (!body.artifact?.trim()) throw new BadRequestException('산출물 파일명을 입력하세요.');
const source = await this.getSourceSettings();
const target = body.target?.trim() || 'launcher-client';
const isBackend = target === 'launcher-backend';
if (isBackend && !body.artifact?.trim()) throw new BadRequestException('이미지 이름을 입력하세요.');
const requestedVersion = body.version?.trim() || 'package-json';
const requestedArtifact = body.artifact?.trim() || 'autonomy-studio-launcher.deb';
const repository = body.repository
? normalizeRepository(body.repository)
: isBackend
@@ -777,20 +779,21 @@ export class LauncherService {
const branch = body.branch?.trim() || (isBackend ? source.backend.branch : source.branch) || 'release';
const commitSha = body.commitSha?.trim() || (isBackend ? source.backend.commitSha : source.commitSha) || null;
const plan = isBackend
? this.createLauncherBackendImagePlan(body.version.trim(), body.artifact.trim(), body.uploadPath?.trim(), body.downloadUrl?.trim())
: this.createLauncherArtifactPlan(body.version.trim(), branch, commitSha, body.artifact.trim(), body.uploadPath?.trim(), body.downloadUrl?.trim());
? this.createLauncherBackendImagePlan(requestedVersion, requestedArtifact, body.uploadPath?.trim(), body.downloadUrl?.trim())
: this.createLauncherArtifactPlan(requestedVersion, branch, commitSha, requestedArtifact, body.uploadPath?.trim(), body.downloadUrl?.trim());
if (!repository) throw new BadRequestException('소스 저장소가 연결되지 않았습니다.');
if (!plan.uploadPath) throw new BadRequestException(isBackend ? 'Dockerfile 경로를 입력하세요.' : '업로드 경로를 입력하세요.');
if (!plan.downloadUrl) throw new BadRequestException(isBackend ? '이미지 태그를 입력하세요.' : '다운로드 URL을 입력하세요.');
const logs = [
`[queued] launcher build created: ${body.artifact.trim()}`,
`[queued] launcher build created: ${requestedArtifact}`,
`[target] ${target}`,
repository ? `[plan] clone ${this.toCloneUrl(repository)}` : '[plan] source repository is not linked',
`[plan] checkout branch ${branch}`,
commitSha ? `[plan] checkout commit ${commitSha}` : '[plan] use branch latest commit',
isBackend ? `[plan] build and push image ${plan.downloadUrl}` : `[plan] build deb package`,
isBackend ? `[plan] dockerfile ${plan.uploadPath}` : `[plan] upload ${body.artifact.trim()} to ${plan.uploadPath}`,
isBackend ? `[plan] dockerfile ${plan.uploadPath}` : `[plan] upload ${requestedArtifact} to ${plan.uploadPath}`,
'[plan] final version will be read from package.json after clone',
isBackend ? `[plan] image tag ${plan.downloadUrl}` : `[plan] publish download url ${plan.downloadUrl}`,
'[pending] waiting for launcher build runner',
].join('\n');
@@ -801,8 +804,8 @@ export class LauncherService {
repository,
branch,
commitSha,
version: body.version.trim(),
artifact: body.artifact.trim(),
version: requestedVersion,
artifact: requestedArtifact,
uploadPath: plan.uploadPath,
downloadUrl: plan.downloadUrl,
logs,
@@ -871,10 +874,17 @@ export class LauncherService {
}
if (job.target === 'launcher-backend') {
const packageVersion = (await this.readPackageVersion(workDir)) || job.version;
const dockerfile = job.uploadPath || 'Dockerfile';
const imageTag = job.downloadUrl || job.artifact;
const imagePlan = this.createLauncherBackendImagePlan(packageVersion, job.artifact, dockerfile);
const imageTag = imagePlan.downloadUrl;
await append(`[backend] package version ${packageVersion}`);
await append(`[backend] dockerfile ${dockerfile}`);
await append(`[backend] image ${imageTag}`);
await this.prisma.launcherBuildJob.update({
where: { id: jobId },
data: { version: packageVersion, uploadPath: dockerfile, downloadUrl: imageTag },
});
await runCommand(
`docker build -t ${quote(imageTag)} -f ${quote(dockerfile)} .`,
workDir,
@@ -888,6 +898,9 @@ export class LauncherService {
data: {
status: 'SUCCEEDED',
finishedAt: new Date(),
version: packageVersion,
uploadPath: dockerfile,
downloadUrl: imageTag,
logs,
},
});
@@ -901,10 +914,35 @@ export class LauncherService {
const debPath = stdout.trim().split('\n').filter(Boolean)[0];
if (!debPath) throw new Error('.deb 산출물을 찾지 못했습니다.');
const uploadedUrl = await this.uploadLauncherArtifactIfConfigured(job, debPath, append);
const packageVersion = (await this.readPackageVersion(workDir)) || job.version;
const artifactName = basename(debPath);
const artifactPlan = this.createLauncherArtifactPlan(packageVersion, job.branch, job.commitSha, artifactName, job.uploadPath, job.downloadUrl);
const finalJob = {
version: packageVersion,
branch: job.branch,
commitSha: job.commitSha,
artifact: artifactName,
uploadPath: artifactPlan.uploadPath,
};
await append(
`[artifact] package version ${packageVersion}`,
`[artifact] found ${artifactName}`,
`[artifact] upload key ${artifactPlan.uploadPath}`,
);
await this.prisma.launcherBuildJob.update({
where: { id: jobId },
data: {
version: packageVersion,
artifact: artifactName,
uploadPath: artifactPlan.uploadPath,
downloadUrl: artifactPlan.downloadUrl,
},
});
const uploadedUrl = await this.uploadLauncherArtifactIfConfigured(finalJob, debPath, append);
if (!uploadedUrl) {
await mkdir(job.uploadPath, { recursive: true });
const destination = `${job.uploadPath.replace(/\/$/, '')}/${job.artifact}`;
await mkdir(artifactPlan.uploadPath, { recursive: true });
const destination = `${artifactPlan.uploadPath.replace(/\/$/, '')}/${artifactName}`;
await copyFile(debPath, destination);
await append(`[upload] ${destination}`);
}
@@ -915,8 +953,11 @@ export class LauncherService {
data: {
status: 'SUCCEEDED',
finishedAt: new Date(),
version: packageVersion,
artifact: artifactName,
uploadPath: artifactPlan.uploadPath,
downloadUrl: uploadedUrl || artifactPlan.downloadUrl,
logs,
...(uploadedUrl ? { downloadUrl: uploadedUrl } : {}),
},
});
} catch (error) {
@@ -1092,6 +1133,18 @@ export class LauncherService {
return this.r2Client;
}
private async readPackageVersion(workDir: string) {
try {
const packageJson = await readFile(`${workDir}/package.json`, 'utf8');
const parsed = JSON.parse(packageJson) as { version?: unknown };
return typeof parsed.version === 'string' && parsed.version.trim()
? parsed.version.trim()
: null;
} catch {
return null;
}
}
private createLauncherArtifactObjectKey(version: string, branch: string, commitSha: string | null, artifact: string) {
const prefix = this.configService.get<string>('R2_ARTIFACT_PREFIX', 'studio-artifacts');
const revision = commitSha?.slice(0, 7) || 'latest';