Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 17 additions & 16 deletions .github/workflows/publish_npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ name: Publish to npm
# Publishing a GitHub release publishes @bloxwap/chart at the version in packages/chart/package.json.
# Release flow: bump the version in a PR, merge it, then create a release tagged v<version>.
#
# npm trusted publishing does not support self-hosted runners
# (https://docs.npmjs.com/trusted-publishers/). This job authenticates with the
# NPM_TOKEN secret instead. That drops OIDC trusted publishing and
# automatic provenance. See docs/ci-runners.md.
# Only the publish job uses GitHub-hosted Ubuntu: npm trusted publishing and
# provenance require it. See docs/ci-runners.md for the transition and approval gate.
on:
release:
types: [published]
Expand Down Expand Up @@ -34,12 +32,12 @@ jobs:

publish:
needs: security
runs-on: [self-hosted, macOS, ARM64]
runs-on: ubuntu-24.04
environment: npm-release
timeout-minutes: 20
permissions:
contents: read
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
id-token: write

steps:
# Job-level env cannot use the runner context; RUNNER_TEMP is set by the runner.
Expand All @@ -48,13 +46,17 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Minis persist ~/.npm on disk. GitHub's remote cache is 0.4–1 MB/s for ~1.4 GB.
# Do not restore dependency caches in the release job.
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
package-manager-cache: false

- name: Install npm with trusted publishing support
# Bootstrap a fixed OIDC-capable npm CLI outside the project dependency lockfile.
run: npm install --global npm@11.6.1 # zizmor: ignore[adhoc-packages]

- run: npm ci

- name: Determine version and npm tag
Expand Down Expand Up @@ -84,12 +86,11 @@ jobs:
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "value=$TAG" >> "$GITHUB_OUTPUT"

- name: Check npm CLI
run: |
npm --version
npx --yes semver -r '>=11.5.1' "$(npm --version)" > /dev/null || npm install -g npm@latest

# prepack builds and prepublishOnly runs the full test suite before anything is uploaded;
# publishConfig.access is public. Token publish does not attach provenance.
# prepack builds and prepublishOnly runs the full Node/Happy DOM test suite;
# no browser download is needed. publishConfig.access is public.
- name: Publish @bloxwap/chart@${{ steps.tag.outputs.version }}
run: npm publish --workspace @bloxwap/chart --tag ${{ steps.tag.outputs.value }}
env:
NPM_TAG: ${{ steps.tag.outputs.value }}
# Transitional fallback: delete once the npm Trusted Publisher is verified.
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: npm publish --workspace @bloxwap/chart --tag "$NPM_TAG" --provenance
4 changes: 3 additions & 1 deletion apps/docs/content/docs/development.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ The preview server reads the exported base path, so this build opens at `http://

## Release to npm

Publishing a GitHub release publishes `@bloxwap/chart` from CI (`.github/workflows/publish_npm.yml`) on the org Mac mini runners. Authentication uses the org `NPM_TOKEN` secret (`NODE_AUTH_TOKEN`). npm trusted publishing and automatic provenance are unavailable on self-hosted runners.
Publishing a GitHub release publishes `@bloxwap/chart` from CI (`.github/workflows/publish_npm.yml`). Only the publish job runs on GitHub-hosted `ubuntu-24.04`, as required by npm trusted publishing; the prerequisite security scan and all other jobs remain on the Mac minis. Node 24 and npm 11.6.1 build and test the package on that runner, with dependency caching disabled. Publishing requests npm provenance, including during token fallback.

Configure the GitHub `npm-release` environment with required reviewers before releasing; the workflow names this environment, but approval rules must be set in GitHub settings. The publish step keeps `NPM_TOKEN` as `NODE_AUTH_TOKEN` only as a transitional fallback, so releases work before npmjs.com setup. Configure the npm Trusted Publisher for organization `bloxwap`, repository `chart`, workflow filename `publish_npm.yml`, environment `npm-release`, allowing direct publishing. Once trusted publishing is verified, remove the fallback and delete this repository's token secret/access. See [CI runner setup](https://github.com/bloxwap/chart/blob/main/docs/ci-runners.md) for details.

1. In a pull request, bump `version` in `packages/chart/package.json` (and the `version` export in `packages/chart/src/index.ts`), then merge it. A test fails if the two disagree.
2. Create a GitHub release tagged `v<version>`, for example `v0.0.2`. The workflow fails if the tag and the package version disagree.
Expand Down
16 changes: 10 additions & 6 deletions docs/ci-runners.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
# CI runners

Every GitHub Actions job in this repository runs on self-hosted Apple Silicon Mac minis (runner group JWS):
Except for the npm publish job in `.github/workflows/publish_npm.yml`, every GitHub Actions job in this repository runs on self-hosted Apple Silicon Mac minis (runner group JWS):

```yaml
runs-on: [self-hosted, macOS, ARM64]
```

These jobs stay queued until a runner with all three labels is online. Workflows do not call `apt`, `sudo`, or `playwright install --with-deps` (that flag installs Linux packages). Shell used by CI is POSIX / BSD-portable, and Mac jobs write scratch files under `$RUNNER_TEMP` (`TMPDIR` and Playwright's browser path are exported from `$RUNNER_TEMP` in a setup step; GitHub Actions does not allow the `runner` context in job-level `env`).
These jobs stay queued until a runner with all three labels is online. Mac mini jobs do not call `apt`, `sudo`, or `playwright install --with-deps` (that flag installs Linux packages). Shell used by CI is POSIX / BSD-portable, and Mac jobs write scratch files under `$RUNNER_TEMP` (`TMPDIR` and Playwright's browser path are exported from `$RUNNER_TEMP` in a setup step; GitHub Actions does not allow the `runner` context in job-level `env`).

`actions/setup-node` sets `package-manager-cache: false` and does not set `cache:`. The minis already keep `~/.npm` on disk across jobs, so GitHub's remote dependency cache is extra upload/download (a ~1.4 GB archive at 0.4–1 MB/s) with no install-time benefit.

## Trusted publishing is unavailable
## npm release exception and setup

[npm trusted publishing does not support self-hosted runners](https://docs.npmjs.com/trusted-publishers/). The publish job therefore authenticates with a long-lived `NPM_TOKEN` secret (`NODE_AUTH_TOKEN`). That **drops OIDC trusted publishing and automatic provenance**. The org-level `NPM_TOKEN` (publish rights for `@bloxwap` packages) must be shared with this repository.
Only `publish` in `.github/workflows/publish_npm.yml` runs on GitHub-hosted `ubuntu-24.04`: [npm trusted publishing requires GitHub-hosted runners](https://docs.npmjs.com/trusted-publishers/). Its existing prerequisite security scan stays on the minis. The job uses Node 24, pinned npm 11.6.1, only `contents: read` and `id-token: write` permissions, and the `npm-release` environment. It builds locally on that runner and runs the full Node/Happy DOM suite through npm's publish lifecycle; no browser or Python installation is needed. Dependency caching is disabled for releases.

Before releasing, configure GitHub **Settings → Environments → npm-release** with required reviewers. Merely naming the environment in YAML does not configure an approval gate. Keep the existing org/repository `NPM_TOKEN` available during the transition; it is exposed as `NODE_AUTH_TOKEN` only on the publish step. npm tries OIDC first and falls back to this token while no trusted publisher is configured, so releases do not require npmjs.com setup first. `npm publish --provenance` attaches provenance even during token fallback.

In the npmjs.com settings for `@bloxwap/chart`, add a GitHub Actions Trusted Publisher with organization `bloxwap`, repository `chart`, workflow filename `publish_npm.yml` (not its path), and environment `npm-release`, allowing direct publishing. Keep that workflow filename unchanged. Verify a release uses trusted publishing, then remove the fallback `NODE_AUTH_TOKEN` entry and delete this repository's `NPM_TOKEN` secret/access (do not revoke a shared org token still used by other repositories). OIDC does not authorize commands such as `npm whoami`, `npm access`, or `npm deprecate`; do not add token-only auth preflights to this publish path.

## Jobs

Expand All @@ -22,7 +26,7 @@ These jobs stay queued until a runner with all three labels is online. Workflows
| [`.github/workflows/coverage.yml`](../.github/workflows/coverage.yml) | `coverage` | Mac mini | LCOV report and Codecov upload |
| [`.github/workflows/docs.yml`](../.github/workflows/docs.yml) | `build` | Mac mini | Docs export, typecheck, lint, Playwright Chrome checks; Pages artifact on `main` only (PRs still build and validate) |
| [`.github/workflows/docs.yml`](../.github/workflows/docs.yml) | `deploy` | Mac mini | `configure-pages` + `deploy-pages` on `main` |
| [`.github/workflows/publish_npm.yml`](../.github/workflows/publish_npm.yml) | `publish` | Mac mini | Version/tag check and `npm publish` of `@bloxwap/chart` (`NPM_TOKEN`; no provenance) |
| [`.github/workflows/publish_npm.yml`](../.github/workflows/publish_npm.yml) | `publish` | GitHub-hosted Ubuntu 24.04 | Approved `npm-release`; version/tag check and publish with provenance (OIDC; transitional token fallback) |

## Prerequisites on each Mac mini

Expand Down Expand Up @@ -61,5 +65,5 @@ Browsers go to `$RUNNER_TEMP/ms-playwright` (`PLAYWRIGHT_BROWSERS_PATH`). There

- Give the runner enough free disk for `npm ci` (and a persistent `~/.npm`), the Next.js static export, and a Chrome for Testing download.
- Headless Chrome on macOS does not need the Accessibility TCC prompt that headed browsers do.
- npm publish (`Publish to npm`) uses the `NPM_TOKEN` secret, not OIDC. GitHub Pages deploy still uses `pages: write` and `id-token: write`.
- npm publish (`Publish to npm`) uses the Ubuntu exception and approval setup above. GitHub Pages deploy still uses `pages: write` and `id-token: write`.
- Fork pull requests on a public repository should stay on GitHub-hosted runners or a dedicated untrusted pool if org policy forbids self-hosted runners on forks. This repo's workflows themselves do not special-case forks.