-
Notifications
You must be signed in to change notification settings - Fork 0
148 lines (135 loc) · 7.53 KB
/
Copy pathrelease.yml
File metadata and controls
148 lines (135 loc) · 7.53 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
name: Release
# Triggered by a successful CI run on main, via `workflow_run`. Using
# `workflow_run` (instead of `push` or `release` triggers) guarantees that
# CI (lint, detekt, Android lint, tests, coverage) has passed BEFORE we
# attempt to publish to Maven Central — so broken code never reaches
# consumers. Mirrors the Convert PHP SDK's release.yml pattern.
on:
workflow_run:
workflows: ['CI']
types: [completed]
branches: [main]
# `contents: write` — semantic-release needs to push the `vX.Y.Z` tag and
# create the GitHub Release via the API. It does NOT push any commit to
# `main` (the branch ruleset would reject that — GH013); only the tag ref
# and the Release are written. (qs-03.)
#
# packages: write is INTENTIONALLY OMITTED — the Android SDK publishes to
# Maven Central only (via the vanniktech plugin). GitHub Packages / GHCR
# is not a target. Adding `packages: write` would widen the blast radius
# of a compromised GITHUB_TOKEN with no functional benefit (least-privilege
# principle). If a GHCR mirror is ever added, re-introduce it then with
# an inline citation to the architecture decision. Matches the PHP SDK's
# release.yml permissions block exactly. (Story 1.4 / AC-1 — F-121.)
permissions:
contents: write
# Serialize releases. If two pushes land on main in quick succession, the
# second workflow_run waits for the first to finish before starting — we
# must not have two concurrent `./gradlew publish…` invocations racing to
# upload artifacts of the same version, or two `@semantic-release/github`
# instances racing to create the tag + Release. `cancel-in-progress: false` because
# cancelling a mid-publish workflow can leave a half-uploaded staged
# deployment in the Central Portal that needs manual cleanup.
concurrency:
group: release
cancel-in-progress: false
jobs:
release:
name: Semantic Release → Maven Central
runs-on: ubuntu-latest
# Twin guards (both required):
# (1) workflow_run.conclusion == 'success' → CI actually passed
# (workflow_run fires on `completed` regardless of outcome).
# (2) workflow_run.event == 'push' → the CI run that triggered us
# was on a push, not a pull_request. FORK PRs fire `pull_request`
# events and DO NOT carry secret access; triggering release on
# them would either fail or (worse) risk leaking secrets into PR
# logs. DO NOT REMOVE THIS GUARD — see RELEASE.md §"Fork-PR
# Safeguard" for why. (Story 1.4 / AC-10.)
if: >
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push'
steps:
- name: Check out
uses: actions/checkout@v4
with:
# semantic-release analyzes all commits since the last tag; the
# default shallow clone (depth 1) would blind it.
fetch-depth: 0
# Required so semantic-release can push the `vX.Y.Z` tag back to
# origin using GITHUB_TOKEN. Only the tag is pushed — no commit to
# `main` (the GitHub Release is created via the API). (qs-03.)
persist-credentials: true
token: ${{ secrets.GITHUB_TOKEN }}
- name: Set up JDK 17 (Temurin)
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- name: Set up Gradle (with caching)
uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4
- name: Ensure gradlew is executable
# Defensive — actions/checkout@v4 should preserve the exec bit but
# some self-hosted runners strip it. CI has the same guard.
run: chmod +x ./gradlew
- name: Set up Node LTS (for semantic-release)
uses: actions/setup-node@v4
with:
node-version: 'lts/*'
# NOTE: `cache: yarn` is intentionally NOT set. This repo is a Yarn
# Berry project (yarn.lock __metadata version 10). setup-node's
# `cache: yarn` resolves the cache folder using the yarn binary on
# PATH *during this step* — which is the runner's default Yarn
# Classic 1.x, because corepack's Berry shim isn't activated until
# the next step. That would cache the wrong (Classic) folder.
# Release runs only on workflow_run after CI success on main (low
# frequency), so the marginal cache benefit doesn't justify the
# misconfiguration risk. (qs-02 / TD-3.)
- name: Enable corepack (activate Yarn Berry from packageManager)
# Runs AFTER setup-node so corepack uses the pinned Node toolchain.
# `corepack enable` installs the yarn shim that resolves the version
# in package.json's `packageManager` field (yarn@4.15.0), so the
# install below runs under Berry — not the runner's default Yarn
# Classic 1.x, which silently ignores --immutable and mis-reads the
# v10 lockfile. (qs-02 / TD-3.)
run: corepack enable
- name: Install Node dependencies
# --immutable: error if yarn.lock drifts from package.json. Matches
# the PHP SDK's pattern — prevents accidental lockfile mutation in
# CI while still catching out-of-sync lockfiles at PR time. Honored
# correctly now that corepack has activated Yarn Berry above.
run: yarn install --immutable
- name: Run semantic-release
# semantic-release orchestrates everything from here: analyze
# commits → bump libs.versions.toml (build-time, not committed) →
# ./gradlew publishAllPublicationsToMavenCentralRepository →
# push the `vX.Y.Z` tag + create the GitHub Release via the API.
# No commit is pushed to `main`.
run: yarn release
env:
# GitHub Actions — used to push the tag and create the Release
# (@semantic-release/github + semantic-release core).
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Sonatype Central Portal credentials. Generate these in the
# Central Portal UI (https://central.sonatype.com) → user tokens.
# See RELEASE.md §"Secrets" for the exact acquisition steps.
# Exposed as Gradle project properties (`mavenCentralUsername` /
# `mavenCentralPassword`) via the `ORG_GRADLE_PROJECT_` prefix —
# the same convention as the `ORG_GRADLE_PROJECT_signingInMemoryKey*`
# lines below. The vanniktech maven-publish plugin reads these
# properties to authenticate `publishToMavenCentral`. (qs-04.)
ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }}
ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }}
# In-memory GPG signing key for vanniktech's plugin. Export the
# private key with `gpg --armor --export-secret-keys <KEY_ID>`
# and paste the full multi-line ASCII-armored output into the
# GitHub secret UI (secret storage handles multi-line correctly).
ORG_GRADLE_PROJECT_signingInMemoryKey: ${{ secrets.GPG_PRIVATE_KEY }}
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.GPG_KEY_PASSWORD }}
# signingInMemoryKeyId is intentionally NOT set: it is optional (the
# plugin derives the key from the armored block) and a mismatched id
# causes "Could not read PGP secret key". A local
# `publishToMavenLocal` with the same key — and NO keyId — signs
# successfully, so CI matches that proven-good config. Re-introduce
# it only if the armored key ever holds multiple keys needing
# disambiguation.