diff --git a/modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy b/modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy index c0751e6aba..1bf137835c 100644 --- a/modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/cli/CmdPlugin.groovy @@ -48,6 +48,9 @@ class CmdPlugin extends CmdBase { @Parameter(hidden = true) List args + + @Parameter(names = ['-template'], description = 'Plugin template version to use', hidden = true) + String templateVersion = 'v0.3.0' @Override void run() { @@ -64,7 +67,7 @@ class CmdPlugin extends CmdBase { Plugins.pull(args[1].tokenize(',')) } else if( args[0] == 'create' ) { - createPlugin(args) + createPlugin(args, templateVersion) } // plugin run command else if( args[0].contains(CMD_SEP) ) { @@ -98,14 +101,14 @@ class CmdPlugin extends CmdBase { } } - static createPlugin(List args) { + static createPlugin(List args, String templateVersion) { if( args != ['create'] && (args[0] != 'create' || !(args.size() in [3, 4])) ) - throw new AbortOperationException("Invalid create parameters - usage: nextflow plugin create ") + throw new AbortOperationException("Invalid create parameters - usage: nextflow plugin create ") final refactor = new PluginRefactor() if( args.size()>1 ) { refactor.withPluginName(args[1]) - refactor.withOrgName(args[2]) + refactor.withProviderName(args[2]) refactor.withPluginDir(Path.of(args[3] ?: refactor.pluginName).toFile()) } else { @@ -113,11 +116,11 @@ class CmdPlugin extends CmdBase { print "Enter plugin name: " refactor.withPluginName(readLine()) - // Prompt for maintainer organization - print "Enter organization: " + // Prompt for provider name + print "Enter provider name: " + refactor.withProviderName(readLine()) - // Prompt for plugin path (default to the normalised plugin name) - refactor.withOrgName(readLine()) + // Prompt for project path (default to the normalised plugin name) print "Enter project path [${refactor.pluginName}]: " refactor.withPluginDir(Path.of(readLine() ?: refactor.pluginName).toFile()) @@ -132,7 +135,7 @@ class CmdPlugin extends CmdBase { final File targetDir = refactor.getPluginDir() // clone the template repo - clonePluginTemplate(targetDir) + clonePluginTemplate(targetDir, templateVersion) // now refactor the template code refactor.apply() // remove git plat @@ -148,15 +151,25 @@ class CmdPlugin extends CmdBase { : new BufferedReader(new InputStreamReader(System.in)).readLine() } - static private void clonePluginTemplate(File targetDir) { + static private void clonePluginTemplate(File targetDir, String templateVersion) { final templateUri = "https://github.com/nextflow-io/nf-plugin-template.git" + final isTag = templateVersion.startsWith('v') + final refSpec = isTag ? "refs/tags/$templateVersion".toString() : templateVersion + try { - Git.cloneRepository() + final gitCmd = Git.cloneRepository() .setURI(templateUri) .setDirectory(targetDir) - .setBranchesToClone(["refs/tags/v0.2.0"]) - .setBranch("refs/tags/v0.2.0") - .call() + + if (isTag) { + gitCmd.setBranchesToClone([refSpec]) + gitCmd.setBranch(refSpec) + } else { + // For branches, let Git handle the default behavior + gitCmd.setBranch(templateVersion) + } + + gitCmd.call() } catch (Exception e) { throw new AbortOperationException("Unable to clone pluging template repository - cause: ${e.message}") diff --git a/modules/nextflow/src/test/groovy/nextflow/cli/CmdPluginCreateTest.groovy b/modules/nextflow/src/test/groovy/nextflow/cli/CmdPluginCreateTest.groovy index 23e506c653..1ca92fa795 100644 --- a/modules/nextflow/src/test/groovy/nextflow/cli/CmdPluginCreateTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/cli/CmdPluginCreateTest.groovy @@ -40,7 +40,7 @@ class CmdPluginCreateTest extends Specification { def args = [ 'create', 'hello world plugin', - 'foo', + 'Foo', folder.toAbsolutePath().toString() + '/hello'] when: @@ -58,7 +58,7 @@ class CmdPluginCreateTest extends Specification { Files.exists(folder.resolve('hello/src/test/groovy/foo/plugin/HelloWorldObserverTest.groovy')) and: Path.of(folder.resolve('hello/settings.gradle').toUri()).text.contains("rootProject.name = 'hello-world-plugin'") - Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'foo'") + Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("provider = 'Foo'") Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("className = 'foo.plugin.HelloWorldPlugin'") Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldExtension'") Path.of(folder.resolve('hello/build.gradle').toUri()).text.contains("'foo.plugin.HelloWorldFactory'") diff --git a/modules/nf-commons/src/main/nextflow/plugin/util/PluginRefactor.groovy b/modules/nf-commons/src/main/nextflow/plugin/util/PluginRefactor.groovy index ce9587a8fa..2b6de5e4a2 100644 --- a/modules/nf-commons/src/main/nextflow/plugin/util/PluginRefactor.groovy +++ b/modules/nf-commons/src/main/nextflow/plugin/util/PluginRefactor.groovy @@ -30,7 +30,9 @@ class PluginRefactor { private String pluginName - private String orgName + private String providerName + + private String packageName private File pluginDir @@ -46,8 +48,12 @@ class PluginRefactor { return pluginName } - String getOrgName() { - return orgName + String getPackageName() { + return packageName + } + + String getProviderName() { + return providerName } File getPluginDir() { @@ -69,18 +75,21 @@ class PluginRefactor { return this } - PluginRefactor withOrgName(String name) { - this.orgName = normalizeToPackageNameSegment(name) - if( !orgName ) - throw new AbortOperationException("Invalid organization name: '$name'") + PluginRefactor withProviderName(String name) { + this.providerName = name?.trim() + if( !providerName ) + throw new AbortOperationException("Provider name cannot be empty or blank") + this.packageName = normalizeToPackageNameSegment(name) + if( !packageName ) + throw new AbortOperationException("Invalid provider name: $name") return this } protected void init() { if( !pluginName ) throw new IllegalStateException("Missing plugin name") - if( !orgName ) - throw new IllegalStateException("Missing organization name") + if( !providerName ) + throw new IllegalStateException("Missing provider name") // initial this.gradleBuildFile = new File(pluginDir, 'build.gradle') this.gradleSettingsFile = new File(pluginDir, 'settings.gradle') @@ -88,18 +97,21 @@ class PluginRefactor { throw new AbortOperationException("Plugin file does not exist: $gradleBuildFile") if( !gradleSettingsFile.exists() ) throw new AbortOperationException("Plugin file does not exist: $gradleSettingsFile") - if( !orgName ) - throw new AbortOperationException("Plugin org name is missing") + if( !providerName ) + throw new AbortOperationException("Plugin provider name is missing") + if( !packageName ) + throw new AbortOperationException("Plugin package name is missing") // packages to be updates - tokenMapping.put('acme', orgName) + tokenMapping.put('acme', packageName) + tokenMapping.put('provider-name', providerName) tokenMapping.put('nf-plugin-template', pluginName) } void apply() { init() replacePrefixInFiles(pluginDir, pluginClassPrefix) - renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${orgName}")) - renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${orgName}")) + renameDirectory(new File(pluginDir, "src/main/groovy/acme"), new File(pluginDir, "src/main/groovy/${packageName}")) + renameDirectory(new File(pluginDir, "src/test/groovy/acme"), new File(pluginDir, "src/test/groovy/${packageName}")) updateClassNamesAndSymbols(pluginDir) } @@ -173,7 +185,9 @@ class PluginRefactor { .trim() // Split by whitespace, capitalize each word, join them final parts = cleaned.split(/\s+/).collect { it.capitalize() } - return parts.join('').replace('Plugin','') + final result = parts.join('').replace('Plugin','') + // Remove "Nf" prefix only + return result.startsWith('Nf') ? result.substring(2) : result } static String normalizeToKebabCase(String input) { diff --git a/modules/nf-commons/src/test/nextflow/plugin/util/PluginRefactorTest.groovy b/modules/nf-commons/src/test/nextflow/plugin/util/PluginRefactorTest.groovy index 39a1d9fc63..3835e2bdb7 100644 --- a/modules/nf-commons/src/test/nextflow/plugin/util/PluginRefactorTest.groovy +++ b/modules/nf-commons/src/test/nextflow/plugin/util/PluginRefactorTest.groovy @@ -40,6 +40,10 @@ class PluginRefactorTest extends Specification { " mixed---separators___" || "MixedSeparators" "alreadyPascalCase" || "AlreadyPascalCase" "foo-plugin" || "Foo" + "nf-hello" || "Hello" + "nf-my-plugin" || "My" + "NfHelloWorld" || "HelloWorld" + "my-nf-plugin" || "MyNf" } def "should normalize strings into kebab-case names"() {