From 4e6fd15dac35b93ea4aca73ad4b63e45300e47ab Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 13 Oct 2026 14:20:14 -0500 Subject: [PATCH 1/3] feat: reusable qbit release-candidate workflow Merges develop into the qbit's release line (release/X.Y.Z or release/X.Y from develop's X.Y.Z-SNAPSHOT revision, created when missing) through the GitHub API, so GitHub signs the merge. The release/* push runs the existing CircleCI RC publish. Supports dry_run. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/qbit-release-rc.yml | 73 +++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/qbit-release-rc.yml diff --git a/.github/workflows/qbit-release-rc.yml b/.github/workflows/qbit-release-rc.yml new file mode 100644 index 0000000..11b2c57 --- /dev/null +++ b/.github/workflows/qbit-release-rc.yml @@ -0,0 +1,73 @@ +# Cut the next release candidate of a qbit (QRun-IO/qqq docs/QRUN-QBIT-REPO-STANDARD.md, C6). +# +# Called from each qbit's .github/workflows/release-rc.yml on a manual dispatch or when a +# develop PR labelled `release:rc` is merged. It merges develop into the release line through +# the GitHub API (GitHub signs the merge commit), and the push of release/* runs the existing +# CircleCI publish_release_candidate workflow (qqq-orb: RC bump, Maven Central, tag). Nothing +# here publishes, tags or touches hotfix/* branches. +# +# Release line: develop's X.Y.Z-SNAPSHOT picks release/X.Y.Z if that branch +# exists, else release/X.Y (created from develop when missing). +name: qbit release candidate + +on: + workflow_call: + inputs: + dry_run: + description: Print the plan without merging or creating branches + type: boolean + default: false + +permissions: + contents: write + +jobs: + cut-rc: + runs-on: ubuntu-24.04 + timeout-minutes: 10 + concurrency: + group: qbit-release-rc-${{ github.repository }} + cancel-in-progress: false + steps: + - name: Merge develop into the release line + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + DRY_RUN: ${{ inputs.dry_run }} + run: | + set -euo pipefail + develop_sha=$(gh api "repos/$REPO/branches/develop" -q .commit.sha) + revision=$(gh api "repos/$REPO/contents/pom.xml?ref=$develop_sha" -q .content | base64 -d \ + | sed -n 's:.*\(.*\).*:\1:p' | head -n 1) + if [[ ! "$revision" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)-SNAPSHOT$ ]]; then + echo "::error::develop pom.xml must be X.Y.Z-SNAPSHOT, found '$revision'" + exit 1 + fi + full="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.${BASH_REMATCH[3]}" + line="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}" + branch_exists() { gh api "repos/$REPO/branches/$1" --silent 2>/dev/null; } + if branch_exists "release/$full"; then branch="release/$full" + elif branch_exists "release/$line"; then branch="release/$line" + else branch=""; fi + + if [[ -z "$branch" ]]; then + branch="release/$line" + echo "Plan: create $branch from develop $develop_sha (first RC of $full)" + [[ "$DRY_RUN" == true ]] && exit 0 + gh api "repos/$REPO/git/refs" -f ref="refs/heads/$branch" -f sha="$develop_sha" --silent + else + echo "Plan: merge develop $develop_sha into $branch" + [[ "$DRY_RUN" == true ]] && exit 0 + # 201 merged, 204 already up to date (nothing new to release), 409 conflict. + response=$(gh api --include "repos/$REPO/merges" -f base="$branch" -f head="$develop_sha" \ + -f commit_message="Merge develop into $branch for the next $full RC" 2>&1) || true + status=$(printf '%s\n' "$response" | sed -n 's:^HTTP/[0-9.]* \([0-9][0-9]*\).*:\1:p' | head -n 1) + case "$status" in + 201) ;; + 204) echo "::warning::$branch already contains develop; no new RC"; exit 0 ;; + 409) echo "::error::Merge conflict between develop and $branch; resolve it in a PR into $branch"; exit 1 ;; + *) echo "::error::Merge of develop into $branch failed (HTTP ${status:-unknown})"; exit 1 ;; + esac + fi + echo "Pushed $branch; CircleCI publish_release_candidate cuts the RC." >> "$GITHUB_STEP_SUMMARY" + echo "Release line: $branch" >> "$GITHUB_STEP_SUMMARY" -- 2.54.0 From fd2304d7d8ad1fe8de527af2ffea617288b3e262 Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 13 Oct 2026 14:25:41 -0500 Subject: [PATCH 2/3] ci: lint workflows on CircleCI instead of reporting a missing config The org CircleCI app builds every repository, so commits here showed a "No configuration was found" error status (main included). Co-Authored-By: Claude Opus 5.5 --- .circleci/config.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..fb1f2ea --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,18 @@ +# The org's CircleCI GitHub App builds every repository; without a config this repository +# reported "No configuration was found" as an error on every commit. Lint the workflows instead. +version: 2.1 + +jobs: + actionlint: + docker: + - image: rhysd/actionlint:1.7.7 + steps: + - checkout + - run: + name: Lint GitHub Actions workflows + command: actionlint -color .github/workflows/*.yml + +workflows: + lint: + jobs: + - actionlint -- 2.54.0 From 616d2f0385abab8a38c9add6b656076b2127b5d9 Mon Sep 17 00:00:00 2001 From: James Maes Date: Tue, 13 Oct 2026 14:26:22 -0500 Subject: [PATCH 3/3] ci: run actionlint from a cimg/go image (checkout needs git and HOME) Co-Authored-By: Claude Opus 5.5 --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fb1f2ea..34b68ca 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,12 +5,12 @@ version: 2.1 jobs: actionlint: docker: - - image: rhysd/actionlint:1.7.7 + - image: cimg/go:1.24 steps: - checkout - run: name: Lint GitHub Actions workflows - command: actionlint -color .github/workflows/*.yml + command: go run github.com/rhysd/actionlint/cmd/actionlint@v1.7.7 -color .github/workflows/*.yml workflows: lint: -- 2.54.0