Files
FreeCADWasmDev/tools/validate_web_offline_static.mjs
wangdequan 9a7c4f0335 Save FreeCAD Web workspace sources and user validation evidence
IFC native binding remains incomplete; preserve accurate test results.
2026-09-15 22:01:58 +08:00

87 lines
4.5 KiB
JavaScript

import crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const toolsRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)));
const projectRoot = path.resolve(toolsRoot, "..");
const packageRoot = path.join(projectRoot, "web", "offline-package");
const docsRoot = path.join(projectRoot, "docs");
const manifestPath = path.join(packageRoot, "manifest.json");
const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
const failures = [];
const references = [];
function sha256(filePath) {
return crypto.createHash("sha256").update(fs.readFileSync(filePath)).digest("hex");
}
function walk(root) {
const files = [];
for (const entry of fs.readdirSync(root, { withFileTypes: true })) {
const absolute = path.join(root, entry.name);
if (entry.isDirectory()) files.push(...walk(absolute));
else if (entry.isFile()) files.push(absolute);
}
return files.sort();
}
function addReference(source, target, kind) {
const normalized = target.startsWith("/") ? target.slice(1) : target;
const resolved = path.resolve(packageRoot, normalized);
const insidePackage = resolved === packageRoot || resolved.startsWith(`${packageRoot}${path.sep}`);
const exists = insidePackage && fs.existsSync(resolved) && fs.statSync(resolved).isFile();
references.push({ source, target, kind, resolved: insidePackage ? path.relative(packageRoot, resolved).split(path.sep).join("/") : null, exists });
if (!exists) failures.push({ type: "missing_reference", source, target, kind });
}
if (!fs.existsSync(packageRoot)) failures.push({ type: "missing_package", path: "FreeCAD/web/offline-package" });
else {
const actual = new Set(walk(packageRoot).map((file) => path.relative(packageRoot, file).split(path.sep).join("/")));
const listed = new Set([...manifest.files.map((file) => file.path), manifest.manifestFile.path]);
for (const file of listed) if (!actual.has(file)) failures.push({ type: "manifest_missing_file", file });
for (const file of actual) if (!listed.has(file)) failures.push({ type: "unlisted_file", file });
for (const file of manifest.files) {
const absolute = path.join(packageRoot, file.path);
if (fs.existsSync(absolute)) {
if (fs.statSync(absolute).size !== file.size) failures.push({ type: "size_mismatch", file: file.path });
if (sha256(absolute) !== file.sha256) failures.push({ type: "hash_mismatch", file: file.path });
}
}
const indexSource = "index.html";
const indexText = fs.readFileSync(path.join(packageRoot, indexSource), "utf8");
for (const match of indexText.matchAll(/(?:src|href)=["']([^"']+)["']/g)) addReference(indexSource, match[1], "html");
for (const file of walk(path.join(packageRoot, "assets"))) {
const relative = path.relative(packageRoot, file).split(path.sep).join("/");
const text = fs.readFileSync(file, "utf8");
for (const match of text.matchAll(/(?:fetch|importScripts|new Worker)\s*\(\s*["'`]([^"'`]+)["'`]/g)) addReference(relative, match[1], "javascript");
for (const match of text.matchAll(/(?:src|href)=["']([^"']+)["']/g)) addReference(relative, match[1], "javascript-html");
if (/(?:fetch|importScripts|new Worker)\s*\(\s*["'`]https?:\/\//.test(text)) failures.push({ type: "external_url", file: relative });
if (/node_modules/.test(text)) failures.push({ type: "node_modules_reference", file: relative });
}
for (const file of walk(path.join(packageRoot, "assets"))) {
const relative = path.relative(packageRoot, file).split(path.sep).join("/");
if (/node_modules/.test(fs.readFileSync(file, "utf8"))) failures.push({ type: "node_modules_reference", file: relative });
}
}
const result = {
schema: "freecad-web-offline-static-check/v1",
package: "FreeCAD/web/offline-package",
manifest: "FreeCAD/web/offline-package/manifest.json",
references,
failures,
summary: {
referenceCount: references.length,
missingReferenceCount: references.filter((reference) => !reference.exists).length,
failureCount: failures.length,
externalNetworkRequestCount: failures.filter((failure) => failure.type === "external_url").length,
result: failures.length === 0 ? "pass_static_package_closed" : "fail_static_package_open",
},
};
fs.writeFileSync(path.join(docsRoot, "freecad-web-offline-static-check.json"), `${JSON.stringify(result, null, 2)}\n`);
console.log(`checked ${result.summary.referenceCount} package references`);
console.log(`failures: ${result.summary.failureCount}; external URLs: ${result.summary.externalNetworkRequestCount}`);
if (failures.length > 0) process.exitCode = 1;