Something went wrong. Try again.
archived: streaming zip archiver/extractor jsr.io/@mary/zip
typescript jsr
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596/** * @module * * produces a stable, diff-friendly rendering of a zip archive — the * archive-level comment plus a per-entry block with every metadata field * we expose. the format is invented here (no upstream tool to mimic); it * is used by the golden-diff regression test to catch silent shifts in * what the parser exposes. * * the output is multi-line, key=value, with one block per entry separated * by a blank line. fields that have a natural sentinel (undefined atime, * empty comment, etc.) are emitted only when set, so a real regression — * going from `uid=1000` back to `uid undefined` — shows up as a deleted * line. * * the dump consumes the parsing layer ({@link walkArchive}) directly so * it stays decoupled from the {@link UnzippedEntry} surface — every field * it renders is a property of {@link ParsedCentralRecord}. */
import { type ParsedCentralRecord, walkArchive } from './record.ts';
import type { Reader } from './reader/types.ts';
/** * renders an archive's metadata as a stable multi-line string. walks the * central directory via {@link walkArchive}; each parsed record is * projected into a key=value block. * * @param source the archive to dump — a single reader, or the ordered set * of volume readers for a multi-volume archive * @returns the dump text (trailing newline included) */export const dumpArchive = async (source: Reader | readonly Reader[]): Promise<string> => { const archive = await walkArchive(source);
const lines: string[] = []; lines.push(`comment=${JSON.stringify(archive.comment)}`);
for await (const record of archive) { lines.push(''); dumpRecord(record, lines); }
return lines.join('\n') + '\n';};
const hex = (n: number, width: number): string => `0x${n.toString(16).padStart(width, '0')}`;
const dumpRecord = (record: ParsedCentralRecord, lines: string[]): void => { lines.push(`entry filename=${JSON.stringify(record.filename)}`); lines.push(` size=${record.size}`); lines.push(` csize=${record.compressedSize}`); lines.push(` method=${record.compressionMethod}`); lines.push(` crc=${hex(record.crc32, 8)}`); lines.push(` mtime=${new Date(record.mtime).toISOString()}`); if (record.atime !== undefined) { lines.push(` atime=${new Date(record.atime).toISOString()}`); } if (record.ctime !== undefined) { lines.push(` ctime=${new Date(record.ctime).toISOString()}`); } lines.push(` mode=0o${record.mode.toString(8)}`); if (record.uid !== undefined) { lines.push(` uid=${record.uid}`); } if (record.gid !== undefined) { lines.push(` gid=${record.gid}`); } if (record.filename.endsWith('/') || (record.mode & 0o170000) === 0o040000) { lines.push(` dir=true`); } if ((record.mode & 0o170000) === 0o120000) { lines.push(` symlink=true`); } if (record.comment !== '') { lines.push(` comment=${JSON.stringify(record.comment)}`); } if (record.isEncrypted) { lines.push(` encrypted=true`); } if (record.aes !== undefined) { lines.push( ` aes=v${record.aes.version},${record.aes.strength},actualMethod=${record.aes.actualCompressionMethod}`, ); } if (record.extraField.length > 0) { const bytes = record.extraField; let s = ''; for (let idx = 0; idx < bytes.length; idx++) { s += bytes[idx].toString(16).padStart(2, '0'); } lines.push(` extra=${s}`); }};