Skip to content

Commit b519837

Browse files
committed
fix: resolve @bitgo/* siblings into npm-shrinkwrap.json
Ticket: WCI-1200
1 parent 1745b4a commit b519837

1 file changed

Lines changed: 31 additions & 17 deletions

File tree

scripts/generate-bitgo-shrinkwrap.ts

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,21 @@
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

Comments
 (0)