diff --git a/.claude/skills/creating-release-vote-mail/SKILL.md b/.claude/skills/creating-release-vote-mail/SKILL.md index e34815b88c..4c14a96fdb 100644 --- a/.claude/skills/creating-release-vote-mail/SKILL.md +++ b/.claude/skills/creating-release-vote-mail/SKILL.md @@ -14,7 +14,8 @@ boilerplate, around a plain-text rendering of the release's Version Notes page. **This is the step after `creating-version-notes`.** That skill produces the page, the GitHub release and the `[TEST]` announcement; this one consumes all three. If they do not exist yet, -you are in the wrong skill. +you are in the wrong skill. `releasing-struts` holds the surrounding phases and what happens +once the vote passes. [`vote-mail-template.md`](vote-mail-template.md) is the source of truth for the artifact. diff --git a/.claude/skills/creating-version-notes/SKILL.md b/.claude/skills/creating-version-notes/SKILL.md index e52ca187d5..fd8a0fd3ca 100644 --- a/.claude/skills/creating-version-notes/SKILL.md +++ b/.claude/skills/creating-version-notes/SKILL.md @@ -13,6 +13,8 @@ A Version Notes page answers one question for a user deciding whether to upgrade **One skill covers every maintenance line.** 6.x and 7.x pages share an identical structure. The line changes the data (version, prior page, JIRA ids), never the process. +**This is phase 3 of a seven-phase release.** `releasing-struts` holds the sequence, the gates and the mechanics either side of it; this skill owns the paperwork. + ## The Iron Rule ``` diff --git a/.claude/skills/releasing-struts/SKILL.md b/.claude/skills/releasing-struts/SKILL.md new file mode 100644 index 0000000000..b94beedd55 --- /dev/null +++ b/.claude/skills/releasing-struts/SKILL.md @@ -0,0 +1,148 @@ +--- +name: releasing-struts +description: Use when running or planning an Apache Struts release on any maintenance line (6.x, 7.x) - cutting the tag, staging artifacts, opening the vote, promoting, updating the site and announcing - or when asked what the next step in a release is. +--- + +# Releasing Struts + +## Overview + +A release is seven phases with a gate between each. Most of the *writing* is already covered by +other skills; this one owns the **order, the gates, and the mechanics** — and it is the only +place that covers the last mile after the vote passes. + +**Core principle:** a phase is finished when its gate is verifiable by someone other than you. +"I ran the command" is not a gate; "the URL resolves" is. + +[`release-runbook.md`](release-runbook.md) holds the commands. This page holds the sequence and +the judgement. + +## The phases + +| # | Phase | Gate before moving on | +|---|---|---| +| 1 | Prepare | Branch green, versions decided, BOM in sync | +| 2 | Cut | Tag pushed, artifacts in a **closed** Nexus staging repo | +| 3 | Stage | Assemblies in `dist/dev`, Version Notes page live, `[TEST]` mail sent | +| 4 | Vote | 72 h elapsed, three binding `+1`, result mail sent | +| 5 | Promote | Nexus repo released, `dist/dev` → `dist/release`, 24 h rsync waited | +| 6 | Publish | Site PR merged, GitHub release un-flagged, `[ANN]` mail delivered | +| 7 | Advisories | Bulletins public, CVE records filled, advisory mails delivered | + +Phase 7 only exists when the release carries a security fix, and *publishing* the advisory is +**strictly after** phase 6 — see *Security work is a separate clock* below. Writing the bulletin +is not: it is usually drafted long before the release exists, and often on its own timetable +entirely. + +## Which skill owns which artifact + +Cross-references, not copies. Do not restate what these settle: + +- **`creating-version-notes`** — the Version Notes page, its Staging Repository block, the + Migration Guide entry, the GitHub release notes, and the `[TEST]` mail. All of phase 3's + paperwork. +- **`creating-release-vote-mail`** — the `[VOTE]` mail. All of phase 4's paperwork. +- **`creating-security-bulletins`** — the S2-XXX page, what may be disclosed and when, + publication, and the advisory mails. + +**That last one is not a phase of this process.** A bulletin gets written when the report is +triaged, which may be months before a release carries the fix, and plenty of bulletins are +handled with no release in flight at all. It is a skill in its own right, invoked whenever it is +needed. Phase 7 is the reverse direction: *if* this release carries a security fix, then once +phase 6 is done, go and follow that skill. + +This skill covers what none of them do: phases 1, 2, 5 and 6, and the ordering that binds them. + +## Two lines, two releases + +`main` is the 7.x line; `support/struts-6-x-x` is 6.x. Both are protected and both require their +build to pass. A change that lands on both is **two releases**, each with its own tag, vote, +site entry and announcement — not one release mentioned twice. + +They can be cut in parallel and voted in parallel, and usually are. Keep the version numbers +independent: 6.11.0 and 7.3.0 shipped together and share nothing but a date. + +**Neither line branch is where the release is cut.** Both August 2026 releases were built on a +`release/X.Y.Z-RC1` branch off the line, so the `[maven-release-plugin]` commits never reach +`main`. A failed vote is then a deleted branch, not a revert. + +## The version number is chosen at release time + +The `-SNAPSHOT` in the pom is a placeholder, not a decision. Pick the number from the semver +impact of what actually landed since the last tag, and say so out loud before cutting — the tag +is the first irreversible act of the release. + +The pom cannot tell you: because releases are cut on a side branch, `main` still read +`7.2.2-SNAPSHOT` after 7.3.0 had shipped. + +## Security work is a separate clock + +**Nothing about an unpublished advisory goes into the release paperwork.** Not the Version +Notes, not the `[TEST]` mail, not the `[VOTE]`, not the commit messages, not the site entry. +The tickets are neutral; that is deliberate and it is what makes the embargo survive a public +release process. + +The advisory follows the release, and the ordering is not negotiable: + +``` +release GA → bulletin unrestricted → advisory mails → CVE pushed to MITRE +``` + +A bulletin published before the fixed artifact is downloadable tells attackers what to look for +and gives operators nothing to do about it. + +**A 6.x release containing only embargoed fixes is self-disclosing** — the diff between the two +tags is the vulnerability whatever the commit messages say. That is a reason to bundle it with +unrelated work, or to publish the bulletins with the release, not a reason to pretend otherwise. + +## What the old cwiki page gets wrong + +[Building Struts 2 — Normal release](https://cwiki.apache.org/confluence/pages/viewpage.action?pageId=27832970) +was last revised in **2017** and is the page a release manager is most likely to find. It is +still right about JIRA, `release:prepare`/`release:perform`, Nexus and `dist.apache.org`, and +wrong about everything downstream: + +| It says | Reality | +|---|---| +| Branches `develop` / `master` | `main` and `support/struts-6-x-x` | +| Tag `STRUTS_2_3_x` | `STRUTS_X_Y_Z` for the version being cut | +| Export the wiki to `/docs` | The site no longer embeds exported Confluence pages | +| Build the site with Docker Jekyll, commit `content/` | The site builds from a PR to `apache/struts-site` | +| `svn co .../infra/websites/production/struts` | Gone; publishing is the merge | +| `people.apache.org`, `source/announce.md`, `downloads.html` | Dead host, and the files are `announce-YYYY.md`, `releases.md` and `index.html` | + +Treat it as history. If you follow it, you will publish to a repository that no longer serves +the site. + +## Gates that are actually load-bearing + +- **A closed Nexus staging repo, not just a successful `release:perform`.** Until it is closed + the URL in the Version Notes resolves to nothing and every tester is blocked. +- **72 hours, and three binding `+1`.** PMC votes are the binding ones; `private@` is on the + vote mail so binding voters see it. +- **24 hours after the `dist` move, before announcing.** ASF mirroring guidance. Announcing into + an unmirrored release sends everyone to a 404. +- **The GitHub release stops being a prerelease at phase 6, not at phase 3.** During the vote it + must still be flagged, or the vote is on an artifact the world already treats as final. + +## Red Flags — STOP + +- Cutting a tag before the version number has been stated and agreed +- A `[VOTE]` opened on a staging repo that is not closed, or on a link that 404s +- Announcing before the 24-hour mirror wait +- Any severity, CVE, S2-XXX or bulletin link in release paperwork +- A bulletin unrestricted before the fixed release is downloadable +- Following the 2017 cwiki page for anything after the Nexus step +- One release "covering" both maintenance lines +- Inferring the release version from the `-SNAPSHOT` in the pom + +## Common Mistakes + +| Mistake | Reality | +|---|---| +| "`release:perform` succeeded, so the artifacts are staged" | They are staged and *open*. Close the repo or nobody can fetch them. | +| "The vote passed, so it's released" | Nexus release, dist move and the mirror wait all come after. | +| "I'll announce now and fix the site after" | The announcement links the site. Merge the site PR first. | +| "The 6.x fix is the same change, so one announcement covers both" | Two artifacts, two downloads, two sets of affected users. | +| "The pom says 7.3.1-SNAPSHOT, so this is 7.3.1" | The placeholder is not a decision. Semver impact decides. | +| "The cwiki page is the official process" | It is the 2017 process. Where they disagree, this skill is current. | diff --git a/.claude/skills/releasing-struts/release-runbook.md b/.claude/skills/releasing-struts/release-runbook.md new file mode 100644 index 0000000000..1985ed6115 --- /dev/null +++ b/.claude/skills/releasing-struts/release-runbook.md @@ -0,0 +1,292 @@ +# Release Runbook + +The commands, in order. [`SKILL.md`](SKILL.md) holds the sequence, the gates and the judgement; +this file is what you type. + +**Provenance.** Everything marked ✔ was verified against the repository or a completed release +(7.3.0 / 6.11.0, August 2026). Everything marked **⚠ unverified** is carried over from the 2017 +cwiki page and has *not* been confirmed against a current run — check it before relying on it, +and correct this file when you do. + +**The scripts.** Phases 3 and 5 ship with this skill, in [`scripts/`](scripts): + +| Script | Phase | What it does | +|---|---|---| +| [`stage-assemblies.sh`](scripts/stage-assemblies.sh) | 3 | Closed staging repo → `dist/dev`, renamed and re-hashed | +| [`promote-dist.sh`](scripts/promote-dist.sh) | 5 | `dist/dev` → `dist/release` | + +Both take `$VERSION` from the environment, refuse to run without it, and refuse a value that is +not a version number — `svn` resolves a `.` path element rather than rejecting it, so a stray +`VERSION` would otherwise move the whole staging tree in one irreversible commit. + +Run them from a scratch directory (`cd "$(mktemp -d)"`), never from a repository checkout: the +staging script creates `./$VERSION` and a temporary svn working copy in the current directory. + +--- + +## Phase 1 — Prepare + +✔ Two lines, two releases: + +| Line | Branch | Build check that must pass | +|---|---|---| +| 7.x | `main` | `Build and Test (JDK 17)` | +| 6.x | `support/struts-6-x-x` | `Build and Test (8)` | + +Both branches are protected in `.asf.yaml` and must be green before you start. + +**Check the JDK before building anything.** The line dictates it — 7.x builds on **JDK 17**, 6.x +on **JDK 8** — and the whole release is produced by whichever JDK happens to be active in the +shell. Cutting 6.x on 17 produces artifacts that will not run for the users that line exists for. + +```bash +mvn -v # reports the JDK Maven is actually using, not just $JAVA_HOME +``` + +**If it is the wrong version, stop and ask the release manager how to switch.** Local +environments differ — jenv, SDKMAN, asdf, `JAVA_HOME` by hand, a Homebrew symlink — and guessing +at someone's toolchain is how you end up building against a JDK they did not intend. Ask, do not +infer. (`.java-version` is gitignored in this repo, so it is not a reliable signal either.) + +```bash +git checkout main && git pull --ff-only +mvn clean install -DskipAssembly +``` + +Then: + +- Decide the version number from semver impact. Do not read it off the `-SNAPSHOT`. +- ✔ Confirm `struts-master` (currently `15`) and `struts-annotations` are released versions, not + snapshots. The root pom's `` must not point at a snapshot. +- ✔ The BOM needs no version sync. `bom/pom.xml` inherits the root version through its + `` and declares members as `${project.version}`. The cwiki's + `struts-version.version` property no longer exists — ignore that step. +- Review JIRA: every issue fixed since the last tag has a fix version; nothing unresolved carries + this one. +- ⚠ unverified: the cwiki's "omnibus ticket" step. The 7.3.0 and 6.11.0 runs show no such ticket + — treat it as abandoned unless the PMC says otherwise. + +## Phase 2 — Cut + +✔ **Cut from a release branch, not from the line branch.** Both August 2026 releases were built +on `release/X.Y.Z-RC1` branched off the line: + +```bash +git checkout -b release/7.3.0-RC1 main # or off support/struts-6-x-x for 6.x +git push -u origin release/7.3.0-RC1 +``` + +The two `[maven-release-plugin]` commits land there and **`main` is never touched** — which is +why the root pom still said `7.2.2-SNAPSHOT` after 7.3.0 shipped, and why the pom is worthless +as a source for the release number. + +✔ `maven-release-plugin` 3.3.1, driven interactively, on that branch: + +```bash +mvn release:prepare +``` + +✔ No flags. `autoVersionSubmodules` is configured in the root pom, along with the ASF parent's +`useReleaseProfile=false`, `goals=deploy` and `releaseProfiles=apache-release`. If you find +yourself passing `-D` to the release plugin, the setting belongs in the pom instead — a flag +that has to be remembered is a flag that will be forgotten. + +✔ **At the SCM tag prompt, type `STRUTS_X_Y_Z`.** The plugin's default would be +`struts2-project-X.Y.Z` (the root artifactId); every Struts tag in history is the underscore +form, and the GitHub release, the Version Notes and the site all assume it. + +This one cannot move into the pom: `tagNameFormat` interpolates `@{project.version}` and has no +string functions, so the best it could produce is `STRUTS_7.3.0`. The prompt stays. + +Dry run first if you want one — add `-DdryRun=true`, then `mvn release:clean` before the real +run. On failure, re-run the same command: `-Dresume` defaults to true and it picks up where it +stopped. + +✔ The result is two commits on the release branch, +`[maven-release-plugin] prepare release STRUTS_X_Y_Z` and +`[maven-release-plugin] prepare for next development iteration`, plus the tag. + +```bash +mvn release:perform +``` + +✔ `retryFailedDeploymentCount=10` is configured on `maven-deploy-plugin` in the root pom, not +passed here. It has to be in the pom to work at all: `release:perform` forks a fresh Maven +build, and that fork does not inherit `-D` properties from the outer invocation — the flag the +cwiki tells you to pass was doing nothing. + +⚠ unverified: the fallback for re-running `perform` elsewhere — +`git checkout STRUTS_X_Y_Z && mvn javadoc:javadoc deploy -DperformRelease=true -Papache-release`. + +**Then the staging repository has to be closed — and that is the release manager's action, not +yours.** It happens in the Nexus web UI at (Staging Repositories +→ select → Close), behind an ASF login. Say so, hand over, and **wait for confirmation before +continuing** — phase 3 fetches from the staging *group* URL and gets nothing while the repo is +open. + +⚠ unverified in detail, but the gate is checkable and worth checking yourself once you are told +it is done: the artifacts must resolve under + +``` +https://repository.apache.org/content/groups/staging/org/apache/struts/struts2-core/$VERSION/ +``` + +The staging repo is keyed by user *and* public IP. If your IP changed mid-release you will have +two; drop the stale one, checking the dates. + +## Phase 3 — Stage + +✔ [`scripts/stage-assemblies.sh`](scripts/stage-assemblies.sh) does this. It runs on your own +machine — the cwiki's "log in to `people.apache.org`" step is dead, that host is gone. + +```bash +VERSION=7.3.0 .claude/skills/releasing-struts/scripts/stage-assemblies.sh +``` + +It fetches `zip`, `md5`, `sha1` and `asc` from the **closed** staging repo, strips the +`2-assembly` infix, drops the `.pom*` files and the legacy `md5`/`sha1` hashes, generates +`.sha256` and `.sha512` locally with `shasum`, prints what it is about to publish, then +`svn add`s the directory to `dist/dev/struts` and commits. It needs your ASF svn credentials. + +✔ Gate, verified against `dist/release/struts/7.3.0/`: + +``` +https://dist.apache.org/repos/dist/dev/struts/$VERSION/ +``` + +holds **six** assemblies — `struts-$VERSION-all.zip`, `-apps.zip`, `-docs.zip`, `-lib.zip`, +`-min-lib.zip` and `-src.zip` — each with `.asc`, `.sha256` and `.sha512` beside it: 24 files. +No `.md5`, no `.sha1`, no `.pom`. `KEYS` lives one level up, in `dist/release/struts/`. + +Count them. `set -eu` stops the script on a step that *fails*, not on a crawl that quietly +returns a subset, so a short upload reaches `dist/dev` looking healthy. + +**The staging repo must be closed before you run this** — the script pulls from the staging +*group* URL, and an open repo serves nothing there. + +Everything else in this phase belongs to **`creating-version-notes`**: the Version Notes page, +its Staging Repository block, the Migration Guide entry, the GitHub release (created as a +**prerelease**), and the `[TEST]` mail to `dev@` and `user@`. + +## Phase 4 — Vote + +**`creating-release-vote-mail`** owns the mail. The mechanics around it: + +- 72 hours minimum, three binding `+1` (PMC members). +- ✔ `To: dev@struts.apache.org`, `Bcc: private@struts.apache.org`. Never `user@`. +- Close with a result mail on the same thread. + +## Phase 5 — Promote + +✔ [`scripts/promote-dist.sh`](scripts/promote-dist.sh) does this — one server-side `svn mv`: + +```bash +VERSION=7.3.0 .claude/skills/releasing-struts/scripts/promote-dist.sh +# svn mv https://dist.apache.org/repos/dist/dev/struts/$VERSION/ \ +# https://dist.apache.org/repos/dist/release/struts/ -m "Release Struts $VERSION" +``` + +Then **release** the staging repository in Nexus, which replicates to Maven Central. + +✔ On pruning old releases: the cwiki says to keep only the latest. Current practice does not — +`dist/release/struts/` held 6.8.0, 6.9.0, 6.10.0, 6.11.0, 7.1.1, 7.2.1, 7.3.0 and `KEYS` in +August 2026. Everything removed stays available at +. Decide deliberately; do not prune on autopilot. + +**Then wait 24 hours** for mirrors before anything in phase 6. + +## Phase 6 — Publish + +### The site — a PR to `apache/struts-site` + +✔ Verified against PR #322 (the 7.3.0 / 6.11.0 GA announcement) and #323. + +`_config.yml` — all of these move together: + +```yaml +current_version: 7.3.0 +current_version_short: 730 +prev_version: 6.11.0 +prev_version_short: 6110 +release_date: 1 August 2026 +prev_release_date: 1 August 2026 +release_date_short: 20260801 +prev_release_date_short: 20260801-6110 +``` + +`release_date` is the **tag** date, not the announcement date — 7.2.1 was tagged 15 June and +announced 30 June, and the site says 15 June. The `*_date_short` values are the anchors in +`announce-YYYY.md`; when two releases share a tag date, disambiguate the second +(`20260801-6110`) so the two home-page boxes link to their own entries. + +Then: + +- `source/announce-YYYY.md` — a new `####` entry at the top, newest first, with its `{#aYYYYMMDD}` + anchor. +- ✔ `source/releases.md` — the release table (Release / Release Date / Vulnerability / Version + Notes). **Easy to miss and both August 2026 PRs changed it**; a site PR without it is + incomplete. +- `source/index.html` — the GA boxes read from `_config.yml`; the security boxes are hand-edited. +- `source/dtds/` — only if a new DTD shipped. + +✔ **Not** `source/download.cgi` — that is a six-line wrapper around `mirrors.cgi` with no release +content in it. `source/download.md` interpolates versions from `_config.yml` and its *Prior +releases* section is a static pointer to `archive.apache.org`, so neither needs a per-release +edit. + +Publishing is the merge. There is no separate deploy step and no svn. + +### GitHub release + +✔ Un-flag the prerelease. Title `Struts X.Y.Z`, tag `STRUTS_X_Y_Z`. + +### The `[ANN]` mail + +✔ Recipients, from the 7.3.0 and 6.11.0 announcements: + +``` +To: user@struts.apache.org +Cc: announce@apache.org, announcements@struts.apache.org +``` + +`dev@` is not on it — the list already saw the `[TEST]` mail and the vote. + +✔ **Plain text only, and sent from the `@apache.org` identity.** `announce@apache.org` rejects +any message carrying a `text/html` part — + +``` +ezmlm-reject: fatal: Sorry, a message part has an unacceptable MIME Content-Type: 'text/html' (#5.2.3) +``` + +— and `announcements@struts.apache.org` answers *"Must be sent from an @apache.org address."* +A draft made with the Gmail tool is an HTML draft whatever you pass it; see *The mail must be +text/plain* in `creating-release-vote-mail` for the full contract. One list accepting the mail +is not evidence the format was right. + +Body: the GA boilerplate ("pleased to announce … General Availability … highest quality grade"), +the Version Notes link, the Migration Guide link for a major line, the minimum JDK/spec +requirements for that line, and the download page. + +## Phase 7 — Advisories + +Only when the release carries a security fix, and the *publication* only after phase 6. The +bulletin itself was almost certainly written when the report was triaged, long before this +release existed. + +**`creating-security-bulletins`** owns all of it: unrestricting the bulletin, the CVE record on +, and the advisory mails from that record's *OSS/ASF Emails* tab. +Follow that skill from here; it is not a step in this runbook. + +The order that matters here: the CVE record goes `RESERVED → DRAFT → READY`, and **READY is the +last state a PMC sets**. ASF Security submits it to the CVE Program and sets `PUBLIC`, so +`cve.org` links 404 until they do. That is expected, and it is not a reason to delay the +bulletin or the mails. + +## Post-release + +- Add the site announcement entry for any advisory (`announce-YYYY.md`), same form as the GA one. +- Check NVD once the CVE is public — affected ranges have been wrong before, and the fix is an + email to `nvd@nist.gov` citing the CVE record. +- Answer any coordinator (JPCERT/CC and similar) in their existing thread once the bulletin is + live; they hold their advisory until you confirm. +- Update the Version Notes page if the vote forced a re-cut. diff --git a/.claude/skills/releasing-struts/scripts/promote-dist.sh b/.claude/skills/releasing-struts/scripts/promote-dist.sh new file mode 100755 index 0000000000..89e4087792 --- /dev/null +++ b/.claude/skills/releasing-struts/scripts/promote-dist.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# +# Phase 5 - promote a release that passed its vote, moving the assemblies from +# dist/dev to dist/release. This is the point at which the artifacts start +# replicating to the mirrors. +# +# Usage: VERSION=7.3.0 ./promote-dist.sh +# +# Run it only after the vote has passed. Wait 24 hours after this before +# announcing anything - the announcement links a download page that the mirrors +# have to have caught up with first. + +set -eu + +if [ -z "${VERSION:-}" ]; then + echo "VERSION is not set. Usage: VERSION=7.3.0 $0" >&2 + exit 1 +fi + +# Not cosmetic. svn resolves a "." path element instead of rejecting it, so +# VERSION="." would move the whole of dist/dev/struts into dist/release in one +# irreversible server-side commit. ".." is rejected by svn; "." is not. +case "$VERSION" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) + echo "VERSION must look like 7.3.0 (got '$VERSION')" >&2 + exit 1 + ;; +esac + +svn mv "https://dist.apache.org/repos/dist/dev/struts/$VERSION/" \ + "https://dist.apache.org/repos/dist/release/struts/" \ + -m "Release Struts $VERSION" + +echo "Done - verify https://dist.apache.org/repos/dist/release/struts/$VERSION/" +echo "Now release the staging repository in Nexus, then wait 24 hours before announcing." diff --git a/.claude/skills/releasing-struts/scripts/stage-assemblies.sh b/.claude/skills/releasing-struts/scripts/stage-assemblies.sh new file mode 100755 index 0000000000..967c746820 --- /dev/null +++ b/.claude/skills/releasing-struts/scripts/stage-assemblies.sh @@ -0,0 +1,103 @@ +#!/bin/sh +# +# Phase 3 - move the release assemblies from the closed Nexus staging repository +# into https://dist.apache.org/repos/dist/dev/struts/$VERSION so they can be tested +# and voted on. +# +# Usage: cd "$(mktemp -d)" && VERSION=7.3.0 /path/to/stage-assemblies.sh +# +# Run it from a scratch directory, not from a repository checkout: it creates +# ./$VERSION and a temporary svn working copy in the current directory. +# +# Requires: the staging repository must already be CLOSED in Nexus (an open repo +# serves nothing under the staging *group* URL this fetches from), and your ASF +# svn credentials for dist.apache.org. + +set -eu + +if [ -z "${VERSION:-}" ]; then + echo "VERSION is not set. Usage: VERSION=7.3.0 $0" >&2 + exit 1 +fi + +# Not cosmetic: a VERSION of "." resolves server-side to the parent directory, +# which would publish the whole staging tree. +case "$VERSION" in + [0-9]*.[0-9]*.[0-9]*) ;; + *) + echo "VERSION must look like 7.3.0 (got '$VERSION')" >&2 + exit 1 + ;; +esac + +STAGING_URL="https://repository.apache.org/content/groups/staging/org/apache/struts/struts2-assembly/$VERSION" +DIST_DEV_URL="https://dist.apache.org/repos/dist/dev/struts/" + +if [ -e "$VERSION" ]; then + echo "Directory $VERSION already exists here - remove it or run elsewhere." >&2 + exit 1 +fi +if [ -e struts-dev ]; then + echo "Directory struts-dev already exists here - remove it or run elsewhere." >&2 + exit 1 +fi + +# Unconditional, as in the original: a half-built working copy left behind can be +# picked up and committed by a later run for a different version. +cleanup() { + rm -rf "$START_DIR/struts-dev" +} +START_DIR=$(pwd) +trap cleanup EXIT + +echo "Creating working dir $VERSION" +mkdir "$VERSION" +cd "$VERSION" + +echo "Getting distro $VERSION from the staging repository" +if ! wget -erobots=off -nv -l 1 --accept=zip,md5,sha1,asc -r --no-check-certificate -nd -nH "$STAGING_URL"; then + echo "Download failed. Is the staging repository closed in Nexus?" >&2 + exit 1 +fi + +if ! ls ./*.zip >/dev/null 2>&1; then + echo "No assemblies downloaded. Is the staging repository closed in Nexus?" >&2 + exit 1 +fi + +# struts2-assembly-7.3.0-all.zip -> struts-7.3.0-all.zip, and the same for the +# .asc/.md5/.sha1 beside each zip. The .pom files keep their name and are removed +# below - narrowing this glob without widening that one republishes them. +echo "Renaming files" +for f in *2-assembly*.zip*; do + [ -e "$f" ] || continue + mv "$f" "$(echo "$f" | sed s/2-assembly//g)" +done + +echo "Removing unneeded files" +rm -f struts2-assembly-*.pom* +rm -f ./*.md5 ./*.sha1 + +# The ASF publishes sha256/sha512; Nexus only carries the legacy hashes. +echo "Generating SHA signatures" +for f in *.zip; do + [ -f "$f" ] || continue + shasum -a 256 "$f" > "$f.sha256" + shasum -a 512 "$f" > "$f.sha512" +done + +echo "Staging the following files:" +ls -1 + +cd "$START_DIR" + +echo "Publishing artifacts for test" +svn --no-auth-cache co --depth empty "$DIST_DEV_URL" struts-dev +mv "$VERSION" struts-dev/ +cd struts-dev +svn add --force ./ +svn --no-auth-cache commit -m "Updates test release $VERSION" + +cd "$START_DIR" + +echo "Done - verify https://dist.apache.org/repos/dist/dev/struts/$VERSION/" diff --git a/.gitignore b/.gitignore index 85c62f2c4b..621f263b2a 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,8 @@ Servers/ # Scripts *.sh +# ... except the ones that are part of a skill and have to travel with it +!.claude/skills/**/scripts/*.sh # jenv .java-version diff --git a/pom.xml b/pom.xml index 03a86c1b15..05de4760a8 100644 --- a/pom.xml +++ b/pom.xml @@ -450,6 +450,19 @@ maven-site-plugin 3.22.0 + + org.apache.maven.plugins + maven-deploy-plugin + + + 10 + + @@ -458,6 +471,20 @@ org.apache.maven.plugins maven-release-plugin 3.3.1 + + + true + org.apache.maven.plugins