61 lines
2.3 KiB
JavaScript
61 lines
2.3 KiB
JavaScript
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;
|
|
}
|