071faa482c
纳入 AGC Cargo workspace 的统一 rustfmt 检查与格式化入口 完成项目 TypeScript/Prettier 与 Rust 全量格式化 修复 Pingora expected executable 门禁的空白敏感误报 同步开发运维文档与 AGC skill pack 格式化忽略规则
277 lines
8.3 KiB
JavaScript
277 lines
8.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { spawnSync } from 'node:child_process';
|
|
import {
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
readFileSync,
|
|
rmSync,
|
|
writeFileSync,
|
|
} from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import path from 'node:path';
|
|
|
|
const failures = [];
|
|
const tmpRoot = mkdtempSync(
|
|
path.join(tmpdir(), 'genarrative-health-patrol-env-check-'),
|
|
);
|
|
|
|
try {
|
|
main();
|
|
} finally {
|
|
rmSync(tmpRoot, { recursive: true, force: true });
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error('[check:production-health-patrol-env] FAILED');
|
|
for (const failure of failures) {
|
|
console.error(`- ${failure}`);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('[check:production-health-patrol-env] OK');
|
|
|
|
function main() {
|
|
assertAcceptsPingoraDirectLoopbackWithHost();
|
|
assertRejectsPingoraDirectLoopbackWithoutHost();
|
|
assertRejectsWrongGatewayMode();
|
|
assertAcceptsNginxWithEmptyPublicHost();
|
|
assertRejectsNginxWithStalePublicHost();
|
|
assertRejectsInvalidPublicHost();
|
|
assertRejectsInvalidEnvFilePaths();
|
|
assertRejectsInvalidBoolEnv();
|
|
assertExampleDocumentsDirectMode();
|
|
}
|
|
|
|
function assertAcceptsPingoraDirectLoopbackWithHost() {
|
|
const envFile = writeEnv('pingora-direct-ok.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'pingora-direct',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'https://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: 'genarrative.example',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'pingora-direct',
|
|
'--expected-public-base-url',
|
|
'https://127.0.0.1',
|
|
'--expected-public-host',
|
|
'genarrative.example',
|
|
]);
|
|
assertStatus(result, 0, 'Pingora direct loopback 配正式 Host 应通过。');
|
|
}
|
|
|
|
function assertRejectsPingoraDirectLoopbackWithoutHost() {
|
|
const envFile = writeEnv('pingora-direct-missing-host.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'pingora-direct',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'https://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: '',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'pingora-direct',
|
|
'--expected-public-base-url',
|
|
'https://127.0.0.1',
|
|
]);
|
|
assertStatus(result, 1, 'Pingora direct loopback 缺 Host 必须失败。');
|
|
assertIncludes(
|
|
`${result.stdout}\n${result.stderr}`,
|
|
'Pingora direct 模式使用本机 public base URL 时必须配置 GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST',
|
|
'Pingora direct loopback 缺 Host 必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertRejectsWrongGatewayMode() {
|
|
const envFile = writeEnv('wrong-mode.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'nginx',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'https://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: 'genarrative.example',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'pingora-direct',
|
|
]);
|
|
assertStatus(result, 1, 'gateway mode 与期望不一致必须失败。');
|
|
assertIncludes(
|
|
`${result.stdout}\n${result.stderr}`,
|
|
'GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE 应为 pingora-direct,实际 nginx',
|
|
'gateway mode mismatch 必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertAcceptsNginxWithEmptyPublicHost() {
|
|
const envFile = writeEnv('nginx-empty-host.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'nginx',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'http://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: '',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'nginx',
|
|
'--expected-public-base-url',
|
|
'http://127.0.0.1',
|
|
'--require-empty-public-host',
|
|
]);
|
|
assertStatus(result, 0, 'Nginx 模式回退且 Host 为空应通过。');
|
|
}
|
|
|
|
function assertRejectsNginxWithStalePublicHost() {
|
|
const envFile = writeEnv('nginx-stale-host.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'nginx',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'http://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: 'genarrative.example',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'nginx',
|
|
'--require-empty-public-host',
|
|
]);
|
|
assertStatus(result, 1, 'Nginx 回退后残留 Host 覆盖必须失败。');
|
|
assertIncludes(
|
|
`${result.stdout}\n${result.stderr}`,
|
|
'GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST 应为空',
|
|
'Nginx 回退后残留 Host 覆盖必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertRejectsInvalidPublicHost() {
|
|
const envFile = writeEnv('invalid-host.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'pingora-direct',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'https://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: 'https://genarrative.example',
|
|
});
|
|
const result = runCheck([
|
|
'--env-file',
|
|
envFile,
|
|
'--expected-gateway-mode',
|
|
'pingora-direct',
|
|
]);
|
|
assertStatus(result, 1, '非法 public Host 必须失败。');
|
|
assertIncludes(
|
|
`${result.stdout}\n${result.stderr}`,
|
|
'GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST 只能是 host 或 host:port',
|
|
'非法 public Host 必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertRejectsInvalidEnvFilePaths() {
|
|
const relativePath = runCheck([
|
|
'--env-file',
|
|
'health-patrol.env',
|
|
'--expected-gateway-mode',
|
|
'nginx',
|
|
]);
|
|
assertStatus(
|
|
relativePath,
|
|
1,
|
|
'health patrol env 复核必须拒绝相对 env 文件路径。',
|
|
);
|
|
assertIncludes(
|
|
`${relativePath.stdout}\n${relativePath.stderr}`,
|
|
'--env-file 必须是绝对路径',
|
|
'相对 env 文件路径必须给出明确错误。',
|
|
);
|
|
|
|
const rootPath = runCheck([
|
|
'--env-file',
|
|
'/',
|
|
'--expected-gateway-mode',
|
|
'nginx',
|
|
]);
|
|
assertStatus(rootPath, 1, 'health patrol env 复核必须拒绝文件系统根目录。');
|
|
assertIncludes(
|
|
`${rootPath.stdout}\n${rootPath.stderr}`,
|
|
'--env-file 不能是文件系统根目录',
|
|
'文件系统根目录 env 文件路径必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertRejectsInvalidBoolEnv() {
|
|
const envFile = writeEnv('invalid-bool-env.env', {
|
|
GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE: 'nginx',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_BASE_URL: 'http://127.0.0.1',
|
|
GENARRATIVE_HEALTH_PATROL_PUBLIC_HOST: '',
|
|
});
|
|
const result = runCheck(
|
|
['--env-file', envFile, '--expected-gateway-mode', 'nginx'],
|
|
{
|
|
GENARRATIVE_HEALTH_PATROL_REQUIRE_EMPTY_PUBLIC_HOST: 'enabled',
|
|
},
|
|
);
|
|
assertStatus(result, 1, 'health patrol env 复核必须拒绝非法布尔 env。');
|
|
assertIncludes(
|
|
`${result.stdout}\n${result.stderr}`,
|
|
'GENARRATIVE_HEALTH_PATROL_REQUIRE_EMPTY_PUBLIC_HOST 必须是布尔值',
|
|
'health patrol env 复核非法布尔 env 必须给出明确错误。',
|
|
);
|
|
}
|
|
|
|
function assertExampleDocumentsDirectMode() {
|
|
const example = readFileSync('deploy/env/health-patrol.env.example', 'utf8');
|
|
assertIncludes(
|
|
example,
|
|
'GENARRATIVE_HEALTH_PATROL_GATEWAY_MODE=nginx',
|
|
'health patrol env 示例必须保留默认 nginx 模式。',
|
|
);
|
|
assertIncludes(
|
|
example,
|
|
'Pingora 直连切换后改为 pingora-direct',
|
|
'health patrol env 示例必须说明 Pingora direct 切换口径。',
|
|
);
|
|
}
|
|
|
|
function writeEnv(name, values) {
|
|
const filePath = path.join(tmpRoot, name);
|
|
mkdirSync(path.dirname(filePath), { recursive: true });
|
|
const body = Object.entries(values)
|
|
.map(([key, value]) => `${key}=${value}`)
|
|
.join('\n');
|
|
writeFileSync(filePath, `${body}\n`, 'utf8');
|
|
return filePath;
|
|
}
|
|
|
|
function runCheck(args, extraEnv = {}) {
|
|
return spawnSync(
|
|
process.execPath,
|
|
['--', 'scripts/check-production-health-patrol-env.mjs', ...args],
|
|
{
|
|
cwd: process.cwd(),
|
|
encoding: 'utf8',
|
|
env: {
|
|
...process.env,
|
|
GENARRATIVE_HEALTH_PATROL_ENV_FILE: '',
|
|
GENARRATIVE_HEALTH_PATROL_EXPECTED_GATEWAY_MODE: '',
|
|
GENARRATIVE_HEALTH_PATROL_EXPECTED_PUBLIC_BASE_URL: '',
|
|
GENARRATIVE_HEALTH_PATROL_EXPECTED_PUBLIC_HOST: '',
|
|
GENARRATIVE_HEALTH_PATROL_REQUIRE_EMPTY_PUBLIC_HOST: '',
|
|
...extraEnv,
|
|
},
|
|
},
|
|
);
|
|
}
|
|
|
|
function assertStatus(result, expected, reason) {
|
|
const actual = result.status ?? 0;
|
|
if (actual !== expected) {
|
|
failures.push(
|
|
`${reason} 预期退出码 ${expected},实际 ${actual}。\nstdout:\n${result.stdout}\nstderr:\n${result.stderr}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
function assertIncludes(content, needle, reason) {
|
|
if (!content.includes(needle)) {
|
|
failures.push(`${reason} 缺少: ${needle}`);
|
|
}
|
|
}
|