From b61778b1ac8f344f90965c3a94323de5c3cc55e0 Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Thu, 23 Apr 2026 14:51:49 +0200 Subject: [PATCH 1/6] Set-up the release process Signed-off-by: Artem Zatsarynnyi --- .DS_Store | Bin 6148 -> 8196 bytes .github/workflows/build.yml | 101 +++++++++++++++ .github/workflows/release.yml | 46 +++++++ build-logic/build.gradle.kts | 7 ++ .../buildlogic/PublishToolboxPlugin.kt | 116 ++++++++++++++++++ .../ToolboxGenerateJsonExtension.kt | 2 +- gradle/libs.versions.toml | 2 + plugin/build.gradle.kts | 3 + 8 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/release.yml create mode 100644 build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt diff --git a/.DS_Store b/.DS_Store index b3f21485ab44287e4e704e15dc896a30dcf6385c..1c4f764f028c71546449a5016812cd17e6e3f39f 100644 GIT binary patch delta 1050 zcma)5&1(}u6o1pCW@A&kX_J14LToNVOHF8L4Tibf9!-BQypm;IP**KTIk-A!XH zB{}q_2q6d2YsI?+LGj{6M8vBHFa84_ya`g!+0BO3oLrdw{bt_py*F>>%~9%TVH5yR zrmUs`T*|TGBHvfPKU|->ghpQF-vycp?j&#=;<$P(h(;QbpjHjR6lkD>13OR#Icrcw zHmK~nvVa%a*!bgpYz3f8Emv&QSHG1_L${q&>MQCA1bfefxDeOJZ3{cJA~=#$wlk8o z>CRRuHRLvzs`8qSDL@@aPR}iB_coQ>#A+x3RdrSd{_5PCW>_yykIvhjLD{E>1wW6w6Rk-#n(lCz*a13 z7

zj!wz?md%bk>)=wB6e&x&$4)wDx+Z%KTQ)vMje%IUk&AE~E7*PY&=4cf Mno94Bo_bCE3l+u&-v9sr delta 104 zcmZp1XfcprU|?W$DortDU=RQ@Ie-{Mvv5r;6q~50$jHAjU^g=(|70G4^v!}IQw&zl> $GITHUB_OUTPUT + + # Build plugin + - name: Build plugin + run: ./gradlew packagePlugin + + # Prepare plugin archive content for creating artifact + - name: Prepare Plugin Artifact + id: artifact + shell: bash + run: | + cd ${{ github.workspace }}/plugin/build/distributions + FILENAME=`ls *.zip` + unzip "$FILENAME" -d content + + echo "filename=${FILENAME:0:-4}" >> $GITHUB_OUTPUT + + # Store already-built plugin as an artifact for downloading + - name: Upload artifact + uses: actions/upload-artifact@v7 + with: + name: ${{ steps.artifact.outputs.filename }} + path: ./build/distributions/content/*/* + + # Prepare a draft release for GitHub Releases page for the manual verification. + # If accepted and published, release workflow would be triggered. + releaseDraft: + name: Release draft + if: github.event_name != 'pull_request' + needs: [ build ] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + + # Check out the current repository + - name: Fetch Sources + uses: actions/checkout@v6 + + # Remove old release drafts by using the curl request for the available releases with a draft flag + - name: Remove Old Release Drafts + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh api repos/{owner}/{repo}/releases \ + --jq '.[] | select(.draft == true) | .id' \ + | xargs -I '{}' gh api -X DELETE repos/{owner}/{repo}/releases/{} + + # Create a new release draft which is not publicly visible and requires manual acceptance + - name: Create Release Draft + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release create "v${{ needs.build.outputs.version }}" \ + --draft \ + --title "v${{ needs.build.outputs.version }}" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..d3edd82 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,46 @@ +# GitHub Actions Workflow created for handling the release process based on the draft release prepared with the Build workflow. + +name: Release +on: + release: + types: [prereleased, released] + +jobs: + + # Prepare and publish the plugin to JetBrains Marketplace + release: + name: Publish Plugin + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + + # Check out the current repository + - name: Fetch Sources + uses: actions/checkout@v6 + with: + ref: ${{ github.event.release.tag_name }} + + # Set up Java environment for the next steps + - name: Setup Java + uses: actions/setup-java@v5 + with: + distribution: zulu + java-version: 17 + + # Setup Gradle + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v6 + + # Publish the plugin to JetBrains Marketplace + - name: Publish Plugin + env: + JETBRAINS_MARKETPLACE_PUBLISH_TOKEN: ${{ secrets.JETBRAINS_MARKETPLACE_TOKEN }} + run: ./gradlew publishPlugin + + # Upload artifact as a release asset + - name: Upload Release Asset + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: gh release upload ${{ github.event.release.tag_name }} ./plugin/build/distributions/* diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index f20c335..28e1f16 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -16,6 +16,7 @@ repositories { dependencies { implementation(libs.plugin.structure) implementation(libs.jackson.kotlin) + implementation(libs.marketplace.client) } gradlePlugin { @@ -32,6 +33,12 @@ gradlePlugin { displayName = "Install Toolbox Plugin" description = "Installs the plugin into the local Toolbox directory" } + create("toolboxPublish") { + id = "com.redhat.devtools.toolbox.publish" + implementationClass = "com.redhat.devtools.toolbox.buildlogic.PublishToolboxPlugin" + displayName = "Publish Toolbox Plugin" + description = "Packages and publishes a JetBrains Toolbox plugin to the JetBrains Marketplace" + } } } diff --git a/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt b/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt new file mode 100644 index 0000000..044af7a --- /dev/null +++ b/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package com.redhat.devtools.toolbox.buildlogic + +import org.gradle.api.DefaultTask +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.bundling.Zip +import org.gradle.work.DisableCachingByDefault +import org.jetbrains.intellij.pluginRepository.PluginRepositoryFactory +import org.jetbrains.intellij.pluginRepository.model.LicenseUrl +import org.jetbrains.intellij.pluginRepository.model.ProductFamily + +/** + * Gradle plugin that packages and publishes a JetBrains Toolbox plugin to the + * [JetBrains Marketplace](https://plugins.jetbrains.com). + */ +class PublishToolboxPlugin : Plugin { + override fun apply(target: Project) { + val packageTask = target.tasks.register("packagePlugin", Zip::class.java) { + dependsOn(target.tasks.named("assemble")) + from(target.layout.buildDirectory.file("generated/extension.json")) + from(target.file("src/main/resources")) { + include("dependencies.json") + include("icon.svg") + into("${target.group}") + } + from(target.tasks.named("jar")) { + into("${target.group}/lib") + } + } + + target.tasks.register("publishPlugin", PublishTask::class.java) { + dependsOn(packageTask) + extensionId.set(target.group.toString()) + pluginZipFile.set(packageTask.flatMap { it.archiveFile }) + vendor.set(target.extensions.extraProperties["vendor"].toString()) + } + } + + /** + * Task that uploads the packaged plugin ZIP to the JetBrains Marketplace. + * + * Requires the `JETBRAINS_MARKETPLACE_PUBLISH_TOKEN` environment variable to be set. + * The token can be generated from your JetBrains Marketplace account. + */ + @DisableCachingByDefault(because = "Publishes plugin to JetBrains Marketplace") + abstract class PublishTask : DefaultTask() { + @get:Input + abstract val extensionId: Property + + /** Path to the plugin ZIP archive produced by the `packagePlugin` task. */ + @get:InputFile + abstract val pluginZipFile: RegularFileProperty + + @get:Input + abstract val vendor: Property + + @TaskAction + fun publish() { + val jbMarketplaceToken: String? = System.getenv("JETBRAINS_MARKETPLACE_PUBLISH_TOKEN") + if (jbMarketplaceToken.isNullOrBlank()) { + error( + "Environment variable `JETBRAINS_MARKETPLACE_PUBLISH_TOKEN` is not set. " + "Please obtain a token from https://plugins.jetbrains.com and set it." + ) + } + + println("Publishing plugin ${extensionId.get()} to JetBrains Marketplace...") + println("Token prefix: ${jbMarketplaceToken.take(5)}*****") + + val instance = PluginRepositoryFactory.create( + "https://plugins.jetbrains.com", jbMarketplaceToken + ) + + val existingPlugin = instance.pluginManager.getPluginByXmlId( + extensionId.get(), ProductFamily.TOOLBOX + ) + + if (existingPlugin != null) { + instance.uploader.uploadUpdateByXmlIdAndFamily( + extensionId.get(), + ProductFamily.TOOLBOX, + pluginZipFile.get().asFile, + null, // channel – not yet supported for Toolbox plugins. + "Bug fixes and improvements", + false + ) + } else { + println("Plugin not found on Marketplace. Uploading as new plugin...") + instance.uploader.uploadNewPlugin( + pluginZipFile.get().asFile, + listOf("toolbox", "gateway"), // do not change + LicenseUrl.ECLIPSE_PUBLIC, + ProductFamily.TOOLBOX, + vendor = vendor.get(), + isHidden = true, + ) + } + println("Plugin published successfully!") + } + } +} diff --git a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt index b30da9f..d46802c 100644 --- a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt +++ b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt @@ -84,7 +84,7 @@ class ToolboxGenerateJsonExtension : Plugin { extensionVersion.set(target.version.toString()) metaName.set("Red Hat OpenShift Dev Spaces") metaDescription.set("Red Hat OpenShift Dev Spaces Plugin for JetBrains Toolbox") - metaVendor.set("Red-Hat") + metaVendor.set(target.extensions.extraProperties["vendor"].toString()) metaUrl.set("https://www.redhat.com") destinationFile.set(extensionJsonFile) } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7d5742f..19862ab 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,12 +2,14 @@ coroutines = "1.10.1" jackson = "2.18.2" kotlin = "2.1.0" +marketplace-client = "2.0.50" toolbox-plugin-api = "1.8.65679" plugin-structure = "3.320" [libraries] coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } jackson-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jackson" } +marketplace-client = { module = "org.jetbrains.intellij:plugin-repository-rest-client", version.ref = "marketplace-client" } toolbox-core-api = { module = "com.jetbrains.toolbox:core-api", version.ref = "toolbox-plugin-api" } toolbox-remote-dev-api = { module = "com.jetbrains.toolbox:remote-dev-api", version.ref = "toolbox-plugin-api" } toolbox-ui-api = { module = "com.jetbrains.toolbox:ui-api", version.ref = "toolbox-plugin-api" } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index e3307af..53b078a 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -3,6 +3,7 @@ plugins { `kotlin-dsl` id("com.redhat.devtools.toolbox.packaging") id("com.redhat.devtools.toolbox.install") + id("com.redhat.devtools.toolbox.publish") `java-library` } @@ -11,6 +12,8 @@ plugins { group = "com.redhat.devtools.toolbox" version = "0.0.1" +extra["vendor"] = "Red-Hat" + kotlin { jvmToolchain(21) } From 5a3f572deb3f49c536305ac289c17fa305ec19b5 Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Thu, 23 Apr 2026 16:09:53 +0200 Subject: [PATCH 2/6] Fix zip path Signed-off-by: Artem Zatsarynnyi --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 076e2c8..ff152d0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -65,7 +65,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: ${{ steps.artifact.outputs.filename }} - path: ./build/distributions/content/*/* + path: ./plugin/build/distributions/content/*/* # Prepare a draft release for GitHub Releases page for the manual verification. # If accepted and published, release workflow would be triggered. From 814749959aaad086d38452d9d02b7b981c4c4f08 Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Mon, 27 Apr 2026 14:14:40 +0200 Subject: [PATCH 3/6] Delete .DS_Store --- .DS_Store | Bin 8196 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 1c4f764f028c71546449a5016812cd17e6e3f39f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHM&2Jk;6o2Em$-1WLrb*fafn?Peq=pbHReM0Fj^hZ-SE6iGl73+JW8!SH-m!Mq zF(r!RJBR~E4qTA<1CWs5#swjSxN_iv#JLB~2npWItljmv5nND^Iuq@@w==)@-pp@j z-|RR5Kx$pD0*20dw%)97F2{&46Y=GoTsJ z3}^=a4-DX*&5Lo$zAt-qtr^e^Je3Ua{7}M+%W5k7Qi{}pl{f;R%ww}CsEbd3lqKGP zvYN`il-PoWNK=&3l%z)tlAauhG{^pGitkG)&4Gx>=qHU#(h~|v4<4i>;6SQU>RL0P z85n1PpWXAY3@z{>QO@r*>|6dz6k#|$z<~`3{zYixKo>O%v10qBVmmdoJwOX*6iuGT z7x_*ml7e=6aVPy<;TxwD_;Iqb@-xXzo|V{1H?G;yU4FO2Sl|qF z+qv!2UelagXJKOdA&rEA?@@%@d&dtw)~m5@=*7a;k{w3DC^XH*_b5jkTgHR1$jTLKSE8JmhT>=(;`0&ruD07 z4klJOUwr1m^hI;#((KIK%>4ZPe#Lyw#v7Dmt>Z!TLa|S7Sckw`bOGi zz%^Kd*TI4V=)uSEDSQE6!*}otJc2(+o?IqZ$u)AFyh3h~*GPlhAujQVPaa77+~85; z>dh7sPNB!pbMy~oj#1SOu z)rU|)M+&+qGLVm04_?HYiA?4yR=+7$lZZNH!-o`b%F^U#EX3>+-K|Z>Ik0-I1#TH;2 zy{?v j@E?Y#m(sp!D*IAm50*3eAwci{qr17@|HWE-nySA6x!@|R From 65213ab1f75edf95669b3390e4fc28f06420e679 Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Wed, 29 Apr 2026 11:16:19 +0200 Subject: [PATCH 4/6] Fix Copilot's review notes Signed-off-by: Artem Zatsarynnyi --- .github/workflows/release.yml | 2 +- .../src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt | 1 - .../kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt | 2 +- plugin/build.gradle.kts | 2 -- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d3edd82..df402b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,7 +27,7 @@ jobs: uses: actions/setup-java@v5 with: distribution: zulu - java-version: 17 + java-version: 21 # Setup Gradle - name: Setup Gradle diff --git a/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt b/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt index 044af7a..95a4c57 100644 --- a/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt +++ b/build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt @@ -80,7 +80,6 @@ class PublishToolboxPlugin : Plugin { } println("Publishing plugin ${extensionId.get()} to JetBrains Marketplace...") - println("Token prefix: ${jbMarketplaceToken.take(5)}*****") val instance = PluginRepositoryFactory.create( "https://plugins.jetbrains.com", jbMarketplaceToken diff --git a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt index d46802c..b30da9f 100644 --- a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt +++ b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt @@ -84,7 +84,7 @@ class ToolboxGenerateJsonExtension : Plugin { extensionVersion.set(target.version.toString()) metaName.set("Red Hat OpenShift Dev Spaces") metaDescription.set("Red Hat OpenShift Dev Spaces Plugin for JetBrains Toolbox") - metaVendor.set(target.extensions.extraProperties["vendor"].toString()) + metaVendor.set("Red-Hat") metaUrl.set("https://www.redhat.com") destinationFile.set(extensionJsonFile) } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index 53b078a..f9ce8ec 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -12,8 +12,6 @@ plugins { group = "com.redhat.devtools.toolbox" version = "0.0.1" -extra["vendor"] = "Red-Hat" - kotlin { jvmToolchain(21) } From c58c8e34e1b5217c04b949022e0882ffe0b66317 Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Wed, 29 Apr 2026 19:06:02 +0200 Subject: [PATCH 5/6] Fix the release job Signed-off-by: Artem Zatsarynnyi --- .../kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt | 2 +- plugin/build.gradle.kts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt index b30da9f..d46802c 100644 --- a/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt +++ b/build-logic/src/main/kotlin/toolbox/buildlogic/ToolboxGenerateJsonExtension.kt @@ -84,7 +84,7 @@ class ToolboxGenerateJsonExtension : Plugin { extensionVersion.set(target.version.toString()) metaName.set("Red Hat OpenShift Dev Spaces") metaDescription.set("Red Hat OpenShift Dev Spaces Plugin for JetBrains Toolbox") - metaVendor.set("Red-Hat") + metaVendor.set(target.extensions.extraProperties["vendor"].toString()) metaUrl.set("https://www.redhat.com") destinationFile.set(extensionJsonFile) } diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index f9ce8ec..53b078a 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -12,6 +12,8 @@ plugins { group = "com.redhat.devtools.toolbox" version = "0.0.1" +extra["vendor"] = "Red-Hat" + kotlin { jvmToolchain(21) } From d62a0e3b7a694708385de3a42c65649b794b1ace Mon Sep 17 00:00:00 2001 From: Artem Zatsarynnyi Date: Wed, 29 Apr 2026 19:18:32 +0200 Subject: [PATCH 6/6] Update the release instruction in the README.md Added instructions for editing and publishing draft releases. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 08675ef..6a4fb7f 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,11 @@ b. Once a workspace is up and running, follow the provided instructions to open c. Once the Toolbox App is connected to a remote, click it and choose an IDE to install in the CDE. d. Once a chosen IDE is installed, go to the previous page and click the project folder. Local ThinClient will connect to CDE. + + +## Release +- Find a draft release on the [Releases](https://github.com/redhat-developer/devspaces-toolbox-plugin/releases) page. The draft is created and updated automatically on each push to the `main` branch. +- Edit the draft: + - Click the `Generate release notes` button and edit the release notes if needed + - Click the `Publish release` button. The [Release](https://github.com/redhat-developer/devspaces-toolbox-plugin/blob/main/.github/workflows/release.yml) Workflow will attach the built plugin artifact to the published release and upload the plugin artifact to [JetBrains Marketplace](https://plugins.jetbrains.com/plugin/31372-red-hat-openshift-dev-spaces). +- Bump the `version` in the [gradle.properties](https://github.com/redhat-developer/devspaces-toolbox-plugin/blob/main/plugin/build.gradle.kts) file.