/** Portable ZIP using Node's built-in compression. No system zip command required. */ import { readFile, readdir, writeFile } from "node:fs/promises"; import { deflateRawSync } from "node:zlib"; import { resolve, relative } from "node:path"; import { build, root } from "./build.mjs"; const roots = [ "src", "scripts", "assets", "tests", "dist", "captures/current", "package.json", "package-lock.json", "vercel.json", ".vercelignore", ".gitignore", "README.md", "ASSETS.md", "CAPTURES.md", "MOTION.md", "CONCEPT-MAP.md", "DEPLOYMENT.md", ]; function crc32(bytes) { let crc = 0xffffffff; for (const byte of bytes) { crc ^= byte; for (let bit = 0; bit < 8; bit++) crc = (crc >>> 1) ^ (crc & 1 ? 0xedb88320 : 0); } return (crc ^ 0xffffffff) >>> 0; } async function filesAt(path) { const entries = await readdir(path, { withFileTypes: true }); const groups = await Promise.all( entries .sort((a, b) => a.name.localeCompare(b.name)) .filter((x) => x.name !== ".DS_Store") .map((x) => x.isDirectory() ? filesAt(resolve(path, x.name)) : [resolve(path, x.name)], ), ); return groups.flat(); } await build(); const files = []; for (const name of roots) { const path = resolve(root, name); if (name.includes(".") && !name.includes("/")) { try { await readFile(path); files.push(path); } catch (error) { if (name !== ".gitignore" || error.code !== "ENOENT") throw error; } } else { try { files.push(...(await filesAt(path))); } catch (error) { if (name !== "captures/current" || error.code !== "ENOENT") throw error; } } } let offset = 0; const records = [], directory = []; for (const path of files) { const body = await readFile(path), data = deflateRawSync(body), name = Buffer.from( "dualentry-landing/" + relative(root, path).split("\\").join("/"), ); const crc = crc32(body), header = Buffer.alloc(30); header.writeUInt32LE(0x04034b50, 0); header.writeUInt16LE(20, 4); header.writeUInt16LE(0x800, 6); header.writeUInt16LE(8, 8); header.writeUInt16LE(33, 12); header.writeUInt32LE(crc, 14); header.writeUInt32LE(data.length, 18); header.writeUInt32LE(body.length, 22); header.writeUInt16LE(name.length, 26); const central = Buffer.alloc(46); central.writeUInt32LE(0x02014b50, 0); central.writeUInt16LE(20, 4); central.writeUInt16LE(20, 6); central.writeUInt16LE(0x800, 8); central.writeUInt16LE(8, 10); central.writeUInt16LE(33, 14); central.writeUInt32LE(crc, 16); central.writeUInt32LE(data.length, 20); central.writeUInt32LE(body.length, 24); central.writeUInt16LE(name.length, 28); central.writeUInt32LE(offset, 42); records.push(header, name, data); directory.push(central, name); offset += header.length + name.length + data.length; } const central = Buffer.concat(directory), end = Buffer.alloc(22); end.writeUInt32LE(0x06054b50, 0); end.writeUInt16LE(files.length, 8); end.writeUInt16LE(files.length, 10); end.writeUInt32LE(central.length, 12); end.writeUInt32LE(offset, 16); const output = resolve(root, "../dualentry-landing.zip"); await writeFile(output, Buffer.concat([...records, central, end])); console.log(`Packaged ${files.length} files: ${output}`);