Skip to content

fix(cli): the browser download unpacks on machines without an unzip command - #5433

Merged
miguel-heygen merged 3 commits into
mainfrom
fix/cli-zip-without-unzip
Oct 11, 2026
Merged

miguel-heygen merged 3 commits into
mainfrom
fix/cli-zip-without-unzip

Conversation

@miguel-heygen

@miguel-heygen miguel-heygen commented Oct 10, 2026 •

Copy link
Copy Markdown
Collaborator

What changes for the user

On a Linux machine without the unzip command (a fresh WSL Ubuntu, slim containers), hyperframes check and render no longer fail with Failed to download chrome-headless-shell: Extraction failed: no zip archiver is available. The managed browser download now unpacks without any system tool.

Root cause

The CLI downloads chrome-headless-shell through @puppeteer/browsers. On Linux and macOS it unzips by running the system unzip. When that is missing, its only fallback is the yauzl package, which @puppeteer/browsers declares as an optional peer dependency. The CLI never installed it, so with no unzip both paths failed and the download stopped.

Fix

yauzl is now a dependency of the CLI, so @puppeteer/browsers finds its fallback unzipper. No CLI code changes: the library already tries unzip first and yauzl second. yauzl is not bundled into the CLI tarball (it is only loaded by @puppeteer/browsers at runtime), so the CLI's package size budget does not change.

Also

yauzl is listed in the Fallow ignoreDependencies, next to debug: no CLI code imports it; @puppeteer/browsers loads it at runtime.

Verification

  • New unzipFallback.test.ts runs the real @puppeteer/browsers install() against a local server serving a chrome-headless-shell-shaped zip, with PATH pointing at an empty directory so no unzip is found. It checks the browser executable is unpacked. Skipped on Windows, where the library unzips with tar.exe by absolute path.
  • From a clean install without yauzl, the test fails with the reported error (Extraction failed: no zip archiver is available); with it, it passes on Linux.
  • The browser manager and fallback tests (60) pass 3 runs in a row; typecheck, oxlint, oxfmt and the comment ratchet are clean.

Known limits

  • On the yauzl path a corrupt download is not re-downloaded automatically: @puppeteer/browsers reports Extraction failed: <archive path> and drops the zip error when it combines provider failures, so the CLI cannot tell a corrupt archive from other unzip failures. Re-running the command downloads again. Upstream note: in @puppeteer/browsers 3.2.x, install() builds its All providers failed error from each provider error's message only; keeping each error as the combined error's cause would let callers detect a corrupt archive on this path.
  • The engine and producer packages, installed on their own, use full puppeteer's browser downloader, which still has no yauzl outside this repo. The published hyperframes CLI is covered.

Why this PR is small

It is a one-dependency fix with its regression tests; the lockfile change adds only yauzl and pend.

@github-actions

github-actions Bot commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Edit accuracy: accurate 2061 (base branch 2061), smooth 1533 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

@terencecho terencecho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Source-level review of 7053ada00cca2fdc2ada74db97e3596eab449c99. Effective merge-base is f2b81b241516531c09ee90da6472beda3d6b4d46; the API main-tip base is faa4913c9ef791733837eae4de1a579f2049b251. The reviewed four-file diff has Git/API path and blob parity and no removals.

Strengths: packages/cli/package.json:51 adds yauzl ^3.4.0 as a regular production dependency alongside @puppeteer/browsers, not a root/dev-only or optional dependency. The exact locked browsers 3.2.2 accepts that optional-peer range. packages/cli/tsup.config.ts:39–43 keeps the browser library external, leaving its dynamic yauzl import to runtime dependency resolution. The name-only publishing rewrite retains CLI dependencies (.github/workflows/publish.yml:165–179, independently source-audited); this is structurally supported for the published CLI, not only workspace hoisting. The lock delta adds only the CLI importer row, yauzl 3.4.0 and pend 1.2.0. The Fallow exception correctly documents the indirect import (.fallowrc.jsonc:429–430).

The actual manager download path calls the upstream headless-shell installer. In the inspected browsers 3.2.2 extractor, missing Unix unzip maps to ArchiverUnavailableError and reaches yauzl; other native extraction errors do not generally trigger that fallback. No manager production-code change remains in the net diff.

Nonblocking evidence limits: packages/cli/src/browser/unzipFallback.test.ts:18–48 calls the real upstream installer against a synthetic local ZIP/base URL with PATH hidden, rather than mocking extraction. Its assertion covers executable-path existence, not executable mode, browser launch, a real vendor archive or a packed clean consumer. Windows is deliberately skipped because its first extractor uses absolute tar.exe; that skip is not Windows runtime coverage. I source-read this test but did not run its server or installer.

The disclosed corrupt-ZIP retry limitation is corroborated: browsers 3.2.2 lib/install.js:110–123 aggregates provider messages without retaining the extraction cause, so the unchanged classifier (packages/cli/src/browser/manager.ts:724–754) cannot recognize that yauzl failure. Archive cleanup (lib/install.js:260–263) and staging cleanup (manager.ts:849–852) remain source-present for a subsequent fresh attempt. This is not presented as a newly introduced blocker. A CLI-only dependency also does not certify standalone engine/producer downloads.

Coverage / execution: I directly read the complete CLI manifest, test, Fallow config, tsup config, manager and exact existing browser-library manifest/extractor, plus the scoped diff/relevant lock entries. Two completed code passes traced packaging/publishing, real render/check consumers, platform/error routes and test setup. One pass literally read all four changed files; the other read three end-to-end and fully parsed/recursively compared the lockfile rather than manually reading every unchanged entry. Broader caller/source coverage and passing built-in-only inert metadata/lock checks are delegated evidence, not my test execution. Fully paginated history has no reviews or inline findings, only automated edit-accuracy telemetry. I did not perform a fresh dependency/packed-consumer install, extraction, browser/native/vendor execution, stock tests/build/pack, CI trigger, source change or direct merge; the two assigned direct code passes report the same operational limits. Author test counts are not my execution.

Verdict: APPROVE
Reasoning: no verified newly introduced correctness or packaging blocker survived the source and adversarial passes. The regular CLI dependency satisfies the actual external library's fallback contract and survives publishing; runtime extraction and clean-install success were not independently demonstrated.

— tai

@miguel-heygen
miguel-heygen added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 9e9b613 Oct 11, 2026
163 of 164 checks passed
@miguel-heygen
miguel-heygen deleted the fix/cli-zip-without-unzip branch October 11, 2026 02:26
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