Skip to content

archiver-web: lazy-load libarchive.js; split api2ai into its own package - #72

Merged
vtempest merged 5 commits into
masterfrom
claude/loving-fermat-fr3ptk
Sep 26, 2026
Merged

vtempest merged 5 commits into
masterfrom
claude/loving-fermat-fr3ptk

Conversation

@vtempest

@vtempest vtempest commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

archiver-web

  • archiver-web now supports TAR, TAR.GZ, TAR.BZ2, TAR.XZ, 7z and RAR, in addition to ZIP and GZIP.
  • ZIP/GZIP still go through JSZip/fflate with no WASM. Every other format is handled by a lazily-loaded libarchive.js (WebAssembly) backend — only fetched (local install, or CDN as a fallback) the first time one of those formats is actually used.
  • extract()/compress() pick the right backend via a new detectArchiveKind() (filename extension), with magic-byte sniffing as a fallback when no filename is given. Both gained optional filename/format options; the CLI (bin-extract) now passes the input filename through automatically.
  • Added safeRelativePath() and applied it across every extraction backend (ZIP, ZIP streaming, and the new libarchive.js path) to reject zip-slip / path-traversal entries (../.., absolute paths) — this package's own .claude/CLAUDE.md calls that out as its main safety invariant, and it wasn't previously enforced anywhere.
  • Worked around two real bugs in libarchive.js@2.0.2 (reproduced directly against the library, not this wrapper) rather than silently mishandling them:
    • It can't write an uncompressed archive at all ("written bytes don't match file size"). A plain .tar is written internally as .tar.gz and gunzipped with fflate to recover raw tar bytes.
    • Its reader can't decode a standalone BZIP2/XZ stream outside a TAR, and it can't write a spec-compliant .7z container. compress() now throws a clear error for .7z output, and extract() throws a clear error for standalone .bz2/.xz, instead of returning an unusable/empty result.
  • Updated README.md/readme.md, packages/README.md, and the package's .claude/CLAUDE.md to document the new format matrix and these limitations.

api2ai

  • The api2ai OpenAPI→MCP-server generator lived at apps/api2ai-mcp-generator/lib/api2ai/generate-mcp-use-server.js, but its bin/files/package name ("api2ai") belonged to the whole api2ai.com Next.js dashboard app's package.json — publishing that app's ~150 dependencies just to ship a zero-dependency CLI script was never actually going to work as documented.
  • Moved the generator to packages/api2ai as its own small package (name api2ai, bin api2ai), so npx api2ai <spec> [output] now works the way both READMEs and the docs site already described.
  • Renamed the app's own package to api2ai-mcp-generator to free up the name and avoid an npm-workspaces name collision.
  • Fixed the generator's isMainModule CLI-entrypoint check, which used a fragile path.includes('api2ai') heuristic, to correctly resolve the npm/npx bin symlink instead.
  • Updated the app's README and docs page, which still referenced the old in-app file path.

Test plan

archiver-web

  • tsc --noEmit passes
  • vite build succeeds, with jszip/fflate/libarchive.js (incl. its Node subpath) confirmed externalized rather than bundled
  • Manual round-trip tests (compress → extract) for .zip, .tar, .tar.gz, .tar.bz2, .tar.xz, standalone .gz
  • Verified the zip-slip guard rejects a crafted ../../evil.txt entry
  • Verified .rar/.7z creation and standalone .bz2/.xz extraction throw clear errors instead of failing silently
  • End-to-end CLI test: bin-compress → .tar.gz, bin-extract → recovers original files
  • No automated test suite exists for this package yet (bun test runs tsx test.ts, but no test.ts file is present) — all verification above was manual

api2ai

  • npm install at the repo root succeeds with no workspace name collision
  • node_modules/.bin/api2ai --help resolves through the real bin symlink and prints usage
  • Ran a full generation through the symlinked bin against a small OpenAPI spec — confirms the isMainModule fix works through the actual npm/npx bin symlink, not just direct node path/to/file.js execution
  • Verified programmatic import { generateMcpServer, extractTools, loadOpenApiSpec } from "api2ai" resolves and exports the expected functions

🤖 Generated with Claude Code

https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi

…d rar

ZIP and GZIP still go through JSZip/fflate with no WASM. Every other
container/compression format is handled by a WebAssembly libarchive.js
backend that's only fetched (local install or CDN) the first time one
of those formats is actually used, via extract()/compress() dispatching
on detectArchiveKind() (filename extension, or magic-byte sniffing when
no filename is given).

Also hardens extraction against zip-slip/path-traversal across all
backends (safeRelativePath), and works around two real libarchive.js
2.0.2 bugs: it can't write an uncompressed archive, and it can't
reliably round-trip standalone bzip2/xz or write a valid .7z container
(surfaced as clear errors instead of silent data loss).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi
@vercel

vercel Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
grab-url Error Error Sep 26, 2026 9:18am UTC

@codecov

codecov Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 64.17445% with 115 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
packages/archiver-web/src/index.ts 64.37% 114 Missing ⚠️
packages/archiver-web/src/bin-extract.ts 0.00% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

The `api2ai` CLI generator lived at
apps/api2ai-mcp-generator/lib/api2ai/generate-mcp-use-server.js, but its
own package.json (name "api2ai") belonged to the whole api2ai.com Next.js
dashboard app — publishing that app's ~150 dependencies just to ship a
zero-dependency CLI script was never going to work as documented.

Move the generator to packages/api2ai as a small, dedicated package
(name "api2ai", bin "api2ai") so `npx api2ai <spec> [output]` works the
way both READMEs and the docs site already described. Renamed the app's
own package to api2ai-mcp-generator to free up the name and avoid an
npm-workspaces name collision, and fixed the generator's `isMainModule`
CLI-entrypoint check to resolve the npm/npx bin symlink correctly
instead of a fragile path.includes('api2ai') heuristic.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi
Finishes the previous commit's package split: removes the now-moved
apps/api2ai-mcp-generator/lib/api2ai/generate-mcp-use-server.js, and
updates the app's README/docs and package.json (which still referenced
the old in-app file path and its own now-freed "api2ai" package name).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi
@vtempest vtempest changed the title archiver-web: lazy-load libarchive.js for tar, bzip2, xz, 7z and rar archiver-web: lazy-load libarchive.js; split api2ai into its own package Sep 26, 2026
CI's "packages"/"test" jobs failed: grab-api.js's and grab-url's "full"
builds trace into content-processors.ts's lazy import("archiver-web"),
and from there into archiver-web's own dynamic import of
libarchive.js/dist/libarchive-node.mjs. Neither config externalized
that specifier, so Rollup bundled it and choked on its bare
`worker_threads`/`url` imports getting browser-stubbed — the same
failure archiver-web's own build hit before its vite.config.ts learned
to externalize libarchive.js.

Apply the same fix here: treat libarchive.js like jszip already is
(resolved at runtime by archiver-web, never bundled into these
packages either). Verified by rebuilding grab-url, grab-api.js,
grab-url-cli, archiver-web and api2client, and running the full
test:coverage suite (368 tests, including packaging.test.ts) locally.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi
…guard

Adds tests for detectArchiveKind(), round-trips for tar/tar.gz/tar.bz2/
tar.xz/standalone-gzip, and the error paths for unsupported creation
(.rar, .7z) and unsupported standalone-.bz2/.xz extraction — addresses
Codecov's patch-coverage flag on PR #72.

The zip-slip test needed a second look: a zip built with an entry named
"../../evil.txt" doesn't actually carry that path through JSZip's own
loadAsync() — JSZip collapses it to "evil.txt" while parsing the zip,
before archiver-web's own safeRelativePath() ever sees it, so asserting
the entry gets dropped was asserting something that isn't true (and
isn't what protects anyone here). Added a second test that hand-builds
a raw USTAR tar buffer with the same traversal name, bypassing any
writer-side sanitization, to exercise safeRelativePath() on the one
path where it's actually load-bearing: libarchive.js's tar reader does
not sanitize entry paths on its own.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014rhu5fjwSki5rfJMg1DnHi

Copy link
Copy Markdown
Collaborator Author

The Vercel deployment status on this PR is failing/erroring on every push, but this isn't this PR's doing: it already errored on the very first commit here (e9ee628), which only touched packages/archiver-web and never went near the Next.js app Vercel builds. It's not one of this repo's own required checks either (packages, test, native-app-wrapper are the GitHub Actions jobs that gate mergeability here). I don't have access to the Vercel project's build logs or settings to root-cause it further from this repo, so I'm treating it as a pre-existing, out-of-band deployment issue rather than something to fix in this PR.


Generated by Claude Code

@vtempest
vtempest merged commit ac8392b into master Sep 26, 2026
5 of 6 checks passed
@vtempest
vtempest deleted the claude/loving-fermat-fr3ptk branch September 26, 2026 22:30

This branch had an error being deployed

1 failed deployment
Preview — 6fc08f72 Deployed Sep 26, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants