B1-B8 建立项目级流程与QA门禁
这个提交包含在:
+136
@@ -0,0 +1,136 @@
|
||||
#!/usr/bin/env node
|
||||
import { existsSync } from "node:fs";
|
||||
|
||||
const baseUrl = (process.env.LIVE_SMOKE_BASE_URL || "https://oa.hyinda.com").trim().replace(/\/$/, "");
|
||||
|
||||
function present(key) {
|
||||
return Boolean(process.env[key]?.trim());
|
||||
}
|
||||
|
||||
function maskedPresence(keys) {
|
||||
return Object.fromEntries(keys.map((key) => [key, present(key)]));
|
||||
}
|
||||
|
||||
function existingFileFrom(keys) {
|
||||
const values = keys.map((key) => process.env[key]?.trim()).filter(Boolean);
|
||||
return values.find((value) => existsSync(value)) || "";
|
||||
}
|
||||
|
||||
async function fetchJson(url) {
|
||||
try {
|
||||
const response = await fetch(url, { signal: AbortSignal.timeout(10_000) });
|
||||
const text = await response.text();
|
||||
let json = null;
|
||||
try {
|
||||
json = text ? JSON.parse(text) : null;
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
return { ok: response.ok, status: response.status, json, text: text.slice(0, 300) };
|
||||
} catch (error) {
|
||||
return { ok: false, status: 0, error: error instanceof Error ? error.message : String(error) };
|
||||
}
|
||||
}
|
||||
|
||||
function buildStaticChecks() {
|
||||
const liveSmokeKeys = [
|
||||
"LIVE_SMOKE_ADMIN_IDENTIFIER",
|
||||
"LIVE_SMOKE_ADMIN_PASSWORD",
|
||||
"LIVE_SMOKE_EMPLOYEE_IDENTIFIER",
|
||||
"LIVE_SMOKE_EMPLOYEE_PASSWORD",
|
||||
];
|
||||
const liveSmokeReady = liveSmokeKeys.every(present);
|
||||
const ocrUserReady = (
|
||||
(present("PLAYWRIGHT_OCR_LIVE_IDENTIFIER") && present("PLAYWRIGHT_OCR_LIVE_PASSWORD")) ||
|
||||
(present("LIVE_SMOKE_EMPLOYEE_IDENTIFIER") && present("LIVE_SMOKE_EMPLOYEE_PASSWORD"))
|
||||
);
|
||||
const invoiceFile = existingFileFrom(["PLAYWRIGHT_OCR_LIVE_FILE", "LIVE_OCR_INVOICE_FILE"]);
|
||||
const invoiceBatchDir = process.env.PLAYWRIGHT_OCR_LIVE_DIR?.trim() || "";
|
||||
const invoiceSampleReady = Boolean(invoiceFile) || (Boolean(invoiceBatchDir) && existsSync(invoiceBatchDir));
|
||||
|
||||
return {
|
||||
baseUrl,
|
||||
liveSmokeReady,
|
||||
liveSmoke: maskedPresence(liveSmokeKeys),
|
||||
liveSmokeOptional: maskedPresence([
|
||||
"LIVE_EXPECT_WECOM_STATUS",
|
||||
"LIVE_SMOKE_MANAGER_IDENTIFIER",
|
||||
"LIVE_SMOKE_MANAGER_PASSWORD",
|
||||
"LIVE_SMOKE_FINANCE_IDENTIFIER",
|
||||
"LIVE_SMOKE_FINANCE_PASSWORD",
|
||||
]),
|
||||
liveOcrReady: ocrUserReady && invoiceSampleReady,
|
||||
liveOcr: {
|
||||
userReady: ocrUserReady,
|
||||
invoiceSampleReady,
|
||||
invoiceFileProvided: Boolean(invoiceFile),
|
||||
invoiceBatchDirProvided: Boolean(invoiceBatchDir),
|
||||
allowDuplicate: process.env.PLAYWRIGHT_OCR_ALLOW_DUPLICATE === "1",
|
||||
},
|
||||
missing: [],
|
||||
};
|
||||
}
|
||||
|
||||
function addMissing(checks) {
|
||||
if (!checks.liveSmokeReady) {
|
||||
for (const [key, value] of Object.entries(checks.liveSmoke)) {
|
||||
if (!value) checks.missing.push(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (!checks.liveOcr.userReady) {
|
||||
checks.missing.push("PLAYWRIGHT_OCR_LIVE_IDENTIFIER/PASSWORD 或 LIVE_SMOKE_EMPLOYEE_*");
|
||||
}
|
||||
if (!checks.liveOcr.invoiceSampleReady) {
|
||||
checks.missing.push("PLAYWRIGHT_OCR_LIVE_FILE 或 PLAYWRIGHT_OCR_LIVE_DIR");
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const strict = process.argv.includes("--strict");
|
||||
const jsonOnly = process.argv.includes("--json");
|
||||
const checks = buildStaticChecks();
|
||||
addMissing(checks);
|
||||
|
||||
const ready = await fetchJson(`${baseUrl}/ready`);
|
||||
const wecomConfig = await fetchJson(`${baseUrl}/api/auth/wecom/config`);
|
||||
const result = {
|
||||
checkedAt: new Date().toISOString(),
|
||||
...checks,
|
||||
publicReady: {
|
||||
ok: ready.ok,
|
||||
status: ready.status,
|
||||
},
|
||||
wecomConfig: {
|
||||
ok: wecomConfig.ok,
|
||||
status: wecomConfig.status,
|
||||
statusText: wecomConfig.json?.data?.status ?? null,
|
||||
entryEnabled: wecomConfig.json?.data?.entryEnabled ?? null,
|
||||
oauthEnabled: wecomConfig.json?.data?.oauthEnabled ?? null,
|
||||
defaultReturnTo: wecomConfig.json?.data?.defaultReturnTo ?? null,
|
||||
loginUrl: wecomConfig.json?.data?.loginUrl ?? null,
|
||||
},
|
||||
};
|
||||
|
||||
if (jsonOnly) {
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} else {
|
||||
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] wecom=${result.wecomConfig.statusText ?? "unknown"} entry=${result.wecomConfig.entryEnabled} oauth=${result.wecomConfig.oauthEnabled}`);
|
||||
console.log(`[live-smoke-env] liveSmokeReady=${result.liveSmokeReady}`);
|
||||
console.log(`[live-smoke-env] liveOcrReady=${result.liveOcrReady}`);
|
||||
if (result.missing.length) {
|
||||
console.log(`[live-smoke-env] missing=${result.missing.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (strict && (!result.liveSmokeReady || !result.liveOcrReady || !result.publicReady.ok || !result.wecomConfig.ok)) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`[live-smoke-env][ERROR] ${error instanceof Error ? error.message : String(error)}`);
|
||||
process.exit(1);
|
||||
});
|
||||
在新工单中引用
屏蔽一个用户