Skip to content

chore: use NPM trusted publishing - #3071

Open
Saadnajmi with Copilot wants to merge 5 commits into
mainfrom
copilot/switch-to-github-actions
Open

chore: use NPM trusted publishing#3071
Saadnajmi with Copilot wants to merge 5 commits into
mainfrom
copilot/switch-to-github-actions

Conversation

Copilot AI commented Jul 31, 2026

Copy link
Copy Markdown

Replaces the ADO-based npm publish pipeline with a GitHub Actions workflow that uses npm Trusted Publishing (OIDC) for the main publish step — no stored npm token required for npm publish.

Changes

  • .github/workflows/microsoft-npm-publish.yml — New workflow triggered on *-stable branch pushes. Runs in an npm-publish GitHub environment (add protection rules there). Uses id-token: write + --provenance so yarn exchanges a GitHub OIDC token directly with npmjs.com rather than a stored secret. The npm dist-tag add step (applying additional dist-tags) still requires a NPM_TOKEN secret since OIDC doesn't cover that operation.

  • .ado/scripts/configure-publish.mts — Added enablePublishingOnGitHubActions() that writes publish_react_native_macos=1 to GITHUB_OUTPUT, so the new workflow can gate its publish steps on this output. Previously only the ADO ##vso[task.setvariable...] signal was emitted.

  • .ado/scripts/apply-additional-tags.mjs — Token can now come from NODE_AUTH_TOKEN env var as a fallback to --token. When using the env-var path (GHA), the token is not passed as a CLI argument — actions/setup-node has already wired NODE_AUTH_TOKEN into .npmrc, so npm dist-tag add picks it up from there. The --token CLI arg path (ADO) is unchanged.

Manual steps required before this workflow runs

  1. npmjs.com: add a Trusted Publisher for react-native-macos and @react-native-macos/virtualized-lists — repo microsoft/react-native-macos, workflow microsoft-npm-publish.yml, environment npm-publish
  2. GitHub repo Settings → Environments: create the npm-publish environment with desired approval rules
  3. GitHub secret: add NPM_TOKEN to that environment (granular automation token scoped to the two packages, used only for dist-tag operations)
  4. ADO: once verified, disable .ado/publish.yml

- Add enablePublishingOnGitHubActions() to configure-publish.mts so the
  new GHA workflow can gate publish steps via GITHUB_OUTPUT
- Update apply-additional-tags.mjs to fall back to NODE_AUTH_TOKEN env
  var when --token is not provided; avoids token-in-process-args on GHA
- Add .github/workflows/microsoft-npm-publish.yml: OIDC trusted publishing
  on *-stable branch pushes, with npm-publish environment protection
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@Saadnajmi Saadnajmi changed the title Switch npm publishing from Azure Pipelines to GitHub Actions with npm Trusted Publishing chore: use npm Trusted Publishing Jul 31, 2026
@Saadnajmi
Saadnajmi marked this pull request as ready for review July 31, 2026 16:19
@Saadnajmi
Saadnajmi requested a review from a team as a code owner July 31, 2026 16:19
Comment on lines +3 to +4
# Mirrors the trigger used by the Azure Pipelines publish.yml:
# runs on every push to a stable release branch.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this comment

Comment thread .ado/scripts/apply-additional-tags.mjs Outdated
Comment on lines +31 to +42
function main({ tags, token, "dry-run": dryRun }) {
function main({ tags, token: tokenArg, "dry-run": dryRun }) {
if (!tags) {
console.log("No additional tags to apply");
return 0;
}

// Prefer explicit --token arg (ADO), fall back to NODE_AUTH_TOKEN env var (GHA OIDC).
const token = tokenArg ?? process.env.NODE_AUTH_TOKEN;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we want to support dry run, and don't want to support the passed in token

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Done — removed --token entirely. Auth now comes exclusively from NODE_AUTH_TOKEN. Updated the ADO job to pass the token via env: NODE_AUTH_TOKEN: $(npmAuthToken) instead of the --token arg.

Comment thread .ado/scripts/apply-additional-tags.mjs Outdated
Comment on lines +71 to +75
// When --token is explicitly provided (ADO path), pass auth inline so
// that npm picks it up without a pre-configured .npmrc.
// When NODE_AUTH_TOKEN is used instead (GHA OIDC path), actions/setup-node
// has already written .npmrc with `//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}`,
// so no inline auth argument is needed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No overly verbose comments. Comments are to describe code, not the last turn of AI

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Removed.

Comment thread .ado/scripts/configure-publish.mts Outdated
Comment on lines +37 to +41
/**
* Writes `publish_react_native_macos=1` to `GITHUB_OUTPUT` to signal that we
* want to enable publishing on GitHub Actions.
*/
function enablePublishingOnGitHubActions() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Function name is self explanatory

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Removed.

Comment thread .ado/scripts/configure-publish.mts Outdated
Comment on lines +216 to +217
enablePublishingOnAzurePipelines();
enablePublishingOnGitHubActions();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we choose one or the other with an extra Boolean in the file?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Added const isGitHubActions = process.env['GITHUB_ACTIONS'] === 'true' at the top of the file and used it to pick one path — enablePublishingOnGitHubActions() on GHA, enablePublishingOnAzurePipelines() otherwise.

# which dist-tag(s) to use. Outputs:
# publish_react_native_macos – '1' when publishing should proceed
# publishTag – the primary npm dist-tag (e.g. 'latest')
# additionalTags – comma-separated extra tags (may be empty)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Examples of an extra tag

# additionalTags – comma-separated extra tags (may be empty)
- name: Verify release config
id: configure-publish
run: node .ado/scripts/configure-publish.mts --verbose --skip-auth

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we still need to skip auth with oidc?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Yes, still needed. verifyNpmAuth() runs npm whoami, which requires a pre-configured token and would fail before any OIDC exchange happens. OIDC auth is initiated by yarn at publish time (via --provenance), not upfront — so there's no token in the environment when configure-publish.mts runs.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no callers actually use auth then and we can remove that flow

Comment on lines +54 to +55
# Configure yarn to target the public npm registry.
# Auth is handled by OIDC Trusted Publishing – do NOT set npmAuthToken here.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove obvious comment

Comment on lines +62 to +64
# Publish all non-private workspace packages.
# --provenance enables npm Trusted Publishing: yarn requests an OIDC token
# from GitHub Actions and exchanges it with npmjs.com – no stored secret needed.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove obvious comment

Comment on lines +73 to +77
# Apply any additional dist-tags (e.g. promoting a branch tag to 'next').
# npm dist-tag add does not support OIDC, so a granular NPM_TOKEN secret
# is required for this step. NODE_AUTH_TOKEN is the conventional env var
# that actions/setup-node (called inside microsoft-setup-toolchain) wires
# into the project-level .npmrc it creates.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Useful comment, can we simplify a little?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Copilot: Simplified to: # npm dist-tag add doesn't support OIDC, so NODE_AUTH_TOKEN (a granular NPM_TOKEN secret) is still required for this step.

- Remove verbose/obvious comments from workflow and scripts
- Drop --token CLI arg from apply-additional-tags.mjs; use NODE_AUTH_TOKEN
  env var only; update ADO job accordingly
- Remove JSDoc from self-explanatory enablePublishingOnGitHubActions()
- Add isGitHubActions boolean to configure-publish.mts and use it to
  select between ADO and GHA signaling (instead of calling both)
Comment thread .ado/scripts/apply-additional-tags.mjs Outdated
Comment on lines +66 to +78

const npmArgs = [
"dist-tag",
"add",
`${pkg}@${version}`,
tag,
"--registry",
registry,
];

const result = spawnSync(
"npm",
[
"dist-tag",
"add",
`${pkg}@${version}`,
tag,
"--registry",
registry,
`--//registry.npmjs.org/:_authToken=${token}`,
],
npmArgs,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to pull this out anymore

Comment thread .ado/scripts/configure-publish.mts Outdated
Comment on lines +216 to +218
} else {
enablePublishingOnAzurePipelines();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we handle if neither (I.E: local run)? Just a log message may be fine

# additionalTags – comma-separated extra tags (may be empty)
- name: Verify release config
id: configure-publish
run: node .ado/scripts/configure-publish.mts --verbose --skip-auth

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think no callers actually use auth then and we can remove that flow

Comment on lines +45 to +49
- name: Configure yarn for npm publishing
if: steps.configure-publish.outputs.publish_react_native_macos == '1'
run: |
yarn config set npmPublishAccess public
yarn config set npmPublishRegistry "https://registry.npmjs.org"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also unset unconditionally after

Copilot AI requested a review from Saadnajmi July 31, 2026 20:36
@Saadnajmi Saadnajmi changed the title chore: use npm Trusted Publishing chore: use NPM trusted publishing Jul 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants