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
4 changes: 2 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ jobs:
registry-url: "https://registry.npmjs.org"
cache: "npm"

# Ensure npm 11.5.1 or later is installed
# Trusted publishing needs npm >= 11.5.1; npm 12 requires a newer Node than this workflow uses.
- name: Update npm
run: npm install -g npm@latest
run: npm install -g npm@^11

- name: Install for publish
run: npm ci
Expand Down
44 changes: 32 additions & 12 deletions docs/development/publishing.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,17 @@ Before using the publish flow, configure the GitHub Actions settings required by
- Configure npm Trusted Publishing (OIDC) for this GitHub repository/workflow.
- Configure branch protection for `main` so the required CI checks for the test workflow must pass before a merge is allowed.
- No npm token secret is required.
- The publish job pins npm to the `11` line: trusted publishing needs npm 11.5.1 or later, while npm 12 requires a newer Node than the workflow uses.

## Goal

- Release without direct pushes to `main`.
- Keep `package.json` version and Git tag aligned.
- Merge a release PR only after its CI checks have been polled to success.
- Publish to npm only after the required CI succeeds for the `main` merge commit.
- Publish to npm automatically only when a release version was intentionally bumped.
- Keep stable releases on npm `latest` and pre-releases on the matching channel (`beta`, `rc`, or `next`).
- Create the git tag and GitHub Release locally after the npm release succeeds.
- Create the git tag and GitHub Release locally only after the publish workflow succeeds.

## Release Policy (Main-First)

Expand All @@ -35,6 +37,14 @@ Before using the publish flow, configure the GitHub Actions settings required by
6. Create the tag and GitHub Release locally from the published `main` commit
- The GitHub Actions publish workflow is intentionally not triggered by a GitHub Release event.

## CI Polling Rule (Required)

- Before merging any release PR, poll its CI checks until every required check succeeds.
- Wait at most 5 minutes. If the checks have not all succeeded by then, treat the release as failed and stop immediately.
- Do not merge, tag, or publish a release while checks are pending, failing, or unresolved.
- Apply the same rule to the publish workflow run on `main`: poll it, cap the wait at 5 minutes, and stop on timeout or failure.
- Never create the tag or GitHub Release before the publish workflow has succeeded.

## npm Distribution Tag Behavior

- Stable versions (for example `0.0.15`) are published with npm dist-tag `latest`.
Expand Down Expand Up @@ -65,11 +75,11 @@ This prevents routine documentation or maintenance merges from accidentally runn
- Example: `release/v0.0.13`
2. Bump version on the release branch only.
- `npm version 0.0.13 --no-git-tag-version`
3. Open a PR from the release branch to `dev`.
4. Open a PR from `dev` to `main`.
5. Wait for CI to pass on the merge commit to `main`.
3. Open a PR from the release branch to `dev`, poll its CI, and merge only after it succeeds.
4. Open a PR from `dev` to `main`, poll its CI, and merge only after it succeeds.
5. Poll the publish workflow run for the `main` merge commit.
6. If the `main` push changed `package.json` or `package-lock.json`, the publish workflow automatically publishes to npm.
7. After successful npm publish, create the tag and GitHub Release locally.
7. After the publish workflow succeeds, create the tag and GitHub Release locally.

## Command Example

Expand All @@ -92,15 +102,25 @@ gh pr create --base dev --head release/v0.0.13 --title "chore(release): 0.0.13"
gh pr create --base main --head dev --title "dev"
```

### 3) Publish to npm after CI success on main
### 3) Poll CI before merging each PR

```bash
# stops with a non-zero status on failure or after 5 minutes
timeout 300 gh pr checks <pr-number> --watch --fail-fast
gh pr merge <pr-number> --merge --delete-branch=false
```

### 4) Poll the publish workflow after the merge to main

```bash
git checkout main
git pull origin main
# The publish workflow runs automatically when package.json / package-lock.json changed.
RUN_ID=$(gh run list --workflow="Secure Publish to npm" --branch main --limit 1 --json databaseId --jq '.[0].databaseId')
timeout 300 gh run watch "${RUN_ID}" --exit-status
```

### 4) Create the tag and GitHub Release locally after npm publish succeeds
### 5) Create the tag and GitHub Release locally after npm publish succeeds

```bash
git checkout main
Expand All @@ -110,7 +130,7 @@ git push origin v0.0.13
gh release create v0.0.13 --target main --title "v0.0.13" --generate-notes
```

### 5) Pre-release example (beta)
### 6) Pre-release example (beta)

```bash
git checkout dev
Expand Down Expand Up @@ -148,15 +168,15 @@ The publish workflow detects the prerelease identifier and maps it to `beta`, `r
- It can unintentionally delete the `dev` branch.
- Always create the release tag on the exact `main` commit that was successfully published to npm.
- Do not rely on GitHub Releases to trigger the publish workflow.
- Do not bypass a blocked or pending merge; a timeout is a release failure, not a reason to force the merge.

## Troubleshooting Checklist

- Was the `main` branch push a deliberate version bump in `package.json` or `package-lock.json`?
- Did every required CI check reach success within the 5-minute polling window before the merge?
- Did the required CI checks pass on the merge commit before the publish workflow started?
- Does the npm dist-tag match the version channel (for example `latest`, `beta`, `rc`, `next`)?
- Were the git tag and GitHub Release created locally after the npm publish succeeded?

- Does the tag name match package version format (e.g., `v0.0.13` or `v0.0.15-beta.1`)?
- Did the `publish.yml` workflow start from the `release` event?
- Does the tag name match the package version format (for example `v0.0.13` or `v0.0.15-beta.1`)?
- Were the git tag and GitHub Release created only after the publish workflow succeeded?
- Did the tag/version consistency check pass?
>>>>>>> origin/main