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
3 changes: 2 additions & 1 deletion .agents/skills/enhance-plugin-with-template/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ Copy these **verbatim**; never hand-edit them in the plugin repo afterwards (the
by the automated template sync):

```
.github/workflows/ # EXCLUDE files-sync.yml (template-only)
.github/workflows/ # EXCLUDE the template-only files-sync.yml, ci-conventions.yml,
# release-conventions.yml and snapshot-conventions.yml
.github/scripts/ # EXCLUDE prepare-sync-matrix.groovy (template-only)
.github/dependabot.yml
.github/renovate.json
Expand Down
3 changes: 3 additions & 0 deletions .agents/skills/repository-structure/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ my-plugin/
├── .github/ # CI/CD workflows and GitHub config
│ ├── workflows/
│ │ ├── ci.yml # Build, test, publish snapshots
│ │ ├── ci-conventions.yml # Tests conventions/ -- template-only, never synced
│ │ ├── files-sync.yml # Syncs shared files (incl. skills) to child repos
│ │ ├── release-notes.yml # Automated release draft notes
│ │ ├── update-contributors.yml # Refreshes contributor list in project.yml
Expand Down Expand Up @@ -215,6 +216,8 @@ owns them, and CI fails if `.sdkmanrc`'s `gradle` version differs from the wrapp

- **CI** (`ci.yml`): Builds and tests on every push/PR (`./gradlew build`, which runs unit tests, integration tests, code
style, and the aggregated JaCoCo report via `check`). Publishes snapshots on push to `main`/release branches.
- **CI Conventions** (`ci-conventions.yml`): Template-only, never synced. Tests `conventions/` (an included build that
`./gradlew build` does not test) on Java 17 and 21; `release-conventions.yml` reuses it before releasing.
- **Release** (`.github/sync/workflows/release.yml`): 4-job pipeline (stage jar -> release staging repo -> publish docs
-> update version index) triggered by a published GitHub release. This workflow is synced into child repos only --
the template repository itself never runs it.
Expand Down
26 changes: 15 additions & 11 deletions .github/scripts/verify-repository.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -435,20 +435,24 @@ if (isTemplate) {
"repo itself (expected its 'if:' to contain \"${repoGuard}\")")
}

// ci.yml is synced, but synced repos have no conventions/ directory to test
def templateOnlyGuard = "github.repository == 'grails-plugins/grails-plugin-template'"
(ciYaml?.jobs?.build?.steps ?: []).each { step ->
def run = step.run as String
def ifCondition = step.'if' as String
if (run != null && run.contains('-p conventions') && (ifCondition == null || !ifCondition.contains(templateOnlyGuard))) {
err("ci.yml step '${step.name}' runs against conventions/, which synced repos do not have — " +
"its 'if:' must contain \"${templateOnlyGuard}\"")
// ci.yml is synced, but synced repos have no conventions/ directory — testing it belongs in the template-only
// ci-conventions.yml
(ciYaml?.jobs ?: [:]).each { jobId, job ->
(job?.steps ?: []).each { step ->
def run = step.run as String
if (run != null && run.contains('-p conventions')) {
err("ci.yml step '${step.name}' (job '${jobId}') runs against conventions/, which synced repos do " +
"not have — move it to the template-only ci-conventions.yml")
}
}
}

if (!filesSyncText.contains("exclude='files-sync.yml'") && !filesSyncText.contains('exclude=\'files-sync.yml\'')) {
err("files-sync.yml no longer excludes itself from the workflows it copies — it would sync itself into " +
"every child repo")
// Workflows only the template runs must never be copied into a child repo
['files-sync.yml', 'release-conventions.yml', 'snapshot-conventions.yml', 'ci-conventions.yml'].each { name ->
if (new File(".github/workflows/${name}").exists() && !filesSyncText.contains("--exclude='${name}'")) {
err("files-sync.yml does not exclude the template-only workflow ${name} from the workflows it copies — " +
"it would be synced into every child repo")
}
}
if (!filesSyncText.contains('prepare-sync-matrix.groovy')) {
warn("files-sync.yml does not mention excluding prepare-sync-matrix.groovy from the scripts it copies")
Expand Down
71 changes: 71 additions & 0 deletions .github/workflows/ci-conventions.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Template-only workflow (files-sync.yml does not copy it): tests the conventions Gradle plugin in conventions/.
# That directory is an included build, so ci.yml's `./gradlew build` does not run its tests, and synced repos consume
# the published plugin and have no conventions/ directory. release-conventions.yml reuses this workflow before it
# stages a release.
name: "CI Conventions"
on:
push:
branches:
- '[0-9]+.[0-9]+.x'
- 'main'
paths:
- 'conventions/**'
- 'gradle.properties'
- '.sdkmanrc'
- '.github/workflows/ci-conventions.yml'
pull_request:
paths:
- 'conventions/**'
- 'gradle.properties'
- '.sdkmanrc'
- '.github/workflows/ci-conventions.yml'
workflow_dispatch:
workflow_call:
inputs:
ref:
description: 'Ref to test (the release tag when called from release-conventions.yml)'
required: false
type: string
permissions:
contents: read
env:
JAVA_DISTRIBUTION: 'liberica'
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
test:
name: "Test Conventions"
if: ${{ github.repository == 'grails-plugins/grails-plugin-template' && !contains(github.event.head_commit.message, '[skip tests]') }}
runs-on: ubuntu-24.04
steps:
- name: "📥 Checkout repository"
uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
- name: "Export .sdkmanrc properties"
uses: apache/grails-github-actions/export-gradle-properties@asf
with:
file: ".sdkmanrc"
prefix: "SDKMANRC_"
- name: "Determine Java Version"
run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV
- name: "☕️ Setup JDK"
uses: actions/setup-java@v6
with:
distribution: ${{ env.JAVA_DISTRIBUTION }}
java-version: ${{ env.SDKMANRC_java }}
- name: "☕️ Setup JDK 21 for the Grails 8 consumer"
# GrailsLineSpec also builds a Grails 8 consumer, whose Gradle plugin needs Java 21
id: setup-java-21
uses: actions/setup-java@v6
with:
distribution: ${{ env.JAVA_DISTRIBUTION }}
java-version: 21
set-default: false
- name: "🐘 Setup Gradle"
uses: gradle/actions/setup-gradle@v6
- name: "🧪 Test convention plugins"
env:
JAVA_HOME: ${{ steps.setup-java-21.outputs.path }}
run: ./gradlew -p conventions test --stacktrace
18 changes: 0 additions & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,24 +70,6 @@ jobs:
--continue
--stacktrace
--rerun-tasks
- name: "☕️ Setup JDK 21 for the convention plugin tests"
# GrailsLineSpec also builds a Grails 8 consumer, whose Gradle plugin needs Java 21
id: setup-java-21
if: ${{ github.repository == 'grails-plugins/grails-plugin-template' && !contains(github.event.head_commit.message, '[skip tests]') }}
uses: actions/setup-java@v6
with:
distribution: ${{ env.JAVA_DISTRIBUTION }}
java-version: 21
set-default: false
- name: "🧪 Test convention plugins"
# template-only: synced repos consume the published plugin and have no conventions/ directory. It is an
# included build, so `./gradlew build` does not run its tests.
if: ${{ github.repository == 'grails-plugins/grails-plugin-template' && !contains(github.event.head_commit.message, '[skip tests]') }}
env:
JAVA_HOME: ${{ steps.setup-java-21.outputs.path }}
run: >
./gradlew -p conventions test
--stacktrace
publish:
# only run the publishing task on this repo (not on forks), and never in the
# template repo itself — grails-plugin-template has no Maven coordinates of its
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/files-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ jobs:
# the second rsync's --delete wipe out what the first one just added.
# So stage them into one directory first, then rsync --delete once.
workflow_stage=$(mktemp -d)
rsync -a --exclude='files-sync.yml' --exclude='release-conventions.yml' --exclude='snapshot-conventions.yml' source/.github/workflows/ "${workflow_stage}/"
rsync -a --exclude='files-sync.yml' --exclude='release-conventions.yml' --exclude='snapshot-conventions.yml' --exclude='ci-conventions.yml' source/.github/workflows/ "${workflow_stage}/"
if [ -d source/.github/sync/workflows ]; then
rsync -a source/.github/sync/workflows/ "${workflow_stage}/"
fi
Expand Down
25 changes: 3 additions & 22 deletions .github/workflows/release-conventions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,30 +21,11 @@ concurrency:
jobs:
test:
name: "Test Conventions"
runs-on: ubuntu-24.04
uses: ./.github/workflows/ci-conventions.yml
with:
ref: ${{ github.event.release.tag_name }}
permissions:
contents: read
steps:
- name: "📥 Checkout repository"
uses: actions/checkout@v7
with:
ref: ${{ env.TAG }}
- name: "Export .sdkmanrc properties"
uses: apache/grails-github-actions/export-gradle-properties@asf
with:
file: ".sdkmanrc"
prefix: "SDKMANRC_"
- name: "Determine Java Version"
run: echo "SDKMANRC_java=${{ env.SDKMANRC_java }}" | sed 's/-.*//' >> $GITHUB_ENV
- name: "☕️ Setup JDK"
uses: actions/setup-java@v6
with:
distribution: ${{ env.JAVA_DISTRIBUTION }}
java-version: ${{ env.SDKMANRC_java }}
- name: "🐘 Setup Gradle"
uses: gradle/actions/setup-gradle@v6
- name: "🧪 Test convention plugins"
run: ./gradlew -p conventions test --stacktrace
stage:
name: "Stage Conventions"
needs: test
Expand Down
2 changes: 2 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,8 @@ Convention plugins in `conventions/src/main/groovy/` standardize build configura

- **CI** (`.github/workflows/ci.yml`): Builds and tests on push/PR; publishes snapshots to Maven Central Snapshots on
push to release branches.
- **CI Conventions** (`.github/workflows/ci-conventions.yml`): Template-only, never synced. Tests `conventions/` on
Java 17 and 21; also reused by `release-conventions.yml`.
- **Release** (`.github/workflows/release.yml`): 4-stage pipeline triggered by GitHub release — stage artifacts, release
to Maven Central, publish docs to GitHub Pages, bump version.
- **Release Notes** (`.github/workflows/release-notes.yml`): Auto-drafts release notes using release-drafter with
Expand Down
6 changes: 5 additions & 1 deletion TEMPLATE_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,14 +288,18 @@ plugin repo can use the `config.*` plugins without carrying the `conventions/` f

**Releasing:** the conventions are versioned together with the template — their version is the template's
`projectVersion`. Publishing a GitHub release of the template (tag `v1.2.3`) runs `release-conventions.yml`
(template-only, never synced). It tests the plugins, stages the signed artifacts, and — after approval of the
(template-only, never synced). It runs `ci-conventions.yml` to test the plugins, stages the signed artifacts, and — after approval of the
`release` environment — releases them to Maven Central, then opens the PR that moves `projectVersion` to the next
snapshot (after approval of the `close` environment), as `release.yml` does in the plugin repos.

**Snapshots:** every push to `main` that touches `conventions/` or `gradle.properties` runs
`snapshot-conventions.yml` (template-only), which publishes the current `-SNAPSHOT` to Maven Central Snapshots.
`ci.yml`'s snapshot job stays skipped in the template repo.

**Testing:** the conventions are an included build, so `./gradlew build` does not run their tests. The
template-only `ci-conventions.yml` (never synced) runs `./gradlew -p conventions test` on pushes and pull requests
that touch `conventions/`, and `release-conventions.yml` reuses it. It keeps `ci.yml` free of conventions references.

**Consuming:** the plugin marker artifacts are not published (their group would be the plugin id, e.g.
`config.compile`, which Maven Central does not accept), so map the ids to the single artifact in `settings.gradle`:

Expand Down
Loading