Publish releases from two scripts, one per remote
A release meant running the RELEASING.md checklist by hand: tag, push twice, create the GitHub release, then remember the Gitea push. The two scripts do the machine-checkable part of it, with the shared flow in scripts/lib/ so they cannot drift apart: clean worktree, not behind the remote, typecheck, tests and the install.sh syntax check, then the tag at HEAD, the branch push and the release object. Only the release object differs. GitHub creates it with gh, and publishing it is what fires the image build. Gitea gets the same notes through its API, with the token from GITEA_TOKEN or the one tea already stored, since nothing there builds images. Both accept --version (defaulting to package.json), --branch, --yes, --dry-run and --skip-checks, and a rerun finishes a release that failed half way instead of starting over: an existing tag is reused, a pushed ref is skipped and an existing release is left alone. A tag sitting on another commit is refused, since publishing would ship that commit's code.
This commit is contained in:
Executable
+213
@@ -0,0 +1,213 @@
|
||||
#!/usr/bin/env bash
|
||||
# The release flow both publish scripts share: argument parsing, preflight
|
||||
# checks, tag preparation and the push.
|
||||
#
|
||||
# . "$(dirname "$0")/lib/publish-common.sh"
|
||||
# . "$(dirname "$0")/lib/publish-release.sh"
|
||||
#
|
||||
# What differs between the two remotes is only the last step: the GitHub
|
||||
# release object (which the release workflow builds the images from) and the
|
||||
# Gitea one. Everything before that lives here, so the two scripts cannot
|
||||
# drift apart.
|
||||
|
||||
# Defaults, overridden by the flags in parse_publish_args.
|
||||
BRANCH="main"
|
||||
VERSION_RAW=""
|
||||
TAG=""
|
||||
SKIP_CHECKS=0
|
||||
HELP=0
|
||||
REMAINING_ARGS=()
|
||||
NOTES_FILE=""
|
||||
TAG_CREATED=0
|
||||
|
||||
parse_publish_args() {
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h | --help) HELP=1; shift ;;
|
||||
--yes | -y) ASSUME_YES=1; shift ;;
|
||||
--dry-run) DRY_RUN=1; shift ;;
|
||||
--branch) BRANCH="${2:?--branch needs a value}"; shift 2 ;;
|
||||
--version) VERSION_RAW="${2:?--version needs a value}"; shift 2 ;;
|
||||
--skip-checks) SKIP_CHECKS=1; shift ;;
|
||||
*)
|
||||
REMAINING_ARGS+=("$1")
|
||||
shift
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
DRY_RUN="${DRY_RUN:-0}"
|
||||
ASSUME_YES="${ASSUME_YES:-0}"
|
||||
}
|
||||
|
||||
# Fails when the platform script was handed a flag it does not know.
|
||||
reject_unknown_args() {
|
||||
local script="$1"
|
||||
if [ "${#REMAINING_ARGS[@]}" -eq 0 ]; then
|
||||
return 0
|
||||
fi
|
||||
printf 'Unknown argument: %s\n\n' "${REMAINING_ARGS[*]}" >&2
|
||||
"$script" --help >&2
|
||||
exit 2
|
||||
}
|
||||
|
||||
# MARK:preflight
|
||||
preflight() {
|
||||
local remote="$1"
|
||||
|
||||
require_repo_root
|
||||
require_cmd git
|
||||
require_cmd "bun" "Install Bun: https://bun.sh"
|
||||
|
||||
step "Checking the checkout"
|
||||
require_clean_worktree
|
||||
ok "The working tree has nothing uncommitted."
|
||||
require_on_branch "$BRANCH"
|
||||
ok "On $BRANCH."
|
||||
require_remote "$remote"
|
||||
ok "Remote '$remote' is $(git remote get-url "$remote")."
|
||||
require_not_behind "$remote" "$BRANCH"
|
||||
ok "$remote/$BRANCH has nothing HEAD is missing."
|
||||
}
|
||||
# END:preflight
|
||||
|
||||
# MARK:resolve-version
|
||||
# Sets TAG (vX.Y.Z) from --version, else from package.json.
|
||||
resolve_version() {
|
||||
step "Resolving the version"
|
||||
if [ -z "$VERSION_RAW" ]; then
|
||||
VERSION_RAW="$(source_version)"
|
||||
if [ -z "$VERSION_RAW" ]; then
|
||||
fail "No version in package.json and none passed with --version."
|
||||
fi
|
||||
detail "Read from package.json: $VERSION_RAW"
|
||||
fi
|
||||
validate_version "$VERSION_RAW"
|
||||
TAG="$(normalize_version "$VERSION_RAW")"
|
||||
ok "Releasing $TAG."
|
||||
|
||||
if [ "$TAG" != "v$(source_version)" ]; then
|
||||
warn "package.json says $(source_version) but the release is $TAG. Bump package.json's version with the release so a dev build reports the same number (RELEASING.md, step 5)."
|
||||
fi
|
||||
}
|
||||
# END:resolve-version
|
||||
|
||||
# MARK:checks
|
||||
# The release gate from RELEASING.md that a machine can check.
|
||||
run_release_checks() {
|
||||
if [ "$SKIP_CHECKS" = "1" ]; then
|
||||
warn "Skipping typecheck, tests and the installer syntax check (--skip-checks)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
step "Running the release checks"
|
||||
run bun run typecheck
|
||||
ok "Type-check passed."
|
||||
run bun run test
|
||||
ok "Tests passed."
|
||||
run bash -n install.sh
|
||||
ok "install.sh parses."
|
||||
}
|
||||
# END:checks
|
||||
|
||||
# MARK:tag
|
||||
# Creates the tag at HEAD, or verifies the existing one is at HEAD.
|
||||
prepare_tag() {
|
||||
step "Preparing the tag $TAG"
|
||||
|
||||
if local_tag_exists "$TAG"; then
|
||||
local tagged
|
||||
tagged="$(git rev-list -n1 "$TAG")"
|
||||
if [ "$tagged" != "$(git rev-parse HEAD)" ]; then
|
||||
fail "Tag $TAG already exists on ${tagged:0:12}, not on HEAD ($(git rev-parse --short HEAD)). Publishing would ship the code that tag was made from. Move it with: git tag -f -a $TAG -m '$TAG'"
|
||||
fi
|
||||
ok "Tag $TAG already points at HEAD; reusing it."
|
||||
return 0
|
||||
fi
|
||||
|
||||
run git tag -a "$TAG" -m "$TAG"
|
||||
if [ "$DRY_RUN" != "1" ]; then
|
||||
TAG_CREATED=1
|
||||
fi
|
||||
ok "Tagged HEAD as $TAG."
|
||||
}
|
||||
# END:tag
|
||||
|
||||
# MARK:notes-file
|
||||
# Writes release_notes into NOTES_FILE, which the caller cleans up.
|
||||
write_notes_file() {
|
||||
local compare_url="$1"
|
||||
local from=""
|
||||
# A dry run makes no tag, so the notes are read up to HEAD instead.
|
||||
local to="$TAG"
|
||||
|
||||
if ! local_tag_exists "$to"; then
|
||||
to="HEAD"
|
||||
fi
|
||||
|
||||
from="$(previous_tag "$to")"
|
||||
|
||||
if [ -n "$from" ]; then
|
||||
detail "Changes since $from."
|
||||
else
|
||||
detail "No earlier tag, so every commit is listed."
|
||||
fi
|
||||
|
||||
NOTES_FILE="$(mktemp -t cobble-release-notes.XXXXXX)"
|
||||
release_notes --from "$from" --to "$to" --compare-url "$compare_url" >"$NOTES_FILE"
|
||||
ok "Wrote the release notes to $NOTES_FILE."
|
||||
}
|
||||
|
||||
cleanup_notes() {
|
||||
if [ -n "$NOTES_FILE" ] && [ -f "$NOTES_FILE" ]; then
|
||||
rm -f "$NOTES_FILE"
|
||||
fi
|
||||
}
|
||||
# END:notes-file
|
||||
|
||||
# MARK:push
|
||||
# Pushes the branch, then the tag. A tag the remote already has is left alone,
|
||||
# so a rerun after a failed release object finishes the job instead of
|
||||
# failing on the push.
|
||||
push_branch_and_tag() {
|
||||
local remote="$1"
|
||||
|
||||
step "Pushing to $remote"
|
||||
run git push "$remote" "$BRANCH"
|
||||
ok "Pushed $BRANCH."
|
||||
|
||||
if [ -n "$(remote_tag_exists "$remote" "$TAG")" ]; then
|
||||
ok "$remote already has $TAG; not pushing it again."
|
||||
return 0
|
||||
fi
|
||||
run git push "$remote" "refs/tags/$TAG"
|
||||
ok "Pushed $TAG."
|
||||
}
|
||||
# END:push
|
||||
|
||||
# MARK:reuse-release
|
||||
# A release object that already exists is left as it is, so rerunning the
|
||||
# script never fails on it and never overwrites a body someone edited.
|
||||
release_already_exists() {
|
||||
local description="$1"
|
||||
warn "$description for $TAG already exists; leaving it untouched."
|
||||
warn "Delete it first if it needs to be recreated."
|
||||
}
|
||||
# END:reuse-release
|
||||
|
||||
print_release_summary() {
|
||||
local remote="$1"
|
||||
local branch="$2"
|
||||
local release_url="$3"
|
||||
|
||||
step "Published $TAG"
|
||||
printf ' %sremote%s %s\n' "$C_DIM" "$C_RESET" "$remote ($(git remote get-url "$remote"))"
|
||||
printf ' %sbranch%s %s\n' "$C_DIM" "$C_RESET" "$branch"
|
||||
printf ' %stag%s %s\n' "$C_DIM" "$C_RESET" "$TAG"
|
||||
if [ -n "$release_url" ]; then
|
||||
printf ' %srelease%s %s\n' "$C_DIM" "$C_RESET" "$release_url"
|
||||
fi
|
||||
if [ "$DRY_RUN" = "1" ]; then
|
||||
printf '\n%sDry run: nothing was tagged, pushed or published.%s\n' "$C_YELLOW" "$C_RESET"
|
||||
fi
|
||||
}
|
||||
Reference in New Issue
Block a user