From 7776958cd98f1e080242397359ddd508e45c6aa3 Mon Sep 17 00:00:00 2001 From: Torsten Ueberschar Date: Sat, 31 Oct 2026 10:03:49 +0100 Subject: [PATCH 1/2] feat(bump)!: nur Tag, kein Versions-Commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Der Build nimmt die Version aus dem Tag (CI_COMMIT_TAG als Build-Arg VERSION), package.json bleibt unberührt. Damit bleibt der Head-Commit eines PRs der des Entwicklers und behält seinen Status-Check; [skip ci] entfällt. - settings.release_branch: nächste stabile Version = höchster Patch der Linie + 1 - ein Commit, der schon ein v*-Tag trägt, wird nicht erneut getaggt (kein Doppel-Build) - Image auf alpine:3.20 wie deploy, node wird nicht mehr gebraucht - author_name/author_email entfallen Co-Authored-By: Claude Fable 5.1 --- README.md | 11 ++++--- woodpecker/bump/Dockerfile | 4 +-- woodpecker/bump/bump.sh | 65 +++++++++++++++++++------------------- 3 files changed, 41 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 58d7d7d..e05300b 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,8 @@ steps: registry: git.uesome.de repo: git.uesome.de/tanztee/oumta-auth tags: ${CI_COMMIT_TAG} + build_args: + VERSION: ${CI_COMMIT_TAG} username: oumta-ci password: from_secret: oumta_ci_token @@ -171,16 +173,16 @@ steps: ### bump -Sets the version for the pushed branch in `package.json`, commits it as `update version: vX.Y.Z [skip ci]`, tags it and pushes branch and tag in one atomic push. The tag event then starts the build; `[skip ci]` only suppresses push and pull_request events. +Derives the next version for the pushed branch and pushes it as a tag on the triggering commit. There is no version commit: the build takes the version from the tag (`CI_COMMIT_TAG`, e.g. as build arg `VERSION`), `package.json` stays untouched. The tag event then starts the build. - `release/X.Y[.Z]`, `hotfix/X.Y.Z`: next `vX.Y.Z-beta.N` of that line (D8). A hotfix needs a patch number. - `production/*`: no bump, the step fails. A release is a retag of a beta image (D9). -- the branch in `release_branch` (repos without majors, e.g. oumta-keyklo `main`): next free `vX.Y.Z`, a patch bump from `package.json` +- the branch in `release_branch` (repos without majors, e.g. oumta-keyklo `main`): next stable `vX.Y.Z`, the highest existing patch of the line plus one - any other branch: `vX.Y.Z-dev.`; the line comes from `release_line`, the branch name, the nearest `release/*` or `hotfix/*` branch (newest merge base), or `major.minor` from `package.json` If the stable tag of a line exists (`v2.4.0` after the release), the next patch of that line is used (`v2.4.1-beta.0`, `v2.4.1-dev.…`). -Only the commit that triggered the pipeline is bumped; if the branch moved on, the step exits without a bump and the newer pipeline takes over. The same happens when the push is rejected because of a race: branch and tag are pushed atomically, so either both land or neither. +Only the commit that triggered the pipeline is tagged; if the branch moved on, the step exits without a tag and the newer pipeline takes over. A commit that already carries a `v*` tag is not tagged again, so a re-run does not build twice. Settings: @@ -188,7 +190,6 @@ Settings: - `git_user`: default `oumta-ci` - `release_branch`: optional, branch that gets stable tags instead of dev tags - `release_line`: optional, `.[.]`, overrides every other rule -- `author_name`, `author_email`: default `oumta-ci`, `oumta-ci@noreply.git.uesome.de` - `remote`: default `CI_REPO_CLONE_URL` Example: @@ -201,7 +202,7 @@ when: steps: - name: bump - image: git.uesome.de/actions/ci-cd/bump:v1.0.0 + image: git.uesome.de/actions/ci-cd/bump:v2.0.0 settings: git_token: from_secret: oumta_ci_token diff --git a/woodpecker/bump/Dockerfile b/woodpecker/bump/Dockerfile index 3a0714a..cde6f3a 100644 --- a/woodpecker/bump/Dockerfile +++ b/woodpecker/bump/Dockerfile @@ -1,5 +1,5 @@ -# Woodpecker plugin: bumps the version in package.json, tags and pushes -FROM node:24-alpine +# Woodpecker plugin: derives the next version and pushes it as a tag +FROM alpine:3.20 RUN apk add --no-cache bash git ca-certificates COPY bump.sh /usr/local/bin/bump.sh ENTRYPOINT ["/usr/local/bin/bump.sh"] diff --git a/woodpecker/bump/bump.sh b/woodpecker/bump/bump.sh index 5b48ca3..ff88bbe 100755 --- a/woodpecker/bump/bump.sh +++ b/woodpecker/bump/bump.sh @@ -1,7 +1,8 @@ #!/usr/bin/env bash -# Woodpecker plugin "bump": sets the version for the pushed branch, commits it -# with [skip ci], tags it and pushes both. The tag event then starts the build; -# [skip ci] only suppresses push and pull_request events, not tag events. +# Woodpecker plugin "bump": derives the next version for the pushed branch and +# pushes it as a tag on the triggering commit. No commit is made; the build +# takes the version from the tag (CI_COMMIT_TAG). The tag event then starts +# the build. # # release/X.Y[.Z], hotfix/X.Y.Z next vX.Y.Z-beta.N (D8) # production/* nothing: releases are retags (D9) @@ -23,9 +24,6 @@ case $BRANCH in production/*) die "no bump on $BRANCH: a release is a retag of a beta image (D9)" ;; esac -export GIT_AUTHOR_NAME=${PLUGIN_AUTHOR_NAME:-oumta-ci} GIT_AUTHOR_EMAIL=${PLUGIN_AUTHOR_EMAIL:-oumta-ci@noreply.git.uesome.de} -export GIT_COMMITTER_NAME=$GIT_AUTHOR_NAME GIT_COMMITTER_EMAIL=$GIT_AUTHOR_EMAIL - # Token only via credential helper, never in a URL or config, so no log shows it export GIT_USER PLUGIN_GIT_TOKEN git config --global --add safe.directory "$PWD" @@ -38,14 +36,20 @@ if [[ $(git rev-parse --is-shallow-repository) == true ]]; then fi git fetch --quiet --tags --force origin "+refs/heads/*:refs/remotes/origin/*" -# Bump exactly the commit that triggered the pipeline. A newer push on the +# Tag exactly the commit that triggered the pipeline. A newer push on the # branch starts its own pipeline, so this one steps aside. head=$(git rev-parse -q --verify "origin/$BRANCH") || die "$BRANCH no longer exists on origin" if [[ $head != "$SHA" ]]; then echo "bump: $BRANCH moved past ${SHA:0:8}, the newer pipeline bumps" exit 0 fi -git checkout --quiet -B "$BRANCH" "$SHA" +git checkout --quiet "$SHA" + +# A re-run must not produce a second tag and a second build for the same commit. +if existing=$(git tag --points-at HEAD 'v*' | head -n 1) && [[ -n $existing ]]; then + echo "bump: ${SHA:0:8} already carries $existing, nothing to do" + exit 0 +fi tag_exists() { git rev-parse -q --verify "refs/tags/v$1" >/dev/null; } @@ -77,23 +81,35 @@ line_of_nearest_branch() { sort -k1,1nr -k2,2n -k3,3n -k4,4n | head -n 1 | { read -r _ _ _ _ line && echo "$line"; } } -package_version() { node -p "require('./package.json').version"; } +# major.minor.0 from the version in package.json, if there is one +line_of_package() { + local version + [[ -f package.json ]] || return 1 + version=$(sed -nE 's/^[[:space:]]*"version"[[:space:]]*:[[:space:]]*"([^"]+)".*/\1/p' package.json | head -n 1) + version=$(parse_line "${version%%[-+]*}") || return 1 + echo "${version%.*}.0" +} # The line for this branch: setting, branch name, nearest release/hotfix # branch, else major.minor of package.json. resolve_line() { - local line if [[ -n $RELEASE_LINE ]]; then parse_line "$RELEASE_LINE" || die "invalid settings.release_line '$RELEASE_LINE', expected .[.]" return fi line_of_branch "$BRANCH" && return line_of_nearest_branch && return - line=$(package_version) - parse_line "${line%%[-+]*}" 2>/dev/null && return + line_of_package && return die "no release line for '$BRANCH': use settings.release_line or a semantic version in package.json" } +# The next stable version of a line: highest existing vX.Y.Z plus one patch. +next_stable() { + local line=$1 highest + highest=$(git tag --list "v${line%.*}.*" | sed -nE 's/^v[0-9]+\.[0-9]+\.([0-9]+)$/\1/p' | sort -n | tail -n 1) + echo "${line%.*}.$(( ${highest:--1} + 1 ))" +} + # A line whose stable tag exists was released; the next patch is the open one. next_free_patch() { local line=$1 patch @@ -126,32 +142,17 @@ case $BRANCH in ;; *) if [[ -n $RELEASE_BRANCH && $BRANCH == "$RELEASE_BRANCH" ]]; then - line=$(package_version) - line=$(parse_line "${line%%[-+]*}") || die "package.json version '$line' is not semantic" - patch=${line##*.} - VERSION=$(next_free_patch "${line%.*}.$((patch + 1))") + VERSION=$(next_stable "$(resolve_line)") else VERSION=$(next_dev "$(next_free_patch "$(resolve_line)")") fi ;; esac -tag_exists "$VERSION" && die "tag v$VERSION already exists" - -npm version "$VERSION" --no-git-tag-version --allow-same-version >/dev/null TAG=v$VERSION -if git diff --quiet; then - echo "bump: version already $VERSION, tagging the current commit" -else - git add package.json - [[ -f package-lock.json ]] && git add package-lock.json - git commit --quiet -m "update version: $TAG [skip ci]" -fi -git tag "$TAG" -# Branch and tag in one atomic push: either both land or neither. If the -# branch moved meanwhile, the push is rejected and nothing is tagged. -if ! git push --quiet --atomic origin "HEAD:refs/heads/$BRANCH" "refs/tags/$TAG"; then - echo "bump: push to $BRANCH rejected, the newer pipeline bumps" +git tag "$TAG" +if ! git push --quiet origin "refs/tags/$TAG"; then + echo "bump: push of $TAG rejected, the newer pipeline bumps" exit 0 fi -echo "bump: $TAG on $BRANCH" +echo "bump: $TAG on $BRANCH (${SHA:0:8})" -- 2.54.0 From 1a9bbbba7b9e2baa516f0a91daddac43e09697d1 Mon Sep 17 00:00:00 2001 From: Torsten Ueberschar Date: Sat, 31 Oct 2026 10:25:22 +0100 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20Build-Arg=20VERSION=20ohne=20f?= =?UTF-8?q?=C3=BChrendes=20v?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e05300b..392ac0e 100644 --- a/README.md +++ b/README.md @@ -155,7 +155,7 @@ steps: repo: git.uesome.de/tanztee/oumta-auth tags: ${CI_COMMIT_TAG} build_args: - VERSION: ${CI_COMMIT_TAG} + VERSION: ${CI_COMMIT_TAG##v} username: oumta-ci password: from_secret: oumta_ci_token @@ -173,7 +173,7 @@ steps: ### bump -Derives the next version for the pushed branch and pushes it as a tag on the triggering commit. There is no version commit: the build takes the version from the tag (`CI_COMMIT_TAG`, e.g. as build arg `VERSION`), `package.json` stays untouched. The tag event then starts the build. +Derives the next version for the pushed branch and pushes it as a tag on the triggering commit. There is no version commit: the build takes the version from the tag (`CI_COMMIT_TAG`, e.g. as build arg `VERSION: ${CI_COMMIT_TAG##v}`), `package.json` stays untouched. The tag event then starts the build. - `release/X.Y[.Z]`, `hotfix/X.Y.Z`: next `vX.Y.Z-beta.N` of that line (D8). A hotfix needs a patch number. - `production/*`: no bump, the step fails. A release is a retag of a beta image (D9). -- 2.54.0