Skip to content

Commit 27ac497

Browse files
committed
chore: script
1 parent 71e0a4d commit 27ac497

File tree

8 files changed

+232
-3
lines changed

8 files changed

+232
-3
lines changed

package-lock.json

Lines changed: 2 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
"test-changed": "lerna run test --stream --concurrency 1 --since origin/HEAD",
4040
"test-ci": "lerna run test-ci --concurrency 1",
4141
"test": "lerna run test --concurrency 1 --stream",
42-
"where": "node ./scripts/src/where.js"
42+
"where": "node ./scripts/src/where.js",
43+
"request-npm-token": "request-npm-token"
4344
},
4445
"dependencies": {
4546
"@mongodb-js/monorepo-tools": "^1.1.18"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
require('../dist/request-npm-token.js');

packages/monorepo-tools/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
"precommit": "./bin/precommit.js",
2727
"depalign": "./bin/depalign.js",
2828
"monorepo-where": "./bin/where.js",
29-
"bump-monorepo-packages": "./bin/bump-packages.js"
29+
"bump-monorepo-packages": "./bin/bump-packages.js",
30+
"request-npm-token": "./bin/request-npm-token.js"
3031
},
3132
"scripts": {
3233
"bootstrap": "npm run compile",

packages/monorepo-tools/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ export * from './utils/update-package-json';
77
export * from './utils/with-progress';
88
export * from './utils/workspace-dependencies';
99
export * from './utils/get-packages-in-topological-order';
10+
export * from './utils/get-npm-token-list';
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#! /usr/bin/env node
2+
/* eslint-disable no-console */
3+
4+
/**
5+
* CLI command to get NPM token requirements for the current monorepo.
6+
*
7+
* Usage:
8+
* npm run request-npm-token
9+
*
10+
* This will output the scopes and packages that need to be included in NPM token permissions.
11+
*/
12+
13+
import { getNpmTokenList } from './utils/get-npm-token-list';
14+
15+
async function main() {
16+
try {
17+
const requirements = await getNpmTokenList();
18+
19+
console.log(
20+
'Open an IAMSEC ticket with https://jira.mongodb.org/plugins/servlet/desk/portal/81/create/1380',
21+
);
22+
23+
console.log('Use the following description for the ticket:');
24+
console.log('--------------------------------');
25+
console.log('Hello,');
26+
console.log(
27+
'We need to update the NPM token for publishing our packages. The token needs Read/Write/Publish access to:\n',
28+
);
29+
30+
console.log('Following Scopes:');
31+
if (requirements.scopes.length > 0) {
32+
requirements.scopes.forEach((scope) => {
33+
console.log(scope);
34+
});
35+
} else {
36+
console.log('(none)');
37+
}
38+
39+
console.log('\nFollowing Packages:');
40+
if (requirements.packages.length > 0) {
41+
requirements.packages.forEach((pkg) => {
42+
console.log(pkg);
43+
});
44+
} else {
45+
console.log('(none)');
46+
}
47+
48+
console.log('');
49+
console.log('Please share it with our team lead: {TEAM LEADER NAME}');
50+
} catch (error) {
51+
console.error(
52+
'Error:',
53+
error instanceof Error ? error.message : String(error),
54+
);
55+
process.exit(1);
56+
}
57+
}
58+
59+
process.on('unhandledRejection', (err: Error) => {
60+
console.error();
61+
console.error(err?.stack || err?.message || err);
62+
process.exitCode = 1;
63+
});
64+
65+
main().catch((err) =>
66+
process.nextTick(() => {
67+
throw err;
68+
}),
69+
);
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { listAllPackages } from './list-all-packages';
2+
3+
export interface NpmTokenRequirements {
4+
scopes: string[];
5+
packages: string[];
6+
}
7+
8+
/**
9+
* Gets all package names and scopes from the current monorepo.
10+
* Returns scoped packages as scopes and unscoped packages as individual packages.
11+
*/
12+
export async function getNpmTokenList(): Promise<NpmTokenRequirements> {
13+
const allPackagesArr = [];
14+
for await (const { packageJson } of listAllPackages()) {
15+
// listAllPackages yields { name: string, ... }
16+
if (packageJson && typeof packageJson?.name === 'string') {
17+
allPackagesArr.push(packageJson.name);
18+
}
19+
}
20+
21+
// Separate scoped and unscoped packages
22+
const scopedPackages = allPackagesArr.filter(
23+
(pkg) => typeof pkg === 'string' && pkg.startsWith('@'),
24+
);
25+
const unscopedPackages = allPackagesArr.filter(
26+
(pkg) => typeof pkg === 'string' && !pkg.startsWith('@'),
27+
);
28+
29+
// Extract unique scopes from scoped packages
30+
const scopes = [
31+
...new Set(
32+
scopedPackages.map((pkg) => {
33+
const scope = pkg.split('/')[0];
34+
return `${scope}/*`;
35+
}),
36+
),
37+
].sort();
38+
39+
// Sort unscoped packages
40+
const packages = [...new Set(unscopedPackages)].sort();
41+
42+
return { scopes, packages };
43+
}

scripts/src/get-packages-list.ts

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/**
2+
* This script collects all package names from specified repositories using Lerna.
3+
* This is useful for generating a list of all packages we own when asking for NPM token updates.
4+
*/
5+
import * as fs from 'fs';
6+
import * as path from 'path';
7+
import { exec as execCallback } from 'child_process';
8+
import { promisify } from 'util';
9+
10+
const exec = promisify(execCallback);
11+
12+
// Repositories to check
13+
const repositories = [
14+
{
15+
name: 'devtools-shared',
16+
isLocal: true,
17+
path: path.resolve('.'),
18+
},
19+
{
20+
name: 'mongosh',
21+
url: '[email protected]:mongodb-js/mongosh.git',
22+
branch: 'main',
23+
},
24+
{
25+
name: 'compass',
26+
url: '[email protected]:mongodb-js/compass.git',
27+
branch: 'main',
28+
},
29+
];
30+
31+
// Packages which exist as single repos and are not managed by Lerna
32+
const manualPackages = ['glibc-version', 'mongodb-mcp-server'];
33+
34+
// Set working directory for cloning repos
35+
const workDir = path.resolve('/tmp/repo-package-check');
36+
const outputFile = path.resolve('./tmp/all-packages.txt');
37+
38+
async function main() {
39+
try {
40+
// Create working directory if it doesn't exist
41+
if (!fs.existsSync(workDir)) {
42+
fs.mkdirSync(workDir, { recursive: true });
43+
}
44+
45+
console.log(`Working directory: ${workDir}`);
46+
console.log('Starting package collection...');
47+
48+
let allPackages: string[] = [];
49+
let allScopes: string[] = [];
50+
51+
// Process each repository
52+
for (const repo of repositories) {
53+
console.log(`\nProcessing ${repo.name}...`);
54+
55+
const repoPath = repo.isLocal ? repo.path : path.join(workDir, repo.name);
56+
57+
// Clone or pull the repository
58+
if (!repo.isLocal) {
59+
if (fs.existsSync(repoPath)) {
60+
console.log(`Repository exists, pulling latest changes...`);
61+
await exec(
62+
`cd ${repoPath} && git fetch && git checkout ${repo.branch} && git pull`,
63+
);
64+
} else {
65+
console.log(`Cloning repository...`);
66+
await exec(
67+
`git clone ${repo.url} ${repoPath} --branch ${repo.branch}`,
68+
);
69+
}
70+
}
71+
72+
// Check if the repository uses Lerna
73+
if (fs.existsSync(path.join(repoPath, 'lerna.json'))) {
74+
console.log(`Running npx lerna list in ${repo.name}...`);
75+
try {
76+
const { stdout } = await exec(`cd ${repoPath} && npx lerna list`);
77+
78+
// Parse package names from lerna output
79+
const packages = stdout
80+
.split('\n')
81+
.map((line) => line.trim())
82+
.filter((line) => line.length > 0);
83+
84+
console.log(`Found ${packages.length} packages in ${repo.name}`);
85+
allPackages = [...allPackages, ...packages];
86+
} catch (error) {
87+
console.error(
88+
`Error running lerna list in ${repo.name}: ${error as string}`,
89+
);
90+
}
91+
} else {
92+
console.log(`${repo.name} does not appear to use Lerna. Skipping.`);
93+
}
94+
}
95+
96+
// Sort packages alphabetically and remove duplicates
97+
const sortedPackages = [...new Set(allPackages), ...manualPackages].sort();
98+
99+
// Write to output file
100+
fs.writeFileSync(outputFile, sortedPackages.join('\n') + '\n');
101+
102+
console.log(`\nComplete! Found ${sortedPackages.length} unique packages.`);
103+
console.log(`Results saved to: ${outputFile}`);
104+
} catch (error) {
105+
console.error(`Error: ${error as string}`);
106+
process.exit(1);
107+
}
108+
}
109+
110+
main();

0 commit comments

Comments
 (0)