Files
workinf_Blender_Wasm/tools/web/create-rc-manifest.mjs
mes123456 7c16b279ae
Some checks failed
M6 deployable RC / quick (push) Has been cancelled
M6 deployable RC / chromium (push) Has been cancelled
M6 deployable RC / release (push) Has been cancelled
Advance M7 workflows and release operations
2026-08-15 17:43:53 -04:00

99 lines
5.8 KiB
JavaScript

import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { execFileSync } from "node:child_process";
import { fileURLToPath } from "node:url";
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
const releaseRoot = path.join(repoRoot, "release");
const sha256Bytes = (value) => crypto.createHash("sha256").update(value).digest("hex");
const sha256 = (file) => sha256Bytes(fs.readFileSync(file));
const archiveEntry = (archive, entry) => execFileSync("tar", ["-xOf", archive, entry], { maxBuffer: 32 * 1024 * 1024 });
const packageJson = JSON.parse(fs.readFileSync(path.join(repoRoot, "web/package.json"), "utf8"));
const engineManifest = JSON.parse(fs.readFileSync(path.join(repoRoot, "web/app/public/engine-manifest.json"), "utf8"));
const binaryArchive = path.join(releaseRoot, "blender-web-offline.tar.gz");
const sourceArchive = path.join(releaseRoot, "blender-web-corresponding-source.tar.gz");
const sbom = path.join(repoRoot, "docs/web/sbom.spdx.json");
const sumsPath = path.join(releaseRoot, "SHA256SUMS.txt");
const packageJsonPath = path.join(repoRoot, "web/package.json");
const packageLockPath = path.join(repoRoot, "web/package-lock.json");
const engineManifestPath = path.join(repoRoot, "web/app/public/engine-manifest.json");
const embeddedPackageJsonBytes = archiveEntry(sourceArchive, "web/package.json");
const embeddedPackageLockBytes = archiveEntry(sourceArchive, "web/package-lock.json");
const embeddedEngineManifestBytes = archiveEntry(binaryArchive, "blender-web-offline/app/engine-manifest.json");
const embeddedMetadataBytes = archiveEntry(binaryArchive, "blender-web-offline/release-metadata.json");
const embeddedSbomBytes = archiveEntry(binaryArchive, "blender-web-offline/sbom.spdx.json");
const sourceManifest = JSON.parse(archiveEntry(sourceArchive, "SOURCE_MANIFEST.json").toString("utf8"));
const embeddedMetadata = JSON.parse(embeddedMetadataBytes.toString("utf8"));
const commit = execFileSync("git", ["rev-parse", "HEAD"], { cwd: repoRoot, encoding: "utf8" }).trim();
const worktreeDirty = execFileSync("git", ["status", "--porcelain", "--untracked-files=all"], {
cwd: repoRoot,
encoding: "utf8",
}).trim().length > 0;
if (!/^\d+\.\d+\.\d+-rc\.\d+$/.test(packageJson.version)) throw new Error("package version is not an RC semver");
if (engineManifest.releaseId !== `blender-wasm-${packageJson.version}`) throw new Error("engine releaseId does not match RC semver");
if (embeddedMetadata.productVersion !== packageJson.version || embeddedMetadata.engineReleaseId !== engineManifest.releaseId) {
throw new Error("binary release metadata does not match the frozen RC identity");
}
if (sourceManifest.version !== packageJson.version || sourceManifest.engineReleaseId !== engineManifest.releaseId) {
throw new Error("source manifest does not match the frozen RC identity");
}
for (const [label, actual, expected] of [
["source package.json", embeddedPackageJsonBytes, fs.readFileSync(packageJsonPath)],
["source package-lock.json", embeddedPackageLockBytes, fs.readFileSync(packageLockPath)],
["binary engine manifest", embeddedEngineManifestBytes, fs.readFileSync(engineManifestPath)],
["binary SBOM", embeddedSbomBytes, fs.readFileSync(sbom)],
]) {
if (!actual.equals(expected)) throw new Error(`${label} does not match the frozen workspace bytes`);
}
const checksumEntries = fs.readFileSync(sumsPath, "utf8").trim().split("\n").map((line) => line.match(/^([a-f0-9]{64}) ([^/]+)$/));
if (checksumEntries.some((entry) => entry === null) || checksumEntries.length !== 2) {
throw new Error("SHA256SUMS.txt must contain exactly two canonical archive entries");
}
const checksums = new Map(checksumEntries.map((entry) => [entry[2], entry[1]]));
for (const file of [binaryArchive, sourceArchive]) {
if (checksums.get(path.basename(file)) !== sha256(file)) throw new Error(`SHA256SUMS.txt does not match ${path.basename(file)}`);
}
const artifact = (file, relative) => ({
path: relative,
bytes: fs.statSync(file).size,
sha256: sha256(file),
});
const operations = ["deploy", "upgrade", "rollback", "diagnostics", "rehearsal"]
.map((name) => [name, path.join(releaseRoot, "operations-reports", `${name}.json`)]);
for (const [name, file] of operations) {
if (!fs.existsSync(file)) throw new Error(`RC operations report is missing: ${name}`);
}
const manifest = {
schemaVersion: 1,
rcId: `web-blender-${packageJson.version}`,
semver: packageJson.version,
gitCommit: commit,
sourceBinding: {
mode: "corresponding-source-archive",
worktreeDirty,
note: "The source archive SHA-256 binds the candidate bytes independently of the base commit.",
},
engineReleaseId: engineManifest.releaseId,
artifacts: {
binaryArchive: artifact(binaryArchive, "release/blender-web-offline.tar.gz"),
sourceArchive: artifact(sourceArchive, "release/blender-web-corresponding-source.tar.gz"),
sbom: artifact(sbom, "docs/web/sbom.spdx.json"),
},
bindings: {
packageJsonSha256: sha256(packageJsonPath),
packageLockSha256: sha256(packageLockPath),
ledgerSha256: sha256(path.join(repoRoot, "docs/status/parity-ledger.json")),
engineManifestSha256: sha256(engineManifestPath),
embeddedReleaseMetadataSha256: sha256Bytes(embeddedMetadataBytes),
sha256SumsSha256: sha256(sumsPath),
},
operations: Object.fromEntries(operations.map(([name, file]) => [name, artifact(file, path.relative(repoRoot, file).replaceAll(path.sep, "/"))])),
};
const output = path.join(releaseRoot, "RC_MANIFEST.json");
fs.writeFileSync(output, `${JSON.stringify(manifest, null, 2)}\n`);
fs.writeFileSync(`${output}.sha256`, `${sha256(output)} ${path.basename(output)}\n`);
process.stdout.write(`rc-manifest-ok id=${manifest.rcId} commit=${commit} binary=${manifest.artifacts.binaryArchive.sha256} source=${manifest.artifacts.sourceArchive.sha256} sbom=${manifest.artifacts.sbom.sha256}\n`);