const crypto = require("crypto"); const fs = require("fs"); const path = require("path"); const AdmZip = require("adm-zip"); class ArtifactManager { constructor(root, options = {}) { this.root = root; this.fetch = options.fetch || global.fetch; fs.mkdirSync(root, { recursive: true }); } status(entry) { const target = path.join(this.root, entry.filename || entry.id); if (!fs.existsSync(target)) return { installed: false, valid: false, path: target }; return { installed: true, valid: sha256File(target) === entry.sha256, path: target, bytes: fs.statSync(target).size }; } async download(entry, options = {}) { validateManifestEntry(entry); if (options.confirmed !== true) throw new Error("Download requires explicit setup confirmation."); const filename = entry.filename || path.basename(new URL(entry.url).pathname); const target = path.join(this.root, filename); const partial = `${target}.${process.pid}.partial`; fs.rmSync(partial, { force: true }); try { const response = await this.fetch(entry.url, { redirect: "follow" }); if (!response.ok || !response.body) throw new Error(`Download failed with HTTP ${response.status}.`); const handle = fs.createWriteStream(partial, { flags: "wx", mode: 0o600 }); for await (const chunk of response.body) { if (!handle.write(chunk)) await new Promise((resolve) => handle.once("drain", resolve)); } await new Promise((resolve, reject) => handle.end((error) => error ? reject(error) : resolve())); const actual = sha256File(partial); if (actual !== entry.sha256) throw new Error(`Checksum mismatch for ${entry.id}.`); fs.renameSync(partial, target); return this.status({ ...entry, filename }); } finally { fs.rmSync(partial, { force: true }); } } installZip(entry, archivePath) { validateManifestEntry(entry); if (sha256File(archivePath) !== entry.sha256) throw new Error(`Checksum mismatch for ${entry.id}.`); const target = path.join(this.root, entry.id); const staged = `${target}.${process.pid}.staged`; fs.rmSync(staged, { recursive: true, force: true }); fs.mkdirSync(staged, { recursive: true }); const zip = new AdmZip(archivePath); for (const item of zip.getEntries()) { const portable = String(item.entryName).replace(/\\/g, "/"); const relative = path.posix.normalize(portable).replace(/^\/+/, ""); if (!relative || relative === ".." || relative.startsWith("../") || /^[A-Za-z]:/.test(relative)) throw new Error("Runtime archive contains an unsafe path."); } zip.extractAllTo(staged, true); for (const expected of entry.expected_paths || []) if (!findBasename(staged, expected)) throw new Error(`Runtime is missing ${expected}.`); fs.rmSync(target, { recursive: true, force: true }); fs.renameSync(staged, target); return { installed: true, path: target, backend: entry.backend, version: entry.id }; } } function validateManifestEntry(entry) { if (!entry?.id || !/^https:\/\//.test(entry.url || "") || !/^[a-f0-9]{64}$/.test(entry.sha256 || "")) throw new Error("Artifact manifest entry is invalid."); } function sha256File(target) { const hash = crypto.createHash("sha256"); const fd = fs.openSync(target, "r"); const buffer = Buffer.alloc(1024 * 1024); try { let read; while ((read = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, read)); } finally { fs.closeSync(fd); } return hash.digest("hex"); } function findBasename(root, basename) { const pending = [root]; while (pending.length) { const current = pending.pop(); for (const entry of fs.readdirSync(current, { withFileTypes: true })) { const target = path.join(current, entry.name); if (entry.isDirectory()) pending.push(target); else if (entry.name === basename) return target; } } return null; } module.exports = { ArtifactManager, sha256File, validateManifestEntry };