예약 초기화에서 구버전 릴리스 빌드 호환성을 확인한다

This commit is contained in:
2026-09-24 05:47:46 +00:00
parent 3c75de16e9
commit 4bf812b601
3 changed files with 53 additions and 1 deletions
+10
View File
@@ -11,6 +11,7 @@ import {
listScenarioPreviews,
resolveGitBranchCommitSha,
resolveGitCommitSha,
supportsProfileReleaseBuild,
type ScenarioPreview,
} from './scenario/scenarioCatalog.js';
import type { UserSanctions, UserServerRestriction } from './auth/userRepository.js';
@@ -1373,6 +1374,7 @@ export const adminRouter = router({
});
}
let selectedScenario: ScenarioPreview | undefined;
let resolvedCommitSha: string;
try {
const resolved =
sourceMode === 'BRANCH'
@@ -1381,6 +1383,7 @@ export const adminRouter = router({
if (sourceMode === 'COMMIT') {
sourceRef = resolved;
}
resolvedCommitSha = resolved;
const scenarios = await listScenarioPreviews({ gitRef: resolved });
selectedScenario = scenarios.find((scenario) => scenario.id === input.install.scenarioId);
if (!selectedScenario) {
@@ -1396,6 +1399,13 @@ export const adminRouter = router({
});
}
if (!(await supportsProfileReleaseBuild(resolvedCommitSha))) {
throw new TRPCError({
code: 'BAD_REQUEST',
message: '선택한 버전은 현재 서버의 배포 빌드와 호환되지 않습니다. 최신 버전을 선택해 주세요.',
});
}
try {
const operation = await ctx.profiles.createOperation({
profileName: input.profileName,
@@ -137,6 +137,37 @@ const readGitJson = async (commitSha: string, relativePath: string): Promise<unk
return JSON.parse(raw) as unknown;
};
const hasScript = (value: unknown, name: string): boolean => {
if (!value || typeof value !== 'object' || !('scripts' in value)) return false;
const scripts = value.scripts;
return Boolean(scripts && typeof scripts === 'object' && name in scripts && typeof scripts[name as keyof typeof scripts] === 'string');
};
export const hasProfileReleaseBuildTasks = (turboConfig: unknown, frontendPackage: unknown): boolean => {
if (!turboConfig || typeof turboConfig !== 'object' || !('tasks' in turboConfig)) return false;
const tasks = turboConfig.tasks;
return Boolean(
tasks &&
typeof tasks === 'object' &&
'typecheck:release' in tasks &&
'build:release' in tasks &&
hasScript(frontendPackage, 'typecheck:release') &&
hasScript(frontendPackage, 'build:release')
);
};
export const supportsProfileReleaseBuild = async (commitSha: string): Promise<boolean> => {
try {
const [turboConfig, frontendPackage] = await Promise.all([
readGitJson(commitSha, 'turbo.json'),
readGitJson(commitSha, 'app/game-frontend/package.json'),
]);
return hasProfileReleaseBuildTasks(turboConfig, frontendPackage);
} catch {
return false;
}
};
const listScenarioIds = async (): Promise<number[]> => {
const root = resolveScenarioRoot();
const entries = await fs.readdir(root, { withFileTypes: true });
+12 -1
View File
@@ -1,8 +1,19 @@
import { describe, expect, it } from 'vitest';
import { listScenarioPreviews, resolveGitCommitSha } from '../src/scenario/scenarioCatalog.js';
import {
hasProfileReleaseBuildTasks,
listScenarioPreviews,
resolveGitCommitSha,
supportsProfileReleaseBuild,
} from '../src/scenario/scenarioCatalog.js';
describe('scenarioCatalog git ref support', () => {
it('rejects a pinned release whose frontend predates the current build contract', async () => {
expect(hasProfileReleaseBuildTasks({ tasks: { build: {} } }, { scripts: { build: 'vite build' } })).toBe(
false
);
expect(await supportsProfileReleaseBuild('HEAD')).toBe(true);
});
it.each([undefined, 'HEAD'])('publishes resolved start years for every scenario (%s)', async (gitRef) => {
const previews = await listScenarioPreviews({ gitRef });
expect(previews.every((scenario) => Number.isFinite(scenario.year))).toBe(true);