diff --git a/package-lock.json b/package-lock.json index a0b7f40f..957a74c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31074,7 +31074,8 @@ "bump-monorepo-packages": "bin/bump-packages.js", "depalign": "bin/depalign.js", "monorepo-where": "bin/where.js", - "precommit": "bin/precommit.js" + "precommit": "bin/precommit.js", + "request-npm-token": "bin/request-npm-token.js" }, "devDependencies": { "@mongodb-js/eslint-config-devtools": "0.9.12", diff --git a/package.json b/package.json index 182378a6..75f968e4 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,8 @@ "test-changed": "lerna run test --stream --concurrency 1 --since origin/HEAD", "test-ci": "lerna run test-ci --concurrency 1", "test": "lerna run test --concurrency 1 --stream", - "where": "node ./scripts/src/where.js" + "where": "node ./scripts/src/where.js", + "request-npm-token": "request-npm-token" }, "dependencies": { "@mongodb-js/monorepo-tools": "^1.1.18" diff --git a/packages/monorepo-tools/bin/request-npm-token.js b/packages/monorepo-tools/bin/request-npm-token.js new file mode 100755 index 00000000..0736f736 --- /dev/null +++ b/packages/monorepo-tools/bin/request-npm-token.js @@ -0,0 +1,3 @@ +#!/usr/bin/env node +'use strict'; +require('../dist/request-npm-token.js'); diff --git a/packages/monorepo-tools/package.json b/packages/monorepo-tools/package.json index 538a9925..751ceaad 100644 --- a/packages/monorepo-tools/package.json +++ b/packages/monorepo-tools/package.json @@ -26,7 +26,8 @@ "precommit": "./bin/precommit.js", "depalign": "./bin/depalign.js", "monorepo-where": "./bin/where.js", - "bump-monorepo-packages": "./bin/bump-packages.js" + "bump-monorepo-packages": "./bin/bump-packages.js", + "request-npm-token": "./bin/request-npm-token.js" }, "scripts": { "bootstrap": "npm run compile", diff --git a/packages/monorepo-tools/src/index.ts b/packages/monorepo-tools/src/index.ts index e07bbd69..64f8a2d3 100644 --- a/packages/monorepo-tools/src/index.ts +++ b/packages/monorepo-tools/src/index.ts @@ -7,3 +7,4 @@ export * from './utils/update-package-json'; export * from './utils/with-progress'; export * from './utils/workspace-dependencies'; export * from './utils/get-packages-in-topological-order'; +export * from './utils/get-npm-token-list'; diff --git a/packages/monorepo-tools/src/request-npm-token.ts b/packages/monorepo-tools/src/request-npm-token.ts new file mode 100644 index 00000000..ed76f544 --- /dev/null +++ b/packages/monorepo-tools/src/request-npm-token.ts @@ -0,0 +1,69 @@ +#! /usr/bin/env node +/* eslint-disable no-console */ + +/** + * CLI command to get NPM token requirements for the current monorepo. + * + * Usage: + * npm run request-npm-token + * + * This will output the scopes and packages that need to be included in NPM token permissions. + */ + +import { getNpmTokenList } from './utils/get-npm-token-list'; + +async function main() { + try { + const { scopes, packages } = await getNpmTokenList(); + + console.log( + 'Open an IAMSEC ticket with https://jira.mongodb.org/plugins/servlet/desk/portal/81/create/1380', + ); + + console.log('Use the following description for the ticket:'); + console.log('--------------------------------'); + console.log('Hello,'); + console.log( + 'We need to update the NPM token for publishing our packages. The token needs Read/Write/Publish access to:\n', + ); + + console.log('Following Scopes:'); + if (scopes.length > 0) { + scopes.forEach((scope) => { + console.log(scope); + }); + } else { + console.log('(none)'); + } + + console.log('\nFollowing Packages:'); + if (packages.length > 0) { + packages.forEach((pkg) => { + console.log(pkg); + }); + } else { + console.log('(none)'); + } + + console.log(''); + console.log('Please share it with our team lead: {TEAM LEADER NAME}'); + } catch (error) { + console.error( + 'Error:', + error instanceof Error ? error.message : String(error), + ); + process.exit(1); + } +} + +process.on('unhandledRejection', (err: Error) => { + console.error(); + console.error(err?.stack || err?.message || err); + process.exitCode = 1; +}); + +main().catch((err) => + process.nextTick(() => { + throw err; + }), +); diff --git a/packages/monorepo-tools/src/utils/get-npm-token-list.ts b/packages/monorepo-tools/src/utils/get-npm-token-list.ts new file mode 100644 index 00000000..1a4eaca5 --- /dev/null +++ b/packages/monorepo-tools/src/utils/get-npm-token-list.ts @@ -0,0 +1,43 @@ +import { listAllPackages } from './list-all-packages'; + +export interface NpmTokenRequirements { + scopes: string[]; + packages: string[]; +} + +/** + * Gets all package names and scopes from the current monorepo. + * Returns scoped packages as scopes and unscoped packages as individual packages. + */ +export async function getNpmTokenList(): Promise { + const allPackagesArr = []; + for await (const { packageJson } of listAllPackages()) { + // listAllPackages yields { name: string, ... } + if (packageJson && typeof packageJson?.name === 'string') { + allPackagesArr.push(packageJson.name); + } + } + + // Separate scoped and unscoped packages + const scopedPackages = allPackagesArr.filter( + (pkg) => typeof pkg === 'string' && pkg.startsWith('@'), + ); + const unscopedPackages = allPackagesArr.filter( + (pkg) => typeof pkg === 'string' && !pkg.startsWith('@'), + ); + + // Extract unique scopes from scoped packages + const scopes = [ + ...new Set( + scopedPackages.map((pkg) => { + const scope = pkg.split('/')[0]; + return `${scope}/*`; + }), + ), + ].sort(); + + // Sort unscoped packages + const packages = [...new Set(unscopedPackages)].sort(); + + return { scopes, packages }; +}