import fs from "node:fs"; import crypto from "node:crypto"; import path from "node:path"; export function copyArtifactAtomically(source, destination, copy = fs.copyFileSync) { let lastError; for (let attempt = 0; attempt < 3; attempt += 1) { const temporary = `${destination}.${process.pid}.${crypto.randomUUID()}.tmp`; try { const before = fs.statSync(source, { bigint: true }); copy(source, temporary); const after = fs.statSync(source, { bigint: true }); if (before.size !== after.size || before.mtimeNs !== after.mtimeNs || before.ino !== after.ino) throw new Error(`Artifact changed while copying; wait for its build to finish: ${source}`); if (destination.endsWith(".wasm") && !WebAssembly.validate(fs.readFileSync(temporary))) throw new Error(`Incomplete or invalid WASM; previous published file retained: ${source}`); fs.renameSync(temporary, destination); return; } catch (error) { lastError = error; } finally { fs.rmSync(temporary, { force: true }); } } throw lastError; } export function missingArtifactPair(source) { const match = path.basename(source).match(/^(FreeCADWeb[A-Za-z0-9]+)\.(?:js|wasm|data|worker\.js)$/); if (!match) return []; const stem = path.join(path.dirname(source), match[1]); return [stem + ".js", stem + ".wasm"].filter(file => !fs.existsSync(file)); } export function pthreadSidecar(source, destination) { if (!source.endsWith(".js") || source.endsWith(".worker.js")) return null; const worker = source.slice(0, -3) + ".worker.js"; return fs.existsSync(worker) ? [worker, destination.slice(0, -3) + ".worker.js"] : null; } export function artifactRevision(hashes) { return crypto.createHash("sha256").update(JSON.stringify(hashes)).digest("hex").slice(0, 16); } export function writeManifestIfChanged(file, manifest) { const stable = value => { const copy = { ...value }; delete copy.generatedAt; return JSON.stringify(copy); }; try { if (stable(JSON.parse(fs.readFileSync(file, "utf8"))) === stable(manifest)) return false; } catch { // Missing or incomplete manifests are replaced below. } const temporary = file + ".tmp"; fs.writeFileSync(temporary, JSON.stringify(manifest, null, 2) + "\n"); fs.renameSync(temporary, file); return true; }