diff --git a/app/release-controller/src/index.ts b/app/release-controller/src/index.ts index 7646c7ef..3d132b27 100644 --- a/app/release-controller/src/index.ts +++ b/app/release-controller/src/index.ts @@ -34,7 +34,7 @@ const main = async (): Promise => { const controller = new GatewayReleaseController( repository, workspaceManager, - releaseBuildRunner, + migrationRunner, processManager, config ); diff --git a/app/release-controller/src/releaseController.ts b/app/release-controller/src/releaseController.ts index 756f17d9..8f457609 100644 --- a/app/release-controller/src/releaseController.ts +++ b/app/release-controller/src/releaseController.ts @@ -146,13 +146,13 @@ export class GatewayReleaseController { constructor( private readonly repository: GatewayReleaseRepository, private readonly workspaceManager: GitWorkspaceManager, - private readonly buildRunner: BuildRunner, + private readonly migrationRunner: BuildRunner, private readonly processManager: ProcessManager, private readonly config: ReleaseControllerConfig, private readonly now: () => Date = () => new Date(), private readonly fetchImpl: typeof fetch = fetch ) { - this.releaseBuildRunner = createReleaseBuildRunner(config.releaseBuilderUrl, buildRunner, fetchImpl); + this.releaseBuildRunner = createReleaseBuildRunner(config.releaseBuilderUrl, migrationRunner, fetchImpl); this.artifactManager = new FrontendArtifactManager(config.frontendArtifactRoot ?? '/srv/frontend-artifacts'); } @@ -345,7 +345,7 @@ export class GatewayReleaseController { : null; await this.appendLog(operation.id, 'migration', 'Gateway database migration을 적용합니다.'); await this.assertOperationLease(operation.id); - const migration = await this.buildRunner.run( + const migration = await this.migrationRunner.run( [buildGatewayMigrationCommand(workspace.root, this.config)], this.buildProgress(operation.id, 'migration'), { signal } diff --git a/app/release-controller/test/releaseController.test.ts b/app/release-controller/test/releaseController.test.ts index d6aea019..396f68da 100644 --- a/app/release-controller/test/releaseController.test.ts +++ b/app/release-controller/test/releaseController.test.ts @@ -388,6 +388,70 @@ describe('GatewayReleaseController', () => { ); }); + it('runs release builds remotely while keeping Gateway migration on the runtime runner', async () => { + const workspace = await createReleaseWorkspace(); + const harness = createRepository(); + const localCommandGroups: string[][] = []; + const remoteRequests: Array<{ commands: Array<{ args: string[] }> }> = []; + const running = new Map(gatewayNames.map((name) => [name, '/srv/sammo/old'])); + const localRunner: BuildRunner = { + run: async (commands) => { + localCommandGroups.push(commands.map((command) => command.args.join(' '))); + return { ok: true, exitCode: 0, output: '' }; + }, + }; + const controller = new GatewayReleaseController( + harness.repository, + { + resolveCommit: async () => SHA, + prepare: async () => ({ root: workspace, created: true, needsInstall: true }), + } as unknown as GitWorkspaceManager, + localRunner, + { + list: async () => + [...running].map(([name, cwd]) => ({ + name, + cwd, + status: 'online', + restartCount: 0, + script: path.join(cwd, 'dist.js'), + })), + start: async (definition) => { + running.set(definition.name, definition.cwd); + }, + stop: async () => {}, + delete: async (name) => { + running.delete(name); + }, + }, + { ...config, releaseBuilderUrl: 'http://builder:15100' }, + () => new Date('2026-08-01T00:00:00.000Z'), + async (input, init) => { + if (String(input) === 'http://builder:15100/v1/builds') { + remoteRequests.push(JSON.parse(String(init?.body)) as { commands: Array<{ args: string[] }> }); + return new Response(`${JSON.stringify({ result: { ok: true, exitCode: 0, output: '' } })}\n`, { + status: 200, + headers: { 'content-type': 'application/x-ndjson' }, + }); + } + return new Response('', { status: 200 }); + } + ); + + await controller.runOnce(); + + expect(remoteRequests).toHaveLength(1); + expect(remoteRequests[0]?.commands.map((command) => command.args.join(' '))).toEqual( + expect.arrayContaining([ + expect.stringContaining('install --frozen-lockfile'), + expect.stringContaining('turbo run build'), + expect.stringContaining('turbo run build:release'), + ]) + ); + expect(localCommandGroups).toEqual([['--filter @sammo-ts/infra prisma:migrate:deploy:gateway']]); + expect(harness.completions).toEqual(['SUCCEEDED']); + }); + it('restores the previous gateway processes when the new process set cannot start', async () => { const workspace = await createReleaseWorkspace(); const harness = createRepository();