mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 08:06:19 +00:00
Require committed, non-empty notes matching the target release version before creating a tag. Read notes from the tagged source and place them before the installation guide and generated release changelog. Add the 3.3.31 feature summary and keep maintainer instructions in the release script help and repository skill instead of the deployment README. Validation: shell syntax and workflow lint passed. Nine isolated checks cover local publication, stale or missing notes, and release-body rendering. Co-authored-by: Codex <noreply@openai.com>
169 lines
4.8 KiB
Bash
Executable File
169 lines
4.8 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
APP_NAME="codex-proxy-rs"
|
|
VERSION_FILE="release/version.yaml"
|
|
NOTES_FILE="release/notes.md"
|
|
TAG_PREFIX="v"
|
|
SEMVER='^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?$'
|
|
SIGNATURE="Co-authored-by: Codex <noreply@openai.com>"
|
|
|
|
usage() {
|
|
cat <<USAGE
|
|
Usage:
|
|
release/publish <version>
|
|
|
|
Example:
|
|
release/publish 0.1.4
|
|
|
|
Before publishing, replace release/notes.md with the new version's notes and
|
|
commit it. Its first line must be "# v<version>".
|
|
USAGE
|
|
}
|
|
|
|
die() {
|
|
echo "error: $*" >&2
|
|
exit 1
|
|
}
|
|
|
|
require_command() {
|
|
command -v "$1" >/dev/null 2>&1 || die "$1 is required"
|
|
}
|
|
|
|
read_version_key() {
|
|
local key="$1"
|
|
ruby -ryaml -e '
|
|
path, key = ARGV
|
|
data = YAML.load_file(path)
|
|
value = data[key] if data.is_a?(Hash)
|
|
unless value.is_a?(String) && !value.strip.empty?
|
|
warn "#{path}: #{key} must be a non-empty string"
|
|
exit 1
|
|
end
|
|
puts value.strip
|
|
' "$VERSION_FILE" "$key"
|
|
}
|
|
|
|
update_version_file() {
|
|
local version="$1"
|
|
ruby - "$VERSION_FILE" "$version" <<'RUBY'
|
|
path, version = ARGV
|
|
content = File.read(path)
|
|
|
|
unless content.match?(/^version:/)
|
|
warn "#{path}: missing version key"
|
|
exit 1
|
|
end
|
|
|
|
content = content.sub(/^version:\s*.*$/, %(version: "#{version}"))
|
|
File.write(path, content)
|
|
RUBY
|
|
}
|
|
|
|
if [[ "${1:-}" == "-h" || "${1:-}" == "--help" ]]; then
|
|
usage
|
|
exit 0
|
|
fi
|
|
|
|
[[ "$#" -eq 1 ]] || {
|
|
usage >&2
|
|
exit 2
|
|
}
|
|
|
|
require_command git
|
|
require_command gh
|
|
require_command ruby
|
|
|
|
gh auth status >/dev/null 2>&1 || die "gh authentication is required; run gh auth login first"
|
|
|
|
version="${1#v}"
|
|
[[ "$version" =~ $SEMVER ]] || die "version must be semver, for example 0.1.4"
|
|
|
|
repo_root="$(git rev-parse --show-toplevel 2>/dev/null)" || die "not inside a git repository"
|
|
cd "$repo_root"
|
|
|
|
git ls-files --error-unmatch "$VERSION_FILE" >/dev/null 2>&1 || die "$VERSION_FILE is not tracked"
|
|
|
|
branch="$(git symbolic-ref --quiet --short HEAD)" || die "publishing from detached HEAD is not supported"
|
|
remote="$(git config --get "branch.$branch.remote" || true)"
|
|
merge_ref="$(git config --get "branch.$branch.merge" || true)"
|
|
[[ -n "$remote" && -n "$merge_ref" ]] || die "current branch has no upstream"
|
|
remote_branch="${merge_ref#refs/heads/}"
|
|
[[ "$remote_branch" != "$merge_ref" && -n "$remote_branch" ]] || die "unsupported upstream ref: $merge_ref"
|
|
remote_url="$(git remote get-url "$remote")" || die "failed to resolve remote URL for $remote"
|
|
repository="$(gh repo view "$remote_url" --json nameWithOwner --jq '.nameWithOwner' 2>/dev/null)" ||
|
|
die "failed to resolve GitHub repository for $remote_url"
|
|
|
|
if ! git diff --quiet --ignore-submodules --; then
|
|
die "tracked files have unstaged changes; commit or stash them first"
|
|
fi
|
|
if ! git diff --cached --quiet --ignore-submodules --; then
|
|
die "index has staged changes; commit or unstage them first"
|
|
fi
|
|
|
|
current_version="$(read_version_key version)"
|
|
current_version="${current_version#v}"
|
|
[[ "$version" != "$current_version" ]] || die "$VERSION_FILE is already $version"
|
|
|
|
tag="${TAG_PREFIX}${version}"
|
|
|
|
git ls-files --error-unmatch "$NOTES_FILE" >/dev/null 2>&1 || die "$NOTES_FILE is not tracked"
|
|
ruby - "$NOTES_FILE" "$tag" <<'RUBY'
|
|
path, tag = ARGV
|
|
lines = File.readlines(path)
|
|
unless lines.first&.strip == "# #{tag}" && !lines.drop(1).join.strip.empty?
|
|
warn "#{path}: replace the contents with notes for #{tag}, starting with '# #{tag}'"
|
|
exit 1
|
|
end
|
|
RUBY
|
|
|
|
if git rev-parse --quiet --verify "refs/tags/$tag" >/dev/null; then
|
|
die "local tag already exists: $tag"
|
|
fi
|
|
|
|
git fetch "$remote"
|
|
|
|
upstream="$remote/$remote_branch"
|
|
local_head="$(git rev-parse HEAD)"
|
|
upstream_head="$(git rev-parse "$upstream")"
|
|
merge_base="$(git merge-base HEAD "$upstream")"
|
|
|
|
if [[ "$local_head" != "$upstream_head" && "$merge_base" != "$upstream_head" ]]; then
|
|
if [[ "$merge_base" == "$local_head" ]]; then
|
|
die "branch is behind $upstream; pull or rebase first"
|
|
fi
|
|
die "branch has diverged from $upstream"
|
|
fi
|
|
|
|
if git ls-remote --exit-code --tags "$remote" "refs/tags/$tag" >/dev/null 2>&1; then
|
|
die "remote tag already exists: $tag"
|
|
fi
|
|
|
|
update_version_file "$version"
|
|
|
|
git add "$VERSION_FILE"
|
|
git commit \
|
|
-m "chore: bump version to $version" \
|
|
-m "$SIGNATURE"
|
|
|
|
git tag -a "$tag" -m "$tag"
|
|
git push --atomic "$remote" "HEAD:$remote_branch" "refs/tags/$tag"
|
|
|
|
# Dispatch from the long-lived release branch instead of the tag so GitHub
|
|
# Actions caches remain reusable across releases.
|
|
if ! gh workflow run release.yml \
|
|
--repo "$repository" \
|
|
--ref "$remote_branch" \
|
|
--raw-field "tag=$tag"; then
|
|
echo "error: commit and tag were pushed, but the Release workflow was not dispatched" >&2
|
|
echo "retry: gh workflow run release.yml --repo $repository --ref $remote_branch --raw-field tag=$tag" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cat <<DONE
|
|
Published $APP_NAME $version
|
|
Commit: $(git rev-parse --short HEAD)
|
|
Tag: $tag
|
|
Release workflow dispatched from: $remote_branch
|
|
DONE
|