From b318708939fd57ac55726828e1397c89952469e2 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Wed, 13 May 2026 11:24:31 -0400 Subject: [PATCH 1/3] Add CI, publish, and upstream-tracking workflows + README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stand up the operational shape that PR eXist-db/exist#6364 needs in order to resolve this artifact from CI: - README.md: what/versioning/transform/release/local-creds/license - ci.yml: smoke-test on PR/push — mvn verify + a JUnit 5 + Mockito test class that constructs WebdavRequestImpl against a jakarta.servlet stub, asserts the constructor's parameter types are jakarta.servlet.*, and scans WebdavRequestImpl.class bytes to confirm no javax/servlet references survived the OpenRewrite transform - publish.yml: v* tag + workflow_dispatch trigger; mvn deploy to maven.pkg.github.com/eXist-db/jackrabbit-webdav-jakarta using GITHUB_TOKEN with packages:write - check-upstream.yml: weekly cron + manual dispatch; queries apache/jackrabbit tags, re-vendors sources.jar from Maven Central, re-extracts into src/main/java, runs mvn rewrite:run to re-apply the Jakarta EE 10 transform, bumps pom.xml version, and opens a PR labelled upstream-bump - pom.xml: add distributionManagement pointing at GitHub Packages, plus JUnit Jupiter + Mockito + surefire-plugin so the smoke test runs Smoke test verified locally on JDK 21: 3 tests pass. Note on the transform mechanism: src/main/java/ already contains the jakarta-transformed sources (committed once, OpenRewrite was run manually). The rewrite-maven-plugin in pom.xml has no executions binding, so day-to-day builds are plain `mvn compile`. The bump workflow is the trigger that re-runs `mvn rewrite:run`. --- .github/workflows/check-upstream.yml | 160 ++++++++++++++++++ .github/workflows/ci.yml | 28 +++ .github/workflows/publish.yml | 41 +++++ README.md | 110 ++++++++++++ pom.xml | 31 ++++ .../webdav/JakartaTransformSmokeTest.java | 65 +++++++ 6 files changed, 435 insertions(+) create mode 100644 .github/workflows/check-upstream.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 README.md create mode 100644 src/test/java/org/exist/jackrabbit/webdav/JakartaTransformSmokeTest.java diff --git a/.github/workflows/check-upstream.yml b/.github/workflows/check-upstream.yml new file mode 100644 index 0000000..b48b361 --- /dev/null +++ b/.github/workflows/check-upstream.yml @@ -0,0 +1,160 @@ +name: Check upstream Jackrabbit release + +on: + schedule: + # Mondays 09:00 UTC + - cron: '0 9 * * 1' + workflow_dispatch: + inputs: + target_version: + description: 'Optional: pin a specific upstream version (e.g. 2.22.4). Leave blank to auto-detect latest.' + required: false + default: '' + +permissions: + contents: write + pull-requests: write + +jobs: + check: + name: Detect and bump + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: maven + + - name: Determine current vendored version + id: current + run: | + current="$(grep -m1 '' pom.xml | sed -E 's@.*([0-9]+\.[0-9]+\.[0-9]+)-jakarta-ee10.*@\1@')" + if [[ -z "$current" ]]; then + echo "Failed to parse current upstream version from pom.xml" >&2 + exit 1 + fi + echo "version=$current" >> "$GITHUB_OUTPUT" + echo "Current vendored upstream version: $current" + + - name: Determine target upstream version + id: target + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + INPUT_TARGET: ${{ github.event.inputs.target_version }} + CURRENT: ${{ steps.current.outputs.version }} + run: | + set -euo pipefail + if [[ -n "$INPUT_TARGET" ]]; then + target="$INPUT_TARGET" + echo "Using pinned target from workflow input: $target" + else + target="$(gh api --paginate repos/apache/jackrabbit/tags --jq '.[].name' \ + | grep -E '^jackrabbit-2\.[0-9]+\.[0-9]+$' \ + | sed 's/^jackrabbit-//' \ + | sort -V \ + | tail -n1)" + echo "Latest upstream tag: $target" + fi + if [[ -z "$target" ]]; then + echo "Could not determine target upstream version" >&2 + exit 1 + fi + higher="$(printf '%s\n%s\n' "$CURRENT" "$target" | sort -V | tail -n1)" + if [[ "$target" == "$CURRENT" || "$higher" != "$target" ]]; then + echo "No newer upstream version available (current=$CURRENT, target=$target)" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + echo "version=$target" >> "$GITHUB_OUTPUT" + fi + + - name: Check if bump branch already exists + if: steps.target.outputs.skip != 'true' + id: branch + env: + TARGET: ${{ steps.target.outputs.version }} + run: | + branch="upstream-bump/${TARGET}" + echo "name=$branch" >> "$GITHUB_OUTPUT" + if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then + echo "Branch $branch already exists on origin; skipping" + echo "skip=true" >> "$GITHUB_OUTPUT" + else + echo "skip=false" >> "$GITHUB_OUTPUT" + fi + + - name: Download new sources.jar from Maven Central + if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' + env: + TARGET: ${{ steps.target.outputs.version }} + run: | + set -euo pipefail + url="https://repo1.maven.org/maven2/org/apache/jackrabbit/jackrabbit-webdav/${TARGET}/jackrabbit-webdav-${TARGET}-sources.jar" + echo "Downloading $url" + curl -fsSL -o sources.jar.new "$url" + mv sources.jar.new sources.jar + + - name: Re-extract sources and re-apply Jakarta EE 10 transform + if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' + run: | + set -euo pipefail + workspace="$PWD" + rm -rf src/main/java src/main/resources + mkdir -p src/main/java src/main/resources + tmp="$(mktemp -d)" + unzip -q sources.jar -d "$tmp" + rm -rf "$tmp/META-INF" + # .java → src/main/java, everything else → src/main/resources + (cd "$tmp" && find . -type f -name '*.java' | while read -r f; do + dest="$workspace/src/main/java/${f#./}" + mkdir -p "$(dirname "$dest")" + cp "$f" "$dest" + done) + (cd "$tmp" && find . -type f ! -name '*.java' | while read -r f; do + dest="$workspace/src/main/resources/${f#./}" + mkdir -p "$(dirname "$dest")" + cp "$f" "$dest" + done) + ls src/main/java + mvn -B -ntp rewrite:run + + - name: Bump pom.xml version + if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' + env: + CURRENT: ${{ steps.current.outputs.version }} + TARGET: ${{ steps.target.outputs.version }} + run: | + sed -i.bak "0,/${CURRENT}-jakarta-ee10<\/version>/s//${TARGET}-jakarta-ee10<\/version>/" pom.xml + rm pom.xml.bak + grep -n "${TARGET}-jakarta-ee10" pom.xml + + - name: Open pull request + if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' + uses: peter-evans/create-pull-request@v7 + with: + branch: ${{ steps.branch.outputs.name }} + base: main + commit-message: | + Bump upstream Apache Jackrabbit WebDAV to ${{ steps.target.outputs.version }} + title: | + Bump upstream Apache Jackrabbit WebDAV to ${{ steps.target.outputs.version }} + body: | + Detected new upstream release `jackrabbit-${{ steps.target.outputs.version }}` + (current vendored: `${{ steps.current.outputs.version }}`). + + This PR: + - re-vendors `sources.jar` from Maven Central + - re-extracts source into `src/main/java/` and resources into `src/main/resources/` + - runs `mvn rewrite:run` to re-apply the Jakarta EE 10 transform + - bumps `pom.xml` `` to `${{ steps.target.outputs.version }}-jakarta-ee10` + + CI gates merge. If the smoke test passes, the bump is safe to merge; + tag `v${{ steps.target.outputs.version }}-jakarta-ee10` after merge to + publish to GitHub Packages. + labels: upstream-bump diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..dc4e9d5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,28 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + build: + name: Build + smoke test + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: maven + + - name: Build and run smoke test + run: mvn -B -ntp verify diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..f97061e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,41 @@ +name: Publish to GitHub Packages + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + ref: + description: 'Ref (branch, tag, or SHA) to publish from' + required: false + default: 'main' + +jobs: + publish: + name: Maven deploy + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.event.inputs.ref || github.ref }} + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: 21 + cache: maven + server-id: github + server-username: GITHUB_ACTOR + server-password: GITHUB_TOKEN + + - name: Deploy + run: mvn -B -ntp -DskipTests deploy + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..8813afe --- /dev/null +++ b/README.md @@ -0,0 +1,110 @@ +# jackrabbit-webdav-jakarta + +A thin fork of [Apache Jackrabbit WebDAV](https://jackrabbit.apache.org/) with the +`javax.servlet` → `jakarta.servlet` namespace transform applied so [eXist-db 7.0](https://github.com/eXist-db/exist) +(Jetty 12, Jakarta Servlet 6.0) can link against it. + +This artifact exists only until Apache publishes a Jakarta-Servlet-native release of +`jackrabbit-webdav` upstream, at which point this repo will be archived and the +eXist-db dependency redirected to the upstream coordinate. + +## Coordinates + +```xml + + org.exist-db.thirdparty.org.apache.jackrabbit + jackrabbit-webdav + 2.22.3-jakarta-ee10 + +``` + +Published to GitHub Packages: + +## Versioning + +`-jakarta-ee10` + +- `2.22.3-jakarta-ee10` → Apache Jackrabbit `2.22.3` + this repo's Jakarta EE 10 transform +- Snapshots use `-jakarta-ee10-SNAPSHOT` + +The version string makes the upstream provenance auditable at a glance — both the +upstream tag and the Jakarta profile are encoded in the version, with no hidden +metadata in classifiers or qualifiers. + +## How the transform works + +Upstream Apache Jackrabbit sources are vendored at `src/main/java/`, pre-transformed +to `jakarta.servlet.*`. The canonical pre-transform source archive is the +`sources.jar` at the repo root (extracted from Maven Central). The transform is +applied via the [OpenRewrite Maven plugin](https://docs.openrewrite.org/) using +the `org.openrewrite.java.migrate.jakarta.JakartaEE10` recipe — but the rewrite +runs **at upstream-bump time**, not at every build: + +```sh +# After extracting a new sources.jar over src/main/java/: +mvn rewrite:run +``` + +The transformed result is committed to the repo, so day-to-day builds are plain +`mvn compile` against already-jakarta source. There are no manual patches, no +`sed` scripts, and no hand-edited source files — the only diff against upstream +is whatever OpenRewrite produces. + +## How upstream tracking works + +A scheduled GitHub Actions workflow ([`check-upstream.yml`](.github/workflows/check-upstream.yml)) +runs weekly, queries Apache Jackrabbit's release tags, and opens a `upstream-bump` +labelled PR when a newer `jackrabbit-2.x.y` tag is published. The PR: + +1. Downloads the new upstream `*-sources.jar` from Maven Central, replacing the + one at the repo root +2. Re-extracts the archive over `src/main/java/` +3. Runs `mvn rewrite:run` so OpenRewrite re-applies the Jakarta EE 10 transform +4. Bumps the `` in `pom.xml` to `-jakarta-ee10` + +The smoke test ([`ci.yml`](.github/workflows/ci.yml)) gates merge: if the new +upstream version still cleanly transforms and links against Jakarta Servlet 6.0, +the bump is safe to merge. + +## How to cut a release + +1. Land all bump / fix PRs on `main` +2. Tag the release commit: + ```sh + git tag v2.22.3-jakarta-ee10 + git push origin v2.22.3-jakarta-ee10 + ``` +3. The publish workflow ([`publish.yml`](.github/workflows/publish.yml)) fires on + `v*` tag push and deploys to GitHub Packages +4. Confirm the artifact resolves at + `https://maven.pkg.github.com/eXist-db/jackrabbit-webdav-jakarta` + +`workflow_dispatch` is also wired up if you need to publish a SNAPSHOT manually. + +## Consuming from a local Maven build + +GitHub Packages requires authentication for read access. Add to `~/.m2/settings.xml`: + +```xml + + + github-jackrabbit-webdav-jakarta + YOUR_GITHUB_USERNAME + YOUR_PAT_WITH_read:packages + + +``` + +The same PAT works across every `github-*` server id the eXist-db org publishes +(`github`, `github-xqts-runner`, and this one) — one PAT, multiple `` +blocks. The repository declaration lives in `exist-parent/pom.xml` in +[eXist-db/exist](https://github.com/eXist-db/exist). + +## License / attribution + +Licensed under the [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0), +inherited from upstream. This repository is a derivative work of +[apache/jackrabbit](https://github.com/apache/jackrabbit); all credit for the +WebDAV implementation belongs to the Apache Jackrabbit project. The only +modifications applied here are the OpenRewrite namespace transforms required for +Jakarta EE 10 compatibility. diff --git a/pom.xml b/pom.xml index 9aa2641..1d3227c 100644 --- a/pom.xml +++ b/pom.xml @@ -61,8 +61,34 @@ jcl-over-slf4j 2.0.17 + + + org.junit.jupiter + junit-jupiter + 5.11.4 + test + + + org.mockito + mockito-core + 5.14.2 + test + + + + github + GitHub Packages - eXist-db/jackrabbit-webdav-jakarta + https://maven.pkg.github.com/eXist-db/jackrabbit-webdav-jakarta + + + github + GitHub Packages - eXist-db/jackrabbit-webdav-jakarta + https://maven.pkg.github.com/eXist-db/jackrabbit-webdav-jakarta + + + @@ -73,6 +99,11 @@ 17 + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.2 + org.openrewrite.maven rewrite-maven-plugin diff --git a/src/test/java/org/exist/jackrabbit/webdav/JakartaTransformSmokeTest.java b/src/test/java/org/exist/jackrabbit/webdav/JakartaTransformSmokeTest.java new file mode 100644 index 0000000..e0526b6 --- /dev/null +++ b/src/test/java/org/exist/jackrabbit/webdav/JakartaTransformSmokeTest.java @@ -0,0 +1,65 @@ +package org.exist.jackrabbit.webdav; + +import jakarta.servlet.http.HttpServletRequest; +import org.apache.jackrabbit.webdav.WebdavRequestImpl; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Litmus test that the OpenRewrite Jakarta EE 10 transform produced a linkable + * artifact: a class that consumes {@code jakarta.servlet} types and contains no + * surviving {@code javax/servlet} references in its bytecode. + */ +class JakartaTransformSmokeTest { + + @Test + void webdavRequestImplConsumesJakartaServletApi() { + Constructor[] ctors = WebdavRequestImpl.class.getConstructors(); + boolean foundJakartaCtor = false; + for (Constructor ctor : ctors) { + for (Class param : ctor.getParameterTypes()) { + if (param.getName().startsWith("jakarta.servlet.")) { + foundJakartaCtor = true; + } + assertFalse(param.getName().startsWith("javax.servlet."), + "Constructor still references javax.servlet: " + ctor); + } + } + assertTrue(foundJakartaCtor, + "Expected at least one WebdavRequestImpl constructor to accept a jakarta.servlet.* type"); + } + + @Test + void webdavRequestImplConstructsAgainstJakartaServletStub() { + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getHeader("Host")).thenReturn("localhost"); + when(req.getScheme()).thenReturn("http"); + when(req.getContextPath()).thenReturn(""); + + WebdavRequestImpl webdavRequest = new WebdavRequestImpl(req, null); + assertNotNull(webdavRequest); + } + + @Test + void compiledBytecodeContainsNoJavaxServletReferences() throws IOException { + String resource = "org/apache/jackrabbit/webdav/WebdavRequestImpl.class"; + try (InputStream in = getClass().getClassLoader().getResourceAsStream(resource)) { + assertNotNull(in, "Could not locate " + resource + " on the test classpath"); + byte[] bytes = in.readAllBytes(); + String asLatin1 = new String(bytes, java.nio.charset.StandardCharsets.ISO_8859_1); + assertFalse(asLatin1.contains("javax/servlet"), + "Compiled bytecode still references javax/servlet — OpenRewrite transform incomplete"); + assertTrue(asLatin1.contains("jakarta/servlet"), + "Compiled bytecode missing expected jakarta/servlet references"); + } + } +} From 1dcf2f745793fbcfab02f37b06b9e73ce4f33f75 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Wed, 13 May 2026 12:39:06 -0400 Subject: [PATCH 2/3] Bump action versions and add Dependabot for github-actions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses @duncdrum's review comment on PR #1: action versions in the initial workflows were behind, and Dependabot should keep them current going forward. - actions/checkout@v4 → v6 - actions/setup-java@v4 → v5 - peter-evans/create-pull-request@v7 → v8 - .github/dependabot.yml: weekly github-actions ecosystem updates The Maven side of @duncdrum's "use Dependabot instead of polling" point is still being designed and will land in a follow-up commit. --- .github/dependabot.yml | 14 ++++++++++++++ .github/workflows/check-upstream.yml | 6 +++--- .github/workflows/ci.yml | 4 ++-- .github/workflows/publish.yml | 4 ++-- 4 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a73adf0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Etc/UTC + labels: + - dependencies + - github-actions + commit-message: + prefix: "ci" diff --git a/.github/workflows/check-upstream.yml b/.github/workflows/check-upstream.yml index b48b361..8d9c4df 100644 --- a/.github/workflows/check-upstream.yml +++ b/.github/workflows/check-upstream.yml @@ -22,10 +22,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 @@ -136,7 +136,7 @@ jobs: - name: Open pull request if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' - uses: peter-evans/create-pull-request@v7 + uses: peter-evans/create-pull-request@v8 with: branch: ${{ steps.branch.outputs.name }} base: main diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc4e9d5..ef7cdb6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index f97061e..5e35e06 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -21,12 +21,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ github.event.inputs.ref || github.ref }} - name: Set up JDK 21 - uses: actions/setup-java@v4 + uses: actions/setup-java@v5 with: distribution: temurin java-version: 21 From 315aada9168db810c5d7f26cfbbb3f11c82dd2c5 Mon Sep 17 00:00:00 2001 From: Joe Wicentowski Date: Wed, 13 May 2026 12:56:07 -0400 Subject: [PATCH 3/3] Replace check-upstream.yml with Dependabot-driven bump flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses @duncdrum's "this looks a bit like rewriting Dependabot" review comment on PR #1. Dependabot now does the polling; the workflow does only the work Dependabot fundamentally can't (re-vendoring sources.jar and re-applying the OpenRewrite Jakarta EE 10 transform). - pom.xml: add upstream.jackrabbit.version property + tracker-only entry for org.apache.jackrabbit:jackrabbit-webdav. No corresponding entry — Dependabot sees it in the manifest but Maven doesn't put it on the compile classpath. - .github/dependabot.yml: add maven ecosystem, weekly, labelled upstream-bump. - .github/workflows/bump-on-dependabot.yml: triggered on pull_request_target from Dependabot-opened, upstream-bump-labelled, same-repo PRs. Guards (per Joe): bot author + label + head.repo == github.repository. Runs the checked-in bump script (never reads logic from the PR's pom), then commits the re-vendored sources back to the Dependabot branch so ci.yml's smoke test re-runs and gates merge. - .github/scripts/bump-upstream.sh: idempotent — reads upstream.jackrabbit.version, downloads matching sources.jar from Maven Central, re-extracts into src/main/{java,resources}, runs `mvn rewrite:run`, and sets project version via `mvn versions:set`. Verified locally: with property and project.version already in sync, exits 0 without modifying any files. - check-upstream.yml: removed (cron-driven polling, superseded). - README.md: updated "How upstream tracking works" section to reflect the new Dependabot-driven flow. --- .github/dependabot.yml | 20 +++ .github/scripts/bump-upstream.sh | 64 +++++++++ .github/workflows/bump-on-dependabot.yml | 73 +++++++++++ .github/workflows/check-upstream.yml | 160 ----------------------- README.md | 36 +++-- pom.xml | 23 ++++ 6 files changed, 203 insertions(+), 173 deletions(-) create mode 100755 .github/scripts/bump-upstream.sh create mode 100644 .github/workflows/bump-on-dependabot.yml delete mode 100644 .github/workflows/check-upstream.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a73adf0..28c3301 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,3 +12,23 @@ updates: - github-actions commit-message: prefix: "ci" + + # Watches the upstream Apache Jackrabbit coordinate declared (tracker-only) + # in pom.xml's . When Apache cuts a new release, + # Dependabot bumps the upstream.jackrabbit.version property; the + # bump-on-dependabot.yml workflow then re-vendors sources.jar, runs + # OpenRewrite, and updates the project version, committing back to the + # Dependabot branch. + - package-ecosystem: maven + directory: / + schedule: + interval: weekly + day: monday + time: "09:00" + timezone: Etc/UTC + labels: + - dependencies + - upstream-bump + commit-message: + prefix: "upstream" + open-pull-requests-limit: 1 diff --git a/.github/scripts/bump-upstream.sh b/.github/scripts/bump-upstream.sh new file mode 100755 index 0000000..4cf4caa --- /dev/null +++ b/.github/scripts/bump-upstream.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash +# +# Re-vendor upstream Apache Jackrabbit WebDAV sources and re-apply the +# Jakarta EE 10 transform. +# +# Invoked from .github/workflows/bump-on-dependabot.yml after Dependabot +# opens a PR bumping the upstream.jackrabbit.version property in pom.xml. +# Reads the new upstream version straight from the pom property, downloads +# the matching sources.jar from Maven Central, replaces src/main/{java, +# resources}, runs OpenRewrite, and updates the project version. Caller is +# responsible for committing and pushing. +# +# Idempotent: if the project version already matches the upstream property +# (i.e. nothing left to bump), exits 0 without modifying any files. + +set -euo pipefail + +UPSTREAM_VERSION="$(mvn -q -B -ntp help:evaluate -Dexpression=upstream.jackrabbit.version -DforceStdout)" +CURRENT_PROJECT_VERSION="$(mvn -q -B -ntp help:evaluate -Dexpression=project.version -DforceStdout)" +EXPECTED_PROJECT_VERSION="${UPSTREAM_VERSION}-jakarta-ee10" + +echo "Upstream version from pom.xml property: ${UPSTREAM_VERSION}" +echo "Current project.version: ${CURRENT_PROJECT_VERSION}" +echo "Target project.version: ${EXPECTED_PROJECT_VERSION}" + +if [[ "${CURRENT_PROJECT_VERSION}" == "${EXPECTED_PROJECT_VERSION}" ]]; then + echo "Project version already matches upstream property — nothing to do." + exit 0 +fi + +URL="https://repo1.maven.org/maven2/org/apache/jackrabbit/jackrabbit-webdav/${UPSTREAM_VERSION}/jackrabbit-webdav-${UPSTREAM_VERSION}-sources.jar" +echo "Downloading ${URL}" +curl -fsSL -o sources.jar.new "${URL}" +mv sources.jar.new sources.jar + +workspace="$PWD" +rm -rf src/main/java src/main/resources +mkdir -p src/main/java src/main/resources + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +unzip -q sources.jar -d "$tmp" +rm -rf "$tmp/META-INF" + +# .java sources → src/main/java; everything else (e.g. .properties) → +# src/main/resources. Upstream sources.jar contains both intermixed. +(cd "$tmp" && find . -type f -name '*.java' | while read -r f; do + dest="$workspace/src/main/java/${f#./}" + mkdir -p "$(dirname "$dest")" + cp "$f" "$dest" + done) +(cd "$tmp" && find . -type f ! -name '*.java' | while read -r f; do + dest="$workspace/src/main/resources/${f#./}" + mkdir -p "$(dirname "$dest")" + cp "$f" "$dest" + done) + +echo "Re-applying Jakarta EE 10 transform via OpenRewrite" +mvn -B -ntp rewrite:run + +echo "Setting project version to ${EXPECTED_PROJECT_VERSION}" +mvn -B -ntp versions:set -DnewVersion="${EXPECTED_PROJECT_VERSION}" -DgenerateBackupPoms=false + +echo "Bump complete: jackrabbit-webdav ${UPSTREAM_VERSION} → ${EXPECTED_PROJECT_VERSION}" diff --git a/.github/workflows/bump-on-dependabot.yml b/.github/workflows/bump-on-dependabot.yml new file mode 100644 index 0000000..78c0e5d --- /dev/null +++ b/.github/workflows/bump-on-dependabot.yml @@ -0,0 +1,73 @@ +name: Apply upstream bump on Dependabot PR + +# Replaces the prior cron-driven check-upstream.yml: Dependabot handles the +# polling (see .github/dependabot.yml's maven ecosystem entry, which watches +# the tracker-only coordinate in pom.xml). When +# Dependabot opens a PR bumping the upstream.jackrabbit.version property, +# this workflow does the irreducible follow-up work that Dependabot itself +# can't do: re-vendor sources.jar, re-apply the OpenRewrite Jakarta EE 10 +# transform, and update the project version — all committed back onto the +# Dependabot branch so ci.yml's smoke test gates merge. + +on: + pull_request_target: + types: [opened, synchronize] + +permissions: + contents: write + pull-requests: write + +jobs: + apply-bump: + # Three guards (per Joe's review): + # 1. Author is Dependabot — bot-opened PRs only + # 2. Label is upstream-bump — distinguishes the Maven ecosystem update + # from the github-actions one (which has no follow-up work) + # 3. PR head is in this repo — Dependabot branches are always in-repo; + # a head from a fork means a malicious actor is impersonating the + # label or actor check, so refuse to run + if: >- + github.actor == 'dependabot[bot]' + && contains(github.event.pull_request.labels.*.name, 'upstream-bump') + && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + + steps: + - name: Checkout PR head + uses: actions/checkout@v6 + with: + ref: ${{ github.event.pull_request.head.ref }} + # Use the workflow token so the follow-up commit can be pushed back + # to the Dependabot branch. + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up JDK 21 + uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: 21 + cache: maven + + - name: Configure git identity + run: | + git config user.name 'dependabot[bot]' + git config user.email '49699333+dependabot[bot]@users.noreply.github.com' + + # The bump script lives at .github/scripts/bump-upstream.sh (checked in, + # never read from the PR's pom.xml). Dependabot's manifest-only PR diffs + # don't touch this path, so in practice we're always executing the + # main-branch script — but the three guards above (bot author, label, + # same-repo head) are what actually keep arbitrary code from running + # under the pull_request_target token. + - name: Run upstream bump script + run: ./.github/scripts/bump-upstream.sh + + - name: Commit and push if changed + run: | + if [[ -z "$(git status --porcelain)" ]]; then + echo "No changes produced by bump script; nothing to commit." + exit 0 + fi + git add -A + git commit -m "Re-vendor sources and re-apply Jakarta EE 10 transform" + git push diff --git a/.github/workflows/check-upstream.yml b/.github/workflows/check-upstream.yml deleted file mode 100644 index 8d9c4df..0000000 --- a/.github/workflows/check-upstream.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Check upstream Jackrabbit release - -on: - schedule: - # Mondays 09:00 UTC - - cron: '0 9 * * 1' - workflow_dispatch: - inputs: - target_version: - description: 'Optional: pin a specific upstream version (e.g. 2.22.4). Leave blank to auto-detect latest.' - required: false - default: '' - -permissions: - contents: write - pull-requests: write - -jobs: - check: - name: Detect and bump - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Set up JDK 21 - uses: actions/setup-java@v5 - with: - distribution: temurin - java-version: 21 - cache: maven - - - name: Determine current vendored version - id: current - run: | - current="$(grep -m1 '' pom.xml | sed -E 's@.*([0-9]+\.[0-9]+\.[0-9]+)-jakarta-ee10.*@\1@')" - if [[ -z "$current" ]]; then - echo "Failed to parse current upstream version from pom.xml" >&2 - exit 1 - fi - echo "version=$current" >> "$GITHUB_OUTPUT" - echo "Current vendored upstream version: $current" - - - name: Determine target upstream version - id: target - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - INPUT_TARGET: ${{ github.event.inputs.target_version }} - CURRENT: ${{ steps.current.outputs.version }} - run: | - set -euo pipefail - if [[ -n "$INPUT_TARGET" ]]; then - target="$INPUT_TARGET" - echo "Using pinned target from workflow input: $target" - else - target="$(gh api --paginate repos/apache/jackrabbit/tags --jq '.[].name' \ - | grep -E '^jackrabbit-2\.[0-9]+\.[0-9]+$' \ - | sed 's/^jackrabbit-//' \ - | sort -V \ - | tail -n1)" - echo "Latest upstream tag: $target" - fi - if [[ -z "$target" ]]; then - echo "Could not determine target upstream version" >&2 - exit 1 - fi - higher="$(printf '%s\n%s\n' "$CURRENT" "$target" | sort -V | tail -n1)" - if [[ "$target" == "$CURRENT" || "$higher" != "$target" ]]; then - echo "No newer upstream version available (current=$CURRENT, target=$target)" - echo "skip=true" >> "$GITHUB_OUTPUT" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - echo "version=$target" >> "$GITHUB_OUTPUT" - fi - - - name: Check if bump branch already exists - if: steps.target.outputs.skip != 'true' - id: branch - env: - TARGET: ${{ steps.target.outputs.version }} - run: | - branch="upstream-bump/${TARGET}" - echo "name=$branch" >> "$GITHUB_OUTPUT" - if git ls-remote --exit-code --heads origin "$branch" >/dev/null 2>&1; then - echo "Branch $branch already exists on origin; skipping" - echo "skip=true" >> "$GITHUB_OUTPUT" - else - echo "skip=false" >> "$GITHUB_OUTPUT" - fi - - - name: Download new sources.jar from Maven Central - if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' - env: - TARGET: ${{ steps.target.outputs.version }} - run: | - set -euo pipefail - url="https://repo1.maven.org/maven2/org/apache/jackrabbit/jackrabbit-webdav/${TARGET}/jackrabbit-webdav-${TARGET}-sources.jar" - echo "Downloading $url" - curl -fsSL -o sources.jar.new "$url" - mv sources.jar.new sources.jar - - - name: Re-extract sources and re-apply Jakarta EE 10 transform - if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' - run: | - set -euo pipefail - workspace="$PWD" - rm -rf src/main/java src/main/resources - mkdir -p src/main/java src/main/resources - tmp="$(mktemp -d)" - unzip -q sources.jar -d "$tmp" - rm -rf "$tmp/META-INF" - # .java → src/main/java, everything else → src/main/resources - (cd "$tmp" && find . -type f -name '*.java' | while read -r f; do - dest="$workspace/src/main/java/${f#./}" - mkdir -p "$(dirname "$dest")" - cp "$f" "$dest" - done) - (cd "$tmp" && find . -type f ! -name '*.java' | while read -r f; do - dest="$workspace/src/main/resources/${f#./}" - mkdir -p "$(dirname "$dest")" - cp "$f" "$dest" - done) - ls src/main/java - mvn -B -ntp rewrite:run - - - name: Bump pom.xml version - if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' - env: - CURRENT: ${{ steps.current.outputs.version }} - TARGET: ${{ steps.target.outputs.version }} - run: | - sed -i.bak "0,/${CURRENT}-jakarta-ee10<\/version>/s//${TARGET}-jakarta-ee10<\/version>/" pom.xml - rm pom.xml.bak - grep -n "${TARGET}-jakarta-ee10" pom.xml - - - name: Open pull request - if: steps.target.outputs.skip != 'true' && steps.branch.outputs.skip != 'true' - uses: peter-evans/create-pull-request@v8 - with: - branch: ${{ steps.branch.outputs.name }} - base: main - commit-message: | - Bump upstream Apache Jackrabbit WebDAV to ${{ steps.target.outputs.version }} - title: | - Bump upstream Apache Jackrabbit WebDAV to ${{ steps.target.outputs.version }} - body: | - Detected new upstream release `jackrabbit-${{ steps.target.outputs.version }}` - (current vendored: `${{ steps.current.outputs.version }}`). - - This PR: - - re-vendors `sources.jar` from Maven Central - - re-extracts source into `src/main/java/` and resources into `src/main/resources/` - - runs `mvn rewrite:run` to re-apply the Jakarta EE 10 transform - - bumps `pom.xml` `` to `${{ steps.target.outputs.version }}-jakarta-ee10` - - CI gates merge. If the smoke test passes, the bump is safe to merge; - tag `v${{ steps.target.outputs.version }}-jakarta-ee10` after merge to - publish to GitHub Packages. - labels: upstream-bump diff --git a/README.md b/README.md index 8813afe..97d01a8 100644 --- a/README.md +++ b/README.md @@ -52,19 +52,29 @@ is whatever OpenRewrite produces. ## How upstream tracking works -A scheduled GitHub Actions workflow ([`check-upstream.yml`](.github/workflows/check-upstream.yml)) -runs weekly, queries Apache Jackrabbit's release tags, and opens a `upstream-bump` -labelled PR when a newer `jackrabbit-2.x.y` tag is published. The PR: - -1. Downloads the new upstream `*-sources.jar` from Maven Central, replacing the - one at the repo root -2. Re-extracts the archive over `src/main/java/` -3. Runs `mvn rewrite:run` so OpenRewrite re-applies the Jakarta EE 10 transform -4. Bumps the `` in `pom.xml` to `-jakarta-ee10` - -The smoke test ([`ci.yml`](.github/workflows/ci.yml)) gates merge: if the new -upstream version still cleanly transforms and links against Jakarta Servlet 6.0, -the bump is safe to merge. +Dependabot handles the polling. A tracker-only `` entry +in `pom.xml` declares `org.apache.jackrabbit:jackrabbit-webdav` at +`${upstream.jackrabbit.version}`, with no corresponding `` entry — +so the coordinate stays off the compile classpath but is visible to Dependabot's +Maven manifest scan ([`.github/dependabot.yml`](.github/dependabot.yml)). + +When Apache cuts a new `jackrabbit-webdav` release, Dependabot opens a PR +labelled `upstream-bump` that bumps the `upstream.jackrabbit.version` property. +That PR fires [`bump-on-dependabot.yml`](.github/workflows/bump-on-dependabot.yml), +which does the work Dependabot can't: + +1. Reads the new upstream version from the bumped property +2. Downloads the matching `*-sources.jar` from Maven Central, replacing the + `sources.jar` at the repo root +3. Re-extracts source into `src/main/java/` (`.java` files) and + `src/main/resources/` (everything else) +4. Runs `mvn rewrite:run` to re-apply the Jakarta EE 10 transform +5. Bumps the project `` to `-jakarta-ee10` +6. Commits and pushes back onto the Dependabot branch + +The smoke test ([`ci.yml`](.github/workflows/ci.yml)) then re-runs against the +follow-up commit and gates merge: if the new upstream version still cleanly +transforms and links against Jakarta Servlet 6.0, the bump is safe to merge. ## How to cut a release diff --git a/pom.xml b/pom.xml index 1d3227c..8ede534 100644 --- a/pom.xml +++ b/pom.xml @@ -26,8 +26,31 @@ UTF-8 17 + + 2.22.3 + + + + + org.apache.jackrabbit + jackrabbit-webdav + ${upstream.jackrabbit.version} + + + + org.osgi