M01 / setup-zig / Initial implementation#1
Merged
Conversation
feat(gc): add zig-cache size limit parsing and purge logic Both modules are pure (cache) or filesystem-only (gc), with no @actions/* dependencies. Includes tests for size parser unit suffixes (binary and decimal), filesystem fixtures, and disabled-state semantics.
Verbatim port to TypeScript with explicit attribution header. Uses node:crypto.subtle for Ed25519 verification and createHash('BLAKE2b512') for the hashed-mode prehash. Tests build round-trip vectors with node:crypto generateKeyPairSync to exercise valid, corrupted, and tampered scenarios without needing a real Zig signature fixture.
…ange enforcement feat(download): add parallel mirror race with AbortController and minisign verification resolve.ts handles input precedence (explicit > version-file > default build.zig.zon > latest), ZON regex extraction (mach_zig_version, minimum_zig_version), and prefix-based enforce-version-range. download.ts implements parallel mirror race using Promise.any over fetch() with a shared AbortController, falls back to the 13-mirror hardcoded list when ziglang.org/download/community-mirrors.txt is unreachable, and last-resorts to the canonical ziglang.org URL when every mirror has failed. Tarball signatures and trusted comments are validated with the ported minisign module.
main.ts threads the full pipeline: resolve version, compute tarball name from os.arch/platform/endianness, restore tarball cache, download with mirror race on miss, save to cache, extract via @actions/tool-cache, exec 'zig version' to set the zig-version output, export ZIG_GLOBAL_CACHE_DIR/ZIG_LOCAL_CACHE_DIR, and restore the .zig-cache prefix when use-cache is true. post.ts reads use-cache state, optionally GCs the .zig-cache via parseSizeLimit + maybeGc, and saves the cache under runId-runAttempt. Also adds allowImportingTsExtensions to tsconfig (required by NodeNext module resolution for cross-module .ts imports), uses webcrypto.CryptoKey type explicitly in minisign.ts so 'lib: dom' is not needed, and simplifies the readdir typing in gc.ts.
ncc cannot use 'allowImportingTsExtensions' because it must emit. Switching imports from './foo.ts' to './foo.js' is the standard NodeNext convention (TypeScript resolves .js to the corresponding .ts source, ncc bundles into single CommonJS files). dist/index.js and dist/post.js are committed (per supply-chain auditability decision in the brief). dist/* is marked linguist-generated in .gitattributes and ignored by ESLint/Prettier so reviewers see human-authored code only.
.github/workflows/test.yml: matrix 4 OS x 5 Zig versions (20 jobs) plus dedicated jobs for cache-hit, version-file, enforce-version-range (success and failure), custom mirror, and custom source query string. .github/workflows/lint.yml: typecheck, eslint, prettier --check, plus a reproducibility check that verifies the committed dist/ matches a fresh 'npm run build'. .forgejo/workflows/test.yml: minimal Codeberg runner verification on codeberg-tiny-lazy (no container.image override per the gotcha noted in the brief).
Adjusted eslint.config.js to disable typed-aware rules (require-await, no-base-to-string, restrict-template-expressions) for tests/ where mocked fetch and template-string assertions trip them, and reformatted source/tests with prettier --write. Switched a redundant ternary in src/version.ts to nullish coalescing. Rebuilt dist/index.js and dist/post.js after the source touch-up. Same logical bundle, only minor diff from prettier's reflow.
Adds three minisign tests to push coverage from 89.87% to 97.46% on this module: a forged trusted-comment header (anchored on the preceding newline so we don't accidentally hit inside 'untrusted comment:'), an unsupported algorithm prefix, and the raw 'Ed' (unhashed) verification path that the action does not exercise but the verifier supports for spec completeness.
The workflow triggers via 'workflow_run' on the Lint workflow completing on main, reads .version from package.json, skips if v$version already exists as a tag, and otherwise creates an annotated tag and a GitHub Release with auto-generated notes (commits since the previous tag). Post-close addendum to milestone M01: extends the brief's CI section beyond the originally-listed three workflows. Decision recorded as a Cas 3 verbal in 'Acted deviations' of briefs/M01-initial-implementation.md.
…ged entries) The real index.json at https://ziglang.org/download/index.json only carries a string '.version' field on the 'master' entry (and a couple of very recent stable releases). Tagged entries like 0.15.1 / 0.14.1 have only 'date', 'docs', 'src', '<arch>-<os>' — and the key IS the version. The previous strict isVersionMap rejected this entirely with 'Malformed index.json' on every CI 'master' / 'latest' job. Relax the type guard to require only object-of-objects; pull the string check up to the specific call sites (getMasterVersion, getMachVersion) where '.version' really must exist. getLatestVersion still uses the key as the version, which matches mlugg. ci: also pin .nvmrc to the exact 24.15.0 patch and switch lint.yml to node-version-file: .nvmrc, so the dist/ reproducibility check no longer drifts between local 24.15.0 and CI's auto-resolved latest 24.x.
… on dist/* Two combined fixes for the dist/ reproducibility check that was failing on CI: 1. dist/index.js and dist/post.js are now built in node:24.15.0-slim (Linux), matching CI's Ubuntu runners. ncc bakes platform-specific bits into the bundle, so a macOS-built dist will not match a Linux-built one byte-for-byte. The Docker build is deterministic across runs (verified by two consecutive builds producing identical output). 2. .gitattributes now sets 'dist/* -text' to opt the bundles out of git's CRLF/LF normalization. Without this, mixed line endings in transitive npm sources (some Windows-authored deps inside the bundle) get rewritten on commit but not on rebuild, causing spurious diffs. Also adds .claude/ to .gitignore so Claude Code's local scheduled-task state is not tracked.
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.
Closes milestone M01 of
weldengine/setup-zig. Brief:briefs/M01-initial-implementation.md.Summary
From-scratch TypeScript reimplementation inspired by
mlugg/setup-zig, with parallel mirror race viaPromise.any+ sharedAbortController, ncc-bundleddist/,node24runtime, and Weld-specific ergonomics (version-file,source,enforce-version-rangeinputs,zig-versionoutput).The minisign verifier is a near-verbatim port from mlugg with explicit attribution in
src/minisign.tsand the double-copyrightLICENSE.Closing notes (from the brief)
vi.spyOn(globalThis, 'fetch')for testing. Parallel race viaPromise.any+ sharedAbortControllerfell out naturally and is testable without ever hitting the network. ThedownloadTarball/downloadTarballWithKeysplit keeps the orchestration tied to the hardcoded Zig public key while still allowing tests to inject a generated test key..nvmrc,.prettierignore, and the inherently-mandatedbriefs/directory. One soft semantic decision documented in code:enforce-version-rangeis implemented as token-prefix match on the version'smajor.minor[.patch](stripped of-devsuffix).src/minisign.ts:78— preserves a known mlugg upstream quirk (stale offset in trailing-bytes guard). Carried verbatim per "minimal changes" mandate; flagged for visibility, out of scope to fix in M01.master,latest,0.14.1,0.15.1,0.16.0. If0.16.0is not yet a real Zig release at run time, those rows will fail until either Zig ships 0.16.0 or the matrix is updated.cache-size-limitparser intentionally rejects bare floats ('1.5'); units are mandatory unless the value is a plain integer.tests/fixtures/build.zig.zondeclaresminimum_zig_version = "0.16.0", used by the CI version-file job.dist/index.js4 351 301 bytes;dist/post.js4 433 650 bytes;dist/licenses.txt60 139 bytes.src/: 877.dist/reproducibility tied to a specific Node 24 patch version (mitigated by.nvmrc); hardcoded fallback mirror list will drift over time and need refreshing in future versions.Validation checklist (Step 4 of the protocol)
npm test— 90 / 90)src/(excluding entry pointsmain.ts/post.tsper vitest exclude list — they are integration-tested via the CI matrix)npm run buildproducesdist/index.jsanddist/post.jswith zero warningsnpm run lintclean (zero errors, zero warnings)npm run format:checkcleandist/matches a freshly-rebuiltdist/byte-for-byte (verified locally; enforced by.github/workflows/lint.yml)ghaccepting them in this push)briefs/M01-initial-implementation.mdClosing notes filled,Status: CLOSED,Closing date: 2026-05-10docs(brief): close M01presentTest plan after merge
v0.1.0on the squashed merge commit.weldengine/setup-zig@v0.1.0to install Zig 0.16.0 and runzig buildon a trivialbuild.zig.v1on the same commit asv0.1.0.