From 417aac8bce406a268ccde845dfb414ec9a421ff4 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 29 Jul 2025 12:47:14 +0200 Subject: [PATCH 1/4] Rename releasePlugin task to publishPlugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rename the releasePlugin task to publishPlugin for consistency with other plugin-specific publishing tasks like publishPluginToRegistry and publishPluginToGithub. This also avoids potential conflicts with the generic 'publish' task from the maven-publish plugin. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy index 7d57f8c..841fe35 100644 --- a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy +++ b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy @@ -161,15 +161,15 @@ class NextflowPlugin implements Plugin { } } - // finally, configure the destination-agnostic 'release' task + // finally, configure the destination-agnostic 'publish' task if (!publishTasks.isEmpty()) { - // releasePlugin - all the release/publishing actions - project.tasks.register('releasePlugin', { + // publishPlugin - all the release/publishing actions + project.tasks.register('publishPlugin', { group = 'Nextflow Plugin' description = 'publish plugin to configured destinations' }) for (task in publishTasks) { - project.tasks.releasePlugin.dependsOn << task + project.tasks.publishPlugin.dependsOn << task } } } From e719712af7ad3da457b832ee04e4c63640dcdf68 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 29 Jul 2025 12:56:37 +0200 Subject: [PATCH 2/4] Add unit tests for publishPlugin task rename MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive Spock tests validating publishPlugin task behavior - Test task registration with different publishing configurations - Test task dependencies for registry and GitHub publishing - Test task is not created when no publishing is configured - Add required test dependencies (Spock framework) 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- build.gradle | 6 + .../nextflow/gradle/NextflowPluginTest.groovy | 167 ++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy diff --git a/build.gradle b/build.gradle index a484fed..3c013ec 100644 --- a/build.gradle +++ b/build.gradle @@ -18,6 +18,12 @@ dependencies { implementation 'com.google.code.gson:gson:2.10.1' implementation 'org.apache.httpcomponents:httpclient:4.5.14' implementation 'org.apache.httpcomponents:httpmime:4.5.14' + + testImplementation('org.spockframework:spock-core:2.3-groovy-3.0') +} + +test { + useJUnitPlatform() } shadowJar { diff --git a/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy new file mode 100644 index 0000000..5742855 --- /dev/null +++ b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy @@ -0,0 +1,167 @@ +package io.nextflow.gradle + +import org.gradle.api.Project +import org.gradle.testfixtures.ProjectBuilder +import spock.lang.Specification + +/** + * Unit tests for NextflowPlugin + */ +class NextflowPluginTest extends Specification { + + Project project + + def setup() { + project = ProjectBuilder.builder() + .withName('test-plugin') + .build() + project.version = '1.0.0' + project.pluginManager.apply('io.nextflow.nextflow-plugin') + } + + def "should register publishPlugin task when publishing is configured"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + publishing { + registry { + url = 'https://example.com/registry' + } + } + } + + when: + project.evaluate() + + then: + project.tasks.findByName('publishPlugin') != null + project.tasks.publishPlugin.group == 'Nextflow Plugin' + project.tasks.publishPlugin.description == 'publish plugin to configured destinations' + } + + def "should register publishPlugin task when GitHub publishing is configured"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + publishing { + github { + repository = 'test-owner/test-repo' + authToken = 'test-token' + } + } + } + + when: + project.evaluate() + + then: + project.tasks.findByName('publishPlugin') != null + } + + def "should make publishPlugin depend on registry publishing task"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + publishing { + registry { + url = 'https://example.com/registry' + } + } + } + + when: + project.evaluate() + + then: + def publishPlugin = project.tasks.publishPlugin + def publishToRegistry = project.tasks.publishPluginToRegistry + publishPlugin.taskDependencies.getDependencies(publishPlugin).contains(publishToRegistry) + } + + def "should make publishPlugin depend on GitHub publishing tasks"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + publishing { + github { + repository = 'test-owner/test-repo' + authToken = 'test-token' + updateIndex = true + } + } + } + + when: + project.evaluate() + + then: + def publishPlugin = project.tasks.publishPlugin + def publishToGithub = project.tasks.publishPluginToGithub + def updateIndex = project.tasks.updateGithubIndex + def dependencies = publishPlugin.taskDependencies.getDependencies(publishPlugin) + dependencies.contains(publishToGithub) + dependencies.contains(updateIndex) + } + + def "should not register publishPlugin task when no publishing is configured"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + } + + when: + project.evaluate() + + then: + project.tasks.findByName('publishPlugin') == null + } + + def "should register publishPlugin with both registry and GitHub publishing"() { + given: + project.nextflowPlugin { + description = 'A test plugin' + provider = 'Test Author' + className = 'com.example.TestPlugin' + nextflowVersion = '24.04.0' + extensionPoints = ['com.example.TestExtension'] + publishing { + registry { + url = 'https://example.com/registry' + } + github { + repository = 'test-owner/test-repo' + authToken = 'test-token' + } + } + } + + when: + project.evaluate() + + then: + def publishPlugin = project.tasks.publishPlugin + def dependencies = publishPlugin.taskDependencies.getDependencies(publishPlugin) + dependencies.contains(project.tasks.publishPluginToRegistry) + dependencies.contains(project.tasks.publishPluginToGithub) + } +} \ No newline at end of file From 4e76b53d1d42543b8fc1af2c28e7a811833d9a90 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Tue, 29 Jul 2025 13:02:08 +0200 Subject: [PATCH 3/4] Remove GitHub publishing functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove GitHub publishing tasks (generateGithubMeta, publishPluginToGithub, updateGithubIndex) - Remove GitHub-related imports from NextflowPlugin.groovy - Delete entire github/ directory with all GitHub publishing classes - Update PluginPublishConfig to remove GitHub configuration - Remove GitHub-related test cases from NextflowPluginTest - Now only supports registry publishing via publishPluginToRegistry task 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../io/nextflow/gradle/NextflowPlugin.groovy | 25 -- .../gradle/PluginPublishConfig.groovy | 12 - .../gradle/github/GithubClient.groovy | 235 ------------------ .../gradle/github/GithubPublishConfig.groovy | 63 ----- .../gradle/github/GithubUploadTask.groovy | 70 ------ .../gradle/github/PluginMetadataTask.groovy | 57 ----- .../gradle/github/PluginsIndex.groovy | 64 ----- .../gradle/github/UpdateJsonIndexTask.groovy | 81 ------ .../nextflow/gradle/NextflowPluginTest.groovy | 78 ------ 9 files changed, 685 deletions(-) delete mode 100644 src/main/groovy/io/nextflow/gradle/github/GithubClient.groovy delete mode 100644 src/main/groovy/io/nextflow/gradle/github/GithubPublishConfig.groovy delete mode 100644 src/main/groovy/io/nextflow/gradle/github/GithubUploadTask.groovy delete mode 100644 src/main/groovy/io/nextflow/gradle/github/PluginMetadataTask.groovy delete mode 100644 src/main/groovy/io/nextflow/gradle/github/PluginsIndex.groovy delete mode 100644 src/main/groovy/io/nextflow/gradle/github/UpdateJsonIndexTask.groovy diff --git a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy index 841fe35..fe84ef2 100644 --- a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy +++ b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy @@ -1,8 +1,5 @@ package io.nextflow.gradle -import io.nextflow.gradle.github.GithubUploadTask -import io.nextflow.gradle.github.PluginMetadataTask -import io.nextflow.gradle.github.UpdateJsonIndexTask import io.nextflow.gradle.registry.RegistryUploadTask import org.gradle.api.Plugin import org.gradle.api.Project @@ -138,28 +135,6 @@ class NextflowPlugin implements Plugin { publishTasks << project.tasks.publishPluginToRegistry } - // add github publish task(s), if configured - if (config.publishing.github) { - // generateGithubMeta - creates the meta.json file - project.tasks.register('generateGithubMeta', PluginMetadataTask) - project.tasks.generateGithubMeta.dependsOn << project.tasks.packagePlugin - project.tasks.assemble.dependsOn << project.tasks.generateGithubMeta - - // publishPluginToGithub - publishes plugin assets to a github repo - project.tasks.register('publishPluginToGithub', GithubUploadTask) - project.tasks.publishPluginToGithub.dependsOn << [ - project.tasks.packagePlugin, - project.tasks.generateGithubMeta - ] - publishTasks << project.tasks.publishPluginToGithub - - // updateGithubIndex - updates the central plugins.json index - if (config.publishing.github.updateIndex) { - project.tasks.register('updateGithubIndex', UpdateJsonIndexTask) - project.tasks.updateGithubIndex.dependsOn << project.tasks.generateGithubMeta - publishTasks << project.tasks.updateGithubIndex - } - } // finally, configure the destination-agnostic 'publish' task if (!publishTasks.isEmpty()) { diff --git a/src/main/groovy/io/nextflow/gradle/PluginPublishConfig.groovy b/src/main/groovy/io/nextflow/gradle/PluginPublishConfig.groovy index a7441f4..9a2d164 100644 --- a/src/main/groovy/io/nextflow/gradle/PluginPublishConfig.groovy +++ b/src/main/groovy/io/nextflow/gradle/PluginPublishConfig.groovy @@ -1,7 +1,6 @@ package io.nextflow.gradle import groovy.transform.CompileStatic -import io.nextflow.gradle.github.GithubPublishConfig import io.nextflow.gradle.registry.RegistryPublishConfig import org.gradle.api.Project @@ -13,11 +12,6 @@ import org.gradle.api.Project class PluginPublishConfig { private final Project project - /** - * Configuration for publishing to github - */ - GithubPublishConfig github - /** * Configuration for publishing to a registry */ @@ -29,12 +23,6 @@ class PluginPublishConfig { def validate() {} - // initialises the 'github' sub-config - def github(Closure config) { - github = new GithubPublishConfig(project) - project.configure(github, config) - } - def registry(Closure config) { registry = new RegistryPublishConfig(project) project.configure(registry, config) diff --git a/src/main/groovy/io/nextflow/gradle/github/GithubClient.groovy b/src/main/groovy/io/nextflow/gradle/github/GithubClient.groovy deleted file mode 100644 index ba7e730..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/GithubClient.groovy +++ /dev/null @@ -1,235 +0,0 @@ -package io.nextflow.gradle.github - -import com.google.gson.Gson -import groovy.transform.CompileStatic -import groovy.transform.PackageScope -import groovy.util.logging.Slf4j - -/** - * Simple Github HTTP client - * - * https://stackoverflow.com/a/63461333/395921 - * - * @author Paolo Di Tommaso - */ -@Slf4j -@PackageScope -class GithubClient { - String authToken - String userName - String branch - String repo - String owner - String email - - private Gson gson = new Gson() - - private String getEncodedAuthToken() { - if (!userName) - throw new IllegalArgumentException("Missing Github userName") - if (!authToken) - throw new IllegalArgumentException("Missing Github authToken") - return "$userName:$authToken".bytes.encodeBase64().toString() - } - - private HttpURLConnection getHttpConnection(String url) { - new URL(url).openConnection() as HttpURLConnection - } - - private sendHttpMessage(String endpoint, String payload, String method = 'POST') { - if (!endpoint) - throw new IllegalArgumentException("Missing Github target endpoint") - - def con = getHttpConnection(endpoint) - // Make header settings - con.setRequestMethod(method) - con.setRequestProperty("Content-Type", "application/json") - con.setRequestProperty("Authorization", "Basic ${getEncodedAuthToken()}") - - con.setDoOutput(true) - - // Send POST request - if (payload) { - DataOutputStream output = new DataOutputStream(con.getOutputStream()) - output.writeBytes(payload) - output.flush() - output.close() - } - - int code - try { - code = con.responseCode - final text = con.getInputStream().text - log.trace "resp code=$code, text=$text" - return gson.fromJson(text, Map) - } - catch (IOException e) { - final text = con.getErrorStream().text - throw new IllegalStateException("Unexpected response code=$code\n- response=$text\n- request=$endpoint\n- payload=$payload") - } - } - - /** - * 1. Get the last commit SHA of a specific branch - * - * @return the SHA id of the last commit - */ - private String lastCommitId() { - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/branches/$branch", null, 'GET') - return resp.commit.sha - } - - /** - * 2. Create the blobs with the file content - * - * @param file content - * @return the SHA id of the uploaded content - */ - private String uploadBlob(String file) { - def content = "{\"encoding\": \"base64\", \"content\": \"${file.bytes.encodeBase64().toString()}\"}" - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/git/blobs", content, 'POST') - return resp.sha - } - - /** - * 3. Create a tree that defines the file structure - * - * @param fileName the name of the file changed - * @param blobId the id of the changed content - * @param lastCommitId the last commit id - * @return the SHA id of the tree structure - */ - private String createTree(String fileName, String blobId, String lastCommitId) { - def content = "{ \"base_tree\": \"$lastCommitId\", \"tree\": [{\"path\": \"$fileName\",\"mode\": \"100644\",\"type\": \"blob\",\"sha\": \"$blobId\"}]}" - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/git/trees", content, 'POST') - return resp.sha - } - - /** - * 4. Create the commit - * - * @param treeId the change tree SHA id - * @param lastCommitId the last commit SHA id - * @param message the commit message - * @param author the commit author name - * @param email the commit author email address - * @return the SHA id of the commit - */ - private String createCommit(String treeId, String lastCommitId, String message, String email) { - def content = "{\"message\": \"$message\", \"author\": {\"name\": \"$userName\", \"email\": \"$email\"}, \"parents\": [\"$lastCommitId\"], \"tree\": \"$treeId\" }" - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/git/commits", content, 'POST') - return resp.sha - } - - /** - * 5. Update the reference of your branch to point to the new commit SHA - * - * @param commitId - * @return the response message - */ - private def updateRef(String commitId) { - def content = "{\"ref\": \"refs/heads/$branch\", \"sha\": \"$commitId\"}" - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/git/refs/heads/$branch", content, 'POST') - return resp - } - - void pushChange(String fileName, String content, String message) { - if (content == null) - throw new IllegalArgumentException("Missing content argument") - if (!fileName) - throw new IllegalArgumentException("Missing fileName argument") - if (!email) - throw new IllegalArgumentException("Missing email argument") - - final lastCommit = lastCommitId() - final blobId = uploadBlob(content) - final treeId = createTree(fileName, blobId, lastCommit) - final commitId = createCommit(treeId, lastCommit, message, email) - updateRef(commitId) - } - - String getContent(String path) { - def resp = sendHttpMessage("https://api.github.com/repos/$owner/$repo/contents/$path", null, 'GET') - def bytes = resp.content?.toString()?.decodeBase64() - return bytes != null ? new String(bytes) : null - } - - Map getRelease(String version) { - def action = "https://api.github.com/repos/$owner/$repo/releases/tags/$version" - try { - def resp = sendHttpMessage(action, null, 'GET') - return resp - } - catch (Exception e) { - return null - } - } - - Map createRelease(String version, boolean prerelease = false) { - final action = "https://api.github.com/repos/${owner}/${repo}/releases" - final payload = "{\"tag_name\":\"$version\", \"name\": \"Version $version\", \"draft\":false, \"prerelease\":$prerelease}" - Map resp = sendHttpMessage(action, payload, 'POST') - return resp - } - - List listReleases() { - final action = "https://api.github.com/repos/${owner}/${repo}/releases" - return (List) sendHttpMessage(action, null, 'GET') - } - - /** - * https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#get-a-release-asset - */ - InputStream getAsset(String assetId) { - final action = "https://api.github.com/repos/$owner/$repo/releases/assets/$assetId" - final con = getHttpConnection(action) - - // Make header settings - con.setRequestMethod('GET') - con.setRequestProperty("Content-Type", "application/json") - con.setRequestProperty("Authorization", "Basic ${getEncodedAuthToken()}") - con.setRequestProperty("Accept", "application/octet-stream") - - con.setDoOutput(true) - - return con.getInputStream() - } - - InputStream getReleaseAsset(Map release, String name) { - if (!release) return null - def asset = (Map) release.assets.find { it.name == name } - if (!asset) return null - - return getAsset((asset.id as Long).toString()) - } - - /** - * https://docs.github.com/en/free-pro-team@latest/rest/reference/repos#upload-a-release-asset - */ - def uploadReleaseAsset(Map release, File file, mimeType) { - if (!release) return - final releaseId = (release.id as Long).toString() - - def action = "https://uploads.github.com/repos/${owner}/${repo}/releases/${releaseId}/assets?name=${file.name}" - - def con = getHttpConnection(action) - // Make header settings - con.setRequestMethod('POST') - con.setRequestProperty("Content-Type", mimeType) - con.setRequestProperty("Content-Length", file.size().toString()) - con.setRequestProperty("Authorization", "Basic ${getEncodedAuthToken()}") - - con.setDoOutput(true) - - DataOutputStream output = new DataOutputStream(con.getOutputStream()) - output.write(file.bytes) - output.flush() - output.close() - - def resp = con.responseCode >= 400 - ? con.getErrorStream().text - : con.getInputStream().text - - return resp - } -} diff --git a/src/main/groovy/io/nextflow/gradle/github/GithubPublishConfig.groovy b/src/main/groovy/io/nextflow/gradle/github/GithubPublishConfig.groovy deleted file mode 100644 index 0d7d1a5..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/GithubPublishConfig.groovy +++ /dev/null @@ -1,63 +0,0 @@ -package io.nextflow.gradle.github - -import groovy.transform.CompileStatic -import org.gradle.api.Project - -@CompileStatic -class GithubPublishConfig { - private final Project project - - /** - * Github repository to upload to (eg. 'nextflow-io/nf-hello') - */ - String repository - - /** - * Github username - */ - String userName - - /** - * Github email address - */ - String email - - /** - * Github authentication token - */ - String authToken - - /** - * Overwrite existing files in the release? - */ - boolean overwrite = false - - /** - * Enable/disable publishing to the plugin index. - */ - boolean updateIndex = true - - /** - * The url of the json plugins index on github - */ - String indexUrl = 'https://github.com/nextflow-io/plugins/main/plugins.json' - - GithubPublishConfig(Project project) { - this.project = project - } - - // split the 'repository' string into (github_org, repo) - def repositoryParts() { - final parts = repository.tokenize('/') - if (parts.size() != 2) { - throw new RuntimeException("nextflow.github.repository should be of form '{github_org}/{repo}', eg 'nextflow-io/nf-hello'") - } - return parts - } - - // the url the published plugin should have - def publishedUrl() { - final fileName = "${project.name}-${project.version}.zip" - return "https://github.com/${repository}/releases/download/${project.version}/${fileName}" - } -} diff --git a/src/main/groovy/io/nextflow/gradle/github/GithubUploadTask.groovy b/src/main/groovy/io/nextflow/gradle/github/GithubUploadTask.groovy deleted file mode 100644 index d642a23..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/GithubUploadTask.groovy +++ /dev/null @@ -1,70 +0,0 @@ -package io.nextflow.gradle.github - -import io.nextflow.gradle.NextflowPluginConfig -import org.gradle.api.DefaultTask -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.TaskAction - -/** - * Gradle task to upload assembled plugin and metadata file to a Github release. - */ -class GithubUploadTask extends DefaultTask { - @InputFile - final RegularFileProperty zipFile - @InputFile - final RegularFileProperty jsonFile - - GithubUploadTask() { - group = 'Nextflow Plugin' - description = 'Publish the assembled plugin to a Github repository' - - final buildDir = project.layout.buildDirectory.get() - zipFile = project.objects.fileProperty() - zipFile.convention(project.provider { - buildDir.file("distributions/${project.name}-${project.version}.zip") - }) - jsonFile = project.objects.fileProperty() - jsonFile.convention(project.provider { - buildDir.file("distributions/${project.name}-${project.version}-meta.json") - }) - } - - @TaskAction - def run() { - final version = project.version.toString() - final plugin = project.extensions.getByType(NextflowPluginConfig) - final config = plugin.publishing.github - - // github client - def (owner, repo) = config.repositoryParts() - final github = new GithubClient(authToken: config.authToken, userName: config.userName, - owner: owner, repo: repo) - - // create the github release, if it doesn't already exist - def release = github.getRelease(version) - if (!release) { - logger.quiet("Creating release ${config.repository} ${version}") - release = github.createRelease(version) - } - - // upload files to github release - final uploader = new Uploader(github: github, config: config) - uploader.uploadAsset(release, project.file(zipFile), 'application/zip') - uploader.uploadAsset(release, project.file(jsonFile), 'application/json') - } - - class Uploader { - GithubClient github - GithubPublishConfig config - - def uploadAsset(Map release, File file, String mimeType) { - if (!config.overwrite && github.getReleaseAsset(release, file.name)) { - logger.quiet("Already exists on release, skipping: '${file.name}'") - } else { - logger.quiet("Uploading ${file.name}") - github.uploadReleaseAsset(release, file, mimeType) - } - } - } -} diff --git a/src/main/groovy/io/nextflow/gradle/github/PluginMetadataTask.groovy b/src/main/groovy/io/nextflow/gradle/github/PluginMetadataTask.groovy deleted file mode 100644 index 5c36a91..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/PluginMetadataTask.groovy +++ /dev/null @@ -1,57 +0,0 @@ -package io.nextflow.gradle.github - -import groovy.json.JsonOutput -import io.nextflow.gradle.NextflowPluginConfig -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction - -import java.security.MessageDigest -import java.time.OffsetDateTime -import java.time.format.DateTimeFormatter - -/** - * Gradle task to create the Nextflow plugin metadata file - */ -class PluginMetadataTask extends DefaultTask { - @InputFile - final RegularFileProperty inputFile - @OutputFile - final RegularFileProperty outputFile - - PluginMetadataTask() { - final buildDir = project.layout.buildDirectory.get() - inputFile = project.objects.fileProperty() - inputFile.convention(project.provider { - buildDir.file("distributions/${project.name}-${project.version}.zip") - }) - - outputFile = project.objects.fileProperty() - outputFile.convention(project.provider { - buildDir.file("distributions/${project.name}-${project.version}-meta.json") - }) - } - - @TaskAction - def run() { - final config = project.extensions.getByType(NextflowPluginConfig) - final metadata = [ - version : "${project.version}", - date : OffsetDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME), - url : config.publishing.github.publishedUrl(), - requires : ">=${config.nextflowVersion}", - sha512sum: computeSha512(project.file(inputFile)) - ] - project.file(outputFile).text = JsonOutput.prettyPrint(JsonOutput.toJson(metadata)) - } - - private static String computeSha512(File file) { - if (!file.exists()) { - throw new GradleException("Missing file: $file -- cannot compute SHA-512") - } - MessageDigest.getInstance("SHA-512").digest(file.bytes).encodeHex().toString() - } -} diff --git a/src/main/groovy/io/nextflow/gradle/github/PluginsIndex.groovy b/src/main/groovy/io/nextflow/gradle/github/PluginsIndex.groovy deleted file mode 100644 index 7281565..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/PluginsIndex.groovy +++ /dev/null @@ -1,64 +0,0 @@ -package io.nextflow.gradle.github - -import com.google.gson.Gson -import com.google.gson.GsonBuilder -import com.google.gson.reflect.TypeToken -import groovy.transform.CompileStatic -import groovy.transform.EqualsAndHashCode -import groovy.transform.PackageScope - -/** - * Represents the data in plugins.json index file - */ -@PackageScope -class PluginsIndex { - private final List plugins - - private PluginsIndex(List plugins) { - this.plugins = plugins - } - - PluginMeta getPlugin(String id) { - this.plugins.find { p -> p.id == id } - } - - def add(PluginMeta plugin) { - this.plugins.add(plugin) - } - - String toJson() { - new GsonBuilder() - .setPrettyPrinting() - .disableHtmlEscaping() - .create() - .toJson(plugins) + '\n' - } - - static PluginsIndex fromJson(String json) { - final type = new TypeToken>() {}.getType() - final data = new Gson().fromJson(json, type) - new PluginsIndex(data) - } -} - -@PackageScope -@CompileStatic -@EqualsAndHashCode -class PluginMeta { - String id - String name - String provider - String description - List releases -} - -@PackageScope -@CompileStatic -@EqualsAndHashCode -class PluginRelease { - String version - String url - String date - String sha512sum - String requires -} \ No newline at end of file diff --git a/src/main/groovy/io/nextflow/gradle/github/UpdateJsonIndexTask.groovy b/src/main/groovy/io/nextflow/gradle/github/UpdateJsonIndexTask.groovy deleted file mode 100644 index cbd0dc7..0000000 --- a/src/main/groovy/io/nextflow/gradle/github/UpdateJsonIndexTask.groovy +++ /dev/null @@ -1,81 +0,0 @@ -package io.nextflow.gradle.github - -import com.google.gson.Gson -import io.nextflow.gradle.NextflowPluginConfig -import org.gradle.api.DefaultTask -import org.gradle.api.GradleException -import org.gradle.api.file.RegularFileProperty -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.TaskAction - -/** - * Gradle task to update the plugins.json index file in a github repo. - */ -class UpdateJsonIndexTask extends DefaultTask { - @InputFile - final RegularFileProperty jsonFile - - UpdateJsonIndexTask() { - group = 'Nextflow Plugin' - description = 'Publish the plugin metadata to the Nextflow plugins index' - - final buildDir = project.layout.buildDirectory.get() - jsonFile = project.objects.fileProperty() - jsonFile.convention(project.provider { - buildDir.file("distributions/${project.name}-${project.version}-meta.json") - }) - } - - @TaskAction - def run() { - final plugin = project.extensions.getByType(NextflowPluginConfig) - final config = plugin.publishing.github - final indexUrl = config.indexUrl - - // github client - def (org, repo, branch, filename) = new URI(indexUrl).path.tokenize('/') - final github = new GithubClient(owner: org, repo: repo, branch: branch, - userName: config.userName, email: config.email, authToken: config.authToken) - - // download the existing plugins index - def index = PluginsIndex.fromJson(github.getContent(filename)) - - // parse the meta.json file for this plugin release - def meta = new PluginMeta(id: project.name, provider: plugin.provider, releases: []) - def release = new Gson().fromJson(project.file(jsonFile).text, PluginRelease) - - // merge it into the index - if (updateIndex(index, meta, release)) { - // push changes to central index - logger.quiet("Pushing merged index to $indexUrl") - github.pushChange(filename, index.toJson(), "${meta.id} version ${release.version}") - } - } - - private static boolean updateIndex(PluginsIndex index, PluginMeta meta, PluginRelease release) { - def updated = false - - // get or add the entry for this plugin id - def plugin = index.getPlugin(meta.id) - if (!plugin) { - index.add(meta) - plugin = meta - } - - // look for an existing release with this version - def existing = plugin.releases.find { r -> r.version == release.version } - if (!existing) { - // add release if doesn't exist - plugin.releases.add(release) - updated = true - } else if (existing.sha512sum != release.sha512sum) { - // error if release exists but checksums different - throw new GradleException(""" - Plugin ${meta.id}@${release.version} already exists in index: - - index sha512sum: ${existing.sha512sum} - - repo sha512sum : ${release.sha512sum} - """) - } - updated - } -} diff --git a/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy index 5742855..759bca5 100644 --- a/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy +++ b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy @@ -43,28 +43,6 @@ class NextflowPluginTest extends Specification { project.tasks.publishPlugin.description == 'publish plugin to configured destinations' } - def "should register publishPlugin task when GitHub publishing is configured"() { - given: - project.nextflowPlugin { - description = 'A test plugin' - provider = 'Test Author' - className = 'com.example.TestPlugin' - nextflowVersion = '24.04.0' - extensionPoints = ['com.example.TestExtension'] - publishing { - github { - repository = 'test-owner/test-repo' - authToken = 'test-token' - } - } - } - - when: - project.evaluate() - - then: - project.tasks.findByName('publishPlugin') != null - } def "should make publishPlugin depend on registry publishing task"() { given: @@ -90,34 +68,6 @@ class NextflowPluginTest extends Specification { publishPlugin.taskDependencies.getDependencies(publishPlugin).contains(publishToRegistry) } - def "should make publishPlugin depend on GitHub publishing tasks"() { - given: - project.nextflowPlugin { - description = 'A test plugin' - provider = 'Test Author' - className = 'com.example.TestPlugin' - nextflowVersion = '24.04.0' - extensionPoints = ['com.example.TestExtension'] - publishing { - github { - repository = 'test-owner/test-repo' - authToken = 'test-token' - updateIndex = true - } - } - } - - when: - project.evaluate() - - then: - def publishPlugin = project.tasks.publishPlugin - def publishToGithub = project.tasks.publishPluginToGithub - def updateIndex = project.tasks.updateGithubIndex - def dependencies = publishPlugin.taskDependencies.getDependencies(publishPlugin) - dependencies.contains(publishToGithub) - dependencies.contains(updateIndex) - } def "should not register publishPlugin task when no publishing is configured"() { given: @@ -136,32 +86,4 @@ class NextflowPluginTest extends Specification { project.tasks.findByName('publishPlugin') == null } - def "should register publishPlugin with both registry and GitHub publishing"() { - given: - project.nextflowPlugin { - description = 'A test plugin' - provider = 'Test Author' - className = 'com.example.TestPlugin' - nextflowVersion = '24.04.0' - extensionPoints = ['com.example.TestExtension'] - publishing { - registry { - url = 'https://example.com/registry' - } - github { - repository = 'test-owner/test-repo' - authToken = 'test-token' - } - } - } - - when: - project.evaluate() - - then: - def publishPlugin = project.tasks.publishPlugin - def dependencies = publishPlugin.taskDependencies.getDependencies(publishPlugin) - dependencies.contains(project.tasks.publishPluginToRegistry) - dependencies.contains(project.tasks.publishPluginToGithub) - } } \ No newline at end of file From 256b986c8703c68f585727076f53632ce3ad6e81 Mon Sep 17 00:00:00 2001 From: Paolo Di Tommaso Date: Wed, 30 Jul 2025 14:24:47 +0200 Subject: [PATCH 4/4] Rename publishPlugin task back to releasePlugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rename publishPlugin -> releasePlugin - Rename publishPluginToRegistry -> releasePluginToRegistry - Update task descriptions to use proper capitalization - Update all tests and documentation references 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- README.md | 6 ++--- .../io/nextflow/gradle/NextflowPlugin.groovy | 18 +++++++-------- .../nextflow/gradle/NextflowPluginTest.groovy | 22 +++++++++---------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 8b93508..1c80791 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ nextflowPlugin { ``` This will add some useful tasks to your Gradle build: -* `assemble` - compile the Nextflow plugin code and assemble it into a zip file -* `installPlugin` - copy the assembled plugin into your local Nextflow plugins dir -* `releasePlugin` - publish the assembled plugin to the plugin registry +* `assemble` - Compile the Nextflow plugin code and assemble it into a zip file +* `installPlugin` - Copy the assembled plugin into your local Nextflow plugins dir +* `releasePlugin` - Release the assembled plugin to the plugin registry You should also ensure that your project's `settings.gradle` declares the plugin name, eg: ```gradle diff --git a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy index fe84ef2..ccda9fd 100644 --- a/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy +++ b/src/main/groovy/io/nextflow/gradle/NextflowPlugin.groovy @@ -129,22 +129,22 @@ class NextflowPlugin implements Plugin { // add registry publish task, if configured if (config.publishing.registry) { - // publishPluginToRegistry - publishes plugin to a plugin registry - project.tasks.register('publishPluginToRegistry', RegistryUploadTask) - project.tasks.publishPluginToRegistry.dependsOn << project.tasks.packagePlugin - publishTasks << project.tasks.publishPluginToRegistry + // releasePluginToRegistry - publishes plugin to a plugin registry + project.tasks.register('releasePluginToRegistry', RegistryUploadTask) + project.tasks.releasePluginToRegistry.dependsOn << project.tasks.packagePlugin + publishTasks << project.tasks.releasePluginToRegistry } - // finally, configure the destination-agnostic 'publish' task + // finally, configure the destination-agnostic 'release' task if (!publishTasks.isEmpty()) { - // publishPlugin - all the release/publishing actions - project.tasks.register('publishPlugin', { + // releasePlugin - all the release/publishing actions + project.tasks.register('releasePlugin', { group = 'Nextflow Plugin' - description = 'publish plugin to configured destinations' + description = 'Release plugin to configured destination' }) for (task in publishTasks) { - project.tasks.publishPlugin.dependsOn << task + project.tasks.releasePlugin.dependsOn << task } } } diff --git a/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy index 759bca5..acf92ce 100644 --- a/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy +++ b/src/test/groovy/io/nextflow/gradle/NextflowPluginTest.groovy @@ -19,7 +19,7 @@ class NextflowPluginTest extends Specification { project.pluginManager.apply('io.nextflow.nextflow-plugin') } - def "should register publishPlugin task when publishing is configured"() { + def "should register releasePlugin task when publishing is configured"() { given: project.nextflowPlugin { description = 'A test plugin' @@ -38,13 +38,13 @@ class NextflowPluginTest extends Specification { project.evaluate() then: - project.tasks.findByName('publishPlugin') != null - project.tasks.publishPlugin.group == 'Nextflow Plugin' - project.tasks.publishPlugin.description == 'publish plugin to configured destinations' + project.tasks.findByName('releasePlugin') != null + project.tasks.releasePlugin.group == 'Nextflow Plugin' + project.tasks.releasePlugin.description == 'Release plugin to configured destination' } - def "should make publishPlugin depend on registry publishing task"() { + def "should make releasePlugin depend on registry publishing task"() { given: project.nextflowPlugin { description = 'A test plugin' @@ -63,13 +63,13 @@ class NextflowPluginTest extends Specification { project.evaluate() then: - def publishPlugin = project.tasks.publishPlugin - def publishToRegistry = project.tasks.publishPluginToRegistry - publishPlugin.taskDependencies.getDependencies(publishPlugin).contains(publishToRegistry) + def releasePlugin = project.tasks.releasePlugin + def releaseToRegistry = project.tasks.releasePluginToRegistry + releasePlugin.taskDependencies.getDependencies(releasePlugin).contains(releaseToRegistry) } - def "should not register publishPlugin task when no publishing is configured"() { + def "should not register releasePlugin task when no publishing is configured"() { given: project.nextflowPlugin { description = 'A test plugin' @@ -83,7 +83,7 @@ class NextflowPluginTest extends Specification { project.evaluate() then: - project.tasks.findByName('publishPlugin') == null + project.tasks.findByName('releasePlugin') == null } -} \ No newline at end of file +}