@@ -58,14 +58,31 @@ export async function publish(args) {
5858 logger . info ( `Package: ${ packageJson . name } @${ packageJson . version } ` ) ;
5959 logger . info ( `Build time: ${ buildTime } ` ) ;
6060 logger . info ( `Short SHA: ${ shortSha } ` ) ;
61- logger . info ( `Token: '${ obfuscateToken ( args . accessToken ) } '` ) ;
61+ if ( args . useOidc ) {
62+ logger . info ( `Authentication: OIDC (Trusted Publishing)` ) ;
63+ } else {
64+ logger . info ( `Token: '${ obfuscateToken ( args . accessToken ) } '` ) ;
65+ }
6266 logger . info ( `Repository: ${ repoUrl } ` ) ;
6367 logger . info ( `Last commit message: ${ commitMessage } ` ) ;
6468 logger . info ( `Last commit author: ${ commitAuthorWithEmail } ` ) ;
6569 logger . info ( `Registry: ${ args . registry || 'https://registry.npmjs.org/' } ` ) ;
6670
67- if ( ! args . accessToken ?. length ) {
68- logger . warn ( `No access token provided. Publishing to registry ${ args . registry } may fail.` ) ;
71+ if ( ! args . useOidc && ! args . accessToken ?. length ) {
72+ logger . warn ( `No access token provided and OIDC not enabled. Publishing to registry ${ args . registry } may fail.` ) ;
73+ }
74+ if ( args . useOidc ) {
75+ logger . info ( `OIDC authentication enabled. Ensure your CI/CD has id-token: write permissions and a trusted publisher is configured on npmjs.com.` ) ;
76+ // Check npm version for OIDC support
77+ const npmVersionResult = tryExecSync ( 'npm --version' , { cwd : packageDirectory } ) ;
78+ if ( npmVersionResult . success ) {
79+ const npmVersion = npmVersionResult . output . trim ( ) ;
80+ const [ major , minor ] = npmVersion . split ( '.' ) . map ( Number ) ;
81+ logger . info ( `npm version: ${ npmVersion } ` ) ;
82+ if ( major < 11 || ( major === 11 && minor < 5 ) ) {
83+ logger . warn ( `⚠ npm version ${ npmVersion } may not support OIDC. Version 11.5+ is recommended.` ) ;
84+ }
85+ }
6986 }
7087
7188 // Remove slahes from the end of the tag (this may happen if the tag is provided by github ref_name
@@ -126,7 +143,7 @@ export async function publish(args) {
126143 msg += `Commit URL: ${ repoUrl } /commit/${ shortSha } \n` ;
127144 msg += `Build time: ${ buildTime } \n` ;
128145 msg += `Registry: ${ args . registry } \n` ;
129- msg += `Token : ${ obfuscateToken ( args . accessToken ) } \n` ;
146+ msg += `Auth : ${ args . useOidc ? 'OIDC (Trusted Publishing)' : obfuscateToken ( args . accessToken ) } \n` ;
130147 msg += `Tag: ${ args . tag || '-' } ${ args . useTagInVersion ? ' (version+tag)' : '' } ${ args . createGitTag ? ' (creating git tag)' : '' } \n` ;
131148 msg += "```" ;
132149 await sendMessageToWebhook ( webhook , msg , { logger } ) ;
@@ -197,13 +214,23 @@ export async function publish(args) {
197214 // Default env
198215 const env = {
199216 ...process . env ,
200- NPM_TOKEN : args . accessToken || undefined ,
201217 NPM_CONFIG_REGISTRY : ( args . registry || 'https://registry.npmjs.org/' ) ,
202218 }
203219
220+ // For OIDC: completely unset token variables so npm falls back to OIDC
221+ // An empty string is still a value - npm will try to use it instead of OIDC
222+ if ( args . useOidc ) {
223+ delete env . NPM_TOKEN ;
224+ delete env . NODE_AUTH_TOKEN ;
225+ delete env . NPM_CONFIG__AUTH ;
226+ delete env . NPM_CONFIG_TOKEN ;
227+ } else {
228+ env . NPM_TOKEN = args . accessToken || undefined ;
229+ }
230+
204231
205232 // set config
206- {
233+ if ( ! args . useOidc ) {
207234 let registryUrlWithoutScheme = ( args . registry || 'https://registry.npmjs.org/' ) . replace ( / h t t p s ? : \/ \/ / , '' ) ;
208235 if ( ! registryUrlWithoutScheme . endsWith ( '/' ) ) registryUrlWithoutScheme += '/' ;
209236
@@ -223,6 +250,45 @@ export async function publish(args) {
223250 // env
224251 // });
225252 // }
253+ } else {
254+ logger . info ( `Skipping npm config auth token setup - using OIDC authentication` ) ;
255+
256+ // Check for OIDC environment variables (GitHub Actions sets these)
257+ const hasOidcEnv = ! ! ( process . env . ACTIONS_ID_TOKEN_REQUEST_URL && process . env . ACTIONS_ID_TOKEN_REQUEST_TOKEN ) ;
258+ if ( hasOidcEnv ) {
259+ logger . info ( `✓ GitHub Actions OIDC environment detected` ) ;
260+ logger . info ( ` ACTIONS_ID_TOKEN_REQUEST_URL: ${ process . env . ACTIONS_ID_TOKEN_REQUEST_URL ? '(set)' : '(not set)' } ` ) ;
261+ logger . info ( ` ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${ process . env . ACTIONS_ID_TOKEN_REQUEST_TOKEN ? '(set)' : '(not set)' } ` ) ;
262+ } else {
263+ logger . warn ( `⚠ GitHub Actions OIDC environment variables not detected!` ) ;
264+ logger . warn ( ` This may indicate:` ) ;
265+ logger . warn ( ` - The workflow doesn't have 'id-token: write' permission` ) ;
266+ logger . warn ( ` - Not running in GitHub Actions` ) ;
267+ logger . warn ( ` - Running on a self-hosted runner without OIDC support` ) ;
268+ }
269+
270+ // Log token-related env vars that might interfere with OIDC
271+ // npm won't use OIDC if ANY token variable is set (even to empty string)
272+ const tokenVars = [ 'NPM_TOKEN' , 'NODE_AUTH_TOKEN' , 'NPM_CONFIG__AUTH' , 'NPM_CONFIG_TOKEN' ] ;
273+ const setTokenVars = tokenVars . filter ( v => v in env ) ;
274+ if ( setTokenVars . length > 0 ) {
275+ logger . warn ( `⚠ Token environment variables detected that may interfere with OIDC: ${ setTokenVars . join ( ', ' ) } ` ) ;
276+ logger . info ( ` Removing these variables to allow OIDC fallback...` ) ;
277+ } else {
278+ logger . info ( `✓ No conflicting token environment variables detected` ) ;
279+ }
280+
281+ // Clear any existing auth tokens that might interfere with OIDC
282+ // npm OIDC works best when there's no conflicting token configuration
283+ let registryUrlWithoutScheme = ( args . registry || 'https://registry.npmjs.org/' ) . replace ( / h t t p s ? : \/ \/ / , '' ) ;
284+ if ( ! registryUrlWithoutScheme . endsWith ( '/' ) ) registryUrlWithoutScheme += '/' ;
285+ try {
286+ const clearCmd = `npm config delete //${ registryUrlWithoutScheme } :_authToken` ;
287+ logger . info ( `Clearing any existing auth token for OIDC: ${ clearCmd } ` ) ;
288+ tryExecSync ( clearCmd , { cwd : packageDirectory , env } ) ;
289+ } catch {
290+ // Ignore errors - token might not exist
291+ }
226292 }
227293
228294 const htmlUrl = args . registry ?. includes ( "npmjs" ) ? `https://www.npmjs.com/package/${ packageJson . name } /v/${ packageJson . version } ` : ( args . registry + `/${ packageJson . name } ` ) ;
@@ -257,7 +323,12 @@ export async function publish(args) {
257323 logger . info ( `Package view result ${ packageVersionPublished } ` ) ;
258324
259325
260- let cmd = `npm publish --access public`
326+ const registryUrl = args . registry || 'https://registry.npmjs.org/' ;
327+ let cmd = `npm publish --access public --registry ${ registryUrl } `
328+ if ( args . useOidc ) {
329+ cmd += ' --provenance' ;
330+ logger . info ( `OIDC authentication enabled, adding --provenance flag for trusted publishing.` ) ;
331+ }
261332 if ( dryRun ) {
262333 cmd += ' --dry-run' ;
263334 logger . info ( `Dry run mode enabled, not actually publishing package.` ) ;
@@ -294,8 +365,84 @@ export async function publish(args) {
294365 }
295366 else {
296367 logger . error ( `❌ Failed to publish package ${ packageJson . name } @${ packageJson . version } \n${ res . error } ` ) ;
368+
369+ // Provide helpful OIDC troubleshooting information if OIDC was enabled
370+ if ( args . useOidc ) {
371+ const errorStr = res . error ?. toString ( ) || res . output ?. toString ( ) || '' ;
372+ const is404Error = errorStr . includes ( '404' ) || errorStr . includes ( 'Not found' ) || errorStr . includes ( 'Not Found' ) ;
373+ const is401Error = errorStr . includes ( '401' ) || errorStr . includes ( 'Unauthorized' ) ;
374+ const is403Error = errorStr . includes ( '403' ) || errorStr . includes ( 'Forbidden' ) ;
375+ const isNeedAuthError = errorStr . includes ( 'ENEEDAUTH' ) || errorStr . includes ( 'need auth' ) || errorStr . includes ( 'You need to authorize' ) ;
376+
377+ logger . error ( `\n${ '=' . repeat ( 80 ) } ` ) ;
378+ logger . error ( `OIDC TROUBLESHOOTING GUIDE` ) ;
379+ logger . error ( `${ '=' . repeat ( 80 ) } ` ) ;
380+
381+ if ( isNeedAuthError ) {
382+ logger . error ( `\n⚠️ ERROR: ENEEDAUTH - Authentication Required` ) ;
383+ logger . error ( ` npm is not detecting the OIDC environment. This usually means:\n` ) ;
384+ logger . error ( ` a) MISSING PERMISSIONS: Workflow needs 'id-token: write' permission` ) ;
385+ logger . error ( ` Add to your workflow:` ) ;
386+ logger . error ( ` permissions:` ) ;
387+ logger . error ( ` contents: read` ) ;
388+ logger . error ( ` id-token: write\n` ) ;
389+ logger . error ( ` b) NPM VERSION TOO OLD: OIDC requires npm >= 11.5` ) ;
390+ logger . error ( ` Update Node.js to v22+ or run: npm install -g npm@latest\n` ) ;
391+ logger . error ( ` c) NOT IN GITHUB ACTIONS: OIDC only works in supported CI environments` ) ;
392+ logger . error ( ` Currently supported: GitHub Actions, GitLab CI/CD\n` ) ;
393+ logger . error ( ` d) SELF-HOSTED RUNNER: OIDC requires cloud-hosted runners\n` ) ;
394+ logger . error ( ` Environment check:` ) ;
395+ logger . error ( ` ACTIONS_ID_TOKEN_REQUEST_URL: ${ process . env . ACTIONS_ID_TOKEN_REQUEST_URL ? '✓ set' : '✗ NOT SET' } ` ) ;
396+ logger . error ( ` ACTIONS_ID_TOKEN_REQUEST_TOKEN: ${ process . env . ACTIONS_ID_TOKEN_REQUEST_TOKEN ? '✓ set' : '✗ NOT SET' } ` ) ;
397+ logger . error ( ` GITHUB_ACTIONS: ${ process . env . GITHUB_ACTIONS ? '✓ set' : '✗ NOT SET' } \n` ) ;
398+ } else if ( is404Error ) {
399+ logger . error ( `\n⚠️ ERROR: 404 Not Found` ) ;
400+ logger . error ( ` This usually means one of the following:\n` ) ;
401+ logger . error ( ` a) FIRST-TIME PUBLISH: The package doesn't exist on npmjs.com yet.` ) ;
402+ logger . error ( ` → First publish MUST be done with a token: --access-token <token>` ) ;
403+ logger . error ( ` → After first publish, configure trusted publisher, then use --oidc\n` ) ;
404+ logger . error ( ` b) TRUSTED PUBLISHER NOT CONFIGURED for this package.` ) ;
405+ logger . error ( ` → Go to: https://www.npmjs.com/package/${ packageJson . name } /access` ) ;
406+ logger . error ( ` → Click "Settings" → "Trusted Publisher" → "GitHub Actions"` ) ;
407+ logger . error ( ` → Configure: organization/user, repository, workflow filename\n` ) ;
408+ logger . error ( ` c) WORKFLOW FILENAME MISMATCH` ) ;
409+ logger . error ( ` → The workflow filename on npmjs.com must EXACTLY match your .yml file` ) ;
410+ logger . error ( ` → Check for typos, case sensitivity, and path differences\n` ) ;
411+ } else if ( is401Error ) {
412+ logger . error ( `\n⚠️ ERROR: 401 Unauthorized` ) ;
413+ logger . error ( ` The OIDC token was not accepted. Check:\n` ) ;
414+ logger . error ( ` a) GitHub Actions must have 'id-token: write' permission` ) ;
415+ logger . error ( ` b) Trusted publisher must be configured on npmjs.com` ) ;
416+ logger . error ( ` c) npm version must be >= 11.5\n` ) ;
417+ } else if ( is403Error ) {
418+ logger . error ( `\n⚠️ ERROR: 403 Forbidden` ) ;
419+ logger . error ( ` Access denied. Check:\n` ) ;
420+ logger . error ( ` a) You have publish permissions for this package` ) ;
421+ logger . error ( ` b) Trusted publisher configuration matches your workflow exactly` ) ;
422+ logger . error ( ` c) Repository owner/name matches the trusted publisher config\n` ) ;
423+ } else {
424+ logger . error ( `\nOIDC authentication failed. Please check the following:\n` ) ;
425+ }
426+
427+ logger . error ( `CHECKLIST:` ) ;
428+ logger . error ( ` □ Package exists on npmjs.com (first publish requires --access-token)` ) ;
429+ logger . error ( ` □ Trusted publisher configured: https://www.npmjs.com/package/${ packageJson . name } /access` ) ;
430+ logger . error ( ` □ Workflow has 'id-token: write' permission` ) ;
431+ logger . error ( ` □ npm version >= 11.5 (current: run 'npm --version' to check)` ) ;
432+ logger . error ( ` □ Using cloud-hosted runner (not self-hosted)` ) ;
433+ logger . error ( ` □ Workflow filename matches exactly (case-sensitive)\n` ) ;
434+ logger . error ( `DOCUMENTATION:` ) ;
435+ logger . error ( ` → npm Trusted Publishing: https://docs.npmjs.com/trusted-publishers/` ) ;
436+ logger . error ( ` → npm Provenance: https://docs.npmjs.com/generating-provenance-statements/` ) ;
437+ logger . error ( `${ '=' . repeat ( 80 ) } \n` ) ;
438+ }
439+
297440 if ( webhook ) {
298- await sendMessageToWebhookWithCodeblock ( webhook , `❌ **Failed to publish package** \`${ packageJson . name } @${ packageJson . version } \`:` , res . error , { logger } ) ;
441+ let errorMsg = `❌ **Failed to publish package** \`${ packageJson . name } @${ packageJson . version } \`:` ;
442+ if ( args . useOidc ) {
443+ errorMsg += `\n⚠️ OIDC was enabled. See logs for troubleshooting guide or visit: https://docs.npmjs.com/trusted-publishers/` ;
444+ }
445+ await sendMessageToWebhookWithCodeblock ( webhook , errorMsg , res . error , { logger } ) ;
299446 }
300447 throw new Error ( `Failed to publish package ${ packageJson . name } @${ packageJson . version } : ${ res . error } ` ) ;
301448 }
0 commit comments