Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: yao-pkg/pkg
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v6.16.0
Choose a base ref
...
head repository: yao-pkg/pkg
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v6.17.0
Choose a head ref
  • 3 commits
  • 25 files changed
  • 3 contributors

Commits on Apr 18, 2026

  1. docs: add in-depth comparison vs Bun and Deno (#249)

    Adds docs-site/guide/vs-bun-deno.md covering native addon support,
    bundle format, cross-compile, bytecode, binary size, plus a real
    @anthropic-ai/claude-code bundling case study across all three tools.
    Includes a self-critical verdict section to prevent the page from
    reading as marketing.
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    robertsLando and claude authored Apr 18, 2026
    Configuration menu
    Copy the full SHA
    0cf75a8 View commit details
    Browse the repository at this point in the history
  2. feat(sea): add per-file compression to SEA archive (closes #250) (#251)

    * feat(sea): add per-file compression to SEA archive (--compress Brotli/GZip/Zstd)
    
    Extends the existing --compress flag to enhanced SEA mode, matching what
    Standard mode has had for years.  Each file in the SEA archive is compressed
    independently with gzip / brotli / zstd and decompressed lazily at first
    fs.readFileSync() / require(), so the cold-start cost is proportional to the
    files actually read — not the full archive.
    
    Measured on claude-code@1.0.100 (node22-linux-x64): 194 MB → 152 MB with
    --compress Zstd (41 MB saved, no measurable startup regression), and
    194 MB → 147 MB with --compress Brotli (~3 min build).  Closes most of the
    size gap between SEA-mode binaries and competitors like Bun.
    
    - lib/compress_type.ts: add Zstd = 3
    - lib/index.ts: accept "Zstd"/"zs" at --compress; refuse --compress for
      simple SEA mode (no walker → nothing to compress)
    - lib/producer.ts: wire Zstd compressor into Standard-mode producer too,
      so the flag is consistent across modes
    - lib/sea-assets.ts: compress each entry during archive write; record
      manifest.compression = numeric CompressType; keep stats[key].size as
      the uncompressed length so fs.statSync() reports the real file size
    - lib/sea.ts, lib/types.ts: thread doCompress through seaEnhanced()
    - prelude/bootstrap.js: add Zstd branch to payloadFile/payloadFileSync
    - prelude/sea-vfs-setup.js: pick a decompressor once at SEAProvider
      construction; decompress on first read, cache the result in _fileCache
    - test/test-93-sea-compress: build the same fixture with None/GZip/
      Brotli/Zstd (Zstd gated on zlib.zstdCompressSync availability) and
      assert every packaged binary prints identical output
    - docs: update compression.md, sea-mode.md, sea-vs-standard.md,
      ARCHITECTURE.md, and vs-bun-deno.md with the new feature and the
      re-measured claude-code numbers
    
    Closes #250
    
    * docs(vs-bun-deno): note trimmed-Node build as a path to further shrink SEA binaries
    
    Binary-size gap to Bun isn't all archive — ~30 MB of the remaining delta
    is full-ICU in the stock Node binary pkg-fetch ships. Spell out that
    ./configure --without-intl --without-inspector --without-npm --without-corepack
    --fully-static (a pkg-fetch concern, not a pkg one) would close most of
    what's left.
    
    * docs(vs-bun-deno): update startup times with fresh first-run measurements
    
    Re-ran all four pkg --sea variants on the same host with consistent
    methodology (first-run, cold ~/.cache/pkg, /usr/bin/time -f %e for
    ./binary --version).  Bun/Deno rows are unchanged from the morning run.
    
    - None:   979 → 610 ms
    - GZip:         590 ms (new)
    - Zstd:         560 ms (new)
    - Brotli:       590 ms (new)
    
    Compression adds ≤0 ms vs uncompressed on this workload because
    claude-code's --version path only touches a handful of files, so the
    sync zlib/zstd decode cost is dwarfed by the startup savings from the
    smaller archive being memory-mapped.
    
    * docs(vs-bun-deno): re-measure all three runtimes side by side
    
    Ran pkg --sea (4 codecs), bun --compile, bun --compile --bytecode, and
    deno compile on the same host with matching methodology (fresh fixture,
    cold ~/.cache/pkg, /usr/bin/time -f %e for ./bin --version first run):
    
      Bun                510 ms (108 MB)
      Bun --bytecode     530 ms (190 MB)
      pkg --sea          560 ms (194 MB)
      pkg --sea --zstd   570 ms (152 MB)
      pkg --sea --gzip   580 ms (154 MB)
      pkg --sea --brotli 590 ms (147 MB)
      Deno               740 ms (183 MB)
    
    The previous numbers (797 Bun / 1256 Deno / 979 pkg) were measured on a
    different run/method, not apples-to-apples.  These six are.  Bun is still
    fastest and smallest; pkg SEA with compression is within ~60 ms of Bun
    while shipping stock Node.js; Deno is the slowest starter on this
    workload.  Narrative paragraphs updated to match.
    
    * refactor(sea): harden compression paths, unify codec picker, restore streaming
    
    Security / correctness:
    - prelude/sea-vfs-setup.js: cap per-file decompression via maxOutputLength and
      assert decompressed length matches manifest stats.size; use Number.isInteger
      for offset/length/size bounds (rejects NaN and non-integer floats that the
      prior typeof-number guard let through).
    - lib/sea-assets.ts: synthesize a stats entry for records that had STORE_CONTENT
      but no STORE_STAT, so every compressed stripe has an authoritative size for
      the runtime to cross-check against. Make resolveCompressor exhaustive — a new
      CompressType without a matching case now fails the build instead of shipping
      an archive that claims compression but contains raw bytes.
    
    Performance:
    - lib/sea-assets.ts: restore createReadStream path for unmodified disk-resident
      files; the prior always-readFileAsync forced peak RSS to grow with total
      asset size even when compression was disabled.
    - Resolve the decompressor/compressor exactly once per path: at module load in
      prelude/bootstrap.js, at SEAProvider construction in sea-vfs-setup.js, before
      the stripe loop in sea-assets.ts, and before Multistream in producer.ts. Fails
      fast when the runtime is missing a Zstd API instead of mid-stripe. Skips
      _fileCache entirely for uncompressed archives so archive subarrays aren't
      pinned unnecessarily.
    
    DRY / surface:
    - prelude/bootstrap-shared.js: single source of truth for COMPRESS_* constants,
      pickDecompressorSync/Async, and a context-aware zstdMissingError (build-host
      vs end-user remediation). Classical bootstrap and SEA VFS both consume it;
      the local zlib require in bootstrap.js is gone.
    - lib/compress_type.ts: getZstdCompressSync / getZstdCompressStream replace the
      duplicated 'zlib as unknown as { ... }' casts in producer.ts and sea-assets.ts
      and emit a single build-error string (now also includes process.version).
    - lib/help.ts: add Zstd to the --compress description and examples.
    - lib/index.ts: the 'invalid compression algorithm' error now lists the real
      accepted tokens (None/none, Brotli/br, GZip/gz/gzip, Zstd/zs/zstd); the
      compression banner goes through log.info instead of console.log.
    
    Tests:
    - test/test-93-sea-compress: assert each compressed binary is at least 50 KB
      smaller than the None build so a silent fallback to uncompressed fails the
      test (the prior byte-equality check couldn't detect that regression).
    - test/test-80-compression: cover --compress Zstd in the classical pipeline
      (lib/producer.ts and prelude/bootstrap.js zstd branches) when zlib.createZstdCompress
      is available on the build host.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * refactor(sea): prune dead code and optimize VFS hot paths
    
    Dead code:
    - Drop SeaAssetsResult.entryIsESM — seaEnhanced destructures only
      { assets, manifestPath } and the value is read via manifest.entryIsESM at
      runtime, so the return-shape field was carrying a stale copy.
    - Drop the 'syscall' parameter from SEAProvider._resolveSymlink: all five
      callers pass only the path, and ELOOP is rare enough that hardcoding
      err.syscall = 'stat' is fine.
    - Drop the 'context' parameter from pickDecompressorSync/Async and merge
      zstdMissingError into a single runtime-wording string: only 'runtime' was
      ever passed (build-side Zstd errors go through lib/compress_type.ts's own
      zstdBuildError).
    - Drop unused COMPRESS_GZIP/BROTLI/ZSTD exports from bootstrap-shared —
      callers now go through pickDecompressor and only COMPRESS_NONE is read
      directly by sea-vfs-setup.
    - Remove the redundant process.argv[1] = entrypoint assignment in
      sea-bootstrap.js; sea-bootstrap-core.js already sets it to the same value.
    - Inline the single-use ZSTD_MISSING_BUILD_REMEDIATION constant.
    
    Hot paths (~30K lookups per startup on large projects):
    - toManifestKey: skip the backslash→slash regex on POSIX hosts where paths
      already match the manifest shape; keep the replace on win32 where it's
      mandatory.
    - _resolveSymlink: short-circuit before entering the MAX_SYMLINK_DEPTH loop
      when the path isn't a symlink key (the common case).
    
    Comments:
    - sea-assets.ts: rename the Zstd-resolution rationale to point at
      zstdBuildError, which is where the wording now lives.
    - bootstrap-shared.js: tighten the COMPRESS_NONE comment now that only it
      is exported.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * test(sea-compress): fix Windows CI failure from CRLF in payload.txt
    
    Root cause: payload.txt starts with 0x0a; on a Windows checkout git's
    autocrlf converted it to 0x0d 0x0a, so PAYLOAD.slice(0, 32) contained a
    leading \r\n that survived in `expected` but got stripped from `actual`
    via the existing replace(/\r\n/g, '\n'), causing the equality assertion
    to fail across every Windows job.
    
    Fix:
    - Add .gitattributes so payload.txt is checked out LF on every platform;
      the SEA archive bytes are now deterministic cross-platform, which also
      keeps the compressed-size assertion stable.
    - Normalize CRLF in `expected` as defense-in-depth so an existing Windows
      clone (cloned before .gitattributes landed) still passes the test.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * fix(sea): drop redundant post-decompress size assert
    
    Per review feedback on PR #251: an attacker who can rewrite the SEA blob
    can also rewrite `manifest.stats[p].size` to match the payload they ship,
    so the post-decompression `buf.length === expected` check does not survive
    a consistent tamper — it only fires on accidental corruption, which is a
    narrow and unlikely case.
    
    Keep `maxOutputLength`: it bounds the zlib allocation up front so a blob
    with a plausible-but-inflated manifest can't request unbounded memory
    before we discover the size mismatch. That bound is cheap and standard
    Node zlib hygiene. Also keep the `stats.size` validation: `maxOutputLength`
    requires a finite integer, so NaN / negative / missing values must still
    be rejected before reaching zlib.
    
    Tightened the comment to reflect the actual threat model (bounded
    allocation vs. tamper detection) instead of the earlier bomb-defense
    framing.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    robertsLando and claude authored Apr 18, 2026
    Configuration menu
    Copy the full SHA
    fdf8046 View commit details
    Browse the repository at this point in the history
  3. Release 6.17.0

    robertsLando committed Apr 18, 2026
    Configuration menu
    Copy the full SHA
    eca804e View commit details
    Browse the repository at this point in the history
Loading