Skip to content

Commit e0f7741

Browse files
authored
Merge pull request #8274 from BitGo/VL-4704
feat: verify packages exist before publishing
2 parents cf6750b + 2324ca6 commit e0f7741

3 files changed

Lines changed: 96 additions & 0 deletions

File tree

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
name: Verify npm Packages Exist
2+
description: Verifies that all non-private packages in the monorepo already exist on the npm registry. Required for trusted publishing.
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Verify all packages exist on npm
8+
id: verify
9+
shell: bash
10+
run: node ${{ github.action_path }}/index.js
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
const fs = require("fs");
2+
const path = require("path");
3+
4+
const REGISTRY_URL = "https://registry.npmjs.org";
5+
const MODULES_DIR = path.resolve(__dirname, "..", "..", "..", "modules");
6+
const CONCURRENCY = 10;
7+
8+
async function getPublicPackages() {
9+
const entries = fs.readdirSync(MODULES_DIR, { withFileTypes: true });
10+
const packages = [];
11+
12+
for (const entry of entries) {
13+
if (!entry.isDirectory()) continue;
14+
15+
const pkgPath = path.join(MODULES_DIR, entry.name, "package.json");
16+
if (!fs.existsSync(pkgPath)) continue;
17+
18+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
19+
if (pkg.private) continue;
20+
21+
packages.push({ name: pkg.name, dir: entry.name });
22+
}
23+
24+
return packages;
25+
}
26+
27+
async function packageExistsOnNpm(packageName) {
28+
const url = `${REGISTRY_URL}/${packageName}`;
29+
const response = await fetch(url, { method: "HEAD" });
30+
return response.status === 200;
31+
}
32+
33+
async function processInBatches(items, fn, concurrency) {
34+
const results = [];
35+
for (let i = 0; i < items.length; i += concurrency) {
36+
const batch = items.slice(i, i + concurrency);
37+
const batchResults = await Promise.all(batch.map(fn));
38+
results.push(...batchResults);
39+
}
40+
return results;
41+
}
42+
43+
async function main() {
44+
const packages = await getPublicPackages();
45+
console.log(`Found ${packages.length} public packages to verify.\n`);
46+
47+
const results = await processInBatches(
48+
packages,
49+
async (pkg) => {
50+
const exists = await packageExistsOnNpm(pkg.name);
51+
if (exists) {
52+
console.log(` ✓ ${pkg.name}`);
53+
} else {
54+
console.error(` ✗ ${pkg.name} — not found on npm`);
55+
}
56+
return { ...pkg, exists };
57+
},
58+
CONCURRENCY
59+
);
60+
61+
const missing = results.filter((r) => !r.exists);
62+
63+
if (missing.length > 0) {
64+
console.log(`\n${missing.length} package(s) not found on npm:\n`);
65+
for (const pkg of missing) {
66+
console.log(` - ${pkg.name} (modules/${pkg.dir})`);
67+
}
68+
console.error(
69+
"\nThese packages must be created on npm before publishing with trusted publishing."
70+
);
71+
process.exit(1);
72+
}
73+
74+
console.log(`\nAll ${packages.length} packages exist on npm.`);
75+
}
76+
77+
main().catch((err) => {
78+
console.error("Failed to verify npm packages:", err);
79+
process.exit(1);
80+
});

.github/workflows/npmjs-release.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,12 @@ jobs:
163163
run: |
164164
yarn check-deps
165165
166+
# Trusted publishing (OIDC) cannot publish a package that doesn't already
167+
# exist on npm. Catch missing packages before the publish step so the
168+
# release doesn't partially fail midway through.
169+
- name: Verify all packages exist on npm
170+
uses: ./.github/actions/verify-npm-packages
171+
166172
- name: Publish new version
167173
if: inputs.dry-run == false
168174
run: |

0 commit comments

Comments
 (0)