mirror of
https://github.com/cozystack/cozystack.git
synced 2026-10-23 03:27:50 +09:00
Extract the promote-time changelog generation into a reusable workflow (changelog-generate.yaml, workflow_call) and add a dispatchable wrapper (changelog-rc.yaml) so a maintainer can generate the release changelog the moment an rc goes green, while there is still time to review it. The reusable core keeps promote-rc's observable behaviour: detached rc-tag checkout, read-only app token for the AI step, the validate-changelog.sh gate, non-blocking AI failure, and the same changelog-vX.Y.Z artifact. .release-tooling is overlaid from github.ref_name, which under workflow_call is the caller's dispatch ref (and coincides with the reusable workflow's own ref for a local uses:), so "current tooling against an old rc tag" is preserved. Secrets are declared explicitly rather than inherited, so a caller that forgets one fails at parse time. The rc tag is parsed by a shared hack/parse-rc-tag.sh that REJECTS whitespace outright (a stripped 'v1. 6.0-rc.2' would silently become a different tag). promote-rc parses it ONCE in a cheap `parse` job that both `promote` and `changelog` depend on — so they still run in parallel with each other while consuming one validated tag, and a whitespace tag fails the whole promotion rather than letting promote succeed while the changelog job rejects the same input and the PR opens empty. The reusable core mirrors the reject-whitespace policy inline because its parse must run before any checkout. changelog-rc.yaml verifies the rc staging branch release-X.Y.Z-rc.N exists UP FRONT (in parse, which generate depends on), so a missing branch fails in seconds rather than after a 30-minute AI run, then commits the result there as docs/changelogs/vX.Y.Z.md idempotently (compare-before-push). promote-rc's changelog job is a thin uses: call. Source selection — validate-then-fall-through across the rc tag tree, the rc staging branch (the rc-time pickup), then AI generation, discarding any invalid source with a loud warning so it never suppresses a valid lower-priority one — lives in hack/select-changelog-source.sh, executed after the tooling overlay. open-pr carries the picked-up file onto release-X.Y.Z and finalize publishes it. Contract and behaviour bats are repointed and extended: the shared parser, the source-selection script, and promote's parse policy are all exercised by execution on adversarial inputs; the remaining grep pins match executable lines with exact counts so a comment or dead block cannot satisfy them. docs/release.md lists the new workflow and the rc-time entry point. actionlint and zizmor (default and pedantic) are clean. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
49 lines
1.8 KiB
Bash
Executable File
49 lines
1.8 KiB
Bash
Executable File
#!/bin/sh
|
|
# Usage: parse-rc-tag.sh <rc_tag>
|
|
#
|
|
# Strictly parse a release-candidate tag and print the derived release
|
|
# identifiers as `key=value` lines, ready to append to "$GITHUB_OUTPUT":
|
|
#
|
|
# rc_tag=v1.6.0-rc.2
|
|
# stable_version=1.6.0
|
|
# rc_branch=release-1.6.0-rc.2
|
|
#
|
|
# Whitespace is REJECTED, never stripped. Stripping (`tr -d '[:space:]'`) would
|
|
# silently "correct" a typo like 'v1. 6.0-rc.2' into a different, valid-looking
|
|
# tag. Worse, a caller that keys its concurrency lane on the raw dispatch input
|
|
# would then run two spellings of the same tag ('v1.6.0-rc.2' and
|
|
# 'v1.6.0-rc.2 ') in different lanes, racing to push the same staging branch.
|
|
# Rejecting outright makes the raw input a caller saw byte-identical to the tag
|
|
# acted on here: one canonical spelling per tag, or a hard error.
|
|
#
|
|
# Exit 0 = valid; the three key=value lines on stdout, nothing on stderr.
|
|
# Exit 1 = invalid; the reason on stderr, nothing on stdout.
|
|
|
|
set -eu
|
|
|
|
RC_TAG="${1?usage: parse-rc-tag.sh <rc_tag>}"
|
|
|
|
fail() {
|
|
echo "$1" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Reject any whitespace (leading, trailing, or internal) and the empty string
|
|
# outright, before the pattern match — a stripped-then-matched value would let a
|
|
# whitespace-bearing spelling through as if it were the canonical tag.
|
|
case "$RC_TAG" in
|
|
'') fail "rc_tag is empty" ;;
|
|
*[[:space:]]*) fail "rc_tag '$RC_TAG' must not contain whitespace" ;;
|
|
esac
|
|
|
|
printf '%s' "$RC_TAG" | grep -Eq '^v[0-9]+\.[0-9]+\.[0-9]+-rc\.[0-9]+$' \
|
|
|| fail "rc_tag '$RC_TAG' must match 'vX.Y.Z-rc.N' (e.g. v1.6.0-rc.1)"
|
|
|
|
rest="${RC_TAG#v}" # 1.6.0-rc.2
|
|
version="${rest%-rc.*}" # 1.6.0
|
|
n="${rest##*-rc.}" # 2
|
|
|
|
printf 'rc_tag=v%s-rc.%s\n' "$version" "$n"
|
|
printf 'stable_version=%s\n' "$version"
|
|
printf 'rc_branch=release-%s-rc.%s\n' "$version" "$n"
|