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

jamesfredley pushed a commit to branch fix/move-plugin-scripts-to-cli-companion
in repository https://gitbox.apache.org/repos/asf/grails-core.git

commit 55db8cc7e26c356a55a8f7cb37fa18d14dec3bc1
Author: James Fredley <[email protected]>
AuthorDate: Sun Aug 2 10:35:38 2026 -0400

    fix: route plugin src/main/scripts into companion -cli jars
    
    When a plugin applies grails-plugin-cli, copyCommands now packages
    src/main/scripts under the cli source set as META-INF/commands instead of
    the runtime jar. Plugins without a companion keep the historical runtime
    packaging so Grails 7 / legacyCommandSupport consumers still discover
    scripts on the application classpath.
    
    Also clear stale META-INF/commands under the configured processResources
    destination on incremental builds after migration, so prior script copies
    do not remain on the runtime classpath while hand-authored
    src/main/resources/META-INF/commands are restored by processResources.
    
    Fixes #16035
    
    Assisted-by: Sisyphus:xai/grok-4.5
---
 .../guide/commandLine/creatingCustomCommands.adoc  |  9 ++-
 .../en/guide/plugins/providingBasicArtefacts.adoc  |  9 +++
 .../src/en/guide/upgrading/upgrading80x.adoc       | 12 +++-
 .../commands/GrailsCliArtifactGradlePlugin.groovy  |  1 +
 .../plugin/core/GrailsPluginGradlePlugin.groovy    | 63 ++++++++++++++++--
 .../PluginScriptCommandPackagingSpec.groovy        | 63 ++++++++++++++++++
 .../plugin-script-commands/gradle.properties       |  2 +
 .../plugin-with-cli/build.gradle                   | 74 ++++++++++++++++++++++
 .../resources/META-INF/commands/hand-authored.yml  |  1 +
 .../src/main/scripts/example-multi-step.yml        |  3 +
 .../src/main/scripts/example-script.groovy         |  5 ++
 .../plugin-with-cli/src/main/templates/example.txt |  1 +
 .../plugin-without-cli/build.gradle                | 36 +++++++++++
 .../src/main/scripts/legacy-only-script.groovy     |  5 ++
 .../src/main/templates/legacy-only.txt             |  1 +
 .../plugin-script-commands/settings.gradle         |  3 +
 16 files changed, 275 insertions(+), 13 deletions(-)

diff --git a/grails-doc/src/en/guide/commandLine/creatingCustomCommands.adoc 
b/grails-doc/src/en/guide/commandLine/creatingCustomCommands.adoc
index 43107b1547..79f00b6a59 100644
--- a/grails-doc/src/en/guide/commandLine/creatingCustomCommands.adoc
+++ b/grails-doc/src/en/guide/commandLine/creatingCustomCommands.adoc
@@ -284,9 +284,12 @@ application's runtime classpath (`bootRun`, `bootJar`, 
`bootWar`). Here is the c
 grails create-plugin my-plugin
 ----
 
-2. **Define the Command in a `cli` source set:** plugin commands live in 
`src/cli/groovy` (not
-`grails-app/commands`, which is compiled into the runtime jar). Give them a 
package that is unique
-to the cli artifact:
+2. **Define the Command in a `cli` source set:** compiled plugin commands live 
in `src/cli/groovy`
+(not `grails-app/commands`, which is compiled into the runtime jar). Give them 
a package that is
+unique to the cli artifact. Code-generation scripts and multi-step YAML/JSON 
commands may stay in
+`src/main/scripts`; when `grails-plugin-cli` is applied they are copied into 
the companion jar as
+`META-INF/commands` and excluded from the runtime plugin jar. You may also 
place them directly under
+`src/cli/resources/META-INF/commands`:
 +
 [source,groovy]
 ----
diff --git a/grails-doc/src/en/guide/plugins/providingBasicArtefacts.adoc 
b/grails-doc/src/en/guide/plugins/providingBasicArtefacts.adoc
index d38201d633..1e604e66e7 100644
--- a/grails-doc/src/en/guide/plugins/providingBasicArtefacts.adoc
+++ b/grails-doc/src/en/guide/plugins/providingBasicArtefacts.adoc
@@ -34,6 +34,15 @@ A plugin can add new commands to the Grails interactive 
shell in one of two ways
 
 Code generation scripts can be used to create artefacts within the project 
tree and automate interactions with Gradle.
 
+When the plugin also applies `org.apache.grails.gradle.grails-plugin-cli` (see
+<<Ship plugin commands as a companion -cli artifact>> below), files under 
`src/main/scripts`
+(including `*.groovy`, `*.yml`, and `*.json` multi-step commands) are packaged 
into the companion
+`-cli` jar as `META-INF/commands` and are **not** copied into the runtime 
plugin jar. That keeps
+command-only resources off `runtimeClasspath`, `bootJar`, and `bootWar` while 
still making them
+discoverable through `grailsCliClasspath`. Plugins that do **not** publish a 
companion keep the
+historical behavior and continue to ship `src/main/scripts` in the runtime jar 
so unmigrated
+Grails 7 plugins remain discoverable under `legacyCommandSupport`.
+
 If you want to create a new shell command that interacts with a loaded Grails 
application instance then you should use the `create-command` command:
 
 [source,groovy]
diff --git a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc 
b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
index cfb81c4111..fa6d03ea2f 100644
--- a/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
+++ b/grails-doc/src/en/guide/upgrading/upgrading80x.adoc
@@ -1857,8 +1857,12 @@ of already-published Grails 7 command plugins must opt 
in with
 **What just works:**
 
 * Code generation scripts created with `create-script`, including scripts 
packaged as
-  `META-INF/commands/*.groovy` such as `audit-quickstart` and `s2-quickstart`, 
are unaffected.
-* YAML commands packaged as `META-INF/commands/*.yml` are unaffected.
+  `META-INF/commands/*.groovy` such as `audit-quickstart` and `s2-quickstart`, 
remain executable
+  through the CLI. When a plugin publishes a companion `-cli` artifact, those 
scripts (and
+  `META-INF/commands/*.yml` / `*.json` multi-step commands under 
`src/main/scripts`) are packaged
+  into the companion only - not the runtime plugin jar - and stay available via
+  `grailsCliClasspath`. Plugins without a companion continue to ship 
`src/main/scripts` in the
+  runtime jar, matching Grails 7 packaging.
 * Existing published Grails 7 application-command plugins continue to work on 
Grails 8 without a
   re-release once `legacyCommandSupport` is enabled.
 
@@ -1874,7 +1878,9 @@ of already-published Grails 7 command plugins must opt in 
with
   `META-INF/grails-cli.factories`.
 * Publish commands in a companion `-cli` artifact for the CLI classpath split 
and per-command
   Gradle task support. See xref:commandLine#creatingCustomCommands[Creating 
Custom Commands] for
-  the complete workflow.
+  the complete workflow. Applying `org.apache.grails.gradle.grails-plugin-cli` 
also routes
+  `src/main/scripts` into that companion automatically - leave scripts under 
`src/main/scripts`
+  or place them under `src/cli/resources/META-INF/commands`.
 
 NOTE: The compatibility layer applies only to the legacy application-command 
contract. Independently
 of it, a Grails 7 plugin that relies on APIs removed in Grails 8, Spring Boot 
4, or other Grails 8
diff --git 
a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/commands/GrailsCliArtifactGradlePlugin.groovy
 
b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/commands/GrailsCliArtifactGradlePlugin.groovy
index 3f047bf5b5..554d58452d 100644
--- 
a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/commands/GrailsCliArtifactGradlePlugin.groovy
+++ 
b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/commands/GrailsCliArtifactGradlePlugin.groovy
@@ -70,6 +70,7 @@ import org.apache.grails.gradle.publish.GrailsPublishExtension
 @CompileStatic
 abstract class GrailsCliArtifactGradlePlugin implements Plugin<Project> {
 
+    public static final String PLUGIN_ID = 
'org.apache.grails.gradle.grails-plugin-cli'
     public static final String CLI_SOURCE_SET_NAME = 'cli'
     public static final String CLI_COMPONENT_NAME = 'cli'
     public static final String CLI_PUBLICATION_NAME = 'cli'
diff --git 
a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsPluginGradlePlugin.groovy
 
b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsPluginGradlePlugin.groovy
index ffabebaf10..95cd126fdc 100644
--- 
a/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsPluginGradlePlugin.groovy
+++ 
b/grails-gradle/plugins/src/main/groovy/org/grails/gradle/plugin/core/GrailsPluginGradlePlugin.groovy
@@ -28,6 +28,7 @@ import org.gradle.api.Task
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.tasks.Copy
 import org.gradle.api.tasks.JavaExec
+import org.gradle.api.tasks.SourceSet
 import org.gradle.api.tasks.TaskProvider
 import org.gradle.api.tasks.SourceSetContainer
 import org.gradle.api.tasks.bundling.Jar
@@ -37,6 +38,7 @@ import 
org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
 
 import org.springframework.boot.gradle.tasks.bundling.BootJar
 
+import org.grails.gradle.plugin.commands.GrailsCliArtifactGradlePlugin
 import org.grails.gradle.plugin.run.FindMainClassTask
 import org.grails.gradle.plugin.util.SourceSets
 
@@ -244,22 +246,69 @@ class GrailsPluginGradlePlugin extends GrailsGradlePlugin 
{
         }
     }
 
+    /**
+     * Packages plugin templates into the runtime jar and routes command 
scripts into either the
+     * runtime jar or the companion {@code -cli} jar.
+     *
+     * <p>When {@link GrailsCliArtifactGradlePlugin} is applied, {@code 
src/main/scripts} is copied
+     * into the cli source set as {@code META-INF/commands} so 
Groovy/YAML/JSON command resources
+     * ship only on {@code grailsCliClasspath} and stay out of {@code 
runtimeClasspath},
+     * {@code bootJar}, and {@code bootWar}. Without a companion, the 
historical behavior is
+     * preserved: scripts remain in the runtime plugin jar so unmigrated 
Grails 7 plugins and
+     * {@code legacyCommandSupport} consumers keep discovering them on the 
application classpath.
+     * Templates always stay on the runtime jar.</p>
+     */
     @CompileDynamic
     protected void configurePluginResources(Project project) {
         project.afterEvaluate() {
             ProcessResources processResources = (ProcessResources) 
project.tasks.getByName('processResources')
+            boolean hasCliCompanion = 
project.pluginManager.hasPlugin(GrailsCliArtifactGradlePlugin.PLUGIN_ID)
+            ProcessResources commandResources = processResources
+            if (hasCliCompanion) {
+                SourceSet cliSourceSet = 
project.extensions.getByType(SourceSetContainer)
+                        
.getByName(GrailsCliArtifactGradlePlugin.CLI_SOURCE_SET_NAME)
+                commandResources = (ProcessResources) 
project.tasks.getByName(cliSourceSet.processResourcesTaskName)
+            }
 
-            TaskProvider<Copy> copyCommands = 
project.tasks.register('copyCommands', Copy) {
-                from("${project.projectDir}/src/main/scripts")
-                into("${processResources.destinationDir}/META-INF/commands")
+            TaskProvider<Copy> copyCommands = 
project.tasks.register('copyCommands', Copy) { Copy copy ->
+                copy.from("${project.projectDir}/src/main/scripts")
+                // Resolve the destination lazily so the process*Resources 
output dir is final.
+                copy.into {
+                    "${commandResources.destinationDir}/META-INF/commands"
+                }
             }
 
-            TaskProvider<Copy> copyTemplates = 
project.tasks.register('copyTemplates', Copy) {
-                from("${project.projectDir}/src/main/templates")
-                into("${processResources.destinationDir}/META-INF/templates")
+            TaskProvider<Copy> copyTemplates = 
project.tasks.register('copyTemplates', Copy) { Copy copy ->
+                copy.from("${project.projectDir}/src/main/templates")
+                copy.into {
+                    "${processResources.destinationDir}/META-INF/templates"
+                }
             }
             processResources.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
-            processResources.dependsOn(copyCommands, copyTemplates)
+            if (hasCliCompanion) {
+                
commandResources.setDuplicatesStrategy(DuplicatesStrategy.INCLUDE)
+                commandResources.dependsOn(copyCommands)
+                processResources.dependsOn(copyTemplates)
+                // A prior non-companion (or pre-migration) build may have 
left src/main/scripts
+                // copies under the main processResources output. Wipe 
META-INF/commands there so
+                // incremental jars do not keep shipping them; 
processResources then re-runs (its
+                // outputs changed) and restores any hand-authored 
src/main/resources/META-INF/commands.
+                // Use the configured processResources destination, not a 
hard-coded build path.
+                TaskProvider cleanStaleRuntimeCommands = project.tasks
+                        .register('cleanStaleRuntimeCommandResources') { Task 
cleanTask ->
+                            cleanTask.outputs.upToDateWhen { false }
+                            cleanTask.doLast {
+                                project.delete(new 
File(processResources.destinationDir, 'META-INF/commands'))
+                            }
+                        }
+                processResources.dependsOn(cleanStaleRuntimeCommands)
+                project.tasks.named('jar', Jar).configure { Jar jarTask ->
+                    jarTask.dependsOn(cleanStaleRuntimeCommands)
+                }
+            }
+            else {
+                processResources.dependsOn(copyCommands, copyTemplates)
+            }
             project.processResources {
                 // spring/resources.groovy is excluded from the published jar 
(see configureJarTask)
                 // but is allowed into build/resources/main/ so plugin 
integration tests can load it.
diff --git 
a/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/commands/PluginScriptCommandPackagingSpec.groovy
 
b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/commands/PluginScriptCommandPackagingSpec.groovy
new file mode 100644
index 0000000000..0daef09a2f
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/groovy/org/grails/gradle/plugin/commands/PluginScriptCommandPackagingSpec.groovy
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    https://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.grails.gradle.plugin.commands
+
+import org.grails.gradle.plugin.core.GradleSpecification
+
+/**
+ * Verifies that {@code src/main/scripts} command resources are packaged 
according to whether the
+ * plugin publishes a companion {@code -cli} artifact (issue #16035).
+ *
+ * <p>With {@code grails-plugin-cli}, Groovy/YAML command scripts must leave 
the runtime plugin jar
+ * and land only in the companion. Without a companion, the historical 
runtime-jar packaging is
+ * preserved so unmigrated Grails 7 plugins keep working under {@code 
legacyCommandSupport}.</p>
+ */
+class PluginScriptCommandPackagingSpec extends GradleSpecification {
+
+    def "companion plugins package src/main/scripts only in the -cli jar"() {
+        given:
+        setupTestResourceProject('plugin-script-commands')
+
+        when:
+        def result = executeTask(':plugin-with-cli:inspectCommandPackaging')
+
+        then:
+        result.output.contains('RUNTIME_HAS_SCRIPT=false')
+        result.output.contains('RUNTIME_HAS_YAML=false')
+        result.output.contains('RUNTIME_HAS_REMOVED=false')
+        result.output.contains('RUNTIME_HAS_HAND_AUTHORED=true')
+        result.output.contains('CLI_HAS_SCRIPT=true')
+        result.output.contains('CLI_HAS_YAML=true')
+
+        and: 'templates remain a runtime concern'
+        result.output.contains('RUNTIME_HAS_TEMPLATE=true')
+    }
+
+    def "plugins without a companion keep src/main/scripts in the runtime 
jar"() {
+        given:
+        setupTestResourceProject('plugin-script-commands')
+
+        when:
+        def result = executeTask(':plugin-without-cli:inspectCommandPackaging')
+
+        then:
+        result.output.contains('RUNTIME_HAS_SCRIPT=true')
+        result.output.contains('RUNTIME_HAS_TEMPLATE=true')
+    }
+}
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/gradle.properties
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/gradle.properties
new file mode 100644
index 0000000000..aa8568e194
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/gradle.properties
@@ -0,0 +1,2 @@
+version=1.0.0
+grailsVersion=__PROJECT_VERSION__
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/build.gradle
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/build.gradle
new file mode 100644
index 0000000000..a52ffe5dc1
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/build.gradle
@@ -0,0 +1,74 @@
+// Plugin that publishes a companion -cli artifact. src/main/scripts must land 
in the companion
+// jar (META-INF/commands) and must not ship in the runtime plugin jar.
+plugins {
+    id 'org.apache.grails.gradle.grails-plugin'
+    id 'org.apache.grails.gradle.grails-plugin-cli'
+}
+
+group = 'org.example.test'
+
+grails {
+    bom = null
+    cliAutoProvision = false
+}
+
+cliArtifact {
+    defaultDependencies = false
+    automaticModuleName = 'org.example.test.plugin.with.cli'
+}
+
+// Simulate a prior non-companion build that left command scripts under main 
resources. The
+// companion packaging path must still keep them out of the runtime jar on an 
incremental build.
+// cleanStaleRuntimeCommandResources is registered in afterEvaluate by 
grails-plugin, so wire here too.
+afterEvaluate {
+    def staleDir = 
layout.buildDirectory.dir('resources/main/META-INF/commands')
+    def seed = tasks.register('seedStaleRuntimeCommands') {
+        outputs.dir(staleDir)
+        doLast {
+            def dir = staleDir.get().asFile
+            dir.mkdirs()
+            // Prior non-companion packaging left script copies in main 
resources.
+            new File(dir, 'example-script.groovy').text = 
"description('stale-copy') {}\n"
+            new File(dir, 'example-multi-step.yml').text = "description: 
stale\n"
+            new File(dir, 'removed-before-migration.groovy').text = 
"description('gone') {}\n"
+        }
+    }
+    tasks.named('cleanStaleRuntimeCommandResources').configure { cleanTask ->
+        cleanTask.mustRunAfter(seed)
+    }
+    tasks.named('jar', Jar).configure { jarTask ->
+        jarTask.dependsOn(seed)
+    }
+}
+
+tasks.register('inspectCommandPackaging') {
+    dependsOn 'jar', 'cliJar'
+    doLast {
+        def runtimeJar = tasks.named('jar', Jar).get().archiveFile.get().asFile
+        def cliJarFile = tasks.named('cliJar', 
Jar).get().archiveFile.get().asFile
+        def runtimeEntries = jarEntries(runtimeJar)
+        def cliEntries = jarEntries(cliJarFile)
+        println "RUNTIME_JAR=${runtimeJar.name}"
+        println "CLI_JAR=${cliJarFile.name}"
+        println 
"RUNTIME_HAS_SCRIPT=${runtimeEntries.contains('META-INF/commands/example-script.groovy')}"
+        println 
"RUNTIME_HAS_YAML=${runtimeEntries.contains('META-INF/commands/example-multi-step.yml')}"
+        println 
"RUNTIME_HAS_REMOVED=${runtimeEntries.contains('META-INF/commands/removed-before-migration.groovy')}"
+        println 
"RUNTIME_HAS_HAND_AUTHORED=${runtimeEntries.contains('META-INF/commands/hand-authored.yml')}"
+        println 
"CLI_HAS_SCRIPT=${cliEntries.contains('META-INF/commands/example-script.groovy')}"
+        println 
"CLI_HAS_YAML=${cliEntries.contains('META-INF/commands/example-multi-step.yml')}"
+        println 
"RUNTIME_HAS_TEMPLATE=${runtimeEntries.contains('META-INF/templates/example.txt')}"
+    }
+}
+
+static Set<String> jarEntries(File jarFile) {
+    def entries = new LinkedHashSet<String>()
+    jarFile.withInputStream { InputStream input ->
+        new java.util.jar.JarInputStream(input).withCloseable { 
java.util.jar.JarInputStream jar ->
+            java.util.jar.JarEntry entry
+            while ((entry = jar.nextJarEntry) != null) {
+                entries << entry.name
+            }
+        }
+    }
+    entries
+}
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/resources/META-INF/commands/hand-authored.yml
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/resources/META-INF/commands/hand-authored.yml
new file mode 100644
index 0000000000..7e38adafbc
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/resources/META-INF/commands/hand-authored.yml
@@ -0,0 +1 @@
+description: keep-me from main resources
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-multi-step.yml
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-multi-step.yml
new file mode 100644
index 0000000000..207165f05e
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-multi-step.yml
@@ -0,0 +1,3 @@
+description: Example multi-step command
+steps:
+  - grailsVersion
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-script.groovy
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-script.groovy
new file mode 100644
index 0000000000..f10b15d646
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/scripts/example-script.groovy
@@ -0,0 +1,5 @@
+description('Example companion script') {
+    usage 'grails example-script'
+}
+
+addStatus 'ran'
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/templates/example.txt
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/templates/example.txt
new file mode 100644
index 0000000000..e839883643
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-with-cli/src/main/templates/example.txt
@@ -0,0 +1 @@
+template body
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/build.gradle
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/build.gradle
new file mode 100644
index 0000000000..fce91ee563
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/build.gradle
@@ -0,0 +1,36 @@
+// Plugin without a companion -cli artifact. Historical packaging keeps 
src/main/scripts in the
+// runtime jar so unmigrated Grails 7 plugins and legacyCommandSupport 
consumers still discover them.
+plugins {
+    id 'org.apache.grails.gradle.grails-plugin'
+}
+
+group = 'org.example.test'
+
+grails {
+    bom = null
+    cliAutoProvision = false
+}
+
+tasks.register('inspectCommandPackaging') {
+    dependsOn 'jar'
+    doLast {
+        def runtimeJar = tasks.named('jar', Jar).get().archiveFile.get().asFile
+        def runtimeEntries = jarEntries(runtimeJar)
+        println "RUNTIME_JAR=${runtimeJar.name}"
+        println 
"RUNTIME_HAS_SCRIPT=${runtimeEntries.contains('META-INF/commands/legacy-only-script.groovy')}"
+        println 
"RUNTIME_HAS_TEMPLATE=${runtimeEntries.contains('META-INF/templates/legacy-only.txt')}"
+    }
+}
+
+static Set<String> jarEntries(File jarFile) {
+    def entries = new LinkedHashSet<String>()
+    jarFile.withInputStream { InputStream input ->
+        new java.util.jar.JarInputStream(input).withCloseable { 
java.util.jar.JarInputStream jar ->
+            java.util.jar.JarEntry entry
+            while ((entry = jar.nextJarEntry) != null) {
+                entries << entry.name
+            }
+        }
+    }
+    entries
+}
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/scripts/legacy-only-script.groovy
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/scripts/legacy-only-script.groovy
new file mode 100644
index 0000000000..913e20752c
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/scripts/legacy-only-script.groovy
@@ -0,0 +1,5 @@
+description('Legacy runtime-packaged script') {
+    usage 'grails legacy-only-script'
+}
+
+addStatus 'ran'
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/templates/legacy-only.txt
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/templates/legacy-only.txt
new file mode 100644
index 0000000000..ddb4f6bd9d
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/plugin-without-cli/src/main/templates/legacy-only.txt
@@ -0,0 +1 @@
+legacy template
diff --git 
a/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/settings.gradle
 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/settings.gradle
new file mode 100644
index 0000000000..e74cc7c976
--- /dev/null
+++ 
b/grails-gradle/plugins/src/test/resources/test-projects/plugin-script-commands/settings.gradle
@@ -0,0 +1,3 @@
+rootProject.name = 'plugin-script-commands'
+
+include 'plugin-with-cli', 'plugin-without-cli'

Reply via email to