Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
8bfee5e
build: simplify install folder resolution
fioan89 Oct 9, 2025
1a3415b
impl: setup auth manager with auth and token endpoints
fioan89 Oct 9, 2025
7685feb
impl: retrieve supported response type and the dynamic client registr…
fioan89 Oct 13, 2025
52648a0
impl: models for dynamic client registration
fioan89 Oct 13, 2025
72a902f
impl: pixy secure code generator
fioan89 Oct 13, 2025
0e03b03
impl: retrofit API for endpoint discovery and dynamic client registra…
fioan89 Oct 13, 2025
79ba4cb
impl: factory method for the auth manager
fioan89 Oct 13, 2025
59d2abd
impl: improve auth manager config
fioan89 Oct 13, 2025
decb082
refactor: simplify OAuth manager architecture and improve dependency …
fioan89 Oct 14, 2025
d432a76
fix: inject mocked PluginAuthManager into UTs
fioan89 Oct 14, 2025
2a28cee
impl: handle the redirect URI
fioan89 Oct 14, 2025
6462f14
fix: wrong client app registration endpoint
fioan89 Oct 16, 2025
0e46da0
impl: simple way of triggering the OAuth flow.
fioan89 Oct 16, 2025
bc09057
Merge branch 'main' into impl-support-for-oauth
fioan89 Oct 20, 2025
8e6c5a2
Merge branch 'main' into impl-support-for-oauth
fioan89 Oct 28, 2025
17b859d
impl: add config to enforce auth via API token
fioan89 Oct 29, 2025
acf4d2c
Merge branch 'main' into impl-support-for-oauth
fioan89 Feb 3, 2026
7bd8035
Merge branch 'main' into impl-support-for-oauth
fioan89 Feb 3, 2026
408cdc4
impl: resolve the account
fioan89 Feb 3, 2026
dca1543
chore: fix UTs
fioan89 Feb 4, 2026
836f45a
impl: rework the first login screen and discover if oauth2 is supported
fioan89 Feb 4, 2026
6aeaf68
impl: prefer client_secret_post as token auth method if available
fioan89 Feb 4, 2026
eaaa88b
fix: missing client secret from authorization request
fioan89 Feb 4, 2026
033104f
fix: prefer client_secret_basic auth method
fioan89 Feb 5, 2026
4aac78e
impl: support for client_secret_basic and client_secret_post for toke…
fioan89 Feb 5, 2026
c333c65
impl: implement our own OAuth2 client (1)
fioan89 Feb 9, 2026
63a81bc
impl: implement our own OAuth2 client (2)
fioan89 Feb 9, 2026
33e076d
impl: implement our own OAuth2 client (3)
fioan89 Feb 9, 2026
35f7624
fix: code challenge was sent twice to the auth endpoint
fioan89 Feb 9, 2026
ba356bc
fix: include state for cross-checking later when the auth code is ret…
fioan89 Feb 9, 2026
51cd195
fix: short circuit the URI handler when handling oauth callbacks
fioan89 Feb 9, 2026
6d26d63
fix: short circuit the URI handler when handling oauth callbacks (2)
fioan89 Feb 10, 2026
3d42eb0
impl: implement our own OAuth2 client (4)
fioan89 Feb 10, 2026
ea747f1
impl: support for token refresh (1)
fioan89 Feb 12, 2026
b989b36
chore: rename context class
fioan89 Feb 16, 2026
b01f0c8
fix: broken UTs
fioan89 Feb 16, 2026
08842cf
chore: dependency declared twice in the build system
fioan89 Feb 17, 2026
1a1d9dc
fix: dependingon the JDK vendor and version zt-exec can raise IOExcep…
fioan89 Feb 17, 2026
dc8e589
impl: persist refresh token between Toolbox restarts (1)
fioan89 Feb 18, 2026
50917ea
impl: persist refresh token between Toolbox restarts (2)
fioan89 Feb 18, 2026
9a78aab
impl: persist refresh token between Toolbox restarts (3)
fioan89 Feb 18, 2026
3a624ba
impl: support logging out and logging back in via OAuth2
fioan89 Feb 25, 2026
24424b9
fix: properly close the setup wizard if connection is successful
fioan89 Feb 27, 2026
ab19d78
fix: mark the header bar as busy
fioan89 Feb 27, 2026
e23f0ce
Merge branch 'main' into impl-support-for-oauth
fioan89 Feb 27, 2026
e920b74
Merge branch 'main' into impl-support-for-oauth
fioan89 Mar 5, 2026
95f99ad
impl: new setting that allows user to use OAuth over API token author…
fioan89 Mar 5, 2026
7d68ce7
impl: group the settings in a couple of sections
fioan89 Mar 5, 2026
dfa24d7
chore: remove unused data models
fioan89 Mar 6, 2026
5dc720f
fix: handle invalid state responses
fioan89 Mar 9, 2026
01742ff
fix: report errors when token exchange fails
fioan89 Mar 9, 2026
32756d7
chore: remove non nullable assertions
fioan89 Mar 9, 2026
a465ace
chore: refactor and reuse OAuth2 logic
fioan89 Mar 9, 2026
3c44b0c
fix: use the registration url from the metadata
fioan89 Mar 9, 2026
738c31e
impl: handle client registration errors
fioan89 Mar 18, 2026
816bdeb
impl: handle auth redirect and token registration errors
fioan89 Mar 18, 2026
a7d499f
chore: update scopes
fioan89 Mar 18, 2026
4a604db
build: upgrade TBX API to version 1.10.76281
fioan89 Mar 19, 2026
0a96a0a
chore: next version is 0.9.0
fioan89 Mar 19, 2026
394ffdc
fix: properly decode query params
fioan89 Apr 1, 2026
0d502db
fix: use - as separator for error codes and error descriptions
fioan89 Apr 1, 2026
e412686
Merge branch 'main' into impl-support-for-oauth
fioan89 Apr 3, 2026
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@

- `Binary destination` can now point directly to an executable, used as-is; otherwise it is treated as a base directory
as before
- support for OAuth2

### Removed

- support for enabling or disabling the fallback to data dir. CLI resolution will always fall back on data dir if binary
destination is not configured.
- redesigned the Settings page, all the options are now grouped in a couple of sections for easy navigation

## 0.8.6 - 2026-03-05

Expand Down
212 changes: 9 additions & 203 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder
import com.github.jk1.license.filter.ExcludeTransitiveDependenciesFilter
import com.github.jk1.license.render.JsonReportRenderer
import com.jetbrains.plugin.structure.toolbox.ToolboxMeta
import com.jetbrains.plugin.structure.toolbox.ToolboxPluginDescriptor
import org.jetbrains.intellij.pluginRepository.PluginRepositoryFactory
import org.jetbrains.intellij.pluginRepository.model.ProductFamily
import org.jetbrains.kotlin.com.intellij.openapi.util.SystemInfoRt
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.div
import kotlin.io.path.writeText

plugins {
alias(libs.plugins.kotlin)
Expand All @@ -23,25 +12,14 @@ plugins {
alias(libs.plugins.changelog)
alias(libs.plugins.gettext)
alias(libs.plugins.detekt)
id("toolbox-convention")
}


repositories {
mavenCentral()
maven("https://packages.jetbrains.team/maven/p/tbx/toolbox-api")
}

buildscript {
repositories {
mavenCentral()
}

dependencies {
classpath(libs.marketplace.client)
classpath(libs.plugin.structure)
}
}

jvmWrapper {
unixJvmInstallDir = "jvm"
winJvmInstallDir = "jvm"
Expand All @@ -64,37 +42,23 @@ dependencies {
implementation(libs.retrofit)
implementation(libs.retrofit.moshi)
implementation(libs.bundles.bouncycastle)

testImplementation(kotlin("test"))
testImplementation(libs.coroutines.test)
testImplementation(libs.mokk)
testImplementation(libs.bundles.toolbox.plugin.api)
testImplementation(libs.coroutines.test)
}

val extension = ExtensionJson(
id = properties("group"),

version = properties("version"),
meta = ExtensionJsonMeta(
name = "Coder",
description = "Connects your JetBrains IDE to Coder workspaces",
vendor = "coder",
url = "https://github.com/coder/coder-jetbrains-toolbox",
)
)

val extensionJsonFile = layout.buildDirectory.file("generated/extension.json")
val extensionJson by tasks.registering {
inputs.property("extension", extension.toString())

outputs.file(extensionJsonFile)
doLast {
generateExtensionJson(extension, extensionJsonFile.get().asFile.toPath())
}
toolbox {
pluginName.set("Coder")
pluginDescription.set("Connects your JetBrains IDE to Coder workspaces")
pluginVendor.set("coder")
pluginUrl.set("https://github.com/coder/coder-jetbrains-toolbox")
apiVersion.set(libs.versions.toolbox.plugin.api)
}

changelog {
version.set(extension.version)
version.set(project.version.toString())
groups.set(emptyList())
title.set("Coder Toolbox Plugin Changelog")
}
Expand Down Expand Up @@ -132,165 +96,7 @@ tasks.withType<io.gitlab.arturbosch.detekt.Detekt>().configureEach {
ignoreFailures = false
}


tasks.jar {
archiveBaseName.set(extension.id)
dependsOn(extensionJson)
from(extensionJson.get().outputs)
}

val copyPlugin by tasks.creating(Sync::class.java) {
dependsOn(tasks.jar)
dependsOn(tasks.getByName("generateLicenseReport"))

fromCompileDependencies()
into(getPluginInstallDir())
}

fun CopySpec.fromCompileDependencies() {
from(tasks.jar)
from(extensionJson.get().outputs.files)
from("src/main/resources") {
include("dependencies.json")
}
from("src/main/resources") {
include("icon.svg")
include("pluginIcon.svg")
}

// Copy dependencies, excluding those provided by Toolbox.
from(
configurations.compileClasspath.map { configuration ->
configuration.files.filterNot { file ->
listOf(
"kotlin",
"remote-dev-api",
"core-api",
"ui-api",
"annotations",
"localization-api",
"slf4j-api"
).any { file.name.contains(it) }
}
},
)
}

/**
* Useful when doing manual local install.
*/
val pluginPrettyZip by tasks.creating(Zip::class) {
archiveBaseName.set(properties("name"))
dependsOn(tasks.jar)
dependsOn(tasks.getByName("generateLicenseReport"))

fromCompileDependencies()
into(extension.id) // folder like com.coder.toolbox
}

val pluginZip by tasks.creating(Zip::class) {
dependsOn(tasks.jar)
dependsOn(tasks.getByName("generateLicenseReport"))

fromCompileDependencies()
archiveBaseName.set(extension.id)
}

tasks.register("cleanAll", Delete::class.java) {
dependsOn(tasks.clean)
delete(getPluginInstallDir())
delete()
}

private fun getPluginInstallDir(): Path {
val userHome = System.getProperty("user.home").let { Path.of(it) }
val toolboxCachesDir = when {
SystemInfoRt.isWindows -> System.getenv("LOCALAPPDATA")?.let { Path.of(it) } ?: (userHome / "AppData" / "Local")
// currently this is the location that TBA uses on Linux
SystemInfoRt.isLinux -> System.getenv("XDG_DATA_HOME")?.let { Path.of(it) } ?: (userHome / ".local" / "share")
SystemInfoRt.isMac -> userHome / "Library" / "Caches"
else -> error("Unknown os")
} / "JetBrains" / "Toolbox"

val pluginsDir = when {
SystemInfoRt.isWindows ||
SystemInfoRt.isLinux ||
SystemInfoRt.isMac -> toolboxCachesDir

else -> error("Unknown os")
} / "plugins"

return pluginsDir / extension.id
}

val publishPlugin by tasks.registering {
dependsOn(pluginZip)

doLast {
val pluginMarketplaceToken: String = if (System.getenv("JETBRAINS_MARKETPLACE_PUBLISH_TOKEN").isNullOrBlank()) {
error("Env. variable `JETBRAINS_MARKETPLACE_PUBLISH_TOKEN` does not exist. Please set the env. variable to a token obtained from the marketplace.")
} else {
System.getenv("JETBRAINS_MARKETPLACE_PUBLISH_TOKEN")
}

println("Plugin Marketplace Token: ${pluginMarketplaceToken.take(5)}*****")

val instance = PluginRepositoryFactory.create(
"https://plugins.jetbrains.com",
pluginMarketplaceToken
)

// !!! subsequent updates !!!
instance.uploader.uploadUpdateByXmlIdAndFamily(
extension.id, // do not change
ProductFamily.TOOLBOX, // do not change
pluginZip.outputs.files.singleFile, // do not change
null, // do not change. Channels will be available later
"Bug fixes and improvements",
false
)
}
}

fun properties(key: String) = project.findProperty(key).toString()

gettext {
potFile = project.layout.projectDirectory.file("src/main/resources/localization/defaultMessages.pot")
keywords = listOf("ptrc:1c,2", "ptrl")
}

// region will be moved to the gradle plugin late
data class ExtensionJsonMeta(
val name: String,
val description: String,
val vendor: String,
val url: String?,
)

data class ExtensionJson(
val id: String,
val version: String,
val meta: ExtensionJsonMeta,
)

fun generateExtensionJson(extensionJson: ExtensionJson, destinationFile: Path) {
val descriptor = ToolboxPluginDescriptor(
id = extensionJson.id,
version = extensionJson.version,
apiVersion = libs.versions.toolbox.plugin.api.get(),
meta = ToolboxMeta(
name = extensionJson.meta.name,
description = extensionJson.meta.description,
vendor = extensionJson.meta.vendor,
url = extensionJson.meta.url,
)
)
destinationFile.parent.createDirectories()
destinationFile.writeText(
jacksonMapperBuilder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build()
.writeValueAsString(descriptor)
)
}
// endregion
14 changes: 14 additions & 0 deletions buildSrc/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
`kotlin-dsl`
}

repositories {
mavenCentral()
maven("https://packages.jetbrains.team/maven/p/tbx/toolbox-api")
}

dependencies {
implementation("com.fasterxml.jackson.module:jackson-module-kotlin:2.18.2")
implementation("org.jetbrains.intellij.plugins:structure-toolbox:3.321")
implementation("org.jetbrains.intellij:plugin-repository-rest-client:2.0.50")
}
13 changes: 13 additions & 0 deletions buildSrc/src/main/kotlin/CleanAllTask.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.Input

abstract class CleanAllTask : Delete() {

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

init {
delete(extensionId.map { getPluginInstallDir(it).toFile() })
}
}
41 changes: 41 additions & 0 deletions buildSrc/src/main/kotlin/ExtensionJson.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.module.kotlin.jacksonMapperBuilder
import com.jetbrains.plugin.structure.toolbox.ToolboxMeta
import com.jetbrains.plugin.structure.toolbox.ToolboxPluginDescriptor
import java.nio.file.Path
import kotlin.io.path.createDirectories
import kotlin.io.path.writeText

data class ExtensionJsonMeta(
val name: String,
val description: String,
val vendor: String,
val url: String?,
)

data class ExtensionJson(
val id: String,
val version: String,
val meta: ExtensionJsonMeta,
)

fun generateExtensionJson(extensionJson: ExtensionJson, apiVersion: String, destinationFile: Path) {
val descriptor = ToolboxPluginDescriptor(
id = extensionJson.id,
version = extensionJson.version,
apiVersion = apiVersion,
meta = ToolboxMeta(
name = extensionJson.meta.name,
description = extensionJson.meta.description,
vendor = extensionJson.meta.vendor,
url = extensionJson.meta.url,
)
)
destinationFile.parent.createDirectories()
destinationFile.writeText(
jacksonMapperBuilder()
.enable(SerializationFeature.INDENT_OUTPUT)
.build()
.writeValueAsString(descriptor)
)
}
Loading
Loading