66// linux-x64-gnu vs linux-x64-musl when resolving from the lockfile and may
77// install (or load) the wrong native binary on Alpine/musl hosts.
88//
9+ // The set of packages to check is derived from `optionalDependencies` in
10+ // package.json, so adding a new linux-* platform there automatically extends
11+ // this guard with no script change required. The expected libc value is
12+ // inferred from the package name's `-gnu`/`-musl` suffix (the napi-rs naming
13+ // convention).
14+ //
915// Run via `npm run lint` (or directly) in CI to catch silent regressions from
1016// Dependabot bumps and contributor `npm install` runs.
1117import { readFileSync } from 'node:fs' ;
1218import { dirname , resolve } from 'node:path' ;
1319import { fileURLToPath } from 'node:url' ;
1420
15- const EXPECTED_LIBC = {
16- '@optave/codegraph-linux-arm64-gnu' : 'glibc' ,
17- '@optave/codegraph-linux-x64-gnu' : 'glibc' ,
18- '@optave/codegraph-linux-x64-musl' : 'musl' ,
21+ const LIBC_BY_SUFFIX = {
22+ gnu : 'glibc' ,
23+ musl : 'musl' ,
1924} ;
25+ const LINUX_PKG_PATTERN = / ^ @ o p t a v e \/ c o d e g r a p h - l i n u x - [ ^ - ] + - ( g n u | m u s l ) $ / ;
2026
2127// Resolve relative to this script's location so it works regardless of CWD
2228// (e.g. running `node scripts/verify-lockfile-libc.mjs` from the `scripts/`
23- // subdirectory still finds the repo-root lockfile ).
29+ // subdirectory still finds the repo-root manifests ).
2430const __dirname = dirname ( fileURLToPath ( import . meta. url ) ) ;
25- const lockfilePath = resolve ( __dirname , '..' , 'package-lock.json' ) ;
26- const lock = JSON . parse ( readFileSync ( lockfilePath , 'utf8' ) ) ;
31+ const repoRoot = resolve ( __dirname , '..' ) ;
32+ const pkg = JSON . parse ( readFileSync ( resolve ( repoRoot , 'package.json' ) , 'utf8' ) ) ;
33+ const lock = JSON . parse ( readFileSync ( resolve ( repoRoot , 'package-lock.json' ) , 'utf8' ) ) ;
34+
35+ const optionalDeps = pkg . optionalDependencies ?? { } ;
36+ const linuxPackages = Object . keys ( optionalDeps ) . filter ( ( name ) =>
37+ name . startsWith ( '@optave/codegraph-linux-' ) ,
38+ ) ;
39+
40+ if ( linuxPackages . length === 0 ) {
41+ console . log ( 'package-lock.json libc check: no @optave/codegraph-linux-* packages declared, skipping' ) ;
42+ process . exit ( 0 ) ;
43+ }
44+
2745const failures = [ ] ;
46+ const unknownSuffixes = [ ] ;
2847
29- for ( const [ pkgName , expectedLibc ] of Object . entries ( EXPECTED_LIBC ) ) {
48+ for ( const pkgName of linuxPackages ) {
49+ const match = LINUX_PKG_PATTERN . exec ( pkgName ) ;
50+ if ( ! match ) {
51+ unknownSuffixes . push ( pkgName ) ;
52+ continue ;
53+ }
54+ const expectedLibc = LIBC_BY_SUFFIX [ match [ 1 ] ] ;
3055 const entry = lock . packages ?. [ `node_modules/${ pkgName } ` ] ;
3156 if ( ! entry ) {
3257 failures . push ( `${ pkgName } : missing from package-lock.json` ) ;
@@ -40,6 +65,18 @@ for (const [pkgName, expectedLibc] of Object.entries(EXPECTED_LIBC)) {
4065 }
4166}
4267
68+ if ( unknownSuffixes . length > 0 ) {
69+ console . error (
70+ 'package-lock.json libc discriminator check cannot infer expected libc for:\n' ,
71+ ) ;
72+ for ( const name of unknownSuffixes ) console . error ( ` - ${ name } ` ) ;
73+ console . error (
74+ `\nExtend LIBC_BY_SUFFIX in ${ import . meta. url . replace ( 'file://' , '' ) } \n` +
75+ 'to cover the new suffix (current rule: -gnu → glibc, -musl → musl).' ,
76+ ) ;
77+ process . exit ( 1 ) ;
78+ }
79+
4380if ( failures . length > 0 ) {
4481 console . error ( 'package-lock.json libc discriminator check failed:\n' ) ;
4582 for ( const f of failures ) console . error ( ` - ${ f } ` ) ;
@@ -51,4 +88,6 @@ if (failures.length > 0) {
5188 process . exit ( 1 ) ;
5289}
5390
54- console . log ( 'package-lock.json libc discriminators OK' ) ;
91+ console . log (
92+ `package-lock.json libc discriminators OK (${ linuxPackages . length } linux package(s) checked)` ,
93+ ) ;
0 commit comments