Files
2026-08-04 00:45:50 +01:00

320 lines
9.5 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
usage() {
cat >&2 <<EOF
Usage:
$0 prepare --scheduled-sha <sha> --output <path>
$0 publish --stable-at-start <version> --nightly-at-start <version> \\
--tags-at-start <snapshot> --version <version> --assets-dir <path>
EOF
exit 2
}
npm_version() {
bash "$ROOT_DIR/scripts/release/npm-view-version.sh" "$1"
}
latest_stable_tag() {
local tag
while IFS= read -r tag; do
if [[ "$tag" =~ ^v[0-9]+\.[0-9]+(\.[0-9]+)?$ ]]; then
printf '%s\n' "$tag"
return 0
fi
done < <(git tag --list 'v*' --sort=-version:refname)
return 0
}
normalize_stable_tag() {
local tag="$1"
if [[ "$tag" =~ ^v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"
elif [[ "$tag" =~ ^v([0-9]+\.[0-9]+)$ ]]; then
printf '%s.0\n' "${BASH_REMATCH[1]}"
fi
}
MATCHED_STABLE_TAG=""
stable_tag_matches_npm() {
local npm_latest="$1"
local tagged_stable
MATCHED_STABLE_TAG="$(latest_stable_tag)"
if [[ -z "$MATCHED_STABLE_TAG" ]]; then
echo "No stable Git tag is available; skipping this best-effort Nightly run."
return 1
fi
tagged_stable="$(normalize_stable_tag "$MATCHED_STABLE_TAG")"
if [[ "$tagged_stable" != "$npm_latest" ]]; then
echo "Highest stable Git tag $MATCHED_STABLE_TAG does not match npm @latest $npm_latest; skipping Nightly."
return 1
fi
return 0
}
write_output() {
printf '%s=%s\n' "$1" "$2" >> "$OUTPUT_PATH"
}
resolve_nightly_commit() {
local version="$1"
local label="$2"
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+-nightly\.sha([0-9a-f]{12})$ ]]; then
echo "$label has unsupported version '$version'; refusing to infer publication order." >&2
return 1
fi
local sha="${BASH_REMATCH[1]}"
local commit
if ! commit="$(git rev-parse --verify "${sha}^{commit}" 2>/dev/null)"; then
echo "Cannot resolve $label commit prefix $sha in main history." >&2
return 1
fi
printf '%s\n' "$commit"
}
prepare() {
local scheduled_sha=""
OUTPUT_PATH=""
while [[ "$#" -gt 0 ]]; do
case "$1" in
--scheduled-sha)
[[ "$#" -ge 2 ]] || usage
scheduled_sha="$2"
shift 2
;;
--output)
[[ "$#" -ge 2 ]] || usage
OUTPUT_PATH="$2"
shift 2
;;
*) usage ;;
esac
done
[[ -n "$scheduled_sha" && -n "$OUTPUT_PATH" ]] || usage
write_output should_publish false
local main_sha
main_sha="$(git rev-parse HEAD)"
if [[ "$main_sha" != "$scheduled_sha" ]]; then
echo "Checked out $main_sha instead of scheduled SHA $scheduled_sha." >&2
exit 1
fi
local latest_stable
if ! latest_stable="$(npm_version kandev@latest)"; then
echo "Could not resolve npm @latest; skipping this best-effort Nightly run."
return
fi
if [[ -z "$latest_stable" ]]; then
echo "npm @latest is missing; skipping this best-effort Nightly run."
return
fi
if ! stable_tag_matches_npm "$latest_stable"; then
return
fi
write_output stable_version "$latest_stable"
local stable_commit
stable_commit="$(git rev-parse "${MATCHED_STABLE_TAG}^{commit}")"
if ! git merge-base --is-ancestor "$stable_commit" "$main_sha"; then
if git merge-base --is-ancestor "$main_sha" "$stable_commit"; then
echo "Scheduled commit $main_sha was superseded by stable v$latest_stable; skipping Nightly."
return
fi
echo "Latest stable v$latest_stable is not an ancestor of scheduled main $main_sha." >&2
exit 1
fi
if [[ "$main_sha" == "$stable_commit" ]]; then
echo "No commits on main since stable v$latest_stable; skipping nightly."
return
fi
local nightly_version
nightly_version="$(node "$ROOT_DIR/scripts/release/nightly-version.mjs" "$latest_stable" "$main_sha")"
write_output version "$nightly_version"
write_output tag "v$nightly_version"
write_output ref "$main_sha"
local published_nightly
if ! published_nightly="$(npm_version kandev@nightly)"; then
echo "Could not resolve npm @nightly; skipping this best-effort Nightly run."
return
fi
write_output nightly_version_at_start "$published_nightly"
# shellcheck source=npm-packages.sh
source "$ROOT_DIR/scripts/release/npm-packages.sh"
local published_commit=""
if [[ -n "$published_nightly" ]]; then
if ! published_commit="$(resolve_nightly_commit "$published_nightly" "kandev@nightly")"; then
exit 1
fi
if [[ "$published_commit" != "$main_sha" ]] &&
git merge-base --is-ancestor "$main_sha" "$published_commit"; then
echo "A Nightly from a newer main commit is already published; skipping superseded commit $main_sha."
return
fi
if ! git merge-base --is-ancestor "$published_commit" "$main_sha"; then
echo "Published Nightly commit $published_commit is not an ancestor of scheduled main $main_sha." >&2
exit 1
fi
fi
local all_packages_published=true
local nightly_tags_at_start=""
local package published_version tagged_version tagged_commit
for package in "${NIGHTLY_PACKAGES[@]}"; do
if ! published_version="$(npm_version "${package}@$nightly_version")"; then
echo "Could not verify ${package}@$nightly_version; skipping this best-effort Nightly run."
return
fi
if ! tagged_version="$(npm_version "${package}@nightly")"; then
echo "Could not verify ${package}@nightly; skipping this best-effort Nightly run."
return
fi
nightly_tags_at_start+="${package}=${tagged_version};"
if [[ -n "$published_version" ]]; then
if [[ "$tagged_version" != "$nightly_version" ]]; then
echo "${package}@$nightly_version exists but @nightly resolves to '${tagged_version:-nothing}'." >&2
exit 1
fi
continue
fi
all_packages_published=false
if [[ -n "$tagged_version" && "$tagged_version" != "$published_nightly" ]]; then
if ! tagged_commit="$(resolve_nightly_commit "$tagged_version" "${package}@nightly")"; then
exit 1
fi
if ! git merge-base --is-ancestor "$tagged_commit" "$main_sha"; then
echo "${package}@nightly commit $tagged_commit is not an ancestor of scheduled main $main_sha." >&2
exit 1
fi
echo "${package}@nightly is an older partial publication; the current publish will repair it."
fi
done
write_output nightly_tags_at_start "$nightly_tags_at_start"
if [[ -n "$published_nightly" && "$all_packages_published" == "true" ]]; then
if [[ "$published_commit" != "$main_sha" ]]; then
echo "Nightly version identity does not resolve to scheduled commit $main_sha." >&2
exit 1
fi
echo "All Nightly packages are already published under @nightly; skipping."
return
fi
write_output should_publish true
}
publish() {
local stable_at_start=""
local nightly_at_start=""
local tags_at_start=""
local version=""
local assets_dir=""
local nightly_set=false
local tags_set=false
while [[ "$#" -gt 0 ]]; do
case "$1" in
--stable-at-start)
[[ "$#" -ge 2 ]] || usage
stable_at_start="$2"
shift 2
;;
--nightly-at-start)
[[ "$#" -ge 2 ]] || usage
nightly_at_start="$2"
nightly_set=true
shift 2
;;
--tags-at-start)
[[ "$#" -ge 2 ]] || usage
tags_at_start="$2"
tags_set=true
shift 2
;;
--version)
[[ "$#" -ge 2 ]] || usage
version="$2"
shift 2
;;
--assets-dir)
[[ "$#" -ge 2 ]] || usage
assets_dir="$2"
shift 2
;;
*) usage ;;
esac
done
if [[ -z "$stable_at_start" || -z "$version" || -z "$assets_dir" ||
"$nightly_set" != "true" || "$tags_set" != "true" ]]; then
usage
fi
local current_latest
if ! current_latest="$(npm_version kandev@latest)"; then
echo "Could not revalidate npm @latest; skipping Nightly publication."
return
fi
if [[ -z "$current_latest" ]]; then
echo "npm @latest is missing; skipping Nightly publication."
return
fi
if ! stable_tag_matches_npm "$current_latest"; then
return
fi
if [[ "$current_latest" != "$stable_at_start" ]]; then
echo "Stable npm baseline moved from $stable_at_start to $current_latest while this run was building; skipping stale Nightly publication."
return
fi
local current_nightly
if ! current_nightly="$(npm_version kandev@nightly)"; then
echo "Could not revalidate npm @nightly; skipping Nightly publication."
return
fi
if [[ "$current_nightly" != "$nightly_at_start" ]]; then
echo "npm @nightly moved from ${nightly_at_start:-nothing} to ${current_nightly:-nothing} while this run was building; skipping superseded publication."
return
fi
# shellcheck source=npm-packages.sh
source "$ROOT_DIR/scripts/release/npm-packages.sh"
local current_nightly_tags=""
local package tagged_version
for package in "${NIGHTLY_PACKAGES[@]}"; do
if ! tagged_version="$(npm_version "${package}@nightly")"; then
echo "Could not revalidate ${package}@nightly; skipping Nightly publication."
return
fi
current_nightly_tags+="${package}=${tagged_version};"
done
if [[ "$current_nightly_tags" != "$tags_at_start" ]]; then
echo "One or more npm @nightly tags moved while this run was building; skipping superseded publication."
return
fi
bash "$ROOT_DIR/scripts/release/publish-npm.sh" \
--version "$version" \
--dist-tag nightly \
--assets-dir "$assets_dir"
}
cd "$ROOT_DIR"
command="${1:-}"
[[ -n "$command" ]] || usage
shift
case "$command" in
prepare) prepare "$@" ;;
publish) publish "$@" ;;
*) usage ;;
esac