feat(deploy): 接入全自动生产发布校验

This commit is contained in:
Script Generator
2026-07-06 14:45:44 +08:00
parent 8d61bb7c13
commit dc780e4c7d
24 changed files with 662 additions and 3 deletions

View File

@@ -0,0 +1,43 @@
import assert from 'node:assert/strict';
import { existsSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import test from 'node:test';
const repoRoot = join(process.cwd(), '../..');
test('production Dockerfiles embed runtime version metadata', () => {
const webDockerfile = readFileSync(join(repoRoot, 'Dockerfile.web'), 'utf8');
const serverDockerfile = readFileSync(join(repoRoot, 'Dockerfile.server'), 'utf8');
assert.match(webDockerfile, /ARG APP_VERSION=unknown/);
assert.match(webDockerfile, /ENV NEXT_PUBLIC_APP_VERSION=\$APP_VERSION/);
assert.match(serverDockerfile, /ARG APP_VERSION=unknown/);
assert.match(serverDockerfile, /ENV APP_VERSION=\$APP_VERSION/);
});
test('production compose can pull immutable web and server images', () => {
const compose = readFileSync(join(repoRoot, 'docker-compose.prod.yml'), 'utf8');
assert.match(compose, /image: \$\{SERVER_IMAGE:\?Set SERVER_IMAGE/);
assert.match(compose, /image: \$\{WEB_IMAGE:\?Set WEB_IMAGE/);
assert.match(compose, /APP_VERSION: \$\{APP_VERSION:-unknown\}/);
});
test('GitHub Actions workflow builds pushes deploys migrates and verifies runtime version', () => {
const workflowPath = join(repoRoot, '.github/workflows/deploy-production.yml');
assert.equal(existsSync(workflowPath), true);
const workflow = readFileSync(workflowPath, 'utf8');
assert.match(workflow, /docker\/build-push-action/);
assert.match(workflow, /appleboy\/ssh-action/);
assert.match(workflow, /docker compose --env-file \.env\.production -f docker-compose\.prod\.yml pull/);
assert.match(workflow, /pnpm --filter server db:deploy/);
assert.match(workflow, /\/api\/v1\/health\/version/);
});
test('deployment runtime version check is available as a repeatable script', () => {
const packageJson = readFileSync(join(repoRoot, 'package.json'), 'utf8');
assert.equal(existsSync(join(repoRoot, 'scripts/check-runtime-version.mjs')), true);
assert.match(packageJson, /"deploy:check-runtime": "node scripts\/check-runtime-version\.mjs"/);
});

View File

@@ -0,0 +1,34 @@
import { strict as assert } from 'node:assert';
import test from 'node:test';
import { getClientRuntimeVersion, shouldPromptForNewRuntimeVersion } from './runtime-version';
test('getClientRuntimeVersion reads web build metadata from public environment variables', () => {
const result = getClientRuntimeVersion({
NEXT_PUBLIC_APP_VERSION: 'commit-789',
NEXT_PUBLIC_APP_BUILD_TIME: '2026-07-06T15:00:00.000Z',
});
assert.deepEqual(result, {
service: 'web',
version: 'commit-789',
buildTime: '2026-07-06T15:00:00.000Z',
});
});
test('getClientRuntimeVersion uses stable fallback values without build metadata', () => {
const result = getClientRuntimeVersion({});
assert.deepEqual(result, {
service: 'web',
version: 'unknown',
buildTime: '',
});
});
test('shouldPromptForNewRuntimeVersion only prompts when both versions are known and different', () => {
assert.equal(shouldPromptForNewRuntimeVersion('commit-a', 'commit-b'), true);
assert.equal(shouldPromptForNewRuntimeVersion('commit-a', 'commit-a'), false);
assert.equal(shouldPromptForNewRuntimeVersion('unknown', 'commit-b'), false);
assert.equal(shouldPromptForNewRuntimeVersion('commit-a', 'unknown'), false);
assert.equal(shouldPromptForNewRuntimeVersion('', 'commit-b'), false);
});

View File

@@ -0,0 +1,36 @@
type RuntimeEnv = {
NEXT_PUBLIC_APP_VERSION?: string;
NEXT_PUBLIC_APP_BUILD_TIME?: string;
};
export interface ClientRuntimeVersion {
service: 'web';
version: string;
buildTime: string;
}
export interface ServerRuntimeVersion {
service: 'server';
version: string;
buildTime: string;
imageTag: string;
}
export function getClientRuntimeVersion(
env: RuntimeEnv = {
NEXT_PUBLIC_APP_VERSION: process.env.NEXT_PUBLIC_APP_VERSION,
NEXT_PUBLIC_APP_BUILD_TIME: process.env.NEXT_PUBLIC_APP_BUILD_TIME,
},
): ClientRuntimeVersion {
return {
service: 'web',
version: env.NEXT_PUBLIC_APP_VERSION || 'unknown',
buildTime: env.NEXT_PUBLIC_APP_BUILD_TIME || '',
};
}
export function shouldPromptForNewRuntimeVersion(clientVersion: string, serverVersion: string): boolean {
if (!clientVersion || !serverVersion) return false;
if (clientVersion === 'unknown' || serverVersion === 'unknown') return false;
return clientVersion !== serverVersion;
}