Skip to content

ci: detect zapi packages via zapi.targets in native portability check - #9644

Merged
nflaig merged 1 commit into
ChainSafe:nflaig/check-native-portabilityfrom
lodekeeper:fix/native-portability-zapi-targets
Jul 10, 2026
Merged

ci: detect zapi packages via zapi.targets in native portability check#9644
nflaig merged 1 commit into
ChainSafe:nflaig/check-native-portabilityfrom
lodekeeper:fix/native-portability-zapi-targets

Conversation

@lodekeeper

Copy link
Copy Markdown
Contributor

Follow-up to #9642 (against nflaig/check-native-portability), addressing the Codex bot P2 comment (r3561870210).

Relevant? Yes

check_native_portability.sh detects native packages via (a) per-target optionalDependencies naming or (b) a prebuilds/ dir. zapi packages (e.g. @chainsafe/lodestar-z) declare their platform matrix as package.json#zapi.targets and build the .node under zig-out/lib. A zapi package with neither per-target optionalDependencies nor a prebuilds/ dir is hasTargetPackages === false && prebuildFiles.length === 0skipped 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.2 happens to also ship per-target optionalDependencies today, so it is detected via (a) — but that relies on the optionalDependency naming convention rather than the authoritative zapi.targets declaration.

Fix

Treat zapi.targets as a native-target signal:

  • add it to native-package detection (hasTargetPackages || prebuildFiles.length > 0 || zapiTargets.length > 0) so zapi packages are never silently skipped;
  • validate it against the required matrix as a fallback, after optionalDependencies and prebuilds.

Precedence is optionalDeps → prebuilds → zapi.targets, so packages that ship both (like lodestar-z) keep their existing verified behavior; zapi.targets only kicks in for the otherwise-skipped shape.

Verification

  • No regression on the real graph: running the patched script against the installed beacon-node/validator dependency graph produces byte-identical output to the base branch (exit 0).
  • Catches the gap: a synthetic pure-zapi package (only zapi.targets, missing aarch64-unknown-linux-musl, .node under zig-out/lib, no optionalDeps/prebuilds) alongside a complete native package:
    • base script → exit 0 (silently passes; package never appears in target coverage)
    • patched script → exit 1, FAIL: @chainsafe/fake-zapi@1.0.0 / missing zapi target for aarch64-unknown-linux-musl

🤖 Generated with AI assistance

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>
@lodekeeper
lodekeeper requested a review from a team as a code owner July 10, 2026 22:19

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +129 to +131
function hasZapiTarget(zapiTargets, target) {
return zapiTargets.some((zapiTarget) => target.aliases.includes(zapiTarget));
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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()));
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@nflaig
nflaig merged commit 17ce354 into ChainSafe:nflaig/check-native-portability Jul 10, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants