fix(gateway): RESET seed 산출물 재사용을 검증한다
DB 보존 DEPLOY가 만든 부분 빌드 worktree를 RESET-ready로 오인하지 않도록 실제 game API와 profile seed entrypoint를 확인한다. seed 전환 전 built CLI도 재검증해 산출물 누락 시 기존 runtime을 먼저 내리지 않는다.
This commit is contained in:
@@ -78,6 +78,25 @@ export interface GatewayProcessConfig {
|
|||||||
baseEnv?: Record<string, string>;
|
baseEnv?: Record<string, string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const PROFILE_BUILD_ENTRYPOINTS = [
|
||||||
|
['app', 'game-api', 'dist', 'index.js'],
|
||||||
|
['app', 'gateway-api', 'dist', 'index.js'],
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export const hasCompleteProfileBuildArtifacts = async (
|
||||||
|
workspaceRoot: string,
|
||||||
|
access: (target: string) => Promise<unknown> = fs.access
|
||||||
|
): Promise<boolean> => {
|
||||||
|
for (const segments of PROFILE_BUILD_ENTRYPOINTS) {
|
||||||
|
try {
|
||||||
|
await access(path.join(workspaceRoot, ...segments));
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
export interface GatewayOrchestratorOptions {
|
export interface GatewayOrchestratorOptions {
|
||||||
repository: GatewayProfileRepository;
|
repository: GatewayProfileRepository;
|
||||||
processManager: ProcessManager;
|
processManager: ProcessManager;
|
||||||
@@ -2386,7 +2405,9 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
|||||||
if (operationId) {
|
if (operationId) {
|
||||||
await this.appendOperationLog(operationId, 'workspace', `worktree 준비 완료: ${workspace.root}`);
|
await this.appendOperationLog(operationId, 'workspace', `worktree 준비 완료: ${workspace.root}`);
|
||||||
}
|
}
|
||||||
const activeWorkspaceReusable = canReuseActiveProfileWorkspace(profile, commitSha, workspace);
|
const activeWorkspaceReusable =
|
||||||
|
canReuseActiveProfileWorkspace(profile, commitSha, workspace) &&
|
||||||
|
(await hasCompleteProfileBuildArtifacts(workspace.root));
|
||||||
if (activeWorkspaceReusable) {
|
if (activeWorkspaceReusable) {
|
||||||
if (operationId) {
|
if (operationId) {
|
||||||
await this.appendOperationLog(
|
await this.appendOperationLog(
|
||||||
@@ -2452,6 +2473,12 @@ export class GatewayOrchestrator implements GatewayOrchestratorHandle {
|
|||||||
} catch {
|
} catch {
|
||||||
throw new Error(`Selected commit does not provide the profile seed CLI: ${sourcePath}`);
|
throw new Error(`Selected commit does not provide the profile seed CLI: ${sourcePath}`);
|
||||||
}
|
}
|
||||||
|
const artifactPath = path.join(workspaceRoot, 'app', 'gateway-api', 'dist', 'index.js');
|
||||||
|
try {
|
||||||
|
await fs.access(artifactPath);
|
||||||
|
} catch {
|
||||||
|
throw new Error(`Selected commit did not build the profile seed CLI artifact: ${artifactPath}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runProfileMigration(
|
private async runProfileMigration(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
buildProcessDefinitions,
|
buildProcessDefinitions,
|
||||||
buildSharedProfileFrontendCommands,
|
buildSharedProfileFrontendCommands,
|
||||||
buildWorkspaceCommands,
|
buildWorkspaceCommands,
|
||||||
|
hasCompleteProfileBuildArtifacts,
|
||||||
planProfileReconcile,
|
planProfileReconcile,
|
||||||
resolveProfileArchiveServerName,
|
resolveProfileArchiveServerName,
|
||||||
resolveResetLifecycleStatus,
|
resolveResetLifecycleStatus,
|
||||||
@@ -391,6 +392,26 @@ describe('sanitizeManagedProcessEnv', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('buildWorkspaceCommands', () => {
|
describe('buildWorkspaceCommands', () => {
|
||||||
|
it('does not reuse a DB-preserving deploy workspace that lacks the profile seed artifact', async () => {
|
||||||
|
const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
|
||||||
|
const gameApiPath = path.join(workspaceRoot, 'app', 'game-api', 'dist', 'index.js');
|
||||||
|
const gatewayApiPath = path.join(workspaceRoot, 'app', 'gateway-api', 'dist', 'index.js');
|
||||||
|
const deployOnlyArtifacts = new Set([gameApiPath]);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
hasCompleteProfileBuildArtifacts(workspaceRoot, async (target) => {
|
||||||
|
if (!deployOnlyArtifacts.has(target)) throw new Error('missing');
|
||||||
|
})
|
||||||
|
).resolves.toBe(false);
|
||||||
|
|
||||||
|
deployOnlyArtifacts.add(gatewayApiPath);
|
||||||
|
await expect(
|
||||||
|
hasCompleteProfileBuildArtifacts(workspaceRoot, async (target) => {
|
||||||
|
if (!deployOnlyArtifacts.has(target)) throw new Error('missing');
|
||||||
|
})
|
||||||
|
).resolves.toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
it('installs and builds runtime dependencies before the profile processes', () => {
|
it('installs and builds runtime dependencies before the profile processes', () => {
|
||||||
const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
|
const workspaceRoot = '/srv/sammo/worktrees/0123456789abcdef';
|
||||||
const commands = buildWorkspaceCommands(workspaceRoot, true, undefined, '/srv/sammo/controller');
|
const commands = buildWorkspaceCommands(workspaceRoot, true, undefined, '/srv/sammo/controller');
|
||||||
|
|||||||
Reference in New Issue
Block a user