From b51e8c012c0e831dc8188596eef81a9dcd165161 Mon Sep 17 00:00:00 2001 From: lodekeeper Date: Fri, 10 Jul 2026 22:18:44 +0000 Subject: [PATCH] ci: detect zapi packages via zapi.targets in native portability check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- scripts/ci/check_native_portability.sh | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/scripts/ci/check_native_portability.sh b/scripts/ci/check_native_portability.sh index f27742a0b5f9..1cd7de7f2391 100755 --- a/scripts/ci/check_native_portability.sh +++ b/scripts/ci/check_native_portability.sh @@ -126,6 +126,10 @@ function hasBundledPrebuild(prebuildFiles, targetName) { } } +function hasZapiTarget(zapiTargets, target) { + return zapiTargets.some((zapiTarget) => target.aliases.includes(zapiTarget)); +} + function visitManifest(manifestPath) { const realManifestPath = fs.realpathSync(manifestPath); if (visited.has(realManifestPath)) { @@ -142,8 +146,12 @@ function visitManifest(manifestPath) { const hasTargetPackages = Object.keys(optionalDependencies).some((name) => requiredTargets.some((target) => target.aliases.some((alias) => name.endsWith(`-${alias}`))) ); + // zapi packages (e.g. @chainsafe/lodestar-z) declare their platform matrix as `zapi.targets` and + // build the `.node` under `zig-out/lib`, so they have neither per-target optionalDependencies nor a + // `prebuilds/` dir. Treat `zapi.targets` as a native-target signal so they aren't silently skipped. + const zapiTargets = Array.isArray(manifest.zapi?.targets) ? manifest.zapi.targets : []; - if (hasTargetPackages || prebuildFiles.length > 0) { + if (hasTargetPackages || prebuildFiles.length > 0 || zapiTargets.length > 0) { nativePackageCount++; const packageErrors = []; const knownPackageGaps = []; @@ -154,8 +162,12 @@ function visitManifest(manifestPath) { if (!optionalDependencyForTarget(optionalDependencies, target)) { error = `missing optional dependency for ${target.name}`; } - } else if (!hasBundledPrebuild(prebuildFiles, target.name)) { - error = `missing bundled prebuild for ${target.name}`; + } else if (prebuildFiles.length > 0) { + if (!hasBundledPrebuild(prebuildFiles, target.name)) { + error = `missing bundled prebuild for ${target.name}`; + } + } else if (!hasZapiTarget(zapiTargets, target)) { + error = `missing zapi target for ${target.name}`; } if (error !== null) {