@webgrip/semantic-release-config (1.3.0)

Published 2026-09-17 08:42:43 +00:00 by webgrip-ci in webgrip/semantic-release-config

Installation

@webgrip:registry=
npm install @webgrip/semantic-release-config@1.3.0
"@webgrip/semantic-release-config": "1.3.0"

About this package

@webgrip/semantic-release-config

One semantic-release configuration for every Webgrip repo. Repos carry a ~3-line .releaserc.cjs; everything plugin-shaped lives here exactly once.

Non-negotiables baked in

  • Forgejo is the sole release authority. GitHub is a tags-only push-mirror; it never runs semantic-release. The factory throws at load time unless SEMANTIC_RELEASE_GITEA is set (the composite action sets it). Misconfiguration fails loudly instead of publishing nowhere.
  • Branches: main cuts stable releases, development cuts -rc.N prereleases. Merging development → main is the promotion act.
  • Tags: v${version} in plain repos; <package-name>-v${version} in monorepos (derived by semantic-release-monorepo from the stub package.json name). The v lives only on the git tag — artifact versions (docker tags, Chart.yaml, npm) are always bare semver.
  • Commit vocabulary: standard conventional commits only. feat → minor; fix/perf/refactor/revert → patch; chore(deps)/build(deps) → patch (Renovate bumps must roll out through GitOps); everything else releases nothing. Legacy types go through extraReleaseRules per repo, not into the shared defaults.
  • Changelog: written and committed by default. All commit types visible; dependency bumps get their own Dependencies section so Renovate noise doesn't drown the rest. A repo that promotes development into main can turn the commit-back off with changelog: false — see below.
  • Outputs: version and tag are always written to $GITHUB_OUTPUT (the composite action's contract; ignore tag if you don't need it).
  • Release commit: chore(release): <tag> [skip ci] — inert on Forgejo and on the GitHub mirror.

Usage

Repos do not install anything and do not need a package.json. The composite action runs npm install --no-save --no-package-lock @webgrip/semantic-release-config@<pin> into the workspace before invoking semantic-release; this package's dependencies (semantic-release itself plus all plugins) hoist into node_modules where they resolve by name. See examples/forgejo-action-release-step.yml.

Single-artifact repo (.releaserc.cjs at the root):

const { makeConfig } = require('@webgrip/semantic-release-config');
module.exports = makeConfig({});

Monorepos

Rules:

  1. Every releasable artifact lives in its own subdirectory with a stub package.json — even helm charts, Go binaries, Rust crates:

    { "name": "app", "private": true, "version": "0.0.0" }
    

    The name becomes the tag prefix (app-v1.2.3) and the directory scopes commit filtering. Use short unscoped names; a scoped name would produce tags like @webgrip/app-v1.2.3.

  2. No artifact at the repo root. A root package matches every commit and over-releases.

  3. Shared code that ships must live inside an artifact directory. Commits touching only files outside every package release nothing — correct for docs and CI, wrong for shipped code.

  4. .releaserc.cjs sits in each package directory; the action runs semantic-release once per package with that directory as cwd. CHANGELOG.md is therefore per-package.

// app/.releaserc.cjs
const { makeConfig, dockerVerifyGate } = require('@webgrip/semantic-release-config');
module.exports = makeConfig({ monorepo: true, manifest: 'npm', verifyReleaseCmd: dockerVerifyGate() });

// chart/.releaserc.cjs
module.exports = makeConfig({ monorepo: true, manifest: 'helm' });

Options

Option Default Effect
monorepo false extends: 'semantic-release-monorepo'; tagFormat left to the package name.
manifest none (tag-only) 'npm': @semantic-release/npm (publish off) + commits package.json back. 'helm': yq bumps Chart.yaml in prepare + commits it back. 'composer': documented no-op — Packagist reads tags.
chartPath '.' Directory containing Chart.yaml (helm only).
npmPublish false Actually publish to the registry (npm only) — used by this package itself.
changelog true false drops @semantic-release/changelog and takes CHANGELOG.md out of the commit-back. With no manifest to bump either, @semantic-release/git is dropped whole and the release becomes a bare tag.
branches main + development/rc Full override, e.g. to add maintenance branches.
prepareCmd — Extra prepare step, appended after the built-in manifest bump.
verifyReleaseCmd — Smoke test that runs before the tag is cut. Usually dockerVerifyGate().
extraReleaseRules [] Prepended, so they win over defaults. For repo-local legacy commit types.
extraNotesTypes [] Prepended changelog section mappings.
extraAssets [] Extra files for the release commit.
releaseAssets — Globs uploaded as Forgejo release assets (binaries, SBOMs).
issueUrlFormat preset default Only needed for external trackers (ClickUp etc.). #123 refs already link to Forgejo issues by default, since the preset derives URLs from the repo URL.
floatingMajorTag false Force-pushes vN on stable releases (composite-action convention, like actions/checkout@v4). Skips prereleases; incompatible with monorepo.
giteaUrl https://forgejo.webgrip.dev Escape hatch.

dockerVerifyGate(opts?)

Returns the verifyReleaseCmd that proves the image still builds before the tag exists, so a broken Dockerfile never burns a version number. With BUILD_CACHE_REF exported by the action, it reads the buildx cache the publish path writes (unchanged Dockerfile → cache hit in seconds) and routes base images through the Harbor proxy. It never writes to the cache — the gate is amd64-only, and writing would clobber the arm64 layers publish builds into the same ref. Without the env var it falls back to a plain docker build. Options: dockerfile, context, platform, buildArgs.

changelog: false and the promotion branch model

The release commit is the reason main and development drift apart. semantic-release cuts the stable version on main, @semantic-release/git pushes chore(release): vX.Y.Z [skip ci] there, and that commit exists nowhere else — so every promotion has to be followed by a back-merge of main into development. Forget it once and the next rc is computed against the last prerelease tag instead of the stable one, which cuts a vX.Y.Z-rc.N+1 for a version that already shipped.

changelog: false removes the cause. With no changelog to commit and no manifest to bump, nothing is pushed back to the branch and the release is the tag alone, so main after a promotion is byte-for-byte the commit that was promoted. The release notes are unaffected — they are generated by @semantic-release/release-notes-generator and published to the Forgejo release page either way. What you give up is CHANGELOG.md in the working tree.

Only worth it for repos that actually run the development → main promotion. A repo releasing straight off main has no drift to avoid and should keep the changelog.

First-release semantics (start from 0.0.0)

We start every repo from 0.0.0, so the first feat cuts 0.1.0 and the first fix cuts 0.0.1 — not semantic-release's default 1.0.0.

This can't live in this config factory. semantic-release hardcodes the first release to FIRST_RELEASE = "1.0.0" whenever no prior release exists, ignoring the bump type, and it reads the tag list into branch.tags during branch discovery — before any plugin runs — so a verifyConditions/exec step that created a tag would be seen too late. The baseline has to exist before semantic-release starts.

So the composite action seeds a v0.0.0 tag (<package-name>-v0.0.0 in monorepos) at the repo's root commit the first time it runs against a repo with no version tag. semantic-release then computes the first real version off that 0.0.0 baseline. The seed is idempotent — once any real release tag exists it's skipped — and requires full history with tags (actions/checkout fetch-depth: 0). See the Seed baseline version tag step in examples/forgejo-action-release-step.yml.

Because the baseline sits at the root commit, the first release's changelog covers the entire pre-existing history — expected, and only happens once. A breaking change before the first release still resolves to 1.0.0 (major bump from 0.0.0).

Local preview

SEMANTIC_RELEASE_GITEA=true npx semantic-release --dry-run --no-ci

Migration notes (behavior changes, per origin config)

  • App+chart trains (A): name the stubs app and chart and the tag sequences continue unchanged (app-v…, chart-v…). Prerelease channel renames -development.N → -rc.N (in-flight prereleases restart their counter). Release rules move from preset defaults to the explicit table: refactors and Renovate bumps start cutting releases. Path filtering is new: chart-only commits no longer bump the app.
  • ploeg (B): near-zero delta; GITEA_ACTIONS sniffing replaced by the hard SEMANTIC_RELEASE_GITEA requirement, and the release commit now also carries CHANGELOG.md per the shared assets.
  • Docker app (C): gains a CHANGELOG.md; loses the @semantic-release/github path entirely (dead code — GitHub never releases); release/* branches dropped for development; legacy feature/bugfix/hotfix kept alive via extraReleaseRules until commitlint retires them.

Dependencies

Dependencies

ID Version
@saithodev/semantic-release-gitea ^2.1.0
@semantic-release/changelog ^7.0.0
@semantic-release/commit-analyzer 14.0.0-beta.3
@semantic-release/exec ^7.1.0
@semantic-release/git ^11.0.1
@semantic-release/npm ^13.1.5
@semantic-release/release-notes-generator 15.0.0-beta.2
conventional-changelog-conventionalcommits ^10.2.1
semantic-release ^25.0.9

Development dependencies

ID Version
semantic-release-monorepo ^8.0.2
Details
npm
2026-09-17 08:42:43 +00:00
4
12 KiB
Assets (1)
Versions (8) View all
1.3.1 2026-09-22
1.3.0 2026-09-17
1.2.3 2026-08-28
1.2.2 2026-08-11
1.2.1 2026-08-11