This is an automated email from the ASF dual-hosted git repository.

lukaszlenart pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/struts-site.git


The following commit(s) were added to refs/heads/main by this push:
     new 9bf4cca78 ci: convert Struts-site-javadocs job to a declarative 
pipeline (#309)
9bf4cca78 is described below

commit 9bf4cca7858f8d4a760ae5ad4defc255faa7c3c5
Author: Lukasz Lenart <[email protected]>
AuthorDate: Wed Jul 1 08:57:33 2026 +0200

    ci: convert Struts-site-javadocs job to a declarative pipeline (#309)
    
    * docs: add design spec for javadocs Jenkins pipeline conversion
    
    Converts the shell-based Struts-site-javadocs freestyle job into a
    declarative Jenkins pipeline (Jenkinsfile.javadocs), mirroring the
    existing root Jenkinsfile conventions. Two intended deviations: use
    plain `mvn` instead of `./mvnw`, and push via the explicit `asf`
    remote.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    
    * docs: add implementation plan for javadocs Jenkins pipeline
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    
    * ci: add declarative pipeline for the javadocs job
    
    Converts the shell-based Struts-site-javadocs freestyle job into
    Jenkinsfile.javadocs. Builds Struts with plain `mvn` and publishes the
    staged Maven site into source/maven on main via the asf remote.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    
    ---------
    
    Co-authored-by: Claude Opus 4.8 <[email protected]>
---
 Jenkinsfile.javadocs                               | 113 ++++++++++
 .../plans/2026-07-01-jenkins-javadocs-pipeline.md  | 234 +++++++++++++++++++++
 .../2026-07-01-jenkins-javadocs-pipeline-design.md | 127 +++++++++++
 3 files changed, 474 insertions(+)

diff --git a/Jenkinsfile.javadocs b/Jenkinsfile.javadocs
new file mode 100644
index 000000000..4fc90a4f6
--- /dev/null
+++ b/Jenkinsfile.javadocs
@@ -0,0 +1,113 @@
+#!groovy
+
+pipeline {
+  agent {
+    label 'git-websites'
+  }
+  parameters {
+    string(name: 'STRUTS_TAG', defaultValue: 'main',
+           description: 'Struts release tag (e.g. STRUTS_7_2_1) or "main"')
+  }
+  options {
+    buildDiscarder logRotator(numToKeepStr: '5')
+    timeout(90)
+    disableConcurrentBuilds()
+    skipStagesAfterUnstable()
+  }
+  environment {
+    MAVEN_OPTS = '-Xmx2048m -Dhttps.protocols=TLSv1.2 -DfailOnError=false'
+    // Required: resolves `mvn` on the ASF agent (we use plain mvn, not the 
wrapper).
+    PATH = "${MAVEN_3_LATEST_HOME}:${env.PATH}"
+  }
+  stages {
+    stage('Build Struts & Maven site') {
+      steps {
+        sh '''
+          echo "Building Struts ${STRUTS_TAG} and staging the Maven site"
+
+          rm -rf target/struts
+          git clone https://gitbox.apache.org/repos/asf/struts.git 
target/struts
+
+          cd target/struts
+          if [ "${STRUTS_TAG}" = "main" ]; then
+            git checkout main
+          else
+            git checkout "tags/${STRUTS_TAG}"
+          fi
+
+          mvn -B -V clean install -DskipTests
+          mvn -B -V site:site site:stage
+        '''
+      }
+    }
+    stage('Publish to source/maven') {
+      steps {
+        sh '''
+          echo "Publishing the staged Maven site into source/maven on main"
+
+          if ! git config remote.asf.url > /dev/null; then
+            git remote add asf 
https://gitbox.apache.org/repos/asf/struts-site.git
+          fi
+
+          git fetch asf
+          git checkout main
+          git pull asf main
+
+          rm -rf source/maven
+          mkdir -p source/maven
+          mv target/struts/target/staging/* source/maven/
+
+          git add source/maven
+          git status
+
+          git diff --cached --quiet || git commit -m "Updates Maven site by 
Jenkins"
+          git push asf main
+        '''
+      }
+    }
+  }
+  post {
+    // If this build failed, send an email to the developers.
+    failure {
+      script {
+        emailext(
+            recipientProviders: [[$class: 'DevelopersRecipientProvider']],
+            from: "Mr. Jenkins <[email protected]>",
+            subject: "Jenkins job ${env.JOB_NAME}#${env.BUILD_NUMBER} failed",
+            body: """
+There is a build failure in ${env.JOB_NAME}.
+
+Build: ${env.BUILD_URL}
+Logs: ${env.BUILD_URL}console
+Changes: ${env.BUILD_URL}changes
+
+--
+Mr. Jenkins
+Director of Continuous Integration
+"""
+        )
+      }
+    }
+    // Send an email, if the last build was not successful and this one is.
+    fixed {
+      script {
+        emailext(
+            recipientProviders: [[$class: 'DevelopersRecipientProvider']],
+            from: 'Mr. Jenkins <[email protected]>',
+            subject: "Jenkins job ${env.JOB_NAME}#${env.BUILD_NUMBER} back to 
normal",
+            body: """
+The build for ${env.JOB_NAME} completed successfully and is back to normal.
+
+Build: ${env.BUILD_URL}
+Logs: ${env.BUILD_URL}console
+Changes: ${env.BUILD_URL}changes
+
+--
+Mr. Jenkins
+Director of Continuous Integration
+"""
+        )
+      }
+    }
+  }
+}
diff --git a/docs/superpowers/plans/2026-07-01-jenkins-javadocs-pipeline.md 
b/docs/superpowers/plans/2026-07-01-jenkins-javadocs-pipeline.md
new file mode 100644
index 000000000..3b6fb75c8
--- /dev/null
+++ b/docs/superpowers/plans/2026-07-01-jenkins-javadocs-pipeline.md
@@ -0,0 +1,234 @@
+# Jenkins JavaDocs Pipeline Conversion Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use 
superpowers:subagent-driven-development (recommended) or 
superpowers:executing-plans to implement this plan task-by-task. Steps use 
checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Replace the shell-based `Struts-site-javadocs` freestyle Jenkins job 
with a version-controlled declarative pipeline, `Jenkinsfile.javadocs`.
+
+**Architecture:** A declarative Jenkins pipeline at the repo root, run on the 
ASF `git-websites` agent, manually triggered with a `STRUTS_TAG` parameter. 
Stage 1 clones + builds Struts and stages its Maven site under the gitignored 
`target/`; Stage 2 publishes the staged site into `source/maven/` on `main` and 
pushes to the `asf` gitbox remote. Mirrors the existing root `Jenkinsfile` 
conventions.
+
+**Tech Stack:** Jenkins declarative pipeline (Groovy DSL), shell, Maven (`mvn` 
on the ASF agent via `MAVEN_3_LATEST_HOME`), git (gitbox).
+
+## Global Constraints
+
+- Spec: 
`docs/superpowers/specs/2026-07-01-jenkins-javadocs-pipeline-design.md`.
+- Behavior must match the current shell script exactly, except two intended 
deviations:
+  - Build with plain `mvn`, not `./mvnw`.
+  - Push via the explicit `asf` remote, not `origin`.
+- Commit message that publishes the site MUST be exactly `Updates Maven site 
by Jenkins`.
+- Empty-commit guard MUST be preserved: `git diff --cached --quiet || git 
commit ...`.
+- Clone location MUST remain `target/struts` (under the gitignored `target/`).
+- `STRUTS_TAG` default is `main`; any other value is treated as a git tag 
(`tags/${STRUTS_TAG}`).
+- Do NOT modify the existing root `Jenkinsfile` (that is the separate 
`Struts-site` job).
+- Declarative pipelines cannot be validated by a standalone `groovy` binary; 
local checks are structural only, real acceptance is a manual Jenkins run (Task 
2).
+
+---
+
+### Task 1: Author `Jenkinsfile.javadocs`
+
+**Files:**
+- Create: `Jenkinsfile.javadocs`
+- Reference (do not modify): `Jenkinsfile` (source of the `post` email blocks 
and style)
+
+**Interfaces:**
+- Consumes: ASF Jenkins agent env var `MAVEN_3_LATEST_HOME`; agent label 
`git-websites`; `emailext` and `DevelopersRecipientProvider` (provided by the 
Jenkins controller, same as the existing `Jenkinsfile`).
+- Produces: a pipeline definition file that the Jenkins job (Task 2) points at 
via "Pipeline script from SCM".
+
+- [ ] **Step 1: Create the file with the full pipeline**
+
+Create `Jenkinsfile.javadocs` with exactly this content:
+
+```groovy
+#!groovy
+
+pipeline {
+  agent {
+    label 'git-websites'
+  }
+  parameters {
+    string(name: 'STRUTS_TAG', defaultValue: 'main',
+           description: 'Struts release tag (e.g. STRUTS_7_2_1) or "main"')
+  }
+  options {
+    buildDiscarder logRotator(numToKeepStr: '5')
+    timeout(90)
+    disableConcurrentBuilds()
+    skipStagesAfterUnstable()
+  }
+  environment {
+    MAVEN_OPTS = '-Xmx2048m -Dhttps.protocols=TLSv1.2 -DfailOnError=false'
+    // Required: resolves `mvn` on the ASF agent (we use plain mvn, not the 
wrapper).
+    PATH = "${MAVEN_3_LATEST_HOME}:${env.PATH}"
+  }
+  stages {
+    stage('Build Struts & Maven site') {
+      steps {
+        sh '''
+          echo "Building Struts ${STRUTS_TAG} and staging the Maven site"
+
+          rm -rf target/struts
+          git clone https://gitbox.apache.org/repos/asf/struts.git 
target/struts
+
+          cd target/struts
+          if [ "${STRUTS_TAG}" = "main" ]; then
+            git checkout main
+          else
+            git checkout "tags/${STRUTS_TAG}"
+          fi
+
+          mvn -B -V clean install -DskipTests
+          mvn -B -V site:site site:stage
+        '''
+      }
+    }
+    stage('Publish to source/maven') {
+      steps {
+        sh '''
+          echo "Publishing the staged Maven site into source/maven on main"
+
+          if ! git config remote.asf.url > /dev/null; then
+            git remote add asf 
https://gitbox.apache.org/repos/asf/struts-site.git
+          fi
+
+          git fetch asf
+          git checkout main
+          git pull asf main
+
+          rm -rf source/maven
+          mkdir -p source/maven
+          mv target/struts/target/staging/* source/maven/
+
+          git add source/maven
+          git status
+
+          git diff --cached --quiet || git commit -m "Updates Maven site by 
Jenkins"
+          git push asf main
+        '''
+      }
+    }
+  }
+  post {
+    // If this build failed, send an email to the developers.
+    failure {
+      script {
+        emailext(
+            recipientProviders: [[$class: 'DevelopersRecipientProvider']],
+            from: "Mr. Jenkins <[email protected]>",
+            subject: "Jenkins job ${env.JOB_NAME}#${env.BUILD_NUMBER} failed",
+            body: """
+There is a build failure in ${env.JOB_NAME}.
+
+Build: ${env.BUILD_URL}
+Logs: ${env.BUILD_URL}console
+Changes: ${env.BUILD_URL}changes
+
+--
+Mr. Jenkins
+Director of Continuous Integration
+"""
+        )
+      }
+    }
+    // Send an email, if the last build was not successful and this one is.
+    fixed {
+      script {
+        emailext(
+            recipientProviders: [[$class: 'DevelopersRecipientProvider']],
+            from: 'Mr. Jenkins <[email protected]>',
+            subject: "Jenkins job ${env.JOB_NAME}#${env.BUILD_NUMBER} back to 
normal",
+            body: """
+The build for ${env.JOB_NAME} completed successfully and is back to normal.
+
+Build: ${env.BUILD_URL}
+Logs: ${env.BUILD_URL}console
+Changes: ${env.BUILD_URL}changes
+
+--
+Mr. Jenkins
+Director of Continuous Integration
+"""
+        )
+      }
+    }
+  }
+}
+```
+
+- [ ] **Step 2: Structural sanity checks**
+
+Run:
+```bash
+# Balanced braces (should print: braces OK)
+awk 
'{for(i=1;i<=length($0);i++){c=substr($0,i,1);if(c=="{")n++;if(c=="}")n--}} 
END{print (n==0)?"braces OK":"UNBALANCED: "n}' Jenkinsfile.javadocs
+
+# Required blocks are present (each grep should print a match)
+grep -n "agent {" Jenkinsfile.javadocs
+grep -n "string(name: 'STRUTS_TAG'" Jenkinsfile.javadocs
+grep -n "mvn -B -V clean install -DskipTests" Jenkinsfile.javadocs
+grep -n "mvn -B -V site:site site:stage" Jenkinsfile.javadocs
+grep -n 'git push asf main' Jenkinsfile.javadocs
+grep -n 'Updates Maven site by Jenkins' Jenkinsfile.javadocs
+
+# The wrapper must NOT be referenced anymore (should print: no mvnw refs OK)
+grep -q './mvnw' Jenkinsfile.javadocs && echo "FOUND mvnw — must be removed" 
|| echo "no mvnw refs OK"
+```
+Expected: `braces OK`; every `grep -n` prints its line; final line prints `no 
mvnw refs OK`.
+
+- [ ] **Step 3: Confirm the existing Jenkinsfile is untouched**
+
+Run: `git status --porcelain Jenkinsfile`
+Expected: no output (the `Struts-site` job's file is unchanged).
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add Jenkinsfile.javadocs
+git commit -m "ci: add declarative pipeline for the javadocs job
+
+Converts the shell-based Struts-site-javadocs freestyle job into
+Jenkinsfile.javadocs. Builds Struts with plain \`mvn\` and publishes the
+staged Maven site into source/maven on main via the asf remote.
+
+Co-Authored-By: Claude Opus 4.8 <[email protected]>"
+```
+
+---
+
+### Task 2: Wire up and verify the Jenkins job (operational — runs in Jenkins, 
not the repo)
+
+This task has no repo commit; its deliverable is a green build that publishes 
the site. It cannot be done until Task 1's branch is merged to `main` (the job 
checks out `main`).
+
+**Interfaces:**
+- Consumes: `Jenkinsfile.javadocs` on `main`; ASF Jenkins credentials on the 
`git-websites` agent.
+- Produces: an updated `source/maven/` on `main` (and, via the ASF Jekyll 
buildbot, `asf-site`).
+
+- [ ] **Step 1: Merge Task 1 to `main`**
+
+Open a PR from the Task 1 branch and merge it, so `Jenkinsfile.javadocs` 
exists on `main`.
+
+- [ ] **Step 2: Reconfigure the Jenkins job**
+
+In `Struts-site-javadocs` → Configure:
+- Definition: **Pipeline script from SCM**
+- SCM: Git, repo `https://gitbox.apache.org/repos/asf/struts-site.git`, branch 
`*/main`
+- Script Path: `Jenkinsfile.javadocs`
+- Save.
+
+- [ ] **Step 3: Dry-run against `main`**
+
+Trigger **Build with Parameters** with `STRUTS_TAG=main`.
+Expected: both stages green; console shows `mvn` (not `mvnw`) being used; 
final step either commits `Updates Maven site by Jenkins` and pushes to `asf` 
`main`, or reports nothing to commit if the site is unchanged.
+
+- [ ] **Step 4: Verify the published output**
+
+On `main` after the run, confirm `source/maven/` contains freshly generated 
Maven site HTML (e.g. `source/maven/index.html` present, timestamps current). 
Confirm the ASF buildbot republished to `asf-site` (check 
https://struts.apache.org/maven/).
+
+- [ ] **Step 5: (Optional) Tag run**
+
+Re-run with a real tag (e.g. `STRUTS_TAG=STRUTS_7_2_1`) to confirm the 
`tags/${STRUTS_TAG}` checkout path works.
+
+---
+
+## Follow-ups (tracked in the spec, not part of this plan)
+
+- Delete `mvnw`, `mvnw.cmd`, `.mvn/` from the **apache/struts** repo (separate 
repo/commit) so the wrapper and CI don't drift now that CI uses plain `mvn`.
+- Revisit PR #293's "sole source of truth" note to confirm the `source/maven` 
on `main` publish path is still intended.
diff --git 
a/docs/superpowers/specs/2026-07-01-jenkins-javadocs-pipeline-design.md 
b/docs/superpowers/specs/2026-07-01-jenkins-javadocs-pipeline-design.md
new file mode 100644
index 000000000..9f72588c7
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-01-jenkins-javadocs-pipeline-design.md
@@ -0,0 +1,127 @@
+# Convert the Struts-site-javadocs Jenkins job to a declarative pipeline
+
+**Date:** 2026-07-01
+**Status:** Approved — ready for implementation plan
+
+## Problem
+
+The `Struts-site-javadocs` Jenkins job 
(https://ci-builds.apache.org/job/Struts/job/Struts-site-javadocs/)
+is currently a freestyle job with an inline `#!/bin/sh` build step. It:
+
+1. Clones `apache/struts` into `target/struts`, checks out `main` or a release 
tag.
+2. Builds the project and generates + stages the Maven site.
+3. Publishes the staged site into `source/maven/` on the `struts-site` `main` 
branch,
+   commits, and pushes. The ASF Jekyll buildbot then republishes `main → 
asf-site`.
+
+We want this expressed as a declarative Jenkins pipeline (Groovy), consistent 
with the
+existing website-build pipeline already in this repo.
+
+## Goals
+
+- Replace the shell freestyle job with a reviewable, version-controlled 
declarative pipeline.
+- Preserve current behavior exactly, except for the two deliberate changes 
below.
+- Match the conventions of the existing root `Jenkinsfile` (the `Struts-site` 
job).
+
+## Non-goals
+
+- No change to what content is published or where (`source/maven/` on `main`).
+- No change to the ASF `main → asf-site` republish mechanism.
+- Removing the `mvnw`/`.mvn/` wrapper files from the **apache/struts** repo 
(separate
+  repo, separate commit — see Follow-ups).
+
+## Deliberate changes vs. the current shell script
+
+1. **`./mvnw` → `mvn`.** Use the Maven installation on the ASF agent instead 
of the
+   Struts project's Maven wrapper. This makes the `MAVEN_3_LATEST_HOME` PATH 
entry
+   load-bearing (it is what resolves `mvn`), so it is kept.
+2. **`git push origin main` → `git push asf main`.** Mirror the existing 
`Jenkinsfile`'s
+   explicit `asf` remote pattern rather than relying on the ambient `origin`.
+
+## Design
+
+### Placement
+
+A new file **`Jenkinsfile.javadocs`** at the repo root. The 
`Struts-site-javadocs` job is
+reconfigured in Jenkins as **Pipeline script from SCM** → this repo, branch 
`main`, script
+path `Jenkinsfile.javadocs`. Both job definitions then live in git.
+
+### Trigger & parameters
+
+Manually triggered ("Build with Parameters"). One parameter:
+
+```groovy
+parameters {
+  string(name: 'STRUTS_TAG', defaultValue: 'main',
+         description: 'Struts release tag (e.g. STRUTS_7_2_1) or "main"')
+}
+```
+
+### Agent, options, environment
+
+```groovy
+agent { label 'git-websites' }
+
+options {
+  buildDiscarder logRotator(numToKeepStr: '5')
+  timeout(90)                    // full Struts build + site:site is slow
+  disableConcurrentBuilds()
+  skipStagesAfterUnstable()
+}
+
+environment {
+  MAVEN_OPTS = '-Xmx2048m -Dhttps.protocols=TLSv1.2 -DfailOnError=false'
+  PATH = "${MAVEN_3_LATEST_HOME}:${env.PATH}"   // required: resolves `mvn` on 
the ASF agent
+}
+```
+
+### Stages
+
+**Stage 1 — Build Struts & Maven site**
+
+Single `sh` step, all work under the gitignored `target/` so the struts clone 
never
+pollutes the site repo:
+
+- `rm -rf target/struts`
+- clone `https://gitbox.apache.org/repos/asf/struts.git` into `target/struts`
+- checkout: if `STRUTS_TAG == main` → `git checkout main`, else `git checkout 
"tags/${STRUTS_TAG}"`
+- `mvn -B -V clean install -DskipTests`
+- `mvn -B -V site:site site:stage`
+
+**Stage 2 — Publish to source/maven**
+
+Mirror the existing `Jenkinsfile` deploy pattern:
+
+- add the `asf` remote if missing → 
`https://gitbox.apache.org/repos/asf/struts-site.git`
+- `git checkout main` && `git pull asf main`
+- `rm -rf source/maven && mkdir -p source/maven`
+- `mv target/struts/target/staging/* source/maven/`
+- `git add source/maven`
+- `git diff --cached --quiet || git commit -m "Updates Maven site by Jenkins"`
+- `git push asf main`
+
+### Post
+
+Copy the `post { failure {} fixed {} }` `emailext` blocks verbatim from the 
existing
+root `Jenkinsfile` (from "Mr. Jenkins", recipients via 
`DevelopersRecipientProvider`).
+
+## Behavior parity checklist
+
+| Aspect                        | Shell script            | Pipeline           
     | Same? |
+|-------------------------------|-------------------------|-------------------------|-------|
+| Clone location                | `target/struts`         | `target/struts`    
     | ✅    |
+| Tag/main selection            | `STRUTS_TAG`            | `STRUTS_TAG` param 
     | ✅    |
+| Build command                 | `./mvnw clean install`  | `mvn clean 
install`     | ⚠️ intended |
+| Site command                  | `./mvnw site:site …`    | `mvn site:site …`  
     | ⚠️ intended |
+| MAVEN_OPTS                     | same                    | same              
      | ✅    |
+| Publish target                | `source/maven` on main  | `source/maven` on 
main  | ✅    |
+| Commit message                | "Updates Maven site …"  | identical          
     | ✅    |
+| Empty-commit guard            | `git diff --cached -q`  | identical          
     | ✅    |
+| Push remote                   | `origin`                | `asf`              
     | ⚠️ intended |
+
+## Follow-ups (out of scope for this change)
+
+- Delete `mvnw`, `mvnw.cmd`, and `.mvn/` from the **apache/struts** repo so 
the wrapper and
+  the CI job do not drift now that CI uses plain `mvn`.
+- Optional: revisit PR #293's note that the javadocs job is the "sole source 
of truth" for
+  `maven/` on `asf-site`, to confirm the `source/maven` on `main` publish path 
is still the
+  intended flow.

Reply via email to