完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -3,6 +3,22 @@ import { existsSync, readdirSync, statSync } from "node:fs";
|
||||
import { extname } from "node:path";
|
||||
|
||||
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").trim().replace(/\/$/, "");
|
||||
const expectedBuildShaInput = String(process.env.LIVE_SMOKE_EXPECTED_BUILD_SHA ?? "").trim();
|
||||
|
||||
function normalizeBuildSha(value) {
|
||||
const normalized = String(value ?? "").trim().toLowerCase();
|
||||
if (normalized === "unknown") {
|
||||
return "unknown";
|
||||
}
|
||||
return /^[0-9a-f]{7,64}$/.test(normalized) ? normalized.slice(0, 12) : null;
|
||||
}
|
||||
|
||||
function buildShaMatches(expected, observed) {
|
||||
if (!expected || !observed || observed === "unknown") {
|
||||
return null;
|
||||
}
|
||||
return expected.startsWith(observed) || observed.startsWith(expected);
|
||||
}
|
||||
|
||||
function present(key) {
|
||||
return Boolean(process.env[key]?.trim());
|
||||
@@ -108,7 +124,7 @@ function addMissing(checks) {
|
||||
checks.missing.push("ocrOrEmployeeCredentialReady");
|
||||
}
|
||||
if (!checks.liveOcr.invoiceSampleReady) {
|
||||
checks.missing.push("PLAYWRIGHT_OCR_LIVE_FILE 或 PLAYWRIGHT_OCR_LIVE_DIR 中的 PDF 样票");
|
||||
checks.missing.push("invoiceSampleReady");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,12 +137,40 @@ async function main() {
|
||||
const ready = await fetchJson(`${baseUrl}/ready`);
|
||||
const wecomConfig = await fetchJson(`${baseUrl}/api/auth/wecom/config`);
|
||||
const authCapabilities = await fetchJson(`${baseUrl}/api/auth/capabilities`);
|
||||
const findings = checks.missing.map((code) => ({ code, severity: "manual_or_config" }));
|
||||
if (!ready.ok) findings.push({ code: "publicReady", severity: "blocker" });
|
||||
const expectedBuildSha = normalizeBuildSha(expectedBuildShaInput);
|
||||
const observedBuildSha = normalizeBuildSha(
|
||||
ready.json?.data?.buildSha ?? ready.json?.error?.details?.buildSha,
|
||||
);
|
||||
const buildMatches = buildShaMatches(expectedBuildSha, observedBuildSha);
|
||||
if (ready.ok && (!observedBuildSha || observedBuildSha === "unknown")) {
|
||||
findings.push({ code: "publicBuildShaUnavailable", severity: "manual_or_config" });
|
||||
} else if (ready.ok && expectedBuildSha && buildMatches === false) {
|
||||
findings.push({ code: "publicBuildShaMismatch", severity: "manual_or_config" });
|
||||
}
|
||||
if (expectedBuildShaInput && !expectedBuildSha) {
|
||||
findings.push({ code: "expectedBuildShaInvalid", severity: "manual_or_config" });
|
||||
}
|
||||
if (!wecomConfig.ok) findings.push({ code: "wecomConfigReady", severity: "blocker" });
|
||||
if (!authCapabilities.ok) findings.push({ code: "authCapabilitiesReady", severity: "blocker" });
|
||||
const status = findings.some((finding) => finding.severity === "blocker")
|
||||
? "FAIL"
|
||||
: findings.length
|
||||
? "PASS_WITH_FINDINGS"
|
||||
: "PASS";
|
||||
const result = {
|
||||
status,
|
||||
findingCount: findings.length,
|
||||
findings,
|
||||
checkedAt: new Date().toISOString(),
|
||||
...checks,
|
||||
publicReady: {
|
||||
ok: ready.ok,
|
||||
status: ready.status,
|
||||
expectedBuildSha,
|
||||
observedBuildSha,
|
||||
buildShaMatches: buildMatches,
|
||||
},
|
||||
wecomConfig: {
|
||||
ok: wecomConfig.ok,
|
||||
@@ -135,7 +179,7 @@ async function main() {
|
||||
entryEnabled: wecomConfig.json?.data?.entryEnabled ?? null,
|
||||
oauthEnabled: wecomConfig.json?.data?.oauthEnabled ?? null,
|
||||
defaultReturnTo: wecomConfig.json?.data?.defaultReturnTo ?? null,
|
||||
loginUrl: wecomConfig.json?.data?.loginUrl ?? null,
|
||||
loginUrlReady: Boolean(wecomConfig.json?.data?.loginUrl),
|
||||
},
|
||||
authCapabilities: {
|
||||
ok: authCapabilities.ok,
|
||||
@@ -150,8 +194,10 @@ async function main() {
|
||||
if (jsonOnly) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
console.log(`[live-smoke-env] status=${result.status} findings=${result.findingCount}`);
|
||||
console.log(`[live-smoke-env] base=${result.baseUrl}`);
|
||||
console.log(`[live-smoke-env] publicReady=${result.publicReady.ok} status=${result.publicReady.status}`);
|
||||
console.log(`[live-smoke-env] buildShaObserved=${result.publicReady.observedBuildSha ?? "unavailable"} match=${result.publicReady.buildShaMatches ?? "not_checked"}`);
|
||||
console.log(`[live-smoke-env] wecom=${result.wecomConfig.statusText ?? "unknown"} entry=${result.wecomConfig.entryEnabled} oauth=${result.wecomConfig.oauthEnabled}`);
|
||||
console.log(`[live-smoke-env] authCapabilities=${result.authCapabilities.ok} emailDelivery=${result.authCapabilities.emailDeliveryReady}`);
|
||||
console.log(`[live-smoke-env] liveSmokeReady=${result.liveSmokeReady}`);
|
||||
@@ -161,7 +207,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
if (strict && (!result.liveSmokeReady || !result.liveOcrReady || !result.publicReady.ok || !result.wecomConfig.ok || !result.authCapabilities.ok)) {
|
||||
if (strict && result.status !== "PASS") {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户