Summary
Introduce a clear, self-contained release process for this repository. Today every push to main builds and publishes to npm automatically via the shared openmfp/gha workflows. This change separates validating a version from cutting a release: main builds and tests only, and releases become an explicit, manually triggered action owned entirely by this repo. It also adds a generated changelog, on-demand release branches for backports, and standard Conventional Commits versioning.
The everyday flow stays simple — open a PR, merge it — and cutting a release is a single manual workflow run.
Current state
.github/workflows/pipeline.yaml delegates to openmfp/gha (pipeline-node-module.yml, job-node-publish.yml), which builds, versions (via ietf-tools/semver-action), and publishes on every push to main.
- npm publish auth (OIDC, npm environments) lives inside
openmfp/gha, not here.
- There is no
CHANGELOG.md, no manual release path, and no release branches.
Goals
main validates but never publishes.
- Releases are cut manually, on demand, from this repo alone.
- Versioning follows standard Conventional Commits / SemVer, documented and validated.
- A changelog is generated and committed on each release.
- Backports are supported via on-demand release branches.
- The next version and changelog can be previewed before releasing.
Design overview
Self-contained. All release logic lives in this repository. We do not depend on the shared openmfp/gha reusable workflows; build, test, and publish steps are defined here directly.
main builds and tests only. A push to main produces no publish, no version bump, and no tag — green CI simply confirms main is releasable. There is one version line and no prerelease/dev versions.
Releases are manual. A workflow_dispatch workflow performs the full release: compute version → build/test → generate changelog → publish both packages to npm → commit, tag, and push → publish a GitHub Release. Publishing happens before the git tag/commit is pushed so that a failed publish never leaves a tag pointing at an unreleased version; if the tag/push fails after a successful publish, re-running is safe because npm rejects a duplicate version. The GitHub Release is created against the new tag with the git-cliff-generated notes as its body. The workflow runs against whichever branch it is triggered on (github.ref_name), which is what enables backports.
One version, two packages. The repo publishes exactly two npm packages, in lockstep at the same version taken from the root package.json:
@openmfp/webcomponents — built to dist/webcomponents; includes the dashboard as its ./dashboard export.
@openmfp/ngx — built to dist/ngx.
The dashboard is not a separate package: scripts/bundle-wc.mjs bundles mfp-wc-dashboard.js into @openmfp/webcomponents and writes the computed version into dist/webcomponents/package.json. @openmfp/ngx needs the equivalent: projects/ngx/package.json carries a stale 0.1.0, so the workflow must write the computed version into dist/ngx/package.json before publishing (the shared workflow did this previously). A single release produces one git tag, one GitHub Release, and one CHANGELOG.md.
npm authentication. Publishing uses npm OIDC Trusted Publishing (no long-lived token). The pipeline already grants id-token: write.
Versioning
Standard Conventional Commits drive the version. Only feat and fix/perf cut a release on their own; breaking changes are signalled by ! or a BREAKING CHANGE: footer.
| Commit (PR title) |
Increment |
Example (pre-1.0) |
feat: … |
minor |
0.18.7 → 0.19.0 |
fix: … / perf: … |
patch |
0.18.7 → 0.18.8 |
feat!: … or BREAKING CHANGE: footer |
breaking (see below) |
0.18.7 → 0.19.0 |
docs: chore: ci: build: refactor: test: style: revert: |
none |
— |
ietf-tools/semver-action remains the single source of truth for the computed version, configured as:
prefix: "" # tags are bare (0.19.0), matching existing history
majorList: "" # breaking handled via "!" / BREAKING CHANGE footer
minorList: "feat"
patchList: "fix, perf"
noVersionBumpBehavior: silent # housekeeping-only PRs cut no release
noNewCommitBehavior: silent
skipInvalidTags: true
Tags are bare (0.19.0), matching the existing 130 tags and the npm version exactly — one identical string across the git tag, GitHub Release, changelog heading, and package.json. No migration or seed tag is needed.
Pre-1.0 breaking changes. While on 0.y.z the public API is not considered stable (SemVer §4), so breaking changes bump the minor version rather than jumping to 1.0.0. semver-action has no native pre-1.0 handling — it calls semver.inc(version, 'major') unconditionally, which yields 1.0.0 from any 0.x breaking change. A small guard step in the workflow therefore downgrades a major result to the corresponding minor while the current version is 0.x. Promotion to 1.0.0 is a deliberate action via the release-as override, at which point this guard no longer applies.
Manual override. The release workflow accepts a release-as input to force an exact version (e.g. to ship 1.0.0).
Validation. Since the repo squash-merges, the PR title becomes the commit that lands on main and is the only message that drives versioning and the changelog. A required check lints the PR title against Conventional Commits and blocks merge if it does not conform (e.g. amannn/action-semantic-pull-request). Individual in-branch commits are not constrained. The accepted types and the table above are documented in CONTRIBUTING.md.
Changelog
git-cliff (via orhun/git-cliff-action) generates both the committed CHANGELOG.md and the GitHub Release notes in one step, with no npm dependency. It acts purely as a formatter: the workflow always passes the version computed by semver-action (--tag <version>), so git-cliff never decides the version itself.
Because versioning uses standard Conventional Commits, git-cliff's default parser (conventional_commits = true) classifies commits the same way semver-action does, with no separate wordlist to maintain. cliff.toml (scaffolded via git cliff --init github) groups feat → Features, fix → Bug Fixes, perf → Performance, breaking changes → Breaking Changes, and either groups or skips housekeeping types.
The release step passes an explicit commit range and version (git cliff <lastTag>..HEAD --tag <version>) rather than bare --unreleased, so changelogs on backport branches are scoped correctly. CHANGELOG.md is committed so history is browsable in-repo.
Preview
Maintainers can see the computed version and changelog before anything is tagged or published:
- CI dry-run (primary): a
dry-run input runs version computation and changelog generation, writes the result to the GitHub Actions step summary ($GITHUB_STEP_SUMMARY), and skips commit, tag, and publish.
- Local (documented in
RELEASING.md): git cliff --bumped-version prints the next version; git cliff --unreleased --bump prints the pending changelog. Both are read-only.
Backports
A normal release is just a tag on main; long-lived release branches are only created when a backport to an older line is actually needed. This on-demand model suits a library that ships forward from main, and avoids idle per-minor branches.
To backport a fix:
- Branch from the relevant tag:
git checkout -b release/0.18 0.18.7.
- Cherry-pick or merge the fix onto that branch.
- Run the release workflow on
release/0.18 to cut 0.18.x.
The workflow derives the baseline from the branch's own history (git describe --tags --abbrev=0) so it computes the next 0.18.x patch rather than picking up newer tags from main.
Tasks
Out of scope
- Prerelease or dev publishing from
main.
- Modifying the shared
openmfp/gha workflows.
- Pre-creating a release branch for every minor version.
- Independent per-package versioning.
Implementation notes
- Tags: bare (
0.19.0), matching the existing 130 tags and the npm version — the git tag, GitHub Release, changelog heading, and package.json are all the same string. semver-action runs with prefix: ""; no migration needed.
- npm OIDC: reuse the existing npm environments (
npmjs:@openmfp/webcomponents, npmjs:@openmfp/ngx); the release job declares the same environment: so branch does not matter and backport branches publish too.
- Publish before tag: publish both packages first, then commit/tag/push, so a failed publish never leaves a dangling tag and a re-run is safe (npm rejects duplicate versions).
- ngx version:
dist/ngx/package.json must be set to the computed version before publish; otherwise the stale 0.1.0 ships.
- Pre-1.0 guard:
semver.inc(major) on a 0.x version yields 1.0.0; downgrade to the corresponding minor until 1.0.0 is intentional.
- PR-title check is load-bearing: with squash-merge the PR title is the message that reaches
main and drives versioning — make the check required.
- Housekeeping-only periods cut no release: a stretch of
docs:/chore: merges intentionally produces no version.
Summary
Introduce a clear, self-contained release process for this repository. Today every push to
mainbuilds and publishes to npm automatically via the sharedopenmfp/ghaworkflows. This change separates validating a version from cutting a release:mainbuilds and tests only, and releases become an explicit, manually triggered action owned entirely by this repo. It also adds a generated changelog, on-demand release branches for backports, and standard Conventional Commits versioning.The everyday flow stays simple — open a PR, merge it — and cutting a release is a single manual workflow run.
Current state
.github/workflows/pipeline.yamldelegates toopenmfp/gha(pipeline-node-module.yml,job-node-publish.yml), which builds, versions (viaietf-tools/semver-action), and publishes on every push tomain.openmfp/gha, not here.CHANGELOG.md, no manual release path, and no release branches.Goals
mainvalidates but never publishes.Design overview
Self-contained. All release logic lives in this repository. We do not depend on the shared
openmfp/ghareusable workflows; build, test, and publish steps are defined here directly.mainbuilds and tests only. A push tomainproduces no publish, no version bump, and no tag — green CI simply confirmsmainis releasable. There is one version line and no prerelease/dev versions.Releases are manual. A
workflow_dispatchworkflow performs the full release: compute version → build/test → generate changelog → publish both packages to npm → commit, tag, and push → publish a GitHub Release. Publishing happens before the git tag/commit is pushed so that a failed publish never leaves a tag pointing at an unreleased version; if the tag/push fails after a successful publish, re-running is safe because npm rejects a duplicate version. The GitHub Release is created against the new tag with thegit-cliff-generated notes as its body. The workflow runs against whichever branch it is triggered on (github.ref_name), which is what enables backports.One version, two packages. The repo publishes exactly two npm packages, in lockstep at the same version taken from the root
package.json:@openmfp/webcomponents— built todist/webcomponents; includes the dashboard as its./dashboardexport.@openmfp/ngx— built todist/ngx.The dashboard is not a separate package:
scripts/bundle-wc.mjsbundlesmfp-wc-dashboard.jsinto@openmfp/webcomponentsand writes the computed version intodist/webcomponents/package.json.@openmfp/ngxneeds the equivalent:projects/ngx/package.jsoncarries a stale0.1.0, so the workflow must write the computed version intodist/ngx/package.jsonbefore publishing (the shared workflow did this previously). A single release produces one git tag, one GitHub Release, and oneCHANGELOG.md.npm authentication. Publishing uses npm OIDC Trusted Publishing (no long-lived token). The pipeline already grants
id-token: write.Versioning
Standard Conventional Commits drive the version. Only
featandfix/perfcut a release on their own; breaking changes are signalled by!or aBREAKING CHANGE:footer.feat: …fix: …/perf: …feat!: …orBREAKING CHANGE:footerdocs:chore:ci:build:refactor:test:style:revert:ietf-tools/semver-actionremains the single source of truth for the computed version, configured as:Tags are bare (
0.19.0), matching the existing 130 tags and the npm version exactly — one identical string across the git tag, GitHub Release, changelog heading, andpackage.json. No migration or seed tag is needed.Pre-1.0 breaking changes. While on
0.y.zthe public API is not considered stable (SemVer §4), so breaking changes bump the minor version rather than jumping to1.0.0.semver-actionhas no native pre-1.0 handling — it callssemver.inc(version, 'major')unconditionally, which yields1.0.0from any0.xbreaking change. A small guard step in the workflow therefore downgrades amajorresult to the correspondingminorwhile the current version is0.x. Promotion to1.0.0is a deliberate action via therelease-asoverride, at which point this guard no longer applies.Manual override. The release workflow accepts a
release-asinput to force an exact version (e.g. to ship1.0.0).Validation. Since the repo squash-merges, the PR title becomes the commit that lands on
mainand is the only message that drives versioning and the changelog. A required check lints the PR title against Conventional Commits and blocks merge if it does not conform (e.g.amannn/action-semantic-pull-request). Individual in-branch commits are not constrained. The accepted types and the table above are documented inCONTRIBUTING.md.Changelog
git-cliff(viaorhun/git-cliff-action) generates both the committedCHANGELOG.mdand the GitHub Release notes in one step, with no npm dependency. It acts purely as a formatter: the workflow always passes the version computed bysemver-action(--tag <version>), sogit-cliffnever decides the version itself.Because versioning uses standard Conventional Commits,
git-cliff's default parser (conventional_commits = true) classifies commits the same waysemver-actiondoes, with no separate wordlist to maintain.cliff.toml(scaffolded viagit cliff --init github) groupsfeat→ Features,fix→ Bug Fixes,perf→ Performance, breaking changes → Breaking Changes, and either groups or skips housekeeping types.The release step passes an explicit commit range and version (
git cliff <lastTag>..HEAD --tag <version>) rather than bare--unreleased, so changelogs on backport branches are scoped correctly.CHANGELOG.mdis committed so history is browsable in-repo.Preview
Maintainers can see the computed version and changelog before anything is tagged or published:
dry-runinput runs version computation and changelog generation, writes the result to the GitHub Actions step summary ($GITHUB_STEP_SUMMARY), and skips commit, tag, and publish.RELEASING.md):git cliff --bumped-versionprints the next version;git cliff --unreleased --bumpprints the pending changelog. Both are read-only.Backports
A normal release is just a tag on
main; long-lived release branches are only created when a backport to an older line is actually needed. This on-demand model suits a library that ships forward frommain, and avoids idle per-minor branches.To backport a fix:
git checkout -b release/0.18 0.18.7.release/0.18to cut0.18.x.The workflow derives the baseline from the branch's own history (
git describe --tags --abbrev=0) so it computes the next0.18.xpatch rather than picking up newer tags frommain.Tasks
.github/workflows/pipeline.yamlwith a self-contained build + test workflow for PRs andmainpushes (no publish, noopenmfp/ghadependency)..github/workflows/release.yaml: manualworkflow_dispatchrelease covering version → build/test → changelog → npm publish (latest, OIDC) → commit/tag/push → publish a GitHub Release (against the new tag, with thegit-cliffnotes as its body); publish before tagging so a failed publish leaves no tag; branch-aware viagithub.ref_name;dry-runandrelease-asinputs; concurrency group.@openmfp/webcomponentsand@openmfp/ngxtogether at the computed version. Write the computed version intodist/ngx/package.jsonbefore publishing (overwriting the stale0.1.0); fail the release if either publish fails.npmjs:@openmfp/webcomponents,npmjs:@openmfp/ngx) so backport branches publish too.semver-actionfor Conventional Commits SemVer (prefix: ""to match the existing bare tags) and add the pre-1.0 major→minor guard.cliff.tomland bootstrap the initialCHANGELOG.md(most existing history predates Conventional Commits, so seed it manually).main/release/*if branch protection is enabled (github-actions[bot]bypass or a token).CONTRIBUTING.md.RELEASING.mddocumenting the process: normal release, dry-run/preview, the backport workflow, and promotion to1.0.0.Out of scope
main.openmfp/ghaworkflows.Implementation notes
0.19.0), matching the existing 130 tags and the npm version — the git tag, GitHub Release, changelog heading, andpackage.jsonare all the same string.semver-actionruns withprefix: ""; no migration needed.npmjs:@openmfp/webcomponents,npmjs:@openmfp/ngx); the release job declares the sameenvironment:so branch does not matter and backport branches publish too.dist/ngx/package.jsonmust be set to the computed version before publish; otherwise the stale0.1.0ships.semver.inc(major)on a0.xversion yields1.0.0; downgrade to the corresponding minor until1.0.0is intentional.mainand drives versioning — make the check required.docs:/chore:merges intentionally produces no version.