ci: detect zapi packages via zapi.targets in native portability check - #9644
Conversation
zapi packages such as @chainsafe/lodestar-z declare their platform matrix as package.json#zapi.targets and build the .node under zig-out/lib, so they have neither per-target optionalDependencies nor a prebuilds/ dir. The coverage check only detected native packages via optionalDependency naming or a prebuilds/ dir, so a zapi package with a missing target (e.g. a dropped aarch64-unknown-linux-musl) was skipped entirely and passed CI - the exact regression the check is meant to catch. Treat zapi.targets as a native-target signal: add it to native-package detection (so the package is never silently skipped) and validate it against the required matrix as a fallback after optionalDependencies and prebuilds. Packages that ship both (lodestar-z@0.1.2) keep their existing behavior since optionalDependencies take precedence. Verified: no change on the real beacon-node/validator graph; a pure-zapi fixture missing a target now fails where it previously passed silently. 🤖 Generated with AI assistance Co-Authored-By: lodekeeper <lodekeeper@users.noreply.github.com>
There was a problem hiding this comment.
Code Review
This pull request updates the native portability check script to support zapi packages, which declare their platform matrix via zapi.targets instead of optional dependencies or prebuilds. The feedback suggests making the target matching in hasZapiTarget more robust by ensuring elements are strings and performing case-insensitive, trimmed comparisons.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function hasZapiTarget(zapiTargets, target) { | ||
| return zapiTargets.some((zapiTarget) => target.aliases.includes(zapiTarget)); | ||
| } |
There was a problem hiding this comment.
To make the target matching more robust and defensive, we should ensure that zapiTarget is a string and perform case-insensitive matching with whitespace trimming. This prevents potential issues if targets in package.json have accidental leading/trailing spaces or different casing.
| function hasZapiTarget(zapiTargets, target) { | |
| return zapiTargets.some((zapiTarget) => target.aliases.includes(zapiTarget)); | |
| } | |
| function hasZapiTarget(zapiTargets, target) { | |
| return zapiTargets.some((zapiTarget) => typeof zapiTarget === "string" && target.aliases.includes(zapiTarget.trim().toLowerCase())); | |
| } |
There was a problem hiding this comment.
Thanks — I'd rather keep the strict match here. zapi.targets are canonical target triples, and this check should match exactly what the build tooling consumes to name the zig-out/lib/*.node artifacts. Lower-casing/trimming would let a non-canonical entry (stray whitespace, odd casing) satisfy the check even though it isn't the exact triple — i.e. a false pass, which is worse than a false fail for a portability guard: a strict miss is immediately visible and fixable, whereas a lenient pass can hide a genuinely missing/misnamed binary (the regression this check exists to catch). It also keeps hasZapiTarget consistent with the strict matching in hasBundledPrebuild / optionalDependencyForTarget.
On the type guard: zapiTargets is already guaranteed to be an array (Array.isArray(...) ? ... : []), and Array.prototype.includes returns false for any non-string entry without throwing, so it's crash-safe as-is — the typeof check is only needed to support the .trim()/.toLowerCase() calls, which I'm leaving out for the reason above.
17ce354
into
ChainSafe:nflaig/check-native-portability
Follow-up to #9642 (against
nflaig/check-native-portability), addressing the Codex bot P2 comment (r3561870210).Relevant? Yes
check_native_portability.shdetects native packages via (a) per-targetoptionalDependenciesnaming or (b) aprebuilds/dir. zapi packages (e.g.@chainsafe/lodestar-z) declare their platform matrix aspackage.json#zapi.targetsand build the.nodeunderzig-out/lib. A zapi package with neither per-target optionalDependencies nor aprebuilds/dir ishasTargetPackages === false && prebuildFiles.length === 0→ skipped entirely for target coverage, so a dropped target (e.g.aarch64-unknown-linux-musl) passes CI — the exact regression this check exists to catch.lodestar-z@0.1.2happens to also ship per-targetoptionalDependenciestoday, so it is detected via (a) — but that relies on the optionalDependency naming convention rather than the authoritativezapi.targetsdeclaration.Fix
Treat
zapi.targetsas a native-target signal:hasTargetPackages || prebuildFiles.length > 0 || zapiTargets.length > 0) so zapi packages are never silently skipped;Precedence is optionalDeps → prebuilds →
zapi.targets, so packages that ship both (likelodestar-z) keep their existing verified behavior;zapi.targetsonly kicks in for the otherwise-skipped shape.Verification
beacon-node/validatordependency graph produces byte-identical output to the base branch (exit 0).zapi.targets, missingaarch64-unknown-linux-musl,.nodeunderzig-out/lib, no optionalDeps/prebuilds) alongside a complete native package:FAIL: @chainsafe/fake-zapi@1.0.0 / missing zapi target for aarch64-unknown-linux-musl🤖 Generated with AI assistance