From 90afaa0e3e5d61ecd3b2242d5a7553b0905c34fc Mon Sep 17 00:00:00 2001 From: Zahin Mohammad Date: Wed, 5 Aug 2026 15:40:25 -0400 Subject: [PATCH] fix: hold bitgo back via committed private + --include-private The runtime manifest flip (set-umbrella-publishable.ts) left modules/bitgo/package.json modified but uncommitted, so `lerna publish from-package` aborted the siblings pass with EUNCOMMIT before publishing anything. Commit bitgo as `private` instead: `from-package` filters it out of pass 1, and pass 2 re-includes it by its on-disk name with `--include-private`, which strips `private` in-memory at pack time. Patch the three checks that skip private packages so they still cover the bitgo directory, and delete the flip script. Ticket: WCI-1271 --- .github/actions/verify-npm-packages/index.js | 3 +- .github/workflows/npmjs-release.yml | 33 ++--------- .github/workflows/publish.yml | 22 +------ modules/bitgo/package.json | 1 + scripts/generate-bitgo-shrinkwrap.ts | 5 +- scripts/set-umbrella-publishable.ts | 61 -------------------- scripts/update-dockerfile.ts | 3 +- scripts/verify-release.ts | 3 +- 8 files changed, 17 insertions(+), 114 deletions(-) delete mode 100644 scripts/set-umbrella-publishable.ts diff --git a/.github/actions/verify-npm-packages/index.js b/.github/actions/verify-npm-packages/index.js index 230e6351bd..7da7bfcca5 100644 --- a/.github/actions/verify-npm-packages/index.js +++ b/.github/actions/verify-npm-packages/index.js @@ -16,7 +16,8 @@ async function getPublicPackages() { if (!fs.existsSync(pkgPath)) continue; const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8")); - if (pkg.private) continue; + // bitgo is private but still published via lerna --include-private. + if (pkg.private && entry.name !== "bitgo") continue; packages.push({ name: pkg.name, dir: entry.name }); } diff --git a/.github/workflows/npmjs-release.yml b/.github/workflows/npmjs-release.yml index a3052f167b..b1b9fe5a44 100644 --- a/.github/workflows/npmjs-release.yml +++ b/.github/workflows/npmjs-release.yml @@ -304,44 +304,19 @@ jobs: run: | yarn lerna version --sign-git-tag --sign-git-commit --include-merged-tags --conventional-commits --conventional-graduate --yes - # Two-phase publish (WCN-1818): bitgo's `prepack` script resolves its - # siblings from the registry, but a single combined `lerna publish` packs - # every package's lifecycle hooks before uploading any of them — so bitgo - # would try to resolve siblings that aren't live yet (this is what broke - # `npm install bitgo` for every consumer, WCI-1200). Hold bitgo back - # (private), publish siblings first, then restore and publish bitgo alone - # once its siblings are actually resolvable. Runs identically for the - # normal path (after the version bump above) and the recovery path (no - # preceding bump) — `from-package` only publishes what's missing from npm - # either way, so re-running is idempotent. - - name: Hold back bitgo umbrella for the siblings publish pass - if: inputs.dry-run == false - run: npx tsx ./scripts/set-umbrella-publishable.ts false - - name: Publish siblings (pass 1) if: inputs.dry-run == false - run: | - # `from-package` reads each package.json's `version`, queries npm, - # and publishes only versions missing from the registry. - yarn lerna publish from-package --yes + run: yarn lerna publish from-package --yes env: NPM_CONFIG_PROVENANCE: true - - name: Restore bitgo umbrella for its own publish pass - if: ${{ always() && inputs.dry-run == false }} - run: npx tsx ./scripts/set-umbrella-publishable.ts true - - name: Publish bitgo (pass 2) if: inputs.dry-run == false run: | - yarn lerna publish from-package --yes + bitgo_name=$(jq -r '.name' modules/bitgo/package.json) + yarn lerna publish from-package --include-private "$bitgo_name" --yes env: NPM_CONFIG_PROVENANCE: true - # Tells bitgo's `prepack` script (scripts/generate-bitgo-shrinkwrap.ts) to - # actually generate npm-shrinkwrap.json — kept opt-in so a plain local - # `npm pack` doesn't force a network install of the full dependency tree. - # Siblings published in the pass above are live on the registry by now, - # so resolution succeeds. BITGO_GENERATE_SHRINKWRAP: true - name: Verify recovery published the missing versions @@ -353,7 +328,7 @@ jobs: # (e.g., another transient registry error). missing=() for f in modules/*/package.json; do - if [ "$(jq -r '.private // false' "$f")" = "true" ]; then continue; fi + if [ "$(jq -r '.private // false' "$f")" = "true" ] && [ "$f" != "modules/bitgo/package.json" ]; then continue; fi name=$(jq -r '.name' "$f") version=$(jq -r '.version' "$f") code=$(curl -sL -o /dev/null -w "%{http_code}" -- "https://registry.npmjs.org/${name}/${version}") diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 6cd3b47402..eeb356ed8f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -100,15 +100,6 @@ jobs: - name: Commit Local Changes run: git commit -am "Auto updated ${{ env.preid }} branch" --no-verify || echo "No changes to commit" - # Two-phase publish (WCN-1818): bitgo's `prepack` script resolves its siblings - # from the registry, but a single combined `lerna publish` packs every - # package's lifecycle hooks before uploading any of them — so bitgo would - # try to resolve siblings that aren't live yet. Hold bitgo back (private), - # publish siblings first, then restore and publish bitgo alone once its - # siblings are actually resolvable. - - name: Hold back bitgo umbrella for the siblings publish pass - run: npx tsx ./scripts/set-umbrella-publishable.ts false - - name: Lerna Publish (siblings) # In recovery mode a stuck package's Rekor conflict fails this step outright; let it # continue so Verify Publish below still gets a chance to bump+retry that package. @@ -117,18 +108,11 @@ jobs: env: NPM_CONFIG_PROVENANCE: true - - name: Restore bitgo umbrella for its own publish pass - if: always() - run: npx tsx ./scripts/set-umbrella-publishable.ts true - - name: Lerna Publish (bitgo) - # Siblings published above are now live on the registry, so bitgo's - # `prepack` script (scripts/generate-bitgo-shrinkwrap.ts) can resolve them - # for real. BITGO_GENERATE_SHRINKWRAP is set here (unlike historically on - # this workflow) so alpha/beta actually exercises shrinkwrap generation - # instead of silently no-oping on it. continue-on-error: ${{ inputs.recovery-mode }} - run: yarn lerna publish from-package --preid ${{ env.preid }} --dist-tag ${{ env.preid }} --force-publish --yes --loglevel silly + run: | + bitgo_name=$(jq -r '.name' modules/bitgo/package.json) + yarn lerna publish from-package --include-private "$bitgo_name" --preid ${{ env.preid }} --dist-tag ${{ env.preid }} --force-publish --yes --loglevel silly env: NPM_CONFIG_PROVENANCE: true BITGO_GENERATE_SHRINKWRAP: true diff --git a/modules/bitgo/package.json b/modules/bitgo/package.json index a067b31336..41c8f1868d 100644 --- a/modules/bitgo/package.json +++ b/modules/bitgo/package.json @@ -2,6 +2,7 @@ "name": "bitgo", "version": "52.4.1", "description": "BitGo JavaScript SDK", + "private": true, "main": "./dist/src/index.js", "types": "./dist/types/src/index.d.ts", "keywords": [ diff --git a/scripts/generate-bitgo-shrinkwrap.ts b/scripts/generate-bitgo-shrinkwrap.ts index fc33a20599..6a0dd4f30d 100644 --- a/scripts/generate-bitgo-shrinkwrap.ts +++ b/scripts/generate-bitgo-shrinkwrap.ts @@ -25,8 +25,9 @@ * package's lifecycle hooks (bitgo's `prepack` included) before uploading any of * them, so bitgo's siblings are not yet published at the point this script tries to * resolve them. The release workflow is responsible for publishing siblings in a - * separate, earlier pass (with bitgo held back via `set-umbrella-publishable.ts`) - * before invoking a second pass that packs bitgo with generation enabled. If a + * separate, earlier pass — bitgo is committed `private`, so `lerna publish + * from-package` filters it out of that pass — before invoking a second pass that + * re-includes bitgo (`--include-private`) and packs it with generation enabled. If a * sibling version genuinely isn't resolvable (wrong pass ordering, a sibling publish * that itself failed, etc.), the `npm install` below fails loudly and the release * fails — which is correct: better a failed release than a silently broken diff --git a/scripts/set-umbrella-publishable.ts b/scripts/set-umbrella-publishable.ts deleted file mode 100644 index 1963393906..0000000000 --- a/scripts/set-umbrella-publishable.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Toggles `private` on modules/bitgo/package.json so the release workflow can hold - * the `bitgo` umbrella package out of a `lerna publish` pass without permanently - * marking it private in the repo. - * - * Why a scripted toggle instead of lerna's `--include-private ` (which lets - * a named private package publish "by temporarily removing the private property - * from the package manifest" on its own): that would require `bitgo` to be - * permanently `private: true` in the committed manifest, and three separate checks - * in the release pipeline enumerate non-private packages to verify they exist/were - * published — a permanently-private bitgo would silently stop being covered by all - * three: - * - the pre-publish existence check (trusted publishing depends on it) - * - recovery verification - * - beta verification / recovery auto-retry - * - * Flipping the flag off, running pass 1 (siblings only — bitgo is skipped because - * lerna filters private packages before packing), then flipping it back on before - * pass 2 (bitgo only) keeps every one of those checks seeing a normal, publishable - * package by the time they run. Callers MUST run the "restore" invocation (`true`) - * under `always()` in the workflow so a failed or cancelled pass 1 cannot leave the - * manifest flipped — this script does not track or restore state on its own, it - * just sets the field to whatever you tell it. - * - * Usage: - * npx tsx scripts/set-umbrella-publishable.ts false # hold back for pass 1 - * npx tsx scripts/set-umbrella-publishable.ts true # restore for pass 2 - */ - -import fs from 'fs'; -import path from 'path'; - -const bitgoPackageJsonPath = path.resolve(__dirname, '..', 'modules', 'bitgo', 'package.json'); - -function parseArg(argv: string[]): boolean { - const raw = argv[2]; - if (raw === 'true') return true; - if (raw === 'false') return false; - throw new Error(`Expected a single argument "true" or "false", got: ${JSON.stringify(raw)}`); -} - -function main(): void { - const publishable = parseArg(process.argv); - const original = fs.readFileSync(bitgoPackageJsonPath, 'utf-8'); - const pkg = JSON.parse(original); - - if (publishable) { - delete pkg.private; - } else { - pkg.private = true; - } - - fs.writeFileSync(bitgoPackageJsonPath, JSON.stringify(pkg, null, 2) + '\n'); - console.log( - publishable - ? `Restored modules/bitgo/package.json to publishable (removed "private").` - : `Marked modules/bitgo/package.json as private — it will be skipped by the next lerna publish pass.` - ); -} - -main(); diff --git a/scripts/update-dockerfile.ts b/scripts/update-dockerfile.ts index 7d83349a36..82ea2117d9 100644 --- a/scripts/update-dockerfile.ts +++ b/scripts/update-dockerfile.ts @@ -30,7 +30,8 @@ const walkDependencies = ( .filter((dep) => graph[dep]) .map((name) => managedModules.find((mod) => mod.name === name)); managedDeps.forEach((module) => { - if (module && !module.private && !setDeps.has(module)) { + // bitgo is private but still shipped in the express image, so keep walking it. + if (module && (!module.private || module.name === 'bitgo') && !setDeps.has(module)) { setDeps.add(module); walkDependencies(module.name, setDeps, graph, managedModules); } diff --git a/scripts/verify-release.ts b/scripts/verify-release.ts index 97b217af50..a2adf66438 100644 --- a/scripts/verify-release.ts +++ b/scripts/verify-release.ts @@ -67,7 +67,8 @@ async function verifyPackage(dir: string, preid = 'beta'): Promise { const json = JSON.parse( readFileSync(path.join(cwd, 'package.json'), { encoding: 'utf-8' }), ); - if (json.private) { + // bitgo is private but still published via lerna --include-private. + if (json.private && path.basename(cwd) !== 'bitgo') { return true; }