For a final version the publish job now pushes branch release/qqq-<version> to QRun-IO/homebrew-tap and QRun-IO/scoop-bucket with PACKAGES_TOKEN and opens a pull request "qqq <version>" in each; a human merges. A prerelease, or a refused push or pull request, attaches qqq.rb / qqq.json to the release as before, and never fails the release. The logic moves to scripts/publish-packaging.sh, with a DRY_RUN=1 path that clones and commits locally but writes nothing remote. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
86 lines
5.1 KiB
Bash
Executable File
86 lines
5.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Copyright 2026 QRun.IO, Inc.
|
|
# Licensed under the Apache License, Version 2.0 (see LICENSE).
|
|
#
|
|
# Offer the filled Homebrew formula and Scoop manifest (scripts/update-packaging.mjs) to the tap and the
|
|
# bucket as pull requests; a human merges them. Called by the release workflow's publish job.
|
|
#
|
|
# final version push branch release/qqq-<version> to QRun-IO/homebrew-tap and QRun-IO/scoop-bucket with
|
|
# PACKAGES_TOKEN and open a pull request "qqq <version>" in each
|
|
# otherwise (prerelease, or the push or the pull request is refused) attach qqq.rb / qqq.json to the
|
|
# release instead, for a hand-made pull request (docs/release-runbook.md step 4)
|
|
#
|
|
# Never fails the release over the tap or the bucket. DRY_RUN=1 clones and commits locally but pushes,
|
|
# opens and attaches nothing; it needs only VERSION (and GITHUB_SERVER_URL, default https://git.kof22.com):
|
|
# DRY_RUN=1 VERSION=1.2.3 scripts/publish-packaging.sh
|
|
#
|
|
# Environment: VERSION, GITHUB_SERVER_URL, GITHUB_REPOSITORY, RELEASE_ID, TOKEN (job token, release assets),
|
|
# PACKAGES_TOKEN (tap and bucket), DRY_RUN.
|
|
set -euo pipefail
|
|
|
|
server=${GITHUB_SERVER_URL:-https://git.kof22.com}
|
|
api="$server/api/v1"
|
|
dry=${DRY_RUN:-0}
|
|
: "${VERSION:?VERSION is required}"
|
|
[[ $dry == 1 ]] || : "${TOKEN:?TOKEN is required}" "${RELEASE_ID:?RELEASE_ID is required}" "${GITHUB_REPOSITORY:?GITHUB_REPOSITORY is required}"
|
|
branch="release/qqq-$VERSION"
|
|
title="qqq $VERSION"
|
|
work=$(mktemp -d)
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
json() { node -e 'const o = {}; for (let i = 1; i < process.argv.length; i += 2) o[process.argv[i]] = process.argv[i + 1];
|
|
process.stdout.write(JSON.stringify(o))' -- "$@"; }
|
|
field() { node -e 'try { const v = JSON.parse(require("fs").readFileSync(0, "utf8")); const r = [].concat(v)
|
|
.find((p) => !process.argv[2] || (p.head && p.head.ref === process.argv[2])); process.stdout.write(String(r ? r[process.argv[1]] ?? "" : "")) }
|
|
catch { }' -- "$@"; }
|
|
|
|
attach() { # attach <file>: the fallback, a hand-made pull request from the release asset
|
|
local src=$1 name
|
|
name=$(basename "$src")
|
|
if [[ $dry == 1 ]]; then echo "DRY RUN: would attach $name to the release"; return 0; fi
|
|
curl -fsS -o /dev/null -X POST -H "Authorization: token $TOKEN" -F "attachment=@$src" \
|
|
"$api/repos/$GITHUB_REPOSITORY/releases/$RELEASE_ID/assets?name=$name" ||
|
|
{ echo "::warning::could not attach $name to the release"; return 0; }
|
|
echo "Attached $name to the release for a hand-made pull request"
|
|
}
|
|
|
|
propose() { # propose <repo> <src> <dest>: branch + pull request; non-zero means fall back
|
|
local repo=$1 src=$2 dest=$3 dir="$work/${1##*/}" header=() base pr body
|
|
[[ $dry == 1 || -n ${PACKAGES_TOKEN:-} ]] || { echo "PACKAGES_TOKEN is empty"; return 1; }
|
|
[[ -n ${PACKAGES_TOKEN:-} ]] && header=(-c "http.extraHeader=Authorization: Basic $(printf 'ci:%s' "$PACKAGES_TOKEN" | base64 | tr -d '\n')")
|
|
git "${header[@]}" clone -q --depth 1 "$server/$repo.git" "$dir" || return 1
|
|
base=$(git -C "$dir" rev-parse --abbrev-ref HEAD)
|
|
git -C "$dir" checkout -q -b "$branch"
|
|
mkdir -p "$(dirname "$dir/$dest")"
|
|
cp "$src" "$dir/$dest"
|
|
git -C "$dir" add "$dest"
|
|
if git -C "$dir" diff --cached --quiet; then echo "$repo $base already has qqq $VERSION"; return 0; fi
|
|
git -C "$dir" -c user.name=CI -c user.email=ci@kof22.com commit -q -m "$title"
|
|
git -C "$dir" --no-pager show --stat --format='%s' HEAD
|
|
if [[ $dry == 1 ]]; then
|
|
echo "DRY RUN: would push $branch to $repo and open pull request \"$title\" into $base"; return 0
|
|
fi
|
|
# CI owns release/qqq-<version>: a re-run replaces it.
|
|
git -C "$dir" "${header[@]}" push -q --force origin "HEAD:refs/heads/$branch" || { echo "push to $repo refused"; return 1; }
|
|
body="Formula/manifest for qqq $VERSION, filled by the qqq-cli release workflow from the release's SHA256SUMS: $server/$GITHUB_REPOSITORY/releases/tag/v$VERSION. Check the sums, then merge."
|
|
pr=$(curl -fsS -X POST -H "Authorization: token $PACKAGES_TOKEN" -H 'Content-Type: application/json' \
|
|
-d "$(json head "$branch" base "$base" title "$title" body "$body")" "$api/repos/$repo/pulls" 2>/dev/null | field html_url) || pr=''
|
|
if [[ -z $pr ]]; then # a re-run: the pull request may already be open
|
|
pr=$(curl -fsS -H "Authorization: token $PACKAGES_TOKEN" "$api/repos/$repo/pulls?state=open&limit=50" 2>/dev/null |
|
|
field html_url "$branch") || pr=''
|
|
fi
|
|
[[ -n $pr ]] || { echo "pushed $branch to $repo but could not open the pull request"; return 1; }
|
|
echo "Opened $pr"
|
|
}
|
|
|
|
node "$(dirname "$0")/update-packaging.mjs" "${SUMS_FILE:-/tmp/assets/SHA256SUMS}"
|
|
for spec in "QRun-IO/homebrew-tap docs/packaging/homebrew-tap/Formula/qqq.rb Formula/qqq.rb" \
|
|
"QRun-IO/scoop-bucket docs/packaging/qqq.json bucket/qqq.json"; do
|
|
read -r repo src dest <<< "$spec"
|
|
# Final versions only: the tap and the bucket follow the stable channel, like scripts/install.sh.
|
|
if [[ $VERSION == *-* ]]; then
|
|
echo "$repo not updated: $VERSION is a prerelease"; attach "$src"; continue
|
|
fi
|
|
propose "$repo" "$src" "$dest" || { echo "$repo not updated: falling back to the release asset"; attach "$src"; }
|
|
done
|