diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d9206753f372..45ab8a4e5af8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -116,6 +116,8 @@ jobs: # Secrets # Note: must use personal access token GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }} + # The prisma package cannot use trusted publishing and requires the Prisma bot token. + PRISMABOT_NPM_TOKEN: ${{ secrets.PRISMABOT_NPM_TOKEN }} REDIS_URL: ${{ secrets.REDIS_URL }} SLACK_RELEASE_FEED_WEBHOOK: ${{ secrets.SLACK_RELEASE_FEED_WEBHOOK }} diff --git a/scripts/ci/publish.ts b/scripts/ci/publish.ts index 0207d94ebe35..84fb854405e1 100644 --- a/scripts/ci/publish.ts +++ b/scripts/ci/publish.ts @@ -15,6 +15,29 @@ import semver from 'semver' const onlyPackages = process.env.ONLY_PACKAGES ? process.env.ONLY_PACKAGES.split(',') : null const skipPackages = process.env.SKIP_PACKAGES ? process.env.SKIP_PACKAGES.split(',') : null +async function validatePrismaBotNpmToken(token: string): Promise { + let response: Response + try { + response = await fetch('https://registry.npmjs.org/-/whoami', { + headers: { + authorization: `Bearer ${token}`, + }, + }) + } catch (error) { + throw new Error(`Could not validate PRISMABOT_NPM_TOKEN with the npm registry: ${String(error)}`) + } + + if (response.status === 401 || response.status === 403) { + throw new Error( + 'PRISMABOT_NPM_TOKEN is invalid, expired, or revoked. Create a new npm access token and update the PRISMABOT_NPM_TOKEN GitHub Actions secret before publishing.', + ) + } + + if (!response.ok) { + throw new Error(`Could not validate PRISMABOT_NPM_TOKEN with the npm registry (HTTP ${response.status})`) + } +} + async function getLatestCommitHash(dir: string): Promise { if (process.env.GITHUB_CONTEXT) { const context = JSON.parse(process.env.GITHUB_CONTEXT) @@ -51,7 +74,13 @@ async function runResult(cwd: string, cmd: string): Promise { * @param cwd cwd for running the command * @param cmd command to run */ -async function run(cwd: string, cmd: string, dry = false, hidden = false): Promise { +async function run( + cwd: string, + cmd: string, + dry = false, + hidden = false, + envOverrides: NodeJS.ProcessEnv = {}, +): Promise { const args = [underline('./' + cwd).padEnd(20), bold(cmd)] if (dry) { args.push(dim('(dry)')) @@ -70,6 +99,7 @@ async function run(cwd: string, cmd: string, dry = false, hidden = false): Promi shell: true, env: { ...process.env, + ...envOverrides, }, }) } catch (_e) { @@ -716,6 +746,15 @@ async function publishPackages( const publishStr = dryRun ? `${bold('Dry publish')} ` : releaseVersion ? 'Releasing ' : 'Publishing ' + let prismaBotNpmToken: string | undefined + if (!dryRun && !isSkipped('prisma')) { + prismaBotNpmToken = process.env.PRISMABOT_NPM_TOKEN + if (!prismaBotNpmToken) { + throw new Error('Missing env var PRISMABOT_NPM_TOKEN required to publish the prisma package') + } + await validatePrismaBotNpmToken(prismaBotNpmToken) + } + if (releaseVersion) { console.log(red(bold(`RELEASE. This will release ${underline(releaseVersion)} on latest!!!`))) if (dryRun) { @@ -816,7 +855,12 @@ async function publishPackages( * - Your working directory is clean (there are no uncommitted changes). * - The branch is up-to-date. */ - await run(pkgDir, `pnpm publish --no-git-checks --access public --tag ${tag}`, dryRun) + const publishEnv: NodeJS.ProcessEnv = {} + if (pkgName === 'prisma' && prismaBotNpmToken) { + publishEnv.NODE_AUTH_TOKEN = prismaBotNpmToken + } + + await run(pkgDir, `pnpm publish --no-git-checks --access public --tag ${tag}`, dryRun, false, publishEnv) } } }