Skip to content

ci: release versions with semantic-release - #9

Open
frogggias wants to merge 5 commits into
mainfrom
ci/semantic-release-versions
Open

frogggias wants to merge 5 commits into
mainfrom
ci/semantic-release-versions

Conversation

@frogggias

@frogggias frogggias commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Adds automated versioning and GitHub releases. :latest is still published on every push to main.

This replaces #7: a follow-up commit there broke commitlint's 72-character header limit, so this branch carries the same change as one clean commit.

What changes

  • .releaserc.json: semantic-release on main with the conventionalcommits preset: a breaking change (! / BREAKING CHANGE:) → major, feat → minor, fix/perf → patch, any other type → no release. The plugins run in this order:

    1. commit-analyzer
    2. release-notes-generator
    3. exec. verifyConditionsCmd checks that the image digest from the build exists before any tag is created. prepareCmd runs docker buildx imagetools create to tag that digest as :X.Y.Z and :sha-<short> (no rebuild). semantic-release runs prepare before it creates and pushes the v* tag, so if tagging the image fails, no tag is left behind and a re-run can release the version.
    4. github. It creates the GitHub release after the image and the git tag exist. Its PR/issue comments and labels are off, so it needs only contents: write.

    Nothing is committed back to main; its ruleset blocks direct pushes.

  • publish.yml:

    • build pushes :latest (and ci-bokvi) as before, and now outputs the digest it pushed.
    • A new release job (needs: build, main only) runs semantic-release. It installs the whole dependency tree from release/package-lock.json with npm ci --ignore-scripts, since the job can push tags, releases and images, and checks out with persist-credentials: false.
    • A workflow-level concurrency group (publish-<ref>, no cancellation) runs publishes one at a time, so :latest and releases follow push order.
    • The tags: ['v*'] trigger is removed. A tag pushed with GITHUB_TOKEN starts no workflow, so the image is tagged inside the release job, and nobody pushes v* tags by hand.
  • dependabot.yml: the root Dockerfile ecosystem now uses the fix prefix, so Node.js 24 updates release a patch version. With chore they would only reach :latest. A new npm entry for /release keeps the lockfile updated, but not to the conventionalcommits preset's next major.

  • README / CONTRIBUTING: the tag table, the publishing steps and the release process now describe the automated flow.

conventional-changelog-conventionalcommits stays on 9.x: 10.x needs conventional-changelog-writer@9, but @semantic-release/release-notes-generator@14 ships writer 8. With 10.4.1, the dry run failed in generateNotes.

Assumptions

  • "Keep latest" means :latest stays on every push to main, as before. Versioned tags are added on top.
  • There are no tags yet, so merging this PR releases v1.0.0, semantic-release's first version. The two existing feat commits go into its notes.

Follow-ups (not in this PR)

  • Floating :X / :X.Y tags.
  • The image's org.opencontainers.image.version label stays latest on versioned tags. imagetools create re-tags the manifest and can't change labels.
  • docs-git's release-guard: Dotykacka/docs-git is private, so a bokvi workflow can't use it, and this repo has no release/* branches.
  • semantic-release can't create the image tag, the git tag and the GitHub release atomically. The image is tagged first. If the git tag or the release then fails, no release describes that image, and the next run overwrites the image tag. If the release fails after the v* tag is pushed, delete the tag and re-run.
  • The base image ghcr.io/actions/actions-runner:latest isn't pinned, so upstream runner updates only reach :latest until the next release (pre-existing).
  • Re-running an older publish run by hand can still move :latest back to the older commit.

Proof

There's no UI to screenshot before merge. This is a semantic-release dry run of the branch with the locked release/ install, IMAGE=ghcr.io/bokvi/actions-runner and DIGEST set to the current :latest digest:

✔  Allowed to push to the Git repository
ℹ  Start step "verifyConditions" of plugin "@semantic-release/exec"
ℹ  Call script docker buildx imagetools inspect "$IMAGE@$DIGEST" > /dev/null
✔  Completed step "verifyConditions" of plugin "@semantic-release/exec"
✔  Completed step "verifyConditions" of plugin "@semantic-release/github"
ℹ  No previous release found, retrieving all commits
ℹ  There is no previous release, the next release version is 1.0.0
⚠  Skip v1.0.0 tag creation in dry-run mode
⚠  Skip step "publish" of plugin "@semantic-release/github" in dry-run mode
✔  Published release 1.0.0 on default channel
## 1.0.0 (2026-10-14)
### Features
    * add runner image with libatomic1 for node.js 25+ (eb25d60)
    * install node.js 24 into the runner tool cache (#2) (61cc3ba), closes bokvi/id#159

With DIGEST empty, verifyConditions fails (EVERIFYCONDITIONS … imagetools inspect) before any tag is created. The rendered prepareCmd (a prepare step is skipped in dry-run mode) is:

docker buildx imagetools create -t "$IMAGE:1.0.0" -t "$IMAGE:sha-$(printf %.7s "$GITHUB_SHA")" "$IMAGE@$DIGEST"

It expands to -t img:1.0.0 -t img:sha-1f828c8 img@sha256:….

actionlint passes. Screenshots of the v1.0.0 release and the :1.0.0 package tag will be added after the merge run.

frogggias and others added 5 commits October 14, 2026 17:23
Every push to main still publishes :latest. After the build, semantic-release
derives the next version from the Conventional Commits since the last release,
creates the v<version> tag, tags the pushed image digest as :<version> and
:sha-<short-sha>, and then creates the GitHub release. A tag pushed with
GITHUB_TOKEN starts no workflow, so the v* tag trigger is gone.

Publish runs are serialized per ref so :latest and releases follow the push
order. Dependabot's Dockerfile updates use the fix prefix so they release a
patch version.

Co-authored-by: Innie of Bokvi <innie@bokvi.com>
Run the image tagging in the prepare step, so a failed retag leaves no v* tag
behind and a re-run can release the version. Name the sha- tag after the
built commit.

Co-authored-by: Innie of Bokvi <innie@bokvi.com>
The release job has write access to tags, releases and GHCR, so install its
whole dependency tree from release/package-lock.json with npm ci instead of
resolving it on every run. Dependabot keeps the lockfile up to date.

Co-authored-by: Innie of Bokvi <innie@bokvi.com>
Co-authored-by: Innie of Bokvi <innie@bokvi.com>
Co-authored-by: Innie of Bokvi <innie@bokvi.com>
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.

1 participant