-
Notifications
You must be signed in to change notification settings - Fork 0
267 lines (246 loc) · 12.6 KB
/
release.yml
File metadata and controls
267 lines (246 loc) · 12.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
name: Release
permissions:
contents: write
packages: write
statuses: write
checks: write
on:
workflow_dispatch:
jobs:
# Phase 5 peer-review fix: branch guard. workflow_dispatch can run from any branch by
# default; we lock to main so an operator clicking "Run workflow" from a feature branch
# does NOT publish a release tag from non-main code.
guard:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- run: echo "branch guard ok — running release on main"
# Pre-flight: fast bash checks (~5s) BEFORE the 5-minute 3-OS verify matrix.
# Catches operator mistakes (forgot to bump pom, missing CHANGELOG section,
# README still on previous version) early so the operator doesn't wait through
# a green verify matrix only to discover the docs aren't ready.
preflight:
needs: guard
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Pre-flight checks (CHANGELOG / README / pom version)
run: |
set -euo pipefail
# 1) pom <version> must be -SNAPSHOT (release:prepare will rewrite to release version).
POM_VERSION=$(grep -oE '<version>[^<]+</version>' pom.xml | head -1 | sed 's|<[^>]*>||g')
if [[ ! "$POM_VERSION" =~ -SNAPSHOT$ ]]; then
echo "::error::pom.xml <version> must end in -SNAPSHOT before release:prepare; got: $POM_VERSION"
exit 1
fi
RELEASE_VERSION="${POM_VERSION%-SNAPSHOT}"
echo "Releasing $RELEASE_VERSION"
# 2) CHANGELOG.md must have a non-empty `## [VERSION]` section so release.yml's
# awk extractor (downstream) finds release notes for the GitHub Release.
if ! awk -v ver="$RELEASE_VERSION" '
$0 ~ "^## \\[" ver "\\]" { found=1; in_section=1; next }
/^## / { in_section=0 }
in_section && /\S/ { has_body=1 }
END { exit !(found && has_body) }
' CHANGELOG.md; then
echo "::error::CHANGELOG.md is missing or has an empty ## [$RELEASE_VERSION] section"
exit 1
fi
# 3) README.md must reference the release version + must NOT contain <!-- pending --> markers.
if ! grep -F "$RELEASE_VERSION" README.md > /dev/null; then
echo "::error::README.md does not reference $RELEASE_VERSION (install snippet not updated?)"
exit 1
fi
if grep -F '<!-- pending -->' README.md > /dev/null; then
echo "::error::README.md still contains <!-- pending --> markers"
exit 1
fi
echo "✓ Pre-flight green for v$RELEASE_VERSION"
# 3-OS verify gate per D-03 — release MUST NOT proceed if any OS leg is red.
# Per P-02: the workflow_call reusable job in ci.yml contains ONLY the verify
# matrix; no deploy step contaminates this gating call.
verify:
needs: preflight
uses: ./.github/workflows/ci.yml
secrets: inherit
release:
needs: verify
permissions:
contents: write
# id-token + attestations needed by actions/attest-build-provenance and
# actions/attest-sbom — these mint sigstore-backed attestations against
# the GitHub OIDC token and write them to the repo's attestation log.
id-token: write
attestations: write
runs-on: ubuntu-latest
steps:
# Checkout with RELEASE_TOKEN (fine-grained PAT owned by repo admin,
# Contents+Workflows R/W on this repo only). actions/checkout configures
# an http.extraheader so subsequent git pushes authenticate as the PAT
# owner. The owner has the Admin role, which is a bypass actor on the
# "main-protection" ruleset — so release:prepare can push the
# `prepare release` and `prepare for next development iteration` commits
# without satisfying the 8 required status checks first.
- uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.RELEASE_TOKEN }}
- uses: actions/setup-java@v5
with:
java-version: '17'
distribution: temurin
cache: maven
# OSSRH_USERNAME/OSSRH_TOKEN secrets generated at https://central.sonatype.com/account
# per P-07 (NOT legacy oss.sonatype.org). Secret names retain "OSSRH" for mjml-java
# parity, but the credentials are Central Portal.
server-id: central
server-username: OSSRH_USERNAME
server-password: OSSRH_TOKEN
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE
- name: Publish package to Maven Central
run: |
git config --global user.name "github-actions[bot]"
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
# pom.xml's <developerConnection> uses an SSH URL for local-dev
# convenience. maven-release-plugin's `prepare` mojo reads pom
# directly and does NOT expose a `developerConnection` CLI
# override (only `connectionUrl`, used by `perform`). So make
# git transparently rewrite the SSH URL to HTTPS — the
# http.extraheader actions/checkout set up then authenticates
# the push with the RELEASE_TOKEN PAT. No pom change needed.
git config --global url."https://github.com/".insteadOf "git@github.com:"
mvn -B -ntp -Dstyle.color=always release:prepare -P sign
cat release.properties
RELEASE_TAG=$(grep '^scm.tag=' release.properties | cut -d'=' -f2)
echo "RELEASE_TAG=${RELEASE_TAG}" >> "$GITHUB_ENV"
mvn -B -ntp -Dstyle.color=always release:perform -P sign \
-DconnectionUrl=scm:git:https://github.com/${REPO}.git
echo "Released ${RELEASE_TAG} 🚀" >> "$GITHUB_STEP_SUMMARY"
env:
OSSRH_USERNAME: ${{ secrets.OSSRH_USERNAME }}
OSSRH_TOKEN: ${{ secrets.OSSRH_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
REPO: ${{ github.repository }}
# release:perform checks the tagged code into target/checkout/ and runs
# the central+sign profiles (see <releaseProfiles> in pom.xml). The
# cyclonedx plugin (bound to the central profile, package phase) drops
# bom.{json,xml} into each module's target/. Collect them under a
# versioned name so consumers downloading from the GH Release can tell
# which module/version the SBOM corresponds to.
- name: Collect release artifacts (JARs + SBOMs)
id: collect
run: |
set -euo pipefail
RELEASE_VERSION="${RELEASE_TAG#v}"
echo "RELEASE_VERSION=${RELEASE_VERSION}" >> "$GITHUB_ENV"
mkdir -p release-assets
missing=0
# SBOMs — one per module, both formats. ALL are required: a missing
# SBOM means the central profile didn't run cyclonedx (broken release
# config) and we'd rather fail here with a precise path than later
# when `gh release create` chokes on an unexpanded glob.
for module in magika-java magika-java-tika; do
for fmt in json xml; do
src="target/checkout/${module}/target/bom.${fmt}"
if [ -f "$src" ]; then
cp "$src" "release-assets/${module}-${RELEASE_VERSION}-cyclonedx.${fmt}"
else
echo "::error::Expected SBOM not found at $src — did the central profile run cyclonedx?"
missing=1
fi
done
done
# Main JARs (for build-provenance attestation; sources/javadoc skipped
# — attesting the primary artifact is what consumers verify against).
for module in magika-java magika-java-tika; do
jar="target/checkout/${module}/target/${module}-${RELEASE_VERSION}.jar"
if [ -f "$jar" ]; then
cp "$jar" "release-assets/"
else
echo "::error::Expected JAR not found at $jar"
missing=1
fi
done
if [ "$missing" -ne 0 ]; then
echo "::error::Required release artifacts are missing (see errors above); aborting before GitHub Release"
exit 1
fi
ls -la release-assets/
- name: Attest build provenance for JARs
uses: actions/attest-build-provenance@v3
with:
subject-path: 'release-assets/*.jar'
- name: Attest magika-java SBOM
uses: actions/attest-sbom@v3
with:
subject-path: 'release-assets/magika-java-${{ env.RELEASE_VERSION }}.jar'
sbom-path: 'release-assets/magika-java-${{ env.RELEASE_VERSION }}-cyclonedx.json'
- name: Attest magika-java-tika SBOM
uses: actions/attest-sbom@v3
with:
subject-path: 'release-assets/magika-java-tika-${{ env.RELEASE_VERSION }}.jar'
sbom-path: 'release-assets/magika-java-tika-${{ env.RELEASE_VERSION }}-cyclonedx.json'
- name: Extract CHANGELOG section + create GitHub Release
run: |
RELEASE_VERSION="${RELEASE_TAG#v}"
# P-03: extract CHANGELOG section using awk -v form (no shell-escape gotchas).
# Brackets MUST be escaped (\\[ \\]) — naive grep "## [0.3.0]" treats them as
# a character class and matches wrong.
awk -v ver="$RELEASE_VERSION" '
$0 ~ "^## \\[" ver "\\]" { flag=1; next }
/^## / { flag=0 }
flag
' CHANGELOG.md | sed '/./,$!d' > release-notes.md
# Fallback to commit list if CHANGELOG section is missing/empty
if [ ! -s release-notes.md ]; then
echo "WARN: CHANGELOG.md missing [${RELEASE_VERSION}] section — falling back to commit list"
PREV_TAG=$(git describe --tags --abbrev=0 "${RELEASE_TAG}^" 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"- %s" \
| grep -v '\[maven-release-plugin\]' > release-notes.md
else
git log "${RELEASE_TAG}" --pretty=format:"- %s" \
| grep -v '\[maven-release-plugin\]' > release-notes.md
fi
fi
# Optional Haiku-categorized commit appendix (Claude's Discretion: do NOT fail
# workflow on Anthropic API failure or absent ANTHROPIC_API_KEY).
PREV_TAG=$(git describe --tags --abbrev=0 "${RELEASE_TAG}^" 2>/dev/null || echo "")
if [ -n "$PREV_TAG" ]; then
COMMITS=$(git log "${PREV_TAG}..${RELEASE_TAG}" --pretty=format:"- %s" | grep -v '\[maven-release-plugin\]')
else
COMMITS=$(git log "${RELEASE_TAG}" --pretty=format:"- %s" | grep -v '\[maven-release-plugin\]')
fi
if [ -n "${ANTHROPIC_API_KEY:-}" ]; then
APPENDIX=$(curl -sf https://api.anthropic.com/v1/messages \
-H "x-api-key: ${ANTHROPIC_API_KEY}" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d "$(jq -n --arg commits "$COMMITS" '{
model: "claude-haiku-4-5-20251001",
max_tokens: 1024,
messages: [{role: "user", content: ("Categorize these git commits into release-notes appendix sections: ## New Features, ## Bug Fixes, ## Dependency Updates, ## Other Changes. Bulleted markdown lists. Only include sections with entries. No preamble.\n\nCommits:\n" + $commits)}]
}')" | jq -r '.content[0].text') || APPENDIX=""
if [ -n "$APPENDIX" ]; then
printf '\n---\n\n## Commit-level Changes\n\n%s\n' "$APPENDIX" >> release-notes.md
fi
fi
# Use --notes-file (NOT --notes) — CHANGELOG content can contain markdown
# that breaks shell-quoting if passed as a string.
# SBOMs are attached as release assets so consumers who don't pull from
# Maven Central (or who want the aggregate parent SBOM at a glance) can
# download them directly. JARs live on Central — no need to duplicate.
# Use explicit paths (not globs) so any missing file is a clear error
# rather than a silent glob mis-expansion passed verbatim to gh.
gh release create "${RELEASE_TAG}" \
--title "${RELEASE_TAG}" \
--notes-file release-notes.md \
--latest \
"release-assets/magika-java-${RELEASE_VERSION}-cyclonedx.json" \
"release-assets/magika-java-${RELEASE_VERSION}-cyclonedx.xml" \
"release-assets/magika-java-tika-${RELEASE_VERSION}-cyclonedx.json" \
"release-assets/magika-java-tika-${RELEASE_VERSION}-cyclonedx.xml"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}