11import { resolve } from 'node:path' ;
2+ import { existsSync , readFileSync , writeFileSync , appendFileSync } from 'node:fs' ;
23import { unlink } from 'node:fs/promises' ;
34import { readJson , writeJson } from '../utils/fs.ts' ;
45import { runAsync } from '../utils/shell.ts' ;
6+ import { tryRun } from '../utils/shell.ts' ;
57import { log , colorize } from '../utils/logger.ts' ;
68import { createTag , tagExists } from './git.ts' ;
79import { DependencyGraph } from './dep-graph.ts' ;
@@ -20,6 +22,79 @@ export interface PublishResult {
2022 failed : { name : string ; error : string } [ ] ;
2123}
2224
25+ /** Check if GitHub Actions OIDC is available (id-token: write permission granted) */
26+ function isOidcAvailable ( ) : boolean {
27+ return ! ! process . env . ACTIONS_ID_TOKEN_REQUEST_URL ;
28+ }
29+
30+ /**
31+ * Set up npm authentication for publishing.
32+ *
33+ * Handles three scenarios:
34+ * 1. **Trusted publishing (OIDC)** — GitHub Actions with `id-token: write`.
35+ * npm >= 11.5.1 authenticates automatically via OIDC token exchange.
36+ * No secret needed, but we check the npm version and warn if too old.
37+ * 2. **Token-based auth** — `NPM_TOKEN` or `NODE_AUTH_TOKEN` env var.
38+ * Writes a project-level `.npmrc` so npm can authenticate.
39+ * 3. **Pre-configured** — user already has `.npmrc` with auth (e.g. via `actions/setup-node`).
40+ */
41+ function setupNpmAuth ( rootDir : string , publishManager : string ) : void {
42+ // Only relevant when publishing via npm CLI
43+ if ( publishManager !== 'npm' ) return ;
44+
45+ const npmrcPath = resolve ( rootDir , '.npmrc' ) ;
46+ const existingNpmrc = existsSync ( npmrcPath ) ? readFileSync ( npmrcPath , 'utf-8' ) : '' ;
47+ const hasAuthConfigured = existingNpmrc . includes ( ':_authToken=' ) ;
48+
49+ // If auth is already configured (e.g. via actions/setup-node), nothing to do
50+ if ( hasAuthConfigured ) {
51+ log . dim ( ' Using existing .npmrc auth configuration' ) ;
52+ return ;
53+ }
54+
55+ // Scenario 1: OIDC trusted publishing
56+ if ( isOidcAvailable ( ) ) {
57+ const npmVersion = tryRun ( 'npm --version' ) ;
58+ if ( npmVersion ) {
59+ const [ major , minor , patch ] = npmVersion . split ( '.' ) . map ( Number ) ;
60+ const meetsMinVersion = major ! > 11 || ( major === 11 && ( minor ! > 5 || ( minor === 5 && patch ! >= 1 ) ) ) ;
61+ if ( ! meetsMinVersion ) {
62+ log . warn ( ` npm ${ npmVersion } detected — trusted publishing (OIDC) requires npm >= 11.5.1` ) ;
63+ log . warn ( ' Add "npm install -g npm@latest" to your workflow before publishing' ) ;
64+ } else {
65+ log . dim ( ` OIDC detected — npm ${ npmVersion } will authenticate via trusted publishing` ) ;
66+ }
67+ }
68+ return ;
69+ }
70+
71+ // Scenario 2: Token-based auth via environment variable
72+ // Support NPM_TOKEN (common convention) by mapping to NODE_AUTH_TOKEN (what npm reads from .npmrc)
73+ const token = process . env . NODE_AUTH_TOKEN || process . env . NPM_TOKEN ;
74+ if ( token ) {
75+ if ( process . env . NPM_TOKEN && ! process . env . NODE_AUTH_TOKEN ) {
76+ process . env . NODE_AUTH_TOKEN = token ;
77+ }
78+ const authLine = '//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}' ;
79+ if ( existingNpmrc ) {
80+ appendFileSync ( npmrcPath , `\n${ authLine } \n` ) ;
81+ } else {
82+ writeFileSync ( npmrcPath , `${ authLine } \n` ) ;
83+ }
84+ log . dim ( ' Configured .npmrc with auth token' ) ;
85+ return ;
86+ }
87+
88+ // No auth detected — warn
89+ if ( process . env . CI ) {
90+ log . warn ( ' No npm authentication detected. Publishing will likely fail.' ) ;
91+ log . warn ( ' Options:' ) ;
92+ log . warn ( ' • Trusted publishing (OIDC): add `id-token: write` permission + npm >= 11.5.1' ) ;
93+ log . warn ( ' • Token auth: set NPM_TOKEN or NODE_AUTH_TOKEN environment variable' ) ;
94+ log . warn ( ' • Manual: add `actions/setup-node` with `registry-url` to your workflow' ) ;
95+ }
96+ }
97+
2398/**
2499 * Publish all packages in the release plan.
25100 * Order: topological (dependencies published before dependents).
@@ -37,6 +112,9 @@ export async function publishPackages(
37112 const result : PublishResult = { published : [ ] , skipped : [ ] , failed : [ ] } ;
38113 const publishConfig = config . publish ;
39114
115+ // Set up npm authentication before publishing
116+ setupNpmAuth ( rootDir , publishConfig . publishManager ) ;
117+
40118 // Resolve "auto" pack manager to detected PM
41119 const packManager = publishConfig . packManager === 'auto' ? detectedPm : publishConfig . packManager ;
42120
0 commit comments