Skip to content

Commit 439e99a

Browse files
committed
ci: tighten publish gate and sync releases with tags
1 parent c6b6f7f commit 439e99a

1 file changed

Lines changed: 108 additions & 31 deletions

File tree

‎.github/workflows/publish.yml‎

Lines changed: 108 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ on:
77
workflow_dispatch:
88

99
permissions:
10-
contents: read
10+
contents: write
1111

1212
jobs:
1313
publish:
@@ -22,6 +22,8 @@ jobs:
2222
steps:
2323
- name: Checkout
2424
uses: actions/checkout@v4
25+
with:
26+
fetch-depth: 0
2527

2628
- name: Setup Java
2729
uses: actions/setup-java@v4
@@ -41,13 +43,16 @@ jobs:
4143
run: |
4244
set -euo pipefail
4345
46+
python3 -m pip install --disable-pip-version-check --quiet semver
47+
4448
python3 <<'PY' >> "$GITHUB_OUTPUT"
4549
import base64
4650
import os
4751
import sys
4852
import urllib.request
4953
import urllib.error
5054
import xml.etree.ElementTree as ET
55+
import semver
5156
5257
ns = {"m": "http://maven.apache.org/POM/4.0.0"}
5358
pom = ET.parse("pom.xml").getroot()
@@ -72,28 +77,22 @@ jobs:
7277
except Exception:
7378
latest = "0.0.0"
7479
75-
def parse_semver(value: str):
76-
core = value.split("+", 1)[0]
77-
main, _, prerelease = core.partition("-")
78-
major, minor, patch = (int(part) for part in main.split(".")[:3])
79-
return major, minor, patch, prerelease
80-
81-
def is_greater(left: str, right: str) -> bool:
82-
left_parts = parse_semver(left)
83-
right_parts = parse_semver(right)
84-
if left_parts[:3] != right_parts[:3]:
85-
return left_parts[:3] > right_parts[:3]
86-
left_pre = left_parts[3]
87-
right_pre = right_parts[3]
88-
if left_pre == right_pre:
89-
return False
90-
if not left_pre:
91-
return True
92-
if not right_pre:
93-
return False
94-
return left_pre > right_pre
95-
96-
publish = is_greater(version, latest)
80+
def to_semver(value: str):
81+
try:
82+
return semver.Version.parse(value)
83+
except ValueError:
84+
return None
85+
86+
current_semver = to_semver(version)
87+
latest_semver = to_semver(latest) or semver.Version.parse("0.0.0")
88+
if current_semver is None:
89+
print(f"current_version={version}")
90+
print(f"latest_version={latest}")
91+
print("publish=false")
92+
print("reason=current version is not valid semver")
93+
sys.exit(0)
94+
95+
publish = current_semver > latest_semver
9796
reason = "current version is newer than published version"
9897
if publish:
9998
# If a deployment for this exact version is already staged/ongoing in the Portal,
@@ -133,6 +132,82 @@ jobs:
133132
if: steps.gate.outputs.publish == 'true'
134133
run: mvn -B -ntp -DskipTests -DpublishRelease=true deploy
135134

135+
- name: Ensure release tag is in sync
136+
if: steps.gate.outputs.publish == 'true' && steps.publish_step.outcome == 'success'
137+
env:
138+
VERSION: ${{ steps.gate.outputs.current_version }}
139+
shell: bash
140+
run: |
141+
set -euo pipefail
142+
tag="v${VERSION}"
143+
current_sha="$(git rev-parse HEAD)"
144+
145+
if git rev-parse "$tag" >/dev/null 2>&1; then
146+
tag_sha="$(git rev-list -n 1 "$tag")"
147+
if [ "$tag_sha" != "$current_sha" ]; then
148+
echo "::error::Tag $tag already exists at $tag_sha, expected $current_sha"
149+
exit 1
150+
fi
151+
echo "Tag $tag already in sync with $current_sha"
152+
exit 0
153+
fi
154+
155+
git config user.name "github-actions[bot]"
156+
git config user.email "github-actions[bot]@users.noreply.github.com"
157+
git tag -a "$tag" -m "Release $tag"
158+
git push origin "$tag"
159+
echo "Created and pushed tag $tag"
160+
161+
- name: Create or update GitHub release notes
162+
if: steps.gate.outputs.publish == 'true' && steps.publish_step.outcome == 'success'
163+
env:
164+
GH_TOKEN: ${{ github.token }}
165+
VERSION: ${{ steps.gate.outputs.current_version }}
166+
shell: bash
167+
run: |
168+
set -euo pipefail
169+
tag="v${VERSION}"
170+
notes_file="/tmp/release_notes.md"
171+
172+
python3 - <<'PY'
173+
import os
174+
from pathlib import Path
175+
176+
version = os.environ["VERSION"]
177+
header = f"## [{version}]"
178+
changelog = Path("CHANGELOG.md")
179+
output = Path("/tmp/release_notes.md")
180+
181+
if not changelog.exists():
182+
output.write_text(f"Release {version}", encoding="utf-8")
183+
raise SystemExit(0)
184+
185+
lines = changelog.read_text(encoding="utf-8").splitlines()
186+
in_target = False
187+
captured = []
188+
for line in lines:
189+
if line.startswith(header):
190+
in_target = True
191+
continue
192+
if in_target and line.startswith("## ["):
193+
break
194+
if in_target:
195+
captured.append(line)
196+
197+
notes = "\n".join(captured).strip()
198+
if not notes:
199+
notes = f"Release {version}"
200+
output.write_text(notes + "\n", encoding="utf-8")
201+
PY
202+
203+
if gh release view "$tag" >/dev/null 2>&1; then
204+
gh release edit "$tag" --title "$tag" --notes-file "$notes_file"
205+
echo "Updated release $tag"
206+
else
207+
gh release create "$tag" --title "$tag" --notes-file "$notes_file"
208+
echo "Created release $tag"
209+
fi
210+
136211
- name: Notify Slack (success)
137212
if: steps.gate.outputs.publish == 'true' && steps.publish_step.outcome == 'success'
138213
env:
@@ -161,29 +236,30 @@ jobs:
161236
162237
changelog = Path("CHANGELOG.md")
163238
latest_changes = "See CHANGELOG for details."
239+
section_header = f"## [{version}]"
164240
if changelog.exists():
165241
lines = changelog.read_text(encoding="utf-8").splitlines()
166-
in_latest = False
242+
in_target = False
167243
bullets = []
168244
for line in lines:
169-
if line.startswith("## [") and not in_latest:
170-
in_latest = True
245+
if line.startswith(section_header):
246+
in_target = True
171247
continue
172-
if in_latest and line.startswith("## ["):
248+
if in_target and line.startswith("## ["):
173249
break
174-
if in_latest and line.startswith("- "):
250+
if in_target and line.startswith("- "):
175251
bullets.append(line[2:].strip())
176252
if bullets:
177253
latest_changes = "\n".join(f"• {b}" for b in bullets[:5])
178254
179255
payload = {
180-
"text": f"🚀 Facturapi Java SDK {version} published",
256+
"text": f"🚀 Facturapi Java SDK {version} validated and submitted",
181257
"blocks": [
182258
{
183259
"type": "header",
184260
"text": {
185261
"type": "plain_text",
186-
"text": f"🚀 Java SDK {version} is live on Maven Central"
262+
"text": f"🚀 Java SDK {version} validated and submitted to Maven Central"
187263
}
188264
},
189265
{
@@ -202,8 +278,9 @@ jobs:
202278
"text": (
203279
"*Useful links*\n"
204280
f"• Maven Central: <https://central.sonatype.com/artifact/io.facturapi/facturapi-java/{version}|View artifact>\n"
281+
f"• Central deployment status: <https://central.sonatype.com/publishing/deployments|Check progress>\n"
205282
f"• Workflow run: <{server_url}/{repo}/actions/runs/{run_id}|Open run>\n"
206-
f"• Changelog: <{server_url}/{repo}/blob/main/CHANGELOG.md|Read changes>"
283+
f"• Changelog: <{server_url}/{repo}/blob/{sha}/CHANGELOG.md|Read changes>"
207284
)
208285
}
209286
},

0 commit comments

Comments
 (0)