99 * BITGO_GENERATE_SHRINKWRAP=true (set by the release workflow) — otherwise a plain
1010 * local/offline `npm pack` would force a network install of the full dependency tree.
1111 *
12- * `@bitgo/*` siblings are deliberately excluded from the resolved tree: `lerna
13- * publish` bumps and packs every package in the same operation, so a sibling's
14- * newly-bumped version is not guaranteed to be live on the registry yet when bitgo
15- * is packed — resolving it here would either fail the release outright or silently
16- * pin a stale sibling version. Excluding them means consumers resolve `@bitgo/*`
17- * siblings normally (by then they are published); everything beneath those siblings
18- * still gets pinned via `overrides` for whichever version ends up installed.
12+ * `@bitgo/*` siblings are resolved as part of the same tree as everything else.
13+ * `lerna publish` publishes packages in dependency-topological order, so by the
14+ * time bitgo (which depends on every sibling) is packed, the sibling versions it
15+ * references are already live on the registry. Resolving them here — rather than
16+ * excluding them and patching their names back into the shrinkwrap metadata after
17+ * the fact — is what makes the generated `packages["node_modules/@bitgo/..."]`
18+ * entries (version/resolved/integrity) actually present, which is what npm uses to
19+ * populate node_modules for consumers. A shrinkwrap that lists a dependency in
20+ * `packages[''].dependencies` without a matching resolved `packages[...]` entry is
21+ * silently dropped from the install by npm rather than falling back to normal
22+ * resolution — that's what an earlier version of this script did, which broke
23+ * `npm install bitgo` for every consumer (siblings never landed in node_modules).
24+ * If a sibling version isn't resolvable yet, the `npm install` below fails loudly
25+ * and the release fails — which is correct: better a failed release than a
26+ * silently broken shrinkwrap.
1927 *
2028 * `npm shrinkwrap` isn't workspace-aware and modules/bitgo/.npmrc sets
2129 * `package-lock=false`, so generation happens in an isolated temp copy outside the
@@ -49,16 +57,13 @@ async function main() {
4957 try {
5058 const siblingNames = Object . keys ( bitgoPackageJson . dependencies ?? { } ) . filter ( ( name ) => name . startsWith ( '@bitgo/' ) ) ;
5159 if ( siblingNames . length > 0 ) {
52- console . log ( `Excluding ${ siblingNames . length } @bitgo/* siblings from shrinkwrap resolution :` ) ;
60+ console . log ( `Resolving ${ siblingNames . length } @bitgo/* siblings as part of the shrinkwrap :` ) ;
5361 siblingNames . forEach ( ( name ) => console . log ( ` - ${ name } ` ) ) ;
5462 }
5563
5664 const isolatedPackageJson : Record < string , unknown > = { ...bitgoPackageJson } ;
5765 delete isolatedPackageJson . devDependencies ;
5866 delete isolatedPackageJson . scripts ;
59- isolatedPackageJson . dependencies = Object . fromEntries (
60- Object . entries ( bitgoPackageJson . dependencies ?? { } ) . filter ( ( [ name ] ) => ! name . startsWith ( '@bitgo/' ) )
61- ) ;
6267 const directDeps = new Set ( Object . keys ( bitgoPackageJson . dependencies ?? { } ) ) ;
6368 const filteredOverrides = Object . fromEntries (
6469 Object . entries ( rootPackageJson . overrides as Record < string , unknown > ) . filter (
@@ -81,13 +86,22 @@ async function main() {
8186 throw new Error ( `npm shrinkwrap did not produce a file at ${ shrinkwrapPath } ` ) ;
8287 }
8388
84- // The shrinkwrap was generated against a package.json with @bitgo /* siblings
85- // removed, so its top-level `dependencies` no longer lists them. Restore the
86- // real dependency list so the shipped shrinkwrap matches what's published.
8789 const shrinkwrap = JSON . parse ( fs . readFileSync ( shrinkwrapPath , 'utf-8' ) ) ;
88- shrinkwrap . dependencies = bitgoPackageJson . dependencies ;
89- if ( shrinkwrap . packages ?. [ '' ] ) {
90- shrinkwrap . packages [ '' ] . dependencies = bitgoPackageJson . dependencies ;
90+
91+ // Every @bitgo /* sibling must have a resolved node_modules entry — that's what
92+ // npm actually installs from. A sibling present only in `packages[''].dependencies`
93+ // (or missing entirely) would be silently skipped by consumers' installs.
94+ const resolvedPackageNames = new Set (
95+ Object . keys ( shrinkwrap . packages ?? { } )
96+ . filter ( ( key ) => key . startsWith ( 'node_modules/' ) )
97+ . map ( ( key ) => key . slice ( 'node_modules/' . length ) )
98+ ) ;
99+ const unresolvedSiblings = siblingNames . filter ( ( name ) => ! resolvedPackageNames . has ( name ) ) ;
100+ if ( unresolvedSiblings . length > 0 ) {
101+ throw new Error (
102+ `The following @bitgo/* siblings have no resolved node_modules entry in the generated ` +
103+ `shrinkwrap and would be silently missing from consumers' installs: ${ unresolvedSiblings . join ( ', ' ) } `
104+ ) ;
91105 }
92106
93107 fs . writeFileSync ( path . join ( bitgoDir , 'npm-shrinkwrap.json' ) , JSON . stringify ( shrinkwrap , null , 2 ) + '\n' ) ;
0 commit comments