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;