Files
cozystack/hack/select-changelog-source.sh
Myasnikov Daniil 2dfbeea776 ci(release): dispatch changelog generation at rc time
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>
2026-07-24 18:16:16 +05:00

65 lines
2.6 KiB
Bash
Executable File

#!/bin/sh
# Usage: select-changelog-source.sh <version> <rc_branch>
#
# Decide which pre-existing changelog (if any) release generation should use,
# WITHOUT running the AI — validate-then-fall-through across the sources, so an
# invalid higher-priority file can never silently suppress a valid lower-priority
# one:
#
# 1. docs/changelogs/vX.Y.Z.md already in the working tree (the rc TAG tree) —
# a maintainer hand-wrote it before the tag was cut.
# 2. docs/changelogs/vX.Y.Z.md on the rc STAGING branch <rc_branch> —
# changelog-rc.yaml committed it there at rc time (the "copy" pickup).
#
# Each candidate is validated with validate-changelog.sh; an invalid one is
# reported with a ::warning:: and DISCARDED (rm) so the next source is tried.
# Exactly one machine-readable line is printed as the LAST line of stdout:
#
# source=rc-tag | source=rc-branch | source=none
#
# On rc-tag / rc-branch the usable changelog is left at docs/changelogs/vX.Y.Z.md.
# On none nothing usable was found and the caller must generate. The ::warning::
# annotations for discarded files are printed to stdout too, ahead of that line.
#
# Run from the repo root AFTER the .release-tooling overlay, with `origin`
# fetch-authenticated (changelog-preserve.sh fetches the branch).
# validate-changelog.sh and changelog-preserve.sh must sit beside this script.
#
# Exit 0 = a decision was reached (source line printed). Exit 1 = bad args.
set -eu
VERSION="${1:?usage: select-changelog-source.sh <version> <rc_branch>}"
RC_BRANCH="${2:?usage: select-changelog-source.sh <version> <rc_branch>}"
HERE="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
VALIDATE="${HERE}/validate-changelog.sh"
PRESERVE="${HERE}/changelog-preserve.sh"
CL="docs/changelogs/v${VERSION}.md"
# 1. The rc tag tree — validate before trusting it.
if [ -s "$CL" ]; then
if REASON="$("$VALIDATE" "$CL" "$VERSION" 2>&1)"; then
echo "::notice::Using ${CL} already in the rc tag tree (${REASON})."
echo "source=rc-tag"
exit 0
fi
echo "::warning::${CL} is in the rc tag tree but is NOT usable (${REASON}); discarding it and trying the rc staging branch ${RC_BRANCH}."
rm -f "$CL"
fi
# 2. The rc staging branch — fetch, then validate before trusting it.
mkdir -p docs/changelogs
if "$PRESERVE" "$RC_BRANCH" "$VERSION" "$CL"; then
if REASON="$("$VALIDATE" "$CL" "$VERSION" 2>&1)"; then
echo "::notice::Copied ${CL} from rc staging branch ${RC_BRANCH} (${REASON})."
echo "source=rc-branch"
exit 0
fi
echo "::warning::${CL} on rc staging branch ${RC_BRANCH} is NOT usable (${REASON}); discarding it and generating."
rm -f "$CL"
fi
echo "source=none"
exit 0