Skip to content

Commit 5cc96e7

Browse files
authored
chore(NODE-7562): use npm trusted publisher for nightly release (#4930)
1 parent 2dae422 commit 5cc96e7

4 files changed

Lines changed: 129 additions & 3 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// @ts-check
2+
// TODO(NODE-7570): replace with workflow_call once GitHub/npm resolve the OIDC
3+
// workflow_ref mismatch (https://github.com/npm/documentation/issues/1755).
4+
//
5+
// Dispatch a workflow_dispatch-triggered GitHub Actions workflow and wait for
6+
// the resulting run to complete (propagating its exit status).
7+
//
8+
// `gh workflow run` prints the created workflow run URL on stdout when the
9+
// server returns it (current github.com API version does); we parse the run
10+
// id out of the URL and pass it to `gh run watch`.
11+
//
12+
// Usage:
13+
// node dispatch-and-wait.mjs <workflow.yml> [key=value ...]
14+
//
15+
// For npm-publish.yml specifically:
16+
// node dispatch-and-wait.mjs npm-publish.yml tag=<tag> version=<v> ref=<sha>
17+
//
18+
// Arguments:
19+
// <workflow.yml> Filename of the target workflow under .github/workflows/
20+
// in the same repo. Must declare `on: workflow_dispatch:`.
21+
// key=value ... Inputs forwarded to the dispatched workflow. Valid keys
22+
// and which of them are required are determined by the
23+
// target workflow's `on.workflow_dispatch.inputs`; the
24+
// dispatch fails if any required input is missing or any
25+
// unknown input is passed. For `npm-publish.yml`, all of
26+
// `tag`, `version`, and `ref` are required.
27+
// Example: tag=nightly version=1.2.3 ref=abc1234
28+
//
29+
// Environment:
30+
// GH_TOKEN (required) used by the gh CLI; in a workflow set
31+
// this to ${{ github.token }}.
32+
// DISPATCH_WORKFLOW_REF (optional, default `main`) git ref the target
33+
// workflow file is loaded from. Hardcoded to main by
34+
// default so callers on backport branches don't have
35+
// to keep a copy of the target workflow on their
36+
// branch.
37+
import * as child_process from 'node:child_process';
38+
import * as process from 'node:process';
39+
40+
const [, , workflow, ...inputArgs] = process.argv;
41+
if (!workflow) {
42+
console.error('usage: dispatch-and-wait.mjs <workflow.yml> [key=value ...]');
43+
process.exit(2);
44+
}
45+
46+
const dispatchRef = process.env.DISPATCH_WORKFLOW_REF || 'main';
47+
48+
const ghArgs = [
49+
'workflow', 'run', workflow,
50+
'--ref', dispatchRef,
51+
...inputArgs.flatMap(kv => ['-f', kv])
52+
];
53+
console.log(`Dispatching ${workflow} from ref ${dispatchRef}`);
54+
55+
const dispatch = child_process.spawnSync('gh', ghArgs, {
56+
encoding: 'utf8',
57+
stdio: ['inherit', 'pipe', 'inherit']
58+
});
59+
if (dispatch.status !== 0) process.exit(dispatch.status ?? 1);
60+
61+
// gh prints e.g. "https://github.com/owner/repo/actions/runs/<id>"
62+
const match = dispatch.stdout.match(/\/actions\/runs\/(\d+)/);
63+
if (!match) {
64+
console.error('Could not extract run id from gh workflow run output:', dispatch.stdout);
65+
process.exit(1);
66+
}
67+
const runId = match[1];
68+
console.log(`Dispatched run ${runId}`);
69+
70+
const watch = child_process.spawnSync('gh', ['run', 'watch', runId, '--exit-status'], {
71+
stdio: 'inherit'
72+
});
73+
process.exit(watch.status ?? 1);

.github/workflows/npm-publish.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# This workflow is invoked by callers via `workflow_dispatch` (the GitHub
2+
# Actions API), NOT as a reusable workflow (`on: workflow_call:`). With
3+
# workflow_call, the OIDC token's workflow_ref claim points to the caller's
4+
# filename rather than this file, which breaks npm Trusted Publishing's
5+
# workflow-filename matching. See npm/documentation#1755.
6+
name: npm-publish
7+
8+
run-name: 'npm-publish ${{ inputs.tag }}@${{ inputs.version }}'
9+
10+
on:
11+
workflow_dispatch:
12+
inputs:
13+
tag:
14+
description: 'npm dist-tag (e.g. nightly, latest, alpha)'
15+
required: true
16+
type: string
17+
version:
18+
description: 'Package version to publish'
19+
required: true
20+
type: string
21+
ref:
22+
description: 'Git ref (commit SHA preferred) to publish from'
23+
required: true
24+
type: string
25+
26+
permissions:
27+
id-token: write
28+
contents: read
29+
30+
jobs:
31+
publish:
32+
runs-on: ubuntu-latest
33+
steps:
34+
- uses: actions/checkout@v5
35+
with:
36+
ref: ${{ inputs.ref }}
37+
- name: Install Node and dependencies
38+
uses: mongodb-labs/drivers-github-tools/node/setup@v3
39+
- run: npm version "${{ inputs.version }}" --git-tag-version=false --allow-same-version
40+
- run: npm publish --provenance --tag="${{ inputs.tag }}"

.github/workflows/release-nightly.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ on:
1111
workflow_dispatch: {}
1212

1313
permissions:
14-
id-token: write
14+
contents: read
15+
actions: write
1516

1617
name: release-nightly
1718

@@ -25,6 +26,12 @@ jobs:
2526
- id: build_nightly
2627
run: npm run build:nightly
2728
- if: ${{ steps.build_nightly.outputs.publish == 'yes' }}
28-
run: npm publish --provenance --tag=nightly
29+
name: Dispatch npm-publish workflow
2930
env:
30-
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
31+
GH_TOKEN: ${{ github.token }}
32+
DISPATCH_WORKFLOW_REF: ${{ github.sha }}
33+
run: |
34+
node ./.github/scripts/dispatch-and-wait.mjs npm-publish.yml \
35+
tag=nightly \
36+
version="$(node -p "require('./package.json').version")" \
37+
ref="${{ github.sha }}"

etc/notes/releasing.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,12 @@ The github action is able to publish with the repository secret `NPM_TOKEN`.
104104
This is a granular API key that is unique to each package and has to be rotated on a regular basis.
105105
The `dbx-node@mongodb.com` npm account is the author of the automated release.
106106

107+
The nightly release flow is an exception: `release-nightly.yml` dispatches
108+
`.github/workflows/npm-publish.yml`, which authenticates to the npm registry
109+
via [npm Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC)
110+
rather than `NPM_TOKEN`. The `mongodb` package's trusted publisher entry on
111+
npmjs.com points at `npm-publish.yml`.
112+
107113
### Prebuilds
108114

109115
Our native packages offer pre built binaries using [`prebuild`](https://github.com/prebuild/prebuild).

0 commit comments

Comments
 (0)