feat(e2e): claude-code smoke with Zstd + tar.gz + archive verify#12
Merged
robertsLando merged 9 commits intomainfrom Apr 23, 2026
Merged
feat(e2e): claude-code smoke with Zstd + tar.gz + archive verify#12robertsLando merged 9 commits intomainfrom
robertsLando merged 9 commits intomainfrom
Conversation
- Add `Zstd` to the compress-node enum (core/inputs.ts), regenerate action.yml / packages/build/action.yml / docs/inputs.md, rebundle dist. Zstd requires Node.js >= 22.15 on the build host. - New `claude-code-smoke` e2e job: installs @anthropic-ai/claude-code from npm, builds via SEA mode with compress-node=Zstd + tar.gz + sha256 on ubuntu-x64/arm64, macos-arm64, windows-x64. Runs the binary with `claude --version`, then recomputes the sha256 against the sidecar and extracts the tarball to re-verify --version on the archived copy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- @anthropic-ai/claude-code >= 2.1.117 ships as a thin wrapper that delegates to a native binary via optionalDependencies; pkg can't bundle that. Pin 2.1.114, the last release whose `bin.claude` points at a real `cli.js`. - pkg 6.17 is the first yao-pkg/pkg to accept `--compress Zstd`; the action's default pkg-version (~6.16.0) rejected it with "Invalid compression algorithm Zstd". Override to ~6.18.0 for this job. - Resolve `pkg.bin.claude` explicitly (instead of Object.values()[0]) so a future per-OS alias can't steer us at the wrong entrypoint. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR extends the action’s compress-node input to support Zstd compression and adds a new real-world E2E smoke workflow job that builds and validates a SEA-packaged @anthropic-ai/claude-code binary across multiple OS/arch runners.
Changes:
- Add
Zstdtocompress-nodeinput parsing/types, plus regeneratedaction.yml,packages/build/action.yml, docs, and bundleddist/artifacts. - Add unit test coverage ensuring
parseInputsaccepts canonicalZstd(and still rejects lowercasezstd). - Add
claude-code-smokeE2E job that builds a SEA binary withcompress-node: Zstd+tar.gz, runs--version, then verifies archive integrity and checksum sidecar.
Reviewed changes
Copilot reviewed 6 out of 8 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
packages/core/src/inputs.ts |
Adds Zstd to input description, type union, and enum parsing. |
packages/core/test/unit/inputs.test.ts |
Adds unit test ensuring Zstd is accepted and validates case-sensitivity behavior. |
.github/workflows/e2e.yml |
Adds claude-code-smoke job to build/run/verify an archived SEA binary across OS/arch matrix. |
action.yml |
Regenerated input docs to include Zstd in compress-node description. |
packages/build/action.yml |
Regenerated input docs to include Zstd in compress-node description. |
docs/inputs.md |
Regenerated inputs documentation for compress-node: Zstd. |
packages/build/dist/index.mjs |
Rebundled dist reflecting new compress-node enum value and description. |
packages/windows-metadata/dist/index.mjs |
Rebundled dist reflecting updated compress-node description. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- 2.1.113 already switched @anthropic-ai/claude-code from a pure-JS cli.js entrypoint to a native-binary wrapper (bin/claude.exe). My earlier pin of 2.1.114 landed inside the new layout, so pkg was compiling a shell-script placeholder that errors at runtime. 2.1.112 is the last pure-JS release — verified locally with SEA+Zstd: builds in ~20s, binary runs and prints "2.1.112 (Claude Code)" on --version. - Previous `--version` step captured output via `out=$(... 2>&1)` under `set -e`, which aborted before echoing when the binary exited non- zero (as it did on every runner). Suspend `set -e` around the capture so the logs actually show what the binary printed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced by the new claude-code-smoke e2e: - macOS: the macos-latest runner's bsdtar rejects `--mtime` outright with "Option --mtime is not supported", even though upstream libarchive accepts it. The action was always passing --mtime; tiny- cjs never hit this because its macOS matrix entry uses zip. Keep --mtime on GNU tar (linux) only; rely on the utimes pre-pin for bsdtar paths (archive() is single-file, so the header inherits that mtime). Test updated to assert --mtime presence only on linux. - Windows: git-bash's `sha256sum` escapes the output line with a leading backslash when the filename contains backslashes (Windows paths), per GNU coreutils convention. The e2e's verify step was comparing `\<digest>` against the sidecar's clean hex. Strip the leading backslash via sed before parsing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GNU tar from git-bash parses `D:\a\...` as a remote host:path target
(colon-separated rsh syntax), producing:
tar (child): Cannot connect to D: resolve failed
Convert any Windows drive-letter path to the POSIX form (`/d/a/...`)
via cygpath before invoking tar. No-op on Linux/macOS — falls back to
echo when cygpath isn't on PATH. --version + sha256 sidecar check
already passed on Windows in the previous run; this unblocks the
tar-round-trip tail.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Review follow-ups on #12: - Guard the pin: assert the resolved bin entry is a JS file (.js/.mjs/ .cjs) before patching it into the fixture package.json. If the @anthropic-ai/claude-code pin ever drifts past 2.1.112 into the native-binary wrapper layout (bin → bin/claude.exe shell-script placeholder), fail loudly rather than bundling garbage. - Replace the ad-hoc `${fix//\\//}` backslash-strip with the same cygpath helper used in the verify step, so the fixture path emitted to GITHUB_OUTPUT is always POSIX-form on Windows git-bash. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…mpatible Reverting the $fix cygpath conversion from 3fca6c7. cygpath -u emits POSIX form (/d/a/...), which GNU tar in git-bash handles correctly but Node's fs on Windows misparses as drive-relative, producing paths like D:\d\a\_temp\... and failing with ENOENT. The composite's build step is Node-based, not shell-tar-based, so it wants the drive-letter- plus-forward-slash form (D:/a/...) that ${fix//\\//} produces. cygpath stays in the verify step where tar is the consumer — different tool, different path convention. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Surfaced by matrix e2e debugging: the orchestrator emitted every pkg argv twice (once as "[pkg-action] pkg …" in main.ts, once as "[pkg-action] Invoking: …" from the runner) and was silent through the finalize stages — no signal for when archive started, when the checksum was computed, or which shasum files were written. - Drop the main.ts pre-log; runPkg's "Invoking:" already carries the command path, and GH Actions renders the raw `[command]` line too, so three copies was overkill. - Log pkg wall time after runPkg completes. - Group the per-target finalize loop under a `logger.startGroup` so each target collapses into its own fold in the GH Actions UI — matters for matrix runs where 4+ targets interleave. - Add explicit archive/checksum progress lines with sizes + timings (`archive → …`, `archived … (17.8 MB, 4.2s)`, `sha256 <digest> <file>`) so any downstream verification failure trivially diffs against the build log. - Log each SHASUMS file as it's written, with entry count. - Include overall wall time in the final "done" line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The pkg CLI emits ~15–30 lines per build (Walking dependencies, node download/extract, SEA asset generation, blob injection, strip warnings) and the GH-Actions exec shim adds `[command]` echo on top. On a multi- target run all of that interleaved with finalize output is hard to skim. Wrap runPkg in startGroup/endGroup with a header that names the target list, so reviewers see a single collapsible "pkg build (targets=…)" block followed by the one-line wall-time summary. Same pattern we already use for the per-target finalize blocks. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
robertsLando
added a commit
that referenced
this pull request
Apr 23, 2026
Main (PR #12) wired the claude-code-smoke job through the action using `mode: sea` and `compress-node: Zstd` inputs. Those inputs no longer exist on this branch, so move both values into the package.json#pkg field that the fixture-prep step already injects. No behavior change on main; on this branch the e2e now exercises the pkg-config path the slim input surface forces users onto.
robertsLando
added a commit
that referenced
this pull request
Apr 24, 2026
…13) * refactor!: drop pkg-CLI mirror inputs, own only action-layer surface Pkg-specific knobs (mode, node-version, compress-node, fallback-to-source, public, public-packages, options, no-bytecode, no-dict, debug, extra-args) are removed from the action input surface. Users express them in their pkg config (.pkgrc, pkg.config.{js,ts,json}, or package.json pkg field) instead. The action keeps only action-layer concerns that do not exist in pkg config: config path, entry, targets, pkg-version/pkg-path, plus the archive, signing, windows-metadata, and performance groups. Motivation: mirroring pkg's CLI forced the action to grow a new input each time pkg added a flag, and to maintain back-compat on every pkg release. By letting pkg own its own schema we decouple the action from pkg's evolution. Docs updated: README gets a new "pkg configuration" section with a migration example; architecture.md §3/§6 note the scoped surface; STATUS.yaml records the dropped-inputs list and migration path. BREAKING CHANGE: the listed inputs are no longer accepted. Setting them now triggers the unknown-input warning and the value is ignored. Migrate into your pkg config file. * review: link STATUS from architecture, cover pkg-version/pkg-path in tests Address PR-review suggestions: - architecture.md §6 no longer duplicates the dropped-input list; points readers at STATUS.yaml#input-surface-slim as the single source. - inputs.test.ts: extend the defaults test with `pkgPath` and add a positive round-trip test for `pkg-version` + `pkg-path`. These remain live inputs and deserve the same explicit coverage the rest of BuildInputs has. 223/223 unit tests, lint clean. * feat: add config-inline input for file-less pkg config New optional input `config-inline` accepts a JSON string carrying the pkg config directly in the workflow. The orchestrator writes it to `${invocationDir}/pkg-config.inline.json` and forwards that path to pkg via `--config`. Mutually exclusive with `config`; the pair is validated at parse time alongside a JSON-object syntax check so a typo fails before pkg runs. Motivation: users with a trivial pkg config (entry + a couple of knobs) no longer need to commit a separate .pkgrc.json just to customize the build. Keeps the "pkg owns its schema" property — users still write pkg config keys (camelCase), we just let them write them inline. Not masked as a secret — README adds a note warning users against embedding secrets in the value. Docs: README gains an "Inline config" subsection with an example; action.yml + docs/inputs.md regenerated. Tests: four new unit tests cover the happy path, mutual exclusion, invalid JSON, and non-object JSON (string/array/null). 227/227 pass. * review(copilot): use correct camelCase pkg-config keys in warnings/docs Three nits from the Copilot review on PR #13: - targets.ts cross-compile warnings referenced `fallback-to-source` (kebab, like the retired CLI flag); pkg config uses `fallbackToSource` (camelCase). Reword both messages to point at the actual config key. - README "pkg configuration" example was fenced as jsonc with a `//` comment and trailing comma but labeled `.pkgrc.json` — .pkgrc.json is strict JSON, so a copy/paste would fail. Move the filename into the prose, fence as json, drop the comment + trailing comma. Unit tests + lint green. Matrix bundle rebuilt to pick up the new warning strings. * e2e(claude-code): move pkg-layer knobs from action inputs to pkg config Main (PR #12) wired the claude-code-smoke job through the action using `mode: sea` and `compress-node: Zstd` inputs. Those inputs no longer exist on this branch, so move both values into the package.json#pkg field that the fixture-prep step already injects. No behavior change on main; on this branch the e2e now exercises the pkg-config path the slim input surface forces users onto. * e2e(claude-code): use sea:true config, acknowledge CLI-only flag gap The merge from main brought in the claude-code-smoke job, which previously drove pkg via `mode: sea` + `compress-node: Zstd` action inputs. On this branch those inputs are dropped. The prior merge-conflict resolution moved both into `package.json#pkg` under the assumption that pkg config would pick them up. That assumption was wrong: - `mode: 'sea'` is not a pkg config key — the correct key is `sea: true`. (This was a user-visible bug; pkg silently ignored `mode` and ran standard-mode, which OOM'd trying to bytecode-compile claude-code's ESM bundle on macos-arm64.) - `compressNode` / `compress` is not a pkg config key at all. `--compress` is CLI-only today on @yao-pkg/pkg. No config equivalent exists. Fix the e2e: - Write `sea: true` into package.json#pkg so SEA is actually enabled. - Drop the Zstd request — there is no way to express it without the dropped CLI input. The job now exercises SEA + tar.gz; the Zstd branch is intentionally deferred until upstream lands config support. - Rename + retone the job's comment block accordingly. Also add an upstream-dependency note to STATUS.yaml and a draft issue body at docs/upstream-pkg-config-issue.md. The draft asks yao-pkg/pkg to accept the currently-CLI-only build flags (compress, fallbackToSource, public, publicPackages, options, noBytecode, noDict, debug, signature) in the config file — that's the piece that makes this PR's scope defensible for the full input set, not just for SEA. 227 unit tests pass; lint + prettier clean. * status: link upstream pkg-config tracking issue (yao-pkg/pkg#262) Issue is open upstream asking @yao-pkg/pkg to accept the currently-CLI-only build flags (compress, fallbackToSource, public, publicPackages, options, noBytecode, noDict, debug, signature) in the config file — the piece that makes this PR's drop-the-CLI-mirror direction fully viable. Replace the local draft with the issue URL in STATUS.yaml#upstream-dependency. * e2e(claude-code): enable Zstd via pkg config; bump pkg → ~6.19.0 pkg v6.19.0 (yao-pkg/pkg#263, closes #262) landed config support for the CLI-only build flags, and ships the SEA-mode detector fix (yao-pkg/pkg#268) that caused the prior OOM at bytecode-compile time. - claude-code-smoke: add `compress: 'Zstd'` alongside `sea: true` in package.json#pkg; bump pkg-version input ~6.18.0 → ~6.19.0. - Default `pkg-version` input bumped ~6.16.0 → ~6.19.0 — the whole config-as-source-of-truth direction of this branch requires it. - Regenerated action.yml / packages/build/action.yml / docs/inputs.md. - STATUS.yaml: upstream-dependency RESOLVED; Zstd gap dropped; e2e-status lists claude-code-smoke; ci-status bumped to 227 tests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: rebuild dist/ bundles after pkg-version bump Rebuild packages/{build,windows-metadata}/dist/index.mjs so the committed bundles match the ~6.19.0 default written into packages/core/src/inputs.ts. Drift gate caught it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * review: correct pkg-config keys, mask config-inline, extract materialize helper Address four items from the self-review on PR #13: - README examples used `mode: "sea"` / `compressNode: "Brotli"` — neither exists in @yao-pkg/pkg's config schema. The authoritative FLAG_SPECS in lib/config.ts (and the docs-site configuration reference) show `sea: true` and `compress: "..."`. Fixed both examples so a copy-paste works. - `config-inline` carried user-authored JSON but was not masked. Mark the spec with `secret: true` so parseInputs registers the raw value via core.setSecret — exact-match redaction in logs costs nothing given pkg doesn't echo the config blob. README warning softened accordingly (still flags the on-disk temp file). - Extract the config-inline "bytes to disk" step out of the orchestrator into `materializePkgConfigInline` in fs-utils, alongside `PKG_CONFIG_INLINE_FILENAME`. Four new unit tests cover pass-through, both-undefined, inline-writes-and-returns-path, and inline-wins-over-config. Orchestrator is now a two-liner that just logs the resulting path. - BASE_BUILD fixture in pkg-runner.test.ts still pinned pkgVersion to the old `~6.16.0` default; bump to `~6.19.0` to match parseInputs. Regenerated action.yml + docs/inputs.md (secret flag on config-inline doesn't affect the YAML shape but the description changed). Rebuilt packages/{build,windows-metadata}/dist/index.mjs. Tests 231/231 pass (was 227/227), typecheck + lint + prettier green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Zstdto thecompress-nodeinput enum inpackages/core/src/inputs.ts(+ regeneratedaction.yml,packages/build/action.yml,docs/inputs.mdviayarn gen, rebundled dist). Zstd requires Node.js ≥ 22.15 on the build host —.node-versionalready pins 22.22.claude-code-smokee2e job that pulls@anthropic-ai/claude-codefrom npm, builds a native binary per runner with SEA +compress-node: Zstd+compress: tar.gz+ sha256 checksum, then runsclaude --versionon the binary and on the tar-extracted copy..tar.gzextension, extracts the tarball, and re-runs--versionon the extracted binary.Test plan
yarn test— 225/225 pass, including newparseInputs accepts Zstd compress-nodecaseyarn lint— cleanyarn gen+yarn build— committed outputs match source (codegen-drift job will re-verify on CI)claude-code-smokejob green on all 4 runners (ubuntu x64, ubuntu arm64, macos arm64, windows x64)🤖 Generated with Claude Code