/** * Minimal, dependency-free ZIP writer (#plugins, M6+). Produces a stored-metadata * + deflate ZIP whose central directory matches exactly what the TREK installer's * reader (server safe-extract.ts) parses: EOCD -> central headers -> local headers. * Kept tiny and in-tree so the SDK stays dependency-free and the artifact format * can never drift from the reader. */ import zlib from 'node:zlib'; export interface ZipInput { /** Archive-relative path, forward slashes (e.g. "server/index.js"). */ name: string; data: Buffer; } const CRC_TABLE = (() => { const t = new Uint32Array(256); for (let n = 0; n < 256; n++) { let c = n; for (let k = 0; k < 8; k++) c = c & 1 ? 0xedb88320 ^ (c >>> 1) : c >>> 1; t[n] = c >>> 0; } return t; })(); function crc32(buf: Buffer): number { let c = 0xffffffff; for (let i = 0; i < buf.length; i++) c = (CRC_TABLE[(c ^ buf[i]) & 0xff] ^ (c >>> 8)) >>> 0; return (c ^ 0xffffffff) >>> 0; } /** List the entry names of a ZIP by walking its central directory (reader for `preflight`). */ export function listZipNames(buf: Buffer): string[] { // Find the End-Of-Central-Directory record (scan back from the end past any comment). let eocd = -1; for (let i = buf.length - 22; i >= 0 && i >= buf.length - 22 - 0xffff; i--) { if (buf.readUInt32LE(i) === 0x06054b50) { eocd = i; break; } } if (eocd < 0) throw new Error('not a ZIP archive (no EOCD record)'); const count = buf.readUInt16LE(eocd + 10); let p = buf.readUInt32LE(eocd + 16); // central directory offset const names: string[] = []; for (let i = 0; i < count; i++) { if (buf.readUInt32LE(p) !== 0x02014b50) throw new Error('corrupt central directory'); const nameLen = buf.readUInt16LE(p + 28); const extraLen = buf.readUInt16LE(p + 30); const commentLen = buf.readUInt16LE(p + 32); names.push(buf.toString('utf8', p + 46, p + 46 + nameLen)); p += 46 + nameLen + extraLen + commentLen; } return names; } /** Build a ZIP buffer from a flat list of file entries (no directory entries). */ export function makeZip(files: ZipInput[]): Buffer { const local: Buffer[] = []; const central: Buffer[] = []; let offset = 0; for (const f of files) { const nameBuf = Buffer.from(f.name.replaceAll(/\\/g, '/'), 'utf8'); const crc = crc32(f.data); // Deflate; fall back to stored if that somehow grows the data. const deflated = zlib.deflateRawSync(f.data, { level: 9 }); const useStore = deflated.length >= f.data.length; const method = useStore ? 0 : 8; const body = useStore ? f.data : deflated; const lfh = Buffer.alloc(30); lfh.writeUInt32LE(0x04034b50, 0); // local file header signature lfh.writeUInt16LE(20, 4); // version needed lfh.writeUInt16LE(0, 6); // flags lfh.writeUInt16LE(method, 8); lfh.writeUInt16LE(0, 10); // mod time lfh.writeUInt16LE(0x21, 12); // mod date (fixed, 1980-01-01) lfh.writeUInt32LE(crc, 14); lfh.writeUInt32LE(body.length, 18); // compressed size lfh.writeUInt32LE(f.data.length, 22); // uncompressed size lfh.writeUInt16LE(nameBuf.length, 26); lfh.writeUInt16LE(0, 28); // extra length local.push(lfh, nameBuf, body); const cdh = Buffer.alloc(46); cdh.writeUInt32LE(0x02014b50, 0); // central header signature cdh.writeUInt16LE(20, 4); // version made by cdh.writeUInt16LE(20, 6); // version needed cdh.writeUInt16LE(0, 8); // flags cdh.writeUInt16LE(method, 10); cdh.writeUInt16LE(0, 12); // mod time cdh.writeUInt16LE(0x21, 14); // mod date cdh.writeUInt32LE(crc, 16); cdh.writeUInt32LE(body.length, 20); cdh.writeUInt32LE(f.data.length, 24); cdh.writeUInt16LE(nameBuf.length, 28); cdh.writeUInt16LE(0, 30); // extra length cdh.writeUInt16LE(0, 32); // comment length cdh.writeUInt16LE(0, 34); // disk number cdh.writeUInt16LE(0, 36); // internal attributes cdh.writeUInt32LE(0, 38); // external attributes (0 = regular file, never symlink) cdh.writeUInt32LE(offset, 42); // local header offset central.push(cdh, nameBuf); offset += lfh.length + nameBuf.length + body.length; } const centralBuf = Buffer.concat(central); const eocd = Buffer.alloc(22); eocd.writeUInt32LE(0x06054b50, 0); // EOCD signature eocd.writeUInt16LE(0, 4); // this disk eocd.writeUInt16LE(0, 6); // central dir start disk eocd.writeUInt16LE(files.length, 8); // entries on this disk eocd.writeUInt16LE(files.length, 10); // total entries eocd.writeUInt32LE(centralBuf.length, 12); // central dir size eocd.writeUInt32LE(offset, 16); // central dir offset eocd.writeUInt16LE(0, 20); // comment length return Buffer.concat([...local, centralBuf, eocd]); }