branch.sh, finish.sh, release.sh, runner-image.sh and release-notes.sh carried multi-paragraph 'how we got here / rejected' essays in comments. They now live in the matching development/ category file, and each script keeps a one-line pointer so the rationale is single-homed and cannot drift.
103 lines
3.8 KiB
Bash
Executable File
103 lines
3.8 KiB
Bash
Executable File
#!/bin/sh
|
|
|
|
set -eu
|
|
|
|
# Release front-door. Run as `npm run create:release`.
|
|
#
|
|
# Graduates the [Unreleased] changelog notes, bumps package.json + the lockfile,
|
|
# and commits then tags the exact SHA that CI publishes. The notes are finalized
|
|
# in VS Code before pubv because pubv's bump heuristic reads the [Unreleased]
|
|
# body.
|
|
#
|
|
# Tooling rationale, the rejected alternatives, and the version source of truth:
|
|
# development/publishing.md.
|
|
|
|
CHANGELOG="CHANGELOG.md"
|
|
BASE="main"
|
|
|
|
if ! command -v code >/dev/null 2>&1; then
|
|
echo "Error: 'code' (VS Code CLI) not found; install it or remove the editor step." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# Releases are cut from `main` (see CONTRIBUTING § Publishing workflow). Make
|
|
# that explicit rather than relying on pubv's default-branch check, so the
|
|
# error names `main` even when the remote's default is configured differently.
|
|
CURRENT=$(git symbolic-ref --quiet --short HEAD || true)
|
|
if [ "${CURRENT}" != "${BASE}" ]; then
|
|
echo "Error: releases are cut from '${BASE}', but HEAD is '${CURRENT:-detached}'." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# pubv decides the "default branch" by reading the *local*
|
|
# `refs/remotes/origin/HEAD`, not by asking the remote, and `git fetch` never
|
|
# updates that ref. After a default-branch change — or a clone from when the
|
|
# default was different — it goes stale and pubv warns/fails because the
|
|
# current branch (main) does not match it, even though main *is* the remote
|
|
# default. Refresh it from the remote first, so pubv's branch preflight
|
|
# compares against reality. (Without a network this fails, but so would the
|
|
# push pubv is about to do, so it is a real error rather than one to swallow.)
|
|
if ! git remote set-head origin --auto >/dev/null 2>&1; then
|
|
echo "Error: could not refresh origin/HEAD; check connectivity to origin." >&2
|
|
exit 1
|
|
fi
|
|
|
|
# The [Unreleased] body drives pubv's bump heuristic, so finalize it first.
|
|
echo "Opening ${CHANGELOG} in VS Code to finalize the release notes..."
|
|
code --wait "${CHANGELOG}"
|
|
|
|
# pubv refuses a dirty tree (its "continue with a dirty tree?" prompt defaults
|
|
# to No), so a changed changelog must be committed before it runs. That commit
|
|
# is staging only — the fold below rewrites it into the single release commit.
|
|
NOTES_MSG=":memo: Finalize release notes"
|
|
if [ -n "$(git status --porcelain -- "${CHANGELOG}")" ]; then
|
|
echo "Committing finalized release notes..."
|
|
git add "${CHANGELOG}"
|
|
git commit -m "${NOTES_MSG}"
|
|
fi
|
|
|
|
echo "Running pubv..."
|
|
pubv --no-tag --no-push --tag-prefix=none
|
|
|
|
echo "Reading version from ${CHANGELOG}..."
|
|
|
|
VERSION=$(
|
|
sed -nE 's/^## \[([0-9]+\.[0-9]+\.[0-9]+)\].*/\1/p' "${CHANGELOG}" |
|
|
head -n 1
|
|
)
|
|
|
|
if [ -z "${VERSION}" ]; then
|
|
echo "Error: Could not determine release version from ${CHANGELOG}." >&2
|
|
exit 1
|
|
fi
|
|
|
|
echo "Release version: ${VERSION}"
|
|
|
|
echo "Updating package.json and package-lock.json..."
|
|
npm version "${VERSION}" --no-git-tag-version
|
|
|
|
# If pubv's graduation commit sits on top of our staging notes commit, drop it
|
|
# back into the index so the amend below rewrites the notes commit into the one
|
|
# release commit. A message check, not a flag, so a re-run after pubv aborted
|
|
# still folds a notes commit left behind by the earlier attempt.
|
|
if [ "$(git log -1 --format=%s HEAD~1 2>/dev/null || true)" = "${NOTES_MSG}" ]; then
|
|
git reset --soft HEAD~1
|
|
fi
|
|
|
|
echo "Amending release commit..."
|
|
git add package.json package-lock.json "${CHANGELOG}"
|
|
# The exact message format is load-bearing: the `release-gate` job in
|
|
# .gitea/workflows/ci.yml recognizes `:rocket: Release x.y.z` on main and
|
|
# skips the full CI run, since the tag push immediately after verifies the
|
|
# identical SHA (and publishes). Keep the two in sync.
|
|
git commit --amend -m ":rocket: Release ${VERSION}"
|
|
|
|
echo "Creating tag ${VERSION}..."
|
|
git tag "${VERSION}"
|
|
|
|
echo "Pushing release..."
|
|
git push
|
|
git push --tags
|
|
|
|
echo "Release ${VERSION} completed."
|