Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 162 additions & 0 deletions .github/scripts/maven-central-release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env bash

set -euo pipefail

readonly MAVEN_CENTRAL_BASE_URL="https://repo.maven.apache.org/maven2/com/openai"

artifacts=()
present_files=()
missing_files=()
transient_errors=()

usage() {
echo "Usage: $0 check <version> <artifact-id,...> | wait <version> <artifact-id,...> [attempts] [interval-seconds]" >&2
exit 2
}

validate_inputs() {
local version=$1
local artifacts_csv=$2

if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error title=Invalid Maven version::$version is not a supported release version"
exit 1
fi
if [[ -z "$artifacts_csv" ]]; then
echo "::error title=No publications::Expected at least one Maven artifact"
exit 1
fi

IFS=, read -r -a artifacts <<< "$artifacts_csv"
for artifact in "${artifacts[@]}"; do
if [[ ! "$artifact" =~ ^[0-9A-Za-z._-]+$ ]]; then
echo "::error title=Invalid artifact ID::$artifact is not safe to publish"
exit 1
fi
done
}

probe_release() {
local version=$1
local artifact
local extension
local file
local status
local url

present_files=()
missing_files=()
transient_errors=()

for artifact in "${artifacts[@]}"; do
for extension in pom jar; do
file="$artifact-$version.$extension"
url="$MAVEN_CENTRAL_BASE_URL/$artifact/$version/$file"
if ! status="$(
curl \
--silent \
--show-error \
--head \
--location \
--connect-timeout 10 \
--max-time 30 \
--retry 2 \
--retry-all-errors \
--retry-max-time 30 \
--output /dev/null \
--write-out '%{http_code}' \
"$url"
)"; then
transient_errors+=("$file (request failed)")
continue
fi
case "$status" in
200) present_files+=("$file") ;;
404) missing_files+=("$file") ;;
*) transient_errors+=("$file (HTTP $status)") ;;
esac
done
done
}

check_release() {
local version=$1
local expected_file_count

probe_release "$version"
expected_file_count=$((${#artifacts[@]} * 2))

if [[ "${#transient_errors[@]}" -ne 0 ]]; then
echo "::error title=Maven Central unavailable::${transient_errors[*]}"
exit 1
fi
if [[ "${#present_files[@]}" -eq 0 ]]; then
echo "already_published=false" >> "$GITHUB_OUTPUT"
elif [[ "${#present_files[@]}" -eq "$expected_file_count" ]]; then
echo "All release artifacts are already public; publication will be skipped"
echo "already_published=true" >> "$GITHUB_OUTPUT"
else
echo "::error title=Partial Maven Central release::Found ${present_files[*]}; immutable releases cannot be overwritten"
exit 1
fi
}

wait_for_release() {
local version=$1
local max_attempts=$2
local interval_seconds=$3
local attempt

if [[ ! "$max_attempts" =~ ^[1-9][0-9]*$ || ! "$interval_seconds" =~ ^[0-9]+$ ]]; then
usage
fi

for ((attempt = 1; attempt <= max_attempts; attempt++)); do
probe_release "$version"
if [[ "${#missing_files[@]}" -eq 0 && "${#transient_errors[@]}" -eq 0 ]]; then
echo "Maven Central serves every $version POM and JAR: ${artifacts[*]}"
return
fi

echo "Maven Central verification attempt $attempt/$max_attempts"
if [[ "${#missing_files[@]}" -ne 0 ]]; then
echo "Missing: ${missing_files[*]}"
fi
if [[ "${#transient_errors[@]}" -ne 0 ]]; then
echo "Transient errors: ${transient_errors[*]}"
fi
if [[ "$attempt" -lt "$max_attempts" ]]; then
sleep "$interval_seconds"
fi
done

echo "::error title=Maven Central publication not visible::Upload completed, but $version was not fully consumable in time; inspect Central Portal before retrying"
exit 1
}

if [[ "$#" -lt 3 ]]; then
usage
fi

readonly command_name=$1
readonly release_version=$2
readonly artifact_list=$3
validate_inputs "$release_version" "$artifact_list"

case "$command_name" in
check)
if [[ "$#" -ne 3 || -z "${GITHUB_OUTPUT:-}" ]]; then
usage
fi
check_release "$release_version"
;;
wait)
if [[ "$#" -gt 5 ]]; then
usage
fi
wait_for_release "$release_version" "${4:-31}" "${5:-60}"
;;
*)
usage
;;
esac
108 changes: 108 additions & 0 deletions .github/scripts/validate-release-source.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
#!/usr/bin/env bash

set -euo pipefail

usage() {
echo "Usage: $0 <source-directory> <release-tag> [expected-sha]" >&2
exit 2
}

if [[ "$#" -lt 2 || "$#" -gt 3 || -z "${GITHUB_OUTPUT:-}" || -z "${GITHUB_REPOSITORY:-}" ]]; then
usage
fi

readonly source_directory=$1
readonly release_tag=$2
readonly expected_sha=${3:-}
script_directory="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly script_directory

if [[ ! "$release_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error title=Invalid release tag::Expected a semantic version tag such as v4.47.0"
exit 1
fi
if [[ -n "$expected_sha" && ! "$expected_sha" =~ ^[0-9a-f]{40}$ ]]; then
echo "::error title=Invalid release commit::Release automation returned an invalid commit SHA"
exit 1
fi

cd "$source_directory"

source_sha="$(git rev-parse HEAD)"
tag_sha="$(git rev-parse "refs/tags/$release_tag^{commit}")"
if [[ "$source_sha" != "$tag_sha" ]]; then
echo "::error title=Release tag mismatch::$release_tag resolves to $tag_sha, but $source_sha was checked out"
exit 1
fi
if [[ -n "$expected_sha" && "$source_sha" != "$expected_sha" ]]; then
echo "::error title=Release commit mismatch::$release_tag resolves to $source_sha, expected $expected_sha"
exit 1
fi
if ! git rev-parse --verify refs/remotes/origin/main >/dev/null; then
echo "::error title=Missing main branch::The release checkout did not fetch origin/main"
exit 1
fi
if ! git merge-base --is-ancestor "$source_sha" refs/remotes/origin/main; then
echo "::error title=Untrusted release source::$release_tag is not an ancestor of main"
exit 1
fi

release_json="$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$release_tag")"
release_draft="$(jq -r '.draft' <<< "$release_json")"
release_prerelease="$(jq -r '.prerelease' <<< "$release_json")"
release_target="$(jq -er '.target_commitish' <<< "$release_json")"
if [[ "$release_draft" != "false" || "$release_prerelease" != "false" ]]; then
echo "::error title=Invalid GitHub release::$release_tag must be a published, non-prerelease release"
exit 1
Comment thread
jbeckwith-oai marked this conversation as resolved.
Outdated
fi
if [[ "$release_target" != "$source_sha" ]]; then
echo "::error title=GitHub release mismatch::$release_tag targets $release_target, expected $source_sha"
exit 1
fi

build_versions=()
while IFS= read -r build_version; do
build_versions+=("$build_version")
done < <(
sed -n \
's/^[[:space:]]*version = "\([^"]*\)" \/\/ x-release-please-version$/\1/p' \
build.gradle.kts
)
if [[ "${#build_versions[@]}" -ne 1 ]]; then
echo "::error title=Invalid Gradle version::Expected one x-release-please-version marker"
exit 1
fi

version="${build_versions[0]}"
manifest_version="$(jq -er '.["."]' .release-please-manifest.json)"
if [[ "$release_tag" != "v$version" || "$manifest_version" != "$version" ]]; then
echo "::error title=Release version mismatch::Tag $release_tag, Gradle $version, manifest $manifest_version"
exit 1
fi

artifacts=()
while IFS= read -r build_file; do
if grep -Fq 'id("openai.publish")' "$build_file"; then
artifacts+=("$(basename "$(dirname "$build_file")")")
fi
done < <(find . -mindepth 2 -maxdepth 2 -type f -name build.gradle.kts | sort)
if [[ "${#artifacts[@]}" -eq 0 ]]; then
echo "::error title=No publications::The release contains no openai.publish projects"
exit 1
fi
for artifact in "${artifacts[@]}"; do
if [[ ! "$artifact" =~ ^[0-9A-Za-z._-]+$ ]]; then
echo "::error title=Invalid artifact ID::$artifact is not safe to publish"
exit 1
fi
done
artifacts_csv="$(IFS=,; echo "${artifacts[*]}")"

{
echo "source_sha=$source_sha"
echo "release_tag=$release_tag"
echo "version=$version"
echo "artifacts=$artifacts_csv"
} >> "$GITHUB_OUTPUT"

bash "$script_directory/maven-central-release.sh" check "$version" "$artifacts_csv"
Loading
Loading