feat: update codex live runtime and restart flow

This commit is contained in:
2026-04-23 18:10:43 +09:00
parent b0b9980a6c
commit 6e863feafd
36 changed files with 1636 additions and 358 deletions

View File

@@ -13,7 +13,7 @@ import {
const execFileAsync = promisify(execFile);
export const serverCommandKeys = ['test', 'rel', 'work-server', 'command-runner'] as const;
export const serverCommandKeys = ['test', 'rel', 'prod', 'work-server', 'command-runner'] as const;
export type ServerCommandKey = (typeof serverCommandKeys)[number];
@@ -135,6 +135,26 @@ const APP_BUILD_INFO_FILE_CANDIDATES = [
'/tmp/ai-code-test-app-dist/assets',
] as const;
export async function readAppBuildTimestamp(definition: ServerDefinition, options?: { allowLocal?: boolean }) {
const allowLocal = options?.allowLocal ?? false;
let latestBuiltAt: string | null = null;
for (const targetPath of APP_BUILD_INFO_FILE_CANDIDATES) {
const candidates = [
allowLocal ? await readLocalBuildTimestamp(targetPath) : null,
await readContainerBuildTimestamp(definition, targetPath),
].filter((value): value is string => Boolean(value));
for (const candidate of candidates) {
if (!latestBuiltAt || candidate > latestBuiltAt) {
latestBuiltAt = candidate;
}
}
}
return latestBuiltAt;
}
async function readLocalBuildTimestamp(targetPath: string) {
try {
const targetStat = await stat(targetPath);
@@ -587,6 +607,26 @@ function getServerDefinitions(): ServerDefinition[] {
},
restartStrategy: 'wait',
},
{
key: 'prod',
label: 'PROD',
summary: '프로덕션 앱 컨테이너',
environment: 'production',
publicUrl: normalizeUrl(env.SERVER_COMMAND_PROD_URL),
checkUrl: normalizeUrl(env.SERVER_COMMAND_PROD_URL),
composeFile: path.join(mainProjectRoot, 'docker-compose.yml'),
serviceName: env.SERVER_COMMAND_PROD_SERVICE,
containerName: 'ai-code-app-prod',
commandScript: resolveCommandScriptPath('restart-prod.sh', [projectRoot, mainProjectRoot, '/workspace/main-project']),
commandWorkingDirectory: mainProjectRoot,
commandEnvironment: {
MAIN_PROJECT_ROOT: mainProjectRoot,
SERVER_COMMAND_COMPOSE_FILE: path.join(mainProjectRoot, 'docker-compose.yml'),
SERVER_COMMAND_SERVICE: env.SERVER_COMMAND_PROD_SERVICE,
SERVER_COMMAND_CONTAINER_NAME: 'ai-code-app-prod',
},
restartStrategy: 'deferred',
},
{
key: 'work-server',
label: 'WORK-SERVER',
@@ -1223,21 +1263,39 @@ async function inspectRuntime(definition: ServerDefinition): Promise<RuntimeInsp
}
async function inspectAppContainerBuild(definition: ServerDefinition): Promise<BuildInspectionResult | null> {
if (definition.key !== 'test') {
if (definition.key !== 'test' && definition.key !== 'prod') {
return null;
}
if (definition.key === 'prod') {
const testDefinition = getServerDefinition('test');
const testBuiltAt = await readAppBuildTimestamp(testDefinition, { allowLocal: true });
const prodBuiltAt = await readAppBuildTimestamp(definition);
const updateAvailable = Boolean(testBuiltAt && (!prodBuiltAt || prodBuiltAt < testBuiltAt));
return {
runningVersion: null,
runningBuiltAt: prodBuiltAt,
latestVersion: null,
latestBuiltAt: testBuiltAt,
latestSourceChangeAt: null,
latestSourceChangePath: null,
buildRequired: false,
updateAvailable,
updateSummary:
updateAvailable && testBuiltAt
? `운영 반영 시각이 TEST보다 이전입니다. TEST ${testBuiltAt}, 운영 ${prodBuiltAt ?? '미확인'}`
: prodBuiltAt
? `운영 반영 기준: ${prodBuiltAt}`
: '운영 빌드 시각을 읽지 못했습니다.',
};
}
const latestSourceChange = await readLatestAppSourceChange();
const latestSourceChangedAt = latestSourceChange?.changedAt ?? null;
const builtAt = await readAppBuildTimestamp(definition, { allowLocal: true });
for (const targetPath of APP_BUILD_INFO_FILE_CANDIDATES) {
const builtAt =
(await readLocalBuildTimestamp(targetPath)) ?? (await readContainerBuildTimestamp(definition, targetPath));
if (!builtAt) {
continue;
}
if (builtAt) {
return {
runningVersion: null,
runningBuiltAt: builtAt,