Skip to content

Commit b9373fc

Browse files
charliecreates[bot]CharlieHelpsshellscape
authored
fix(versioner): publish with npm via OIDC (#6)
* fix(repo,versioner): publish with npm via OIDC Switch @dot/versioner to pack with pnpm and publish via an OIDC-capable npm CLI. Update the release workflow and .npmrc to drop token-based auth. * fix(versioner): accept --registry for publish * fix(versioner): log and normalize publish registry * fix(repo,versioner): harden npm publish - Fail if pnpm pack emits != 1 tarball. - Add --registry option (default: https://registry.npmjs.org/) and log it. - Release workflow: fetch full git history and publish the triggering SHA. * fix(versioner): validate registry flag * Apply suggestion from @shellscape * fix(versioner): simplify --registry validation * fix(versioner): simplify registry logging --------- Co-authored-by: CharlieHelps <charlie@charlielabs.ai> Co-authored-by: Andrew Powell <shellscape@users.noreply.github.com>
1 parent a094d9d commit b9373fc

3 files changed

Lines changed: 85 additions & 19 deletions

File tree

.github/workflows/release.yml

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
branches:
66
- master
77

8+
permissions:
9+
contents: read
10+
811
jobs:
912
publish:
1013
if: |
@@ -15,29 +18,31 @@ jobs:
1518

1619
name: release
1720

21+
permissions:
22+
contents: write
23+
id-token: write
24+
1825
steps:
19-
- name: Checkout Commit
20-
uses: actions/checkout@v1
26+
- name: Checkout
27+
uses: actions/checkout@v4
28+
with:
29+
fetch-depth: 0
30+
fetch-tags: true
2131

2232
- name: Setup Node
23-
uses: actions/setup-node@v1
33+
uses: actions/setup-node@v4
2434
with:
25-
node-version: 18
35+
node-version: 20
2636

27-
- name: Checkout Master
28-
run: |
29-
git branch -f master origin/master
30-
git checkout master
37+
- name: Install PNPM
38+
uses: pnpm/action-setup@v4
3139

3240
- name: Sanity Check
3341
run: |
3442
echo branch `git branch --show-current`;
3543
echo node `node -v`;
3644
echo pnpm `pnpm -v`
3745
38-
- name: Install PNPM
39-
uses: pnpm/action-setup@v4
40-
4146
- name: Set Git Config
4247
run: |
4348
git config pull.rebase false
@@ -65,8 +70,16 @@ jobs:
6570
# Note: this satisfies aws sdk for @dot/config tests
6671
AWS_REGION: 'us-east-1'
6772

73+
- name: OIDC Preflight
74+
shell: bash
75+
run: |
76+
if [ -z "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ] || [ -z "${ACTIONS_ID_TOKEN_REQUEST_TOKEN:-}" ]; then
77+
echo "Missing GitHub Actions OIDC env vars (ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN)." >&2
78+
echo "Ensure the job requests permissions: id-token: write." >&2
79+
exit 1
80+
fi
81+
82+
echo "OIDC env vars detected."
83+
6884
- name: Release and Publish Changed Packages
6985
run: pnpm --filter [HEAD^] --workspace-concurrency=1 release
70-
env:
71-
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
72-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

.npmrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
2-
31
# npm options
42
auth-type=legacy
53

4+
# Publishing in CI uses GitHub OIDC (npm Trusted Publisher). For local publishing, authenticate via ~/.npmrc.
5+
66
# pnpm options
77
always-auth = true
88
enable-pre-post-scripts = true

packages/versioner/src/versioner.ts

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import 'source-map-support';
22

33
import { dirname, join, resolve } from 'path';
4-
import { existsSync, readFileSync, writeFileSync } from 'fs';
4+
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, writeFileSync } from 'fs';
5+
import { tmpdir } from 'os';
56

67
import { getLog } from '@dot/log';
78
import parser from 'conventional-commits-parser';
@@ -25,6 +26,8 @@ const parserOptions = {
2526
noteKeywords: ['BREAKING CHANGE', 'Breaking Change']
2627
};
2728
const reBreaking = new RegExp(`(${parserOptions.noteKeywords.join(')|(')})`);
29+
const NPM_CLI_SPEC = 'npm@11.5.1';
30+
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
2831

2932
type Commit = parser.Commit<string | number | symbol>;
3033

@@ -151,9 +154,59 @@ const publish = async (cwd: string) => {
151154
return;
152155
}
153156

154-
log.info(chalk`\n{cyan Publishing to NPM}`);
157+
const registryOverrideRaw = argv.registry;
158+
if (argv.registry && typeof argv.registry !== 'string') {
159+
throw new TypeError(
160+
`--registry must be a string (e.g. "${DEFAULT_NPM_REGISTRY}"), received ${typeof registryOverrideRaw}: ${String(
161+
argv.registry
162+
)}`
163+
);
164+
}
165+
166+
const registry = argv.registry || DEFAULT_NPM_REGISTRY;
155167

156-
await execa('pnpm', ['publish', '--no-git-checks'], { cwd, stdio: 'inherit' });
168+
log.info(chalk`\n{cyan Publishing to NPM}: {grey ${registry}}`);
169+
170+
const packDir = mkdtempSync(join(tmpdir(), 'versioner-pack-'));
171+
try {
172+
await execa('pnpm', ['pack', '--pack-destination', packDir], { cwd, stdio: 'inherit' });
173+
174+
const tarballs = readdirSync(packDir)
175+
.filter((file) => file.endsWith('.tgz'))
176+
.sort();
177+
178+
if (tarballs.length !== 1) {
179+
throw new RangeError(
180+
`Expected exactly 1 packed tarball in: ${packDir} for cwd=${cwd} (found ${
181+
tarballs.length
182+
}): ${tarballs.join(', ')}`
183+
);
184+
}
185+
186+
const tarballPath = join(packDir, tarballs[0]);
187+
const hasOidcEnv =
188+
!!process.env.ACTIONS_ID_TOKEN_REQUEST_URL && !!process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
189+
const provenanceArgs = hasOidcEnv ? ['--provenance'] : [];
190+
191+
log.info(chalk`{grey Using npm CLI:} ${NPM_CLI_SPEC}`);
192+
193+
await execa(
194+
'pnpm',
195+
[
196+
'dlx',
197+
NPM_CLI_SPEC,
198+
'publish',
199+
'--no-git-checks',
200+
'--registry',
201+
registry,
202+
...provenanceArgs,
203+
tarballPath
204+
],
205+
{ cwd, stdio: 'inherit' }
206+
);
207+
} finally {
208+
rmSync(packDir, { force: true, recursive: true });
209+
}
157210
};
158211

159212
const pull = async () => {

0 commit comments

Comments
 (0)