Files
tyx_AI_xhs/scripts/lib/wp7-07-final-release.mjs
T
suyx 42f993378f
Dada P0-A isolated Windows CI / validate-and-package (push) Waiting to run
fix(POSTV1-browser): 同时支持 Chrome 150 与 151
2026-08-06 00:42:19 +08:00

118 lines
6.1 KiB
JavaScript

import { createHash } from "node:crypto";
import { readFileSync, readdirSync, statSync } from "node:fs";
import { extname, join, relative } from "node:path";
const SHA40 = /^[a-f0-9]{40}$/i;
const SHA64 = /^[a-f0-9]{64}$/i;
const VERSION = /^[1-9][0-9]*\.[0-9]+\.[0-9]+\.[0-9]+$/;
const ABSOLUTE_PATH = /(?:[A-Za-z]:[\\/](?:Users|Documents)[\\/][^\\/"'\s]+[\\/]|\/Users\/[^/"'\s]+\/|\/home\/[^/"'\s]+\/)/;
const CREDENTIAL = /\b(?:sk|key)-[A-Za-z0-9_-]{16,}\b|-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/i;
const TEXT_EXTENSIONS = new Set([".cjs", ".cs", ".css", ".html", ".js", ".json", ".mjs", ".ts", ".tsx", ".txt", ".xml", ".yaml", ".yml"]);
export const DEFERRED_EXTERNAL_TASKS = Object.freeze(["TASK-WP7-03", "TASK-WP7-04"]);
export function buildFinalReleaseRecord({ appVersion, browsers, buildCommit, frozenFromCommit, recordedAt, windows }) {
const record = {
appVersion,
browsers: browsers.map(({ brand, fullVersion, supportedMajorVersions }) => ({
brand,
fullVersion,
...(supportedMajorVersions ? { supportedMajorVersions: [...supportedMajorVersions] } : {}),
})),
buildCommit: buildCommit.toLowerCase(),
deferredExternalTasks: [...DEFERRED_EXTERNAL_TASKS],
finalRelease: true,
fixedPort: 43121,
frozenFromCommit: frozenFromCommit.toLowerCase(),
recordedAt,
releaseStatus: "first_version_internal",
schemaVersion: "1.0",
windows: { arch: windows.arch, build: windows.build, displayVersion: windows.displayVersion },
};
return validateFinalReleaseRecord(record);
}
export function validateFinalReleaseRecord(record) {
const errors = [];
if (record?.schemaVersion !== "1.0") errors.push("schemaVersion");
if (record?.releaseStatus !== "first_version_internal") errors.push("releaseStatus");
if (record?.finalRelease !== true) errors.push("finalRelease");
if (record?.fixedPort !== 43121) errors.push("fixedPort");
if (!SHA40.test(record?.buildCommit ?? "")) errors.push("buildCommit");
if (!SHA40.test(record?.frozenFromCommit ?? "")) errors.push("frozenFromCommit");
if (!Number.isFinite(Date.parse(record?.recordedAt ?? ""))) errors.push("recordedAt");
if (!Array.isArray(record?.deferredExternalTasks) || record.deferredExternalTasks.join("|") !== DEFERRED_EXTERNAL_TASKS.join("|")) errors.push("deferredExternalTasks");
if (record?.windows?.arch !== "x64" || !/^\d+\.\d+$/.test(record?.windows?.build ?? "")) errors.push("windows");
if (!Array.isArray(record?.browsers) || record.browsers.length !== 2) {
errors.push("browsers");
} else {
const brands = record.browsers.map(({ brand }) => brand).sort();
if (brands.join("|") !== "Google Chrome|Microsoft Edge") errors.push("browserBrands");
const supportedMajorCount = record.browsers.reduce(
(count, browser) => count + (Array.isArray(browser.supportedMajorVersions)
? browser.supportedMajorVersions.length
: 1),
0,
);
if (supportedMajorCount > 8) errors.push("supportedMajorVersions.total");
for (const browser of record.browsers) {
if (!VERSION.test(browser.fullVersion ?? "")) errors.push(`${browser.brand}.fullVersion`);
if (browser.supportedMajorVersions !== undefined) {
const values = browser.supportedMajorVersions;
const baselineMajor = Number.parseInt(browser.fullVersion.split(".")[0] ?? "0", 10);
if (!Array.isArray(values) || values.length === 0 || values.length > 8
|| values.some((value) => !Number.isSafeInteger(value) || value < 1)
|| new Set(values).size !== values.length
|| !values.includes(baselineMajor)) {
errors.push(`${browser.brand}.supportedMajorVersions`);
}
}
if ("path" in browser || "executablePath" in browser || "executableSha256" in browser) errors.push(`${browser.brand}.privateMetadata`);
}
}
const serialized = JSON.stringify(record);
if (ABSOLUTE_PATH.test(serialized) || CREDENTIAL.test(serialized)) errors.push("sensitiveValue");
if (errors.length > 0) throw new Error(`WP7_07_RELEASE_INVALID:${[...new Set(errors)].join(",")}`);
return record;
}
export function sha256File(path) {
return createHash("sha256").update(readFileSync(path)).digest("hex").toUpperCase();
}
export function scanReleaseFiles({ roots, allowedFixturePaths = [] }) {
const allowed = new Set(allowedFixturePaths.map((value) => value.replaceAll("\\", "/")));
const findings = [];
let scannedFiles = 0;
function visit(root, current = root) {
for (const entry of readdirSync(current, { withFileTypes: true })) {
if ([".git", ".pnpm-store", "node_modules", "bin", "obj"].includes(entry.name)) continue;
const path = join(current, entry.name);
if (entry.isDirectory()) {
visit(root, path);
continue;
}
if (!entry.isFile()) continue;
scannedFiles += 1;
if (!TEXT_EXTENSIONS.has(extname(entry.name).toLowerCase())) continue;
const logicalPath = relative(root, path).replaceAll("\\", "/");
const content = readFileSync(path, "utf8");
if (!allowed.has(logicalPath) && ABSOLUTE_PATH.test(content)) findings.push({ path: logicalPath, rule: "absolute_user_path" });
if (!allowed.has(logicalPath) && CREDENTIAL.test(content)) findings.push({ path: logicalPath, rule: "credential_shape" });
}
}
for (const root of roots) {
if (!statSync(root).isDirectory()) throw new Error(`WP7_07_SCAN_ROOT_INVALID:${root}`);
visit(root);
}
return { findings, scanned_files: scannedFiles, status: findings.length === 0 ? "passed" : "failed" };
}
export function validateFinalEvidence({ packageManifest, release, releaseSha256, scan }) {
validateFinalReleaseRecord(release);
if (!SHA64.test(releaseSha256 ?? "")) throw new Error("WP7_07_RELEASE_HASH_INVALID");
if (packageManifest?.release_status !== release.releaseStatus || !SHA64.test(packageManifest?.zip_sha256 ?? "")) throw new Error("WP7_07_PACKAGE_MANIFEST_INVALID");
if (scan?.status !== "passed" || scan.findings?.length !== 0) throw new Error("WP7_07_LEAK_SCAN_FAILED");
return { release_sha256: releaseSha256.toUpperCase(), status: "passed", zip_sha256: packageManifest.zip_sha256.toUpperCase() };
}