Skip to content

ci: semantic releases - #335

Open
dhensby wants to merge 5 commits into
node-oauth:masterfrom
dhensby:pulls/semantic-release
Open

dhensby wants to merge 5 commits into
node-oauth:masterfrom
dhensby:pulls/semantic-release

Conversation

@dhensby

@dhensby dhensby commented Mar 13, 2025 •

Copy link
Copy Markdown
Contributor

Summary

This change adds semantic releases to the release pipeline. Commits are automatically analysed when they are merged into master. Depending on the commit type (fix, perf, feat, a git revert or a breaking change), a new release will be authored (tagged in git, pushed to GH releases and NPM); this includes updating the release version in package.json and package-lock.json, and updating CHANGELOG.md.

Other added benefits are NPM attestation of releases, no human dependencies on releases, fixes are released as soon as they are merged.

The release job runs semantic-release with npx, pinned to exact versions of semantic-release and the three extra packages that release.config.mjs needs. So this PR adds only commitlint to devDependencies. The dependencies of the pinned packages resolve when the job runs, and npm installs only versions that are more than 7 days old.

commitlint ignores the "Merge commit from fork" message that GitHub writes when it merges a security advisory, and Dependabot updates the two commitlint packages together.

This PR also removes the "Tests for Release" workflow (tests-release.yml). It runs only for release-* branches, which semantic-release replaces, and it has never run. It also updates AGENTS.md, so that it no longer refers to that workflow or to the outdated CONTRIBUTING.md instructions.

Contributors will now have to ensure their commits conform to the conventional commits standard

Outstanding actions:

  1. Someone with publish permissions to the NPM repository will need to create a fine-grained access token to allow publishing of the package - this must then be added to the github repo secrets with the name NPM_TOKEN.
  2. Someone with admin rights to the GH repo will need to create a fine-grained personal access token from an account that can push to the protected master branch. The token needs read and write access to Contents, Issues and Pull requests for this repository. Add it to the github repo secrets with the name RELEASE_GITHUB_TOKEN.
  3. Someone with publish/admin access to the @node-oauth/oauth2-server npm package will need to configure a trusted publisher for it (on npmjs.com: the package → Settings → Trusted Publisher → GitHub Actions). The configuration must match the release workflow exactly — organisation/user node-oauth, repository node-oauth2-server, workflow filename tests.yml, and a blank environment — otherwise the OIDC token exchange fails, and the release job stops with ENONPMTOKEN before it publishes anything. Requires npm ≥ 11.5.1 / Node ≥ 22.14.0 (the release job runs Node 24).
    • Select Allow npm publish. Configurations created after 3 September 2026 allow only npm stage publish by default. @semantic-release/npm runs npm publish.
    • A new configuration expires if its first publish doesn't succeed within 2 days, so create it shortly before the first release.
    • Until a working configuration exists, the release job fails with ENONPMTOKEN on every push to master, including the push that merges this PR. Each failure also opens or comments on a "The automated release is failing 🚨" issue. This is expected until trusted publishing is in place.
    • After the first trusted publish, npm recommends the Require two-factor authentication and disallow tokens option (the package → Settings → Publishing access).
  4. I recommend that someone with admin rights to the GH repo turns off Allow squash merging (Settings → General → Pull Requests). CI can't check the message of a squash commit before it lands. GitHub fills it in from the PR title, or from the commit when the PR has only one commit, and the person who merges can edit it. CI lints neither the PR title nor the merge dialog. If the squash message fails commitlint, the push lint on master fails, so the release job for that push doesn't run. For example, feat: Add X fails, because its subject starts with a capital letter. If the message has no conventional type, semantic-release never counts the change towards a release, because only the squash commit reaches master. Merge commits keep the PR's commits, which CI has already linted, and commitlint ignores GitHub's default "Merge pull request #…" message.

The release job doesn't need an NPM_TOKEN secret, because NPM supports trusted publishing with short-lived OIDC credentials (see action 3 above for the one-time setup this requires). With tests-release.yml removed, no workflow uses the NPM_TOKEN secret, so it can be deleted.

The release job grants the built-in GITHUB_TOKEN these permissions. semantic-release uses that token only when RELEASE_GITHUB_TOKEN isn't set. id-token: write is always needed, because npm trusted publishing and provenance use the job's OIDC token:

      contents: write # to be able to publish a GitHub release & commit changelog changes
      issues: write # to be able to comment on released issues
      pull-requests: write # to be able to comment on released pull requests
      id-token: write # to enable OIDC for npm trusted publishing and provenance

Linked issue(s)

See discussion #310

Involved parts of the project

Releases / automation.

Added tests?

N/A

OAuth2 standard

N/A

Reproduction

N/A

@dhensby
dhensby force-pushed the pulls/semantic-release branch 2 times, most recently from 2ce5b21 to 69a3c8c Compare March 13, 2025 13:24
@jankapunkt

Copy link
Copy Markdown
Member

Thanks a lot for this @dhensby

I have nom publishing access but I'd like to limit this only to the master branch so new releases are definitely reviewed beforehand by at least two of us. What do you think?

@dhensby

dhensby commented Mar 13, 2025

Copy link
Copy Markdown
Contributor Author

By default the release process won't work unless it's in a list of the default branches (master, next, and then N.x for changes to other versions). We can limit this to just master either in the release config or in the GH workflow (or both).

@jankapunkt

Copy link
Copy Markdown
Member

Ok then we can leverage npm tags so everything on next is also tagged @next (like alpha beta release candidate) and not auto installed by users while releases on master can be tagged @latest so they are installable without explicit version pinning

@dhensby

dhensby commented Mar 14, 2025

Copy link
Copy Markdown
Contributor Author

Yep - that's how it works "out of the box" 👍

@jankapunkt

Copy link
Copy Markdown
Member

@dhensby I just merged some dependabot dev-dependencies. Would these kinds of merges be ignored by your current PR?

@dhensby

dhensby commented May 20, 2025

Copy link
Copy Markdown
Contributor Author

I just merged some dependabot dev-dependencies. Would these kinds of merges be ignored by your current PR?

No; they are flagged as build: or sometimes chore(deps): commits that don't trigger releases. Also, if you use @dependabot merge command, the CI is run in reduced-privilege mode which means that the runs don't have access to the secrets and so they can't be used to perform releases.

In regard to dependencies, our version constraints should be permissive enough that any upstream consumer can be responsible for upgrading any packages that have vulnerabilities or bugs; if we start to depend on new major versions of dependencies that form part of our outward facing APIs (I don't think they do), then we should probably manually update them with at least minor and maybe major change releases.

@dhensby
dhensby force-pushed the pulls/semantic-release branch from 69a3c8c to cac8b96 Compare May 20, 2025 08:12
@dhensby
dhensby force-pushed the pulls/semantic-release branch 4 times, most recently from 832e5bd to 22bbdd9 Compare June 15, 2026 22:15
@dhensby
dhensby requested review from Copilot and jankapunkt June 15, 2026 22:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces automated semantic-versioned releases using semantic-release, along with commit message linting to enforce Conventional Commits as part of the CI pipeline.

Changes:

  • Add semantic-release configuration (including changelog + git commit of release artifacts).
  • Add commitlint configuration and CI steps to validate commit messages for pushes/PRs.
  • Extend the existing CI workflow to include a release job and adjust lint scripts/changelog header to match the release tooling.

Reviewed changes

Copilot reviewed 5 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
release.config.mjs Defines semantic-release plugins and release/changelog/git behavior.
commitlint.config.mjs Adds Conventional Commits lint rules for CI enforcement.
package.json Adds devDependencies for commitlint/semantic-release tooling and updates lint scripts.
CHANGELOG.md Updates heading to match semantic-release changelog plugin title.
.github/workflows/tests.yml Adds commitlint checks, fetch-depth changes, and a semantic-release publish job.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread release.config.mjs Resolved
Comment thread .github/workflows/tests.yml Resolved
Comment thread .github/workflows/tests.yml Resolved
Comment thread .github/workflows/tests.yml Resolved
@dhensby
dhensby force-pushed the pulls/semantic-release branch 2 times, most recently from a82ad95 to e8ac50b Compare June 16, 2026 22:45
@dhensby

dhensby commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review comments (as fixup! commits, ready to autosquash):

  1. release.config.mjs preset — moved preset: 'conventionalcommits' onto @semantic-release/commit-analyzer and @semantic-release/release-notes-generator (it's not a recognized core option at the top level, so the plugins were silently defaulting). The preset itself resolves via semantic-release's bundled conventional-changelog-conventionalcommits, so no extra dependency is needed.
  2. commitlint on push — replaced --last with --from ${{ github.event.before }} --to ${{ github.sha }} so every commit in a push is validated, not just the tip. The lint job already checks out with fetch-depth: 0, and pushes are limited to master/development (so before is always a real SHA).
  3. Action versions — bumped the release job's actions/checkout / actions/setup-node from @v4 to @v6 (and node 22 → 24) to match the other jobs.
  4. Release token — GITHUB_TOKEN now uses ${{ secrets.RELEASE_GITHUB_TOKEN || secrets.GITHUB_TOKEN }}, so a dedicated PAT can be supplied to push the release commit/tag to the protected master branch, falling back to the built-in token when it isn't set.

Re the branch discussion above: kept the semantic-release default branches (master → @latest, next/N.x → prerelease/@next tags), so master releases still go through PR review and next stays non-default-installable.

@dhensby
dhensby force-pushed the pulls/semantic-release branch from 7e35163 to 2f486d2 Compare July 22, 2026 23:49
Copilot AI review requested due to automatic review settings July 22, 2026 23:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 7 changed files in this pull request and generated 1 comment.

Comment thread .github/workflows/tests.yml Resolved
@jankapunkt jankapunkt modified the milestones: 5.3.1, 5.4 Oct 13, 2026
@dhensby
dhensby force-pushed the pulls/semantic-release branch from 2f486d2 to 47d1c7b Compare October 15, 2026 10:33
@jankapunkt

Copy link
Copy Markdown
Member

I am ready to merge but I want to ask a "policy" question. Consider the scenario where we may want to provide a "maintenance" release that consists of two fixes that are in separate PRs (for the sake of review-ability). How can we make sure these are not ending up in two separate releases?

@dhensby

dhensby commented Oct 16, 2026

Copy link
Copy Markdown
Contributor Author

How can we make sure these are not ending up in two separate releases?

That is tricky, unless they are merged at the same time. semantic-release will not release a version if it is behind the HEAD of the branch it is releasing from, so two PRs merged "at the same time" (or rather, as long as the last one is merged before semantic-release gets to its release step) will result in 1 release.

If we had several PRs and we merged them over the space of several days, then we'd have several releases.

In practice I've never really found this to be problem myself, if there's a flurry of work you get a few releases.

The other option, if we were really concerned about it, would be to add some extra maintenance by having a beta channel and then merging that into the maintenance channel after.

I've found the benefits of the automated releases (and the security improvement that provides) outweigh this limitation.

@dhensby
dhensby force-pushed the pulls/semantic-release branch from c1b7dc4 to 343068f Compare October 16, 2026 07:38
@jankapunkt

Copy link
Copy Markdown
Member

@dhensby thanks I think this is a minor use case, I just wanted to be clear how we handle it. I think a beta channel (a release branch?) would be the way to go here.

Comment thread CONTRIBUTING.md Resolved
Comment thread package.json Outdated
"@commitlint/cli": "^21.0.2",
"@commitlint/config-conventional": "^21.0.2",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I was thinking, if it makes sense to add an npm script for commitlint and semantic release to "simulate" behavior that will be expected from CI so we and others can locally ensure the current state of code complies with both?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, good idea. I've added npm run lint:commits, which checks the commits on your branch with the same rules as CI. CONTRIBUTING explains the upstream remote that it needs.

I haven't added a semantic-release script. semantic-release now runs only in the release job, through npx, so it isn't installed locally. A local dry run also needs extra flags, or it moves local branches and reads npm credentials. The commit types in CONTRIBUTING show which release a PR will cause.

jankapunkt
jankapunkt previously approved these changes Oct 29, 2026
@dhensby
dhensby marked this pull request as draft November 2, 2026 11:27
@dhensby

dhensby commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review and the approval, @jankapunkt. Before we merge, I tested the release job against a local copy of master. The first fix: or feat: release would fail at the release-notes step: the conventionalcommits preset gets installed only through commitlint, at a major version that semantic-release 25 can't use. I'll push a fix for that and a few release-job hardening changes, then reply to your two comments.

@dhensby

dhensby commented Nov 2, 2026

Copy link
Copy Markdown
Contributor Author

I've pushed the changes from my earlier comment. Since your approval, the PR has changed in these ways:

  • The release job runs semantic-release with pinned npx packages, so semantic-release is no longer a devDependency. It also pins the conventionalcommits preset to 9.x, which fixes the release-notes failure.
  • semantic-release now pushes with RELEASE_GITHUB_TOKEN, not the checkout token, and the release commit message has [skip ci] again.
  • commitlint ignores GitHub's "Merge commit from fork" message for security advisories, and Dependabot updates the commitlint packages together.
  • I removed tests-release.yml, because it runs only for release-* branches and has never run. I also updated the parts of AGENTS.md that would be wrong after the merge.
  • The description lists the setup steps. Until trusted publishing is configured, the release job fails on each push to master, and I'm fine with that.

I also recommend turning off squash merging (action 4 in the description). Could you take another look when you have time?

dhensby and others added 5 commits November 2, 2026 13:23
semantic-release and the commitlint CI gate replace the previous gitflow
`development` branch model and the ad-hoc Angular-style commit convention. Update
the contributor guide to match: document Conventional Commits (required, linted
in CI) and which types trigger releases, note that the version and CHANGELOG.md
are produced automatically, and switch all branching/PR guidance from
`development` to `master`. Also fix the stale `npm run docs` script reference.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The "Tests for Release" workflow runs only for release-* branches.
Releases now come from master through semantic-release, and the
workflow has never run. Remove it, and stop excluding release-*
branches from the main test workflow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch removes the release branch workflow and the outdated
development and npm run docs instructions from CONTRIBUTING.md, and
semantic-release now updates the changelog and version. Remove the
AGENTS.md statements that would be wrong after the merge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dhensby
dhensby force-pushed the pulls/semantic-release branch from d9206d3 to d7d5f12 Compare November 2, 2026 13:23
@dhensby
dhensby marked this pull request as ready for review November 2, 2026 13:24
@dhensby
dhensby requested a review from jankapunkt November 2, 2026 13:27

This branch has not been deployed

No deployments
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.

3 participants