diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c0d7aca..7058f26 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -57,6 +57,11 @@ jobs:
grep -Fq "## [${RELEASE_VERSION}] -" CHANGELOG.md
test -s ".github/release-notes/${RELEASE_VERSION}.md"
scripts/verify-revapi-allowlist.sh
+ ./mvnw --quiet --batch-mode -N -Prelease \
+ -Dcentral.autoPublish=true -Dcentral.waitUntil=published \
+ help:effective-pom -Doutput="${RUNNER_TEMP}/effective-pom.xml"
+ grep -Fq "true" "${RUNNER_TEMP}/effective-pom.xml"
+ grep -Fq "published" "${RUNNER_TEMP}/effective-pom.xml"
test -n "${CENTRAL_USERNAME}"
test -n "${CENTRAL_TOKEN}"
test -n "${GPG_PASSPHRASE}"
@@ -131,65 +136,46 @@ jobs:
shell: bash
run: |
set -euo pipefail
- base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j"
- missing=0
- while read -r artifact_id _packaging; do
- [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
- pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom"
- if ! curl \
- --connect-timeout 5 \
- --max-time 10 \
- --fail \
- --silent \
- --show-error \
- --head \
- "${pom_url}" >/dev/null; then
- missing=1
- break
- fi
- done < config/release/public-artifacts.txt
- if [[ "${missing}" -eq 0 ]]; then
+ status=0
+ scripts/check-central-release.sh "${RELEASE_VERSION}" || status=$?
+ if [[ "${status}" -eq 0 ]]; then
echo "Release ${RELEASE_VERSION} is already available from Maven Central; skipping deploy."
exit 0
fi
+ if [[ "${status}" -ne 1 ]]; then
+ echo "Could not determine whether ${RELEASE_VERSION} is published; refusing to deploy." >&2
+ exit "${status}"
+ fi
./mvnw --batch-mode --no-transfer-progress \
- -Prelease -DskipTests -DautoPublish=true -DwaitUntil=published deploy
+ -Prelease -DskipTests \
+ -Dcentral.autoPublish=true -Dcentral.waitUntil=published deploy
- name: Wait for Maven Central resolution
shell: bash
run: |
set -euo pipefail
- base_url="https://repo1.maven.org/maven2/dev/juherr/datex4j"
- for attempt in $(seq 1 30); do
- missing=0
- while read -r artifact_id _packaging; do
- [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
- pom_url="${base_url}/${artifact_id}/${RELEASE_VERSION}/${artifact_id}-${RELEASE_VERSION}.pom"
- if ! curl \
- --connect-timeout 5 \
- --max-time 10 \
- --fail \
- --silent \
- --show-error \
- --head \
- "${pom_url}" >/dev/null; then
- missing=1
- break
- fi
- done < config/release/public-artifacts.txt
- [[ "${missing}" -eq 0 ]] && break
- if [[ "${attempt}" -eq 30 ]]; then
+ for attempt in $(seq 1 60); do
+ # Unlike the deploy guard, a transport failure is retried here: this
+ # loop only waits, so an unreachable Central is just another retry.
+ status=0
+ scripts/check-central-release.sh "${RELEASE_VERSION}" || status=$?
+ if [[ "${status}" -eq 0 ]]; then
+ break
+ fi
+ if [[ "${attempt}" -eq 60 ]]; then
echo "Release did not become resolvable from Maven Central in time." >&2
+ echo "Check the deployment state at https://central.sonatype.com/publishing/deployments" >&2
+ echo "Once it reports published, re-run this failed job to keep the released commit." >&2
exit 1
fi
- sleep 20
+ sleep 30
done
consumer_repository=$(mktemp -d)
./mvnw --batch-mode --no-transfer-progress \
-f config/release-smoke/pom.xml \
-Dmaven.repo.local="${consumer_repository}" \
-Ddatex4j.version="${RELEASE_VERSION}" \
- -Ddatex4j.repository="${base_url%/dev/juherr/datex4j}" \
+ -Ddatex4j.repository="https://repo1.maven.org/maven2" \
verify
finalize:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ddf836c..c270b92 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -169,3 +169,16 @@ Release steps:
The workflow safely resumes when its signed tag or GitHub Release already exists at the same
commit. It refuses conflicting tags or releases.
+
+Resume a failed release with `gh run rerun --failed` rather than a new dispatch. A re-run
+keeps the original commit, so the signed tag still points at the code that produced the published
+artifacts. The publishing job skips the deploy only when `scripts/check-central-release.sh` resolves
+every published file — each POM plus the main, sources, and javadoc jars — so a partially propagated
+release still redeploys instead of being frozen. The re-run then replays the remaining verification,
+tag, and release steps.
+
+The publishing job drives `central-publishing-maven-plugin` through the `central.autoPublish` and
+`central.waitUntil` properties. The root POM binds them into an explicit plugin ``,
+which takes precedence over the plugin's own `autoPublish` and `waitUntil` user properties — those
+`-D` flags are silently ignored. `Validate release inputs` asserts the effective POM resolves to
+`true` and `published` before anything is uploaded.
diff --git a/scripts/check-central-release.sh b/scripts/check-central-release.sh
new file mode 100755
index 0000000..62c887b
--- /dev/null
+++ b/scripts/check-central-release.sh
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+if [[ $# -ne 1 ]]; then
+ echo "Usage: $0 " >&2
+ exit 2
+fi
+
+version=$1
+project_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
+inventory="${project_root}/config/release/public-artifacts.txt"
+base_url=${CENTRAL_BASE_URL:-https://repo1.maven.org/maven2/dev/juherr/datex4j}
+
+# Exit 0: every payload resolves. Exit 1: Central answered, something is absent.
+# Exit 2: Central could not be questioned. Callers must not read 2 as "absent" —
+# deploying on a transport failure would republish an already published version.
+resolves() {
+ local url=$1
+ local http_code curl_status=0
+
+ http_code=$(curl \
+ --connect-timeout 5 \
+ --max-time 10 \
+ --silent \
+ --head \
+ --output /dev/null \
+ --write-out '%{http_code}' \
+ "${url}") || curl_status=$?
+
+ if [[ "${curl_status}" -ne 0 ]]; then
+ echo "Maven Central is unreachable (curl exit ${curl_status}): ${url}" >&2
+ exit 2
+ fi
+
+ case "${http_code}" in
+ 200) return 0 ;;
+ 404) return 1 ;;
+ *)
+ echo "Unexpected HTTP ${http_code} from Maven Central: ${url}" >&2
+ exit 2
+ ;;
+ esac
+}
+
+# Mirrors the payload set asserted locally by verify-central-bundle.sh, so a
+# release is only considered complete once every published file resolves.
+while read -r artifact_id packaging; do
+ [[ -z "${artifact_id}" || "${artifact_id}" == \#* ]] && continue
+
+ base="${base_url}/${artifact_id}/${version}/${artifact_id}-${version}"
+ payloads=("${base}.pom")
+ case "${packaging}" in
+ jar)
+ payloads+=("${base}.jar" "${base}-sources.jar" "${base}-javadoc.jar")
+ ;;
+ pom) ;;
+ *)
+ echo "Unsupported packaging '${packaging}' for ${artifact_id}" >&2
+ exit 2
+ ;;
+ esac
+
+ for payload in "${payloads[@]}"; do
+ if ! resolves "${payload}"; then
+ echo "Not published to Maven Central: ${payload}" >&2
+ exit 1
+ fi
+ done
+done < "${inventory}"