Skip to content

ci: publish JS packages from GitHub Actions with npm trusted publishing - #157

Draft
lucarachiteanu wants to merge 5 commits into
masterfrom
feat/npm-trusted-publishing
Draft

lucarachiteanu wants to merge 5 commits into
masterfrom
feat/npm-trusted-publishing

Conversation

@lucarachiteanu

@lucarachiteanu lucarachiteanu commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Moves publishing of the two JS client packages out of ADO and into GitHub Actions, in two steps — a pack job that produces the .tgz files and publish jobs that push those same tarballs — and adds public npmjs as a second target via Trusted Publishing.

Depends on #158, which fixes repository.url for the JS client. npm provenance verifies that URL against the repository the build ran in and refuses to publish on a mismatch, so npmjs publishing cannot work until #158 merges.

Why

ADO cannot publish to npmjs with Trusted Publishing at all: npm only trusts GitHub Actions OIDC identities. Once publishing has to happen in Actions for npmjs, doing GitHub Packages from the same place costs nothing and removes the PublishNPM service connection from the picture — a workflow in this repo gets a GITHUB_TOKEN with packages: write, and both coreipc and coreipc-web are already linked to UiPath/coreipc.

To be clear about the motivation: the GitHub Packages publish is not currently broken. The continueOnError comment in azp-js.publish-npm.steps.yaml says it is expected to fail after the May 2026 classic-PAT revocation, but coreipc has versions dated 2026-07-24 (2.5.2, 2.5.2-20260724-01/02), so that comment is stale. This is consolidation and hardening, not a repair.

What changed

.github/workflows/cd-npm.yml (new) — repository_dispatch: publish-npm, payload {sha, version}, three jobs:

  • pack — validates the payload (40-char SHA, plain semver), checks out that exact commit, cross-checks the dispatched version against the csproj <Version> at that commit (must equal it, or be it plus a build-number suffix), refuses any commit not merged to master, then npm ci → stamp → npm run build → explicit npm pack of both prepack directories → verifies exactly two correctly-named tarballs → uploads them.
  • publish-github-packages — environment github-packages, packages: write + GITHUB_TOKEN, publishes both tarballs untouched. Runs for every build, prereleases included.
  • publish-npmjs — stable versions only, environment npm, id-token: write, npm upgraded to ^11.5.1, publishes @uipath/coreipc only.

Both publishes skip if that exact version is already on the registry, matching skip-existing: true on the PyPI side — so a re-run resumes instead of dying on a duplicate after GitHub Packages has already gone out. @uipath/coreipc-web stays GitHub-Packages-only: it has never been published to npmjs, and a Trusted Publisher can only be configured on a name that already exists.

src/CI/azp-js.publish-npm.steps.yaml — the two Npm@1 publish tasks are replaced by a dispatch step. It still downloads and extracts the artifact, but only to read the version out of the packed dist/prepack/node/package.json rather than recompute it, so there is one source of truth for what version the CI build actually produced.

src/CI/azp-publish.yamlPublish_NPM gains the github-dispatch variable group; parameter label updated.

Security posture

The repo is public, so the trigger and runner choices matter as much as the code:

  • Nothing off master is publishable. The ancestor-of-master check covers both registries and both channels. Fork pull-request heads are fetchable by SHA from refs/pull/* on a public repo, and this is what keeps one out.
  • Outsiders cannot trigger it. repository_dispatch requires a token with contents: write, and it always executes the default-branch copy of the workflow — so a PR cannot modify cd-npm.yml and have its version run.
  • All jobs run on GitHub-hosted ubuntu-latest, not the uipath-* managed pool, so no untrusted code can reach UiPath machines. This diverges from cd.yml, which commit 3853f6f (chore: change to centralized managed GitHub pool #150) moved onto the managed pool — worth a decision on whether that migration should hold for publish workflows on a public repo.
  • Neither credential is long-lived. GITHUB_TOKEN is job-scoped; npmjs uses OIDC with no token at all.
  • The payload is read through env:, never interpolated into a run: block. All actions are pinned to commit SHAs.

This PR cannot be tested by CI

repository_dispatch always runs the workflow file from the default branch, so cd-npm.yml will not run from this branch under any circumstances. There is no green check to wait for here — the first real execution is the first dispatch after merge. cd.yml has the same property.

Prerequisites before merge

  • Merge fix: point js client package metadata at UiPath/coreipc #158.
  • Configure the Trusted Publisher on npmjs for @uipath/coreipc: repo UiPath/coreipc, workflow cd-npm.yml, environment npm.
  • Create both environments, npm and github-packages, with required reviewers and "Protected branches only". Referencing an environment that does not exist auto-creates it unprotected, so without this the gate is decorative — and github-packages is what replaces the approval ADO's NPM-Packages environment provided.
  • Confirm the github-dispatch variable group is visible to the Publish_NPM stage, not just Notify_GitHub.
  • Confirm GitHub-hosted runners are permitted for this repo — if the org restricts Actions to the managed pool, the ubuntu-latest jobs will queue indefinitely rather than fail.

Known risk

It is not verified that npm generates provenance when publishing a pre-built tarball rather than from the package directory. If the first stable run fails on provenance, the fix is to move npm pack into the npmjs job — which costs the guarantee that npmjs and GitHub Packages receive byte-identical artifacts.

Not in scope

Build and test stay in ADO. Moving those too is viable — the uipath-ipc-deps feed is a transparent mirror (every resolved entry in package-lock.json points at registry.npmjs.org) and NuGet.Config targets only nuget.org, so nothing here needs an internal feed. But it needs ChromeHeadless and a Windows runner label confirmed first, it changes the prerelease version scheme since Actions has no equivalent of ADO's $(Rev:-rr) per-day counter, and it ends the Notify_GitHubPublish_NPM coupling that currently gates public PyPI on npm succeeding.

🤖 Generated with Claude Code

lucarachiteanu and others added 4 commits September 15, 2026 14:47
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n master

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.

1 participant