runlocally

runlocally engineering notes

Remove from ZIP

How Remove from ZIP is built

By Geppetto · · Open Remove from ZIP →

These are the engineering notes for Remove from ZIP: the technologies it is built on, what each one is, and how it is used in the tool.

Tech used

Why a ZIP entry can’t be deleted in place

A ZIP has no in-place delete. Each entry’s compressed data and the central-directory index (from the Unzip notes) reference each other by byte offset, so dropping an entry means rewriting the file: read the source, and write a new archive containing only the entries you keep. That read-list, then re-pack shape is the whole tool.

Listing with @zip.js/zip.js

The archive is enumerated with the @zip.js/zip.js ZipReader (introduced in the Create ZIP notes). getEntries() reads only the central directory — no decompression — so the UI can show every entry’s name, size, timestamp, and encrypted / filenameUTF8 flags before anything is rewritten.

Re-packing kept entries with ZipWriter

Removal is done by a buildTrimmedZip pass: it opens a ZipWriter(new BlobWriter('application/zip'), { useUnicodeFileNames: true }) and, for each source entry whose name a keep() predicate accepts, streams the data across — entry.getData(new BlobWriter()) out of the source, writer.add(name, new BlobReader(data)) into the destination. Directory entries are re-added as directories. Each entry keeps its lastModDate, and the UTF-8 filename flag (bit 11, from the Create ZIP notes) is set, so folder structure, per-entry timestamps, and non-ASCII names all carry over unchanged. Entries that fail keep() are never written.

Folder selection that cascades

Removal is driven by name, but the selection UI works on the folder tree: unchecking a folder cascades to its children, so deselecting docs/ drops docs/readme.txt, docs/img/logo.png, and the rest in one action. The engine only ever sees the resulting set of kept names.

Shell

Same static Astro + Preact island and Service-Worker PWA shell as the other tools (see the HEIC notes); zip.js runs deflate in the Web Workers described in the Create ZIP notes.

Implementation & operational notes

Encrypted archives are refused up front. A kept entry that is encrypted can’t be re-packed without its password, so rather than write a broken output the pass throws an EncryptedEntryError the moment it reaches one. Removing entries from a password-protected archive is out of scope here — Unlock ZIP strips the password first.

Re-packed, not copied through. Because the output is a fresh archive, kept entries are re-compressed on the way in rather than byte-copied. The contents are identical; the compressed representation may differ from the source.

Malformed archives throw a clear error. If getEntries() can’t parse the central directory, the failure is caught and reported as an invalid ZIP instead of crashing the island.

Try it / source