feat(npm): per-platform binary distribution with OIDC release pipeline - #10
Open
younna-ai-opencode wants to merge 10 commits into
Open
feat(npm): per-platform binary distribution with OIDC release pipeline#10younna-ai-opencode wants to merge 10 commits into
younna-ai-opencode wants to merge 10 commits into
Conversation
added 10 commits
August 17, 2026 03:36
- targets.json: five platform targets (KD1/KTD2) - generate-platform-manifest.ts: emit per-platform package.json (--dry-run) - sync-root-version.ts: inject version + five optionalDeps pins at publish - check-matrix.ts: identity rules, product set, cross-file workflow agreement - launcher manifest carries no optionalDeps pre-publish (pnpm cannot lock unresolvable optional deps; pins injected at release time)
- matrix job per target: build, gate (check-matrix + smoke), sha256 sidecar,
tar.gz upload, platform publish with trusted publishing
- publish-npm-main: tag gates (semver, reachability), registry re-verify
(version/os/cpu/libc vs table), binary sha cross-check, root publish
- upload-gh-release-assets: tarballs attached via pinned action
- workflow-level permissions: {}; per-job id-token/contents grants only
- every third-party action pinned by full commit SHA
- src/platform.ts: pure optionalDepName/binaryFileName (no Effect imports) - index.ts imports from platform; dist emits index.mjs + platform.mjs - launcher.test.mjs: shim spawn + arg passthrough, exit-code propagation, BinaryNotFound naming host package, targets.json round-trip (15 tests)
- README Publishing section: tag flow, trusted-publisher bindings, verify steps - docs/publishing/first-release-checklist.md: one-time org setup, tag watch, post-publish verification, deprecate/bump escape hatch, action-pin duty
- scripts/release/args.ts: shared flag parser (was triplicated; identical missing-value/unknown-arg semantics, no new deps) - release.yml: derive SUFFIXES + BINARY_NAME from targets.json (KTD2); merge registry gates into one npm view per package; one root verify view; use shasum -a 256 (macOS runners lack sha256sum) - launcher: drop unreachable fs.exists pre-check (single BinaryNotFound arm, removes TOCTOU window + message duplication) - launcher test: hostDep derived from optionalDepName - README: stop leaking plan-internal unit id
Review 20260817-043848-ac825c53 found 3 P0 pipeline blockers plus silent-pass gates; all fixed: - P0 matrix: linux-arm64 builds on ubuntu-24.04-arm (native aarch64); cross-linker now only for x64 runners - P0 registry gate: jq deep-equality now shape-normalizes both sides (libc array vs bare string, absent vs null) and no longer feeds an --argjson object to fromjson; Dar'in/windows rows verified - P0 sync-root-version needs --allow-env (env VERSION): workflow step and deno.jsonc task both declare it - P1 portable sha256_of helper (sha256sum first, shasum fallback) in stage and cross-check steps - P1 npm pack retry (5x/5s) after publish read-after-write race - P1 platform packages no longer declare a bin (esbuild precedent); test asserts pkg.bin === undefined - P1 ci.yml npm job gates lint, build, typecheck, node + deno suites, and check-matrix on push/PR - P2 check-matrix --workflow-path + tests for missing/extra/swapped matrix rows - P2 parseFlags rejects flag-shaped/missing values; eq-form covered; shebang + exec bit pinned on dist/index.mjs - P3 comment fix (platforms.json -> targets.json) Gates: deno task lint/test (23/23), check-matrix ok, pnpm lint/build/ typecheck, node suite 16/16, frozen install ok.
Solution doc (docs/solutions/architecture-patterns/rust-cli-npm-distribution.md) grounds the launcher + five per-platform package pattern, the pnpm#3960 constraint that keeps optionalDependencies out of the committed manifest, the inject-at-publish sync, and the tag-triggered OIDC release pipeline with its observed silent-gate failure modes. CONCEPTS.md seeds the npm distribution vocabulary (launcher, platform package, release lane). All doc citations validated by validate-doc-claims.py (0 flags).
This was referenced Aug 17, 2026
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.
comment-checker is now installable as an npm package. The launcher resolves the platform-specific binary package at runtime, and all five platform packages plus the root launcher are published by a tag-triggered GitHub Actions pipeline with npm OIDC provenance — no static tokens anywhere.
What changed
Distribution (npm)
npm/packages/comment-checker/(bin: comment-checker) built with tsdown, Node >= 18, Effect v4rc vendored deps pinned.linux-x64,linux-arm64,darwin-x64,darwin-arm64,win32-x64) generated from the single canonical tablescripts/release/targets.json.optionalDependencies— pnpm cannot record unresolvable optional deps (pnpm#3960), which breaks--frozen-lockfileon fresh clones.sync-root-version.tsinjects the five pins at publish time. The published root therefore pins them exactly to the tag version.Release pipeline (
.github/workflows/release.yml)v*);VERSIONderives from the git tag only (KTD5).pnpm publish --provenance(OIDC, no static token). Thenpublish-npm-maingates every published platform package against the table (os/cpu/libc, shape-normalized deep equality), cross-checks published tarball sha256 vs recorded sidecar (retried for registry RYW), injects pins via sync-root-version, publishes the root, and verifies the published pins.check-matrix.tsenforces table ↔ workflow agreement including target/suffix pairing (a swapped suffix fails), plus launcher-manifest pin agreement.permissions: {}at workflow level with per-job grants.ci.ymlgains annpmjob gating lint, build, typecheck, both suites, and check-matrix on push/PR (previously the entirely new JS surface was ungated).Review-driven hardening (ce-code-review
20260817-043848-ac825c53)linux-arm64lane now builds onubuntu-24.04-arm(native) instead of cross-executing an aarch64 binary on an x86 runner.--allow-env(workflow step + deno task) and a fidelity test that replays declared flags.sha256_of(sha256sum, else shasum) in stage and cross-check.npm packretry (5x/5s) for the registry read-after-write.bin(esbuild precedent — only the launcher owns the shim).check-matrix --workflow-path+ missing/extra/swapped failure fixtures.Verification
deno task lint,deno task test(23/23),deno task check-matrixgreen.node --test);pnpm lint,pnpm -r build,pnpm -r typecheck,pnpm install --frozen-lockfilegreen.docs/publishing/first-release-checklist.md.Session-settled decisions carried from planning (
docs/plans/2026-08-17-001, in-tree): five platform suffixes + canonical targets.json (KTD1/2), libc glibc (KTD4), git tag as the single version source (KTD5), committed manifest without optional pins (pnpm#3960). Durable residual findings are filed as issues #3–#9 (Related).Related
Note (proposal only — AGENTS.md is a Locked file)
Proposed one-line Directory-map addition in
AGENTS.md(not applied by this PR):New concepts
Per-platform binary packages + optionalDependencies (the esbuild pattern). A compiled CLI cannot ship one tarball for every OS/arch. Instead, one tarball is published per platform (
-linux-x64,-darwin-arm64, …), each declaringos/cpu/libc; the root package lists all five asoptionalDependencies, so npm installs only the host-compatible one. Used by esbuild, Biome, Turbo. Preferred when the binary is self-contained; different architecture when the package ships in-process bindings (napi-rs) or needs on-consumer compilation.