Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed .DS_Store
Binary file not shown.
101 changes: 101 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# GitHub Actions Workflow is created for testing and preparing the plugin release draft.

name: Build
on:
# Trigger the workflow on pushes to only the 'main' branch (this avoids duplicate checks being run e.g., for dependabot pull requests)
push:
branches: [ main ]
# Trigger the workflow on any pull request
pull_request:

concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:

# Prepare environment and build the plugin
build:
name: Build
runs-on: ubuntu-latest
outputs:
version: ${{ steps.properties.outputs.version }}
steps:

# Check out the current repository
- name: Fetch Sources
uses: actions/checkout@v6

# Set up Java environment for the next steps
- name: Setup Java
uses: actions/setup-java@v5
with:
distribution: zulu
java-version: 21

# Setup Gradle
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v6

# Set environment variables
- name: Export Properties
id: properties
shell: bash
run: |
VERSION="$(grep '^version = ' plugin/build.gradle.kts | cut -d'"' -f2)"
echo "version=$VERSION" >> $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
Comment thread
azatsarynnyy marked this conversation as resolved.

# Store already-built plugin as an artifact for downloading
- name: Upload artifact
uses: actions/upload-artifact@v7
with:
name: ${{ steps.artifact.outputs.filename }}
path: ./plugin/build/distributions/content/*/*
Comment thread
azatsarynnyy marked this conversation as resolved.

# 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 }}"
46 changes: 46 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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: 21

# 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/*
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 7 additions & 0 deletions build-logic/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ repositories {
dependencies {
implementation(libs.plugin.structure)
implementation(libs.jackson.kotlin)
implementation(libs.marketplace.client)
}

gradlePlugin {
Expand All @@ -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"
}
}
}

Expand Down
115 changes: 115 additions & 0 deletions build-logic/src/main/kotlin/toolbox/buildlogic/PublishToolboxPlugin.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* 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<Project> {
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}")
Comment thread
azatsarynnyy marked this conversation as resolved.
}
from(target.tasks.named("jar")) {
into("${target.group}/lib")
Comment thread
azatsarynnyy marked this conversation as resolved.
}
}

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<String>

/** Path to the plugin ZIP archive produced by the `packagePlugin` task. */
@get:InputFile
abstract val pluginZipFile: RegularFileProperty

@get:Input
abstract val vendor: Property<String>

@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...")

Comment thread
azatsarynnyy marked this conversation as resolved.
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!")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class ToolboxGenerateJsonExtension : Plugin<Project> {
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)
}
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
3 changes: 3 additions & 0 deletions plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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`
}

Expand All @@ -11,6 +12,8 @@ plugins {
group = "com.redhat.devtools.toolbox"
version = "0.0.1"

extra["vendor"] = "Red-Hat"

kotlin {
jvmToolchain(21)
}
Expand Down