Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
99 changes: 99 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: Publish to Maven Central

# Publishes the v*-tagged artefact set to Maven Central via the
# central-publishing-maven-plugin in the `release` profile (Track D3).
# Triggered by the same `v*` tag push that fires release.yml (Track D4).
# The two workflows are independent — release.yml creates the GitHub
# Release; this one publishes the Maven Central artefacts. They can
# succeed or fail independently, and a maintainer can re-run this
# workflow alone via workflow_dispatch if Central had a transient
# validator hiccup without re-cutting the tag.
#
# Hyphenated tags (rc / alpha / beta / SNAPSHOT) are skipped: those go
# only to JitPack and the GitHub Release pre-release, never to Central
# (Central's validator rejects SNAPSHOT-style coordinates anyway).
#
# Human prerequisites (one-time per repo):
# 1. Generate a GPG key locally; upload the public key to a
# keyserver pool (keys.openpgp.org, keyserver.ubuntu.com).
# 2. Register at https://central.sonatype.com — verify the
# `io.github.demchaav` namespace via GitHub OAuth or DNS TXT.
# 3. Generate a Central user token at Account -> Generate User Token.
# 4. Add four GitHub repo secrets at Settings -> Secrets and
# variables -> Actions:
# MAVEN_GPG_PRIVATE_KEY — full ASCII-armored private key
# MAVEN_GPG_PASSPHRASE — passphrase for the key above
# CENTRAL_USERNAME — Central user-token username half
# CENTRAL_TOKEN — Central user-token password half
# See docs/contributing/release-process.md for the full runbook.

on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Existing v*-prefixed tag to (re-)publish'
required: true
type: string

permissions:
contents: read

jobs:
publish:
name: Publish ${{ github.ref_name }} to Maven Central
runs-on: ubuntu-latest
# Only ship plain semver tags (vX.Y.Z) to Central. Pre-release tags
# like v1.7.0-rc.1 ship to JitPack + the GitHub Release pre-release
# surface only.
if: |
github.event_name == 'workflow_dispatch' ||
(!contains(github.ref, '-rc') && !contains(github.ref, '-alpha') && !contains(github.ref, '-beta') && !contains(github.ref, '-snapshot'))
env:
JAVA_TOOL_OPTIONS: -Djava.awt.headless=true

steps:
- name: Check out repository at tag
uses: actions/checkout@v6
with:
ref: ${{ github.event.inputs.tag || github.ref }}

- name: Set up Temurin JDK 17 with Central credentials and GPG key
uses: actions/setup-java@v5
with:
distribution: temurin
java-version: '17'
cache: maven
# `setup-java@v5` writes <server id="central"> into
# ~/.m2/settings.xml mapping these two env-var names to
# <username> and <password>. The plugin's
# publishingServerId=central in pom.xml's release profile
# (Track D3) wires up to this entry.
server-id: central
server-username: CENTRAL_USERNAME
server-password: CENTRAL_TOKEN
# Imports the GPG key into the runner's keyring so the
# maven-gpg-plugin (Track D2) can sign without an
# interactive prompt.
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }}
gpg-passphrase: MAVEN_GPG_PASSPHRASE

- name: Verify (full canonical suite green at tagged commit)
# Re-verify the tagged commit before publishing — defence in
# depth against a tag pushed from a broken branch by mistake.
# The publish step below would also fail at signing/upload,
# but failing here surfaces a clearer error.
run: ./mvnw -B -ntp clean verify -pl .

- name: Publish to Maven Central
# Activates the release profile (sources + javadoc + gpg sign +
# central-publishing) and flips gpg.skip=false. The deploy
# phase invokes central-publishing-maven-plugin's upload goal
# which blocks until Sonatype's validator confirms validation.
run: ./mvnw -B -ntp -P release -DskipTests -Dgpg.skip=false deploy
env:
CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }}
CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }}
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,31 @@ JitPack continue to resolve through the existing coordinates.
`./mvnw -DskipTests -P japicmp verify -pl .`; HTML/MD/XML reports
land in `target/japicmp/`. JitPack repository is scoped to the
`japicmp` profile, so downstream consumers do not inherit it.
- **Maven Central publish workflow** (Track D4). New
[`.github/workflows/publish.yml`](.github/workflows/publish.yml) fires
on the same `v*` tag push that triggers the existing
`release.yml`. It re-runs `mvnw verify` at the tagged commit, imports
the GPG key (Track D2) into the runner keyring, writes the
`<server id="central">` credentials block into `~/.m2/settings.xml`
via `actions/setup-java@v5`, then invokes
`./mvnw -P release -Dgpg.skip=false deploy` — the
`central-publishing-maven-plugin` (Track D3) uploads to Central and
blocks until Sonatype's validator responds. Hyphenated tags
(`-rc`, `-alpha`, `-beta`, `-snapshot`) are explicitly skipped — those
ship only to JitPack and the GitHub Release pre-release surface.
A `workflow_dispatch` input lets the maintainer re-publish an
existing tag without re-cutting it if Central had a transient
validator hiccup. The workflow is dormant until four GitHub repo
secrets are wired: `MAVEN_GPG_PRIVATE_KEY`, `MAVEN_GPG_PASSPHRASE`,
`CENTRAL_USERNAME`, `CENTRAL_TOKEN`.
- **`docs/contributing/release-process.md` updated** with the
end-to-end Maven Central runbook (Track D4 docs). New § 2.C
"One-time Maven Central setup (maintainer)" walks through GPG key
generation, keyserver upload, Sonatype account / namespace
verification, Central user-token generation, the four GitHub
secrets, and the release-candidate dry-run strategy. § 2.B
post-release checklist gains a new step 9 for the Central publish
alongside the existing JitPack step.
- **`central-publishing-maven-plugin` in the `release` profile**
(Track D3). Adds Sonatype's `central-publishing-maven-plugin` 0.7.0
to the existing `release` profile as a packaging extension. Replaces
Expand Down
34 changes: 32 additions & 2 deletions docs/contributing/release-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,39 @@ Run within 1 hour of the tag push. Independent steps can run in parallel.
6. **Re-run all examples against the published artifact** — `./mvnw -f examples/pom.xml clean package` followed by `exec:java -Dexec.mainClass=com.demcha.examples.GenerateAllExamples`. Expect 26+ `Generated:` lines.
7. **Flip ShowcaseMetadata back to develop** — `pwsh ./scripts/cut-release.ps1 -PostReleaseOnly`. This restores linkable "View Code" buttons for ongoing v1.x.y dev work.
8. **GitHub Release — automated.** Pushing the `v<target>` tag triggers [`.github/workflows/release.yml`](../../.github/workflows/release.yml): it re-runs `./mvnw clean verify -pl .` against the tagged commit, then creates the Release with that version's CHANGELOG section as the body (hyphenated tags like `v1.7.0-rc.1` ship as pre-releases; the step is idempotent — it edits the notes if the Release already exists). The workflow titles it `GraphCompose v<target>`; for a **minor** release, edit the title to add the codename (`v1.4`=cinematic, `v1.5`=intuitive, `v1.6`=expressive; patches drop it). Create the Release by hand (`gh release create v<target> --notes-file <CHANGELOG section>`) only if the workflow is unavailable.
9. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post.
9. **Maven Central publish — automated (from v1.6.6).** The same `v<target>` tag push triggers [`.github/workflows/publish.yml`](../../.github/workflows/publish.yml): it re-runs `mvnw verify` at the tagged commit, signs the four artefacts (main / sources / javadoc / pom) with the repo's GPG key, and uploads to Maven Central via the `central-publishing-maven-plugin`. Hyphenated tags (`-rc`, `-alpha`, `-beta`, `-snapshot`) are skipped — those go only to JitPack + the GitHub Release pre-release surface. `autoPublish=false` in the plugin config means the artefact lands in the Central validation queue; the maintainer flips the switch on [central.sonatype.com](https://central.sonatype.com) for the first publish, then can opt into auto-release in a follow-up. Verify via `mvn dependency:get -DgroupId=io.github.demchaav -DartifactId=graphcompose -Dversion=<target>` once the artifact appears (usually 5–15 minutes after the workflow turns green).
10. **Optional**: GitHub Discussions announcement (mirror the prior release's style; close with *"author intent, not coordinates"*), LinkedIn post, r/java post.

The release is **done** only when steps 1–7 are all green.
The release is **done** only when steps 1–7 are all green; step 9 adds Maven Central availability once the D-track of v1.6.6 has shipped.

---

## 2.C One-time Maven Central setup (maintainer)

These steps are done **once per repo** before the publish workflow can succeed; they are *not* part of every release. Listed here so a future maintainer (or a future me) can reproduce the setup without spelunking through commit history.

1. **Generate a GPG key.**
```bash
gpg --full-generate-key # RSA 4096, no expiry, real-name / email of maintainer
gpg --list-secret-keys --keyid-format=long
gpg --armor --export-secret-keys <KEYID> > private-key.asc # never commit this file
```
2. **Publish the public key to a keyserver pool.** Central validates the signature against one of these. Two redundant pools is the conventional minimum:
```bash
gpg --keyserver keys.openpgp.org --send-keys <KEYID>
gpg --keyserver keyserver.ubuntu.com --send-keys <KEYID>
```
`keys.openpgp.org` requires a one-time email-verification round-trip on the address attached to the key.
3. **Register a Sonatype Central account.** At [central.sonatype.com](https://central.sonatype.com) — sign in with GitHub for the auto-verified `io.github.<gh-handle>` namespace (the `io.github.demchaav` namespace this repo claims). Verify the namespace via GitHub-account check or DNS TXT record on the published domain.
4. **Generate a Central user token.** Account → Generate User Token → copy the `username` and `password` halves. These are credentials, not the GitHub login.
5. **Wire four GitHub repo secrets.** Repo Settings → Secrets and variables → Actions → New repository secret:
- `MAVEN_GPG_PRIVATE_KEY` — full ASCII-armored private key (the contents of `private-key.asc` from step 1).
- `MAVEN_GPG_PASSPHRASE` — the passphrase guarding the key.
- `CENTRAL_USERNAME` — token username from step 4.
- `CENTRAL_TOKEN` — token password from step 4.
6. **Test the wiring on a release-candidate tag** *before* the first real release. `v1.6.6-rc.1` (hyphenated) skips Central per `publish.yml`'s `if:` guard, so it's safe — alternatively, cut `v1.6.6` for real and observe the workflow; `autoPublish=false` means a failed validation does not pollute Central, the artefact just sits in the validation queue until manually released or deleted.

If any of these stop working between releases (key expired, token rotated), the publish workflow surfaces the failure inside the workflow run — the GitHub Release and the JitPack artefact are still cut by the other workflows.

---

Expand Down