Skip to content

Commit 63d56ea

Browse files
phorcys420matifaliCopilot
authored
feat: add vscode-desktop-core module (#278)
Co-authored-by: Atif Ali <[email protected]> Co-authored-by: Copilot <[email protected]>
1 parent 507b73a commit 63d56ea

File tree

3 files changed

+225
-0
lines changed

3 files changed

+225
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
display_name: VSCode Desktop Core
3+
description: Building block for modules that need to link to an external VSCode-based IDE
4+
icon: ../../../../.icons/coder.svg
5+
verified: true
6+
tags: [internal, library]
7+
---
8+
9+
# VS Code Desktop Core
10+
11+
> [!CAUTION]
12+
> We do not recommend using this module directly. Instead, please consider using one of our [Desktop IDE modules](https://registry.coder.com/modules?search=tag%3Aide).
13+
14+
The VSCode Desktop Core module is a building block for modules that need to expose access to VSCode-based IDEs. It is intended primarily to be used as a library to create modules for VSCode-based IDEs.
15+
16+
```tf
17+
module "vscode-desktop-core" {
18+
source = "registry.coder.com/coder/vscode-desktop-core/coder"
19+
version = "1.0.0"
20+
21+
agent_id = var.agent_id
22+
23+
coder_app_icon = "/icon/code.svg"
24+
coder_app_slug = "vscode"
25+
coder_app_display_name = "VS Code Desktop"
26+
coder_app_order = var.order
27+
coder_app_group = var.group
28+
29+
folder = var.folder
30+
open_recent = var.open_recent
31+
protocol = "vscode"
32+
}
33+
```
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { describe, expect, it } from "bun:test";
2+
import {
3+
runTerraformApply,
4+
runTerraformInit,
5+
testRequiredVariables,
6+
} from "~test";
7+
8+
// hardcoded coder_app name in main.tf
9+
const appName = "vscode-desktop";
10+
11+
const defaultVariables = {
12+
agent_id: "foo",
13+
coder_app_icon: "/icon/code.svg",
14+
coder_app_slug: "vscode",
15+
coder_app_display_name: "VS Code Desktop",
16+
protocol: "vscode",
17+
}
18+
19+
describe("vscode-desktop-core", async () => {
20+
await runTerraformInit(import.meta.dir);
21+
22+
testRequiredVariables(import.meta.dir, defaultVariables);
23+
24+
it("default output", async () => {
25+
const state = await runTerraformApply(import.meta.dir, defaultVariables);
26+
expect(state.outputs.ide_uri.value).toBe(
27+
`${defaultVariables.protocol}://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN`,
28+
);
29+
30+
const coder_app = state.resources.find(
31+
(res) => res.type === "coder_app" && res.name === appName,
32+
);
33+
34+
expect(coder_app).not.toBeNull();
35+
expect(coder_app?.instances.length).toBe(1);
36+
expect(coder_app?.instances[0].attributes.order).toBeNull();
37+
});
38+
39+
it("adds folder", async () => {
40+
const state = await runTerraformApply(import.meta.dir, {
41+
folder: "/foo/bar",
42+
43+
...defaultVariables
44+
});
45+
46+
expect(state.outputs.ide_uri.value).toBe(
47+
`${defaultVariables.protocol}://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN`,
48+
);
49+
});
50+
51+
it("adds folder and open_recent", async () => {
52+
const state = await runTerraformApply(import.meta.dir, {
53+
folder: "/foo/bar",
54+
open_recent: "true",
55+
56+
...defaultVariables,
57+
});
58+
expect(state.outputs.ide_uri.value).toBe(
59+
`${defaultVariables.protocol}://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN`,
60+
);
61+
});
62+
63+
it("adds folder but not open_recent", async () => {
64+
const state = await runTerraformApply(import.meta.dir, {
65+
folder: "/foo/bar",
66+
openRecent: "false",
67+
68+
...defaultVariables,
69+
});
70+
expect(state.outputs.ide_uri.value).toBe(
71+
`${defaultVariables.protocol}://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN`,
72+
);
73+
});
74+
75+
it("adds open_recent", async () => {
76+
const state = await runTerraformApply(import.meta.dir, {
77+
open_recent: "true",
78+
79+
...defaultVariables,
80+
});
81+
expect(state.outputs.ide_uri.value).toBe(
82+
`${defaultVariables.protocol}://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN`,
83+
);
84+
});
85+
86+
it("expect order to be set", async () => {
87+
const state = await runTerraformApply(import.meta.dir, {
88+
coder_app_order: "22",
89+
...defaultVariables
90+
});
91+
92+
const coder_app = state.resources.find(
93+
(res) => res.type === "coder_app" && res.name === appName,
94+
);
95+
96+
expect(coder_app).not.toBeNull();
97+
expect(coder_app?.instances.length).toBe(1);
98+
expect(coder_app?.instances[0].attributes.order).toBe(22);
99+
});
100+
});
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
terraform {
2+
required_version = ">= 1.0"
3+
4+
required_providers {
5+
coder = {
6+
source = "coder/coder"
7+
version = ">= 2.5"
8+
}
9+
}
10+
}
11+
12+
variable "agent_id" {
13+
type = string
14+
description = "The ID of a Coder agent."
15+
}
16+
17+
variable "folder" {
18+
type = string
19+
description = "The folder to open in the IDE."
20+
default = ""
21+
}
22+
23+
variable "open_recent" {
24+
type = bool
25+
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
26+
default = false
27+
}
28+
29+
variable "protocol" {
30+
type = string
31+
description = "The URI protocol for the IDE."
32+
}
33+
34+
variable "coder_app_icon" {
35+
type = string
36+
description = "The icon of the coder_app."
37+
}
38+
39+
variable "coder_app_slug" {
40+
type = string
41+
description = "The slug of the coder_app."
42+
}
43+
44+
variable "coder_app_display_name" {
45+
type = string
46+
description = "The display name of the coder_app."
47+
}
48+
49+
variable "coder_app_order" {
50+
type = number
51+
description = "The order of the coder_app."
52+
default = null
53+
}
54+
55+
variable "coder_app_group" {
56+
type = string
57+
description = "The group of the coder_app."
58+
default = null
59+
}
60+
61+
data "coder_workspace" "me" {}
62+
data "coder_workspace_owner" "me" {}
63+
64+
resource "coder_app" "vscode-desktop" {
65+
agent_id = var.agent_id
66+
external = true
67+
68+
icon = var.coder_app_icon
69+
slug = var.coder_app_slug
70+
display_name = var.coder_app_display_name
71+
72+
order = var.coder_app_order
73+
group = var.coder_app_group
74+
75+
# While the call to "join" is not strictly necessary, it makes the URL more readable.
76+
url = join("", [
77+
"${var.protocol}://coder.coder-remote/open",
78+
"?owner=${data.coder_workspace_owner.me.name}",
79+
"&workspace=${data.coder_workspace.me.name}",
80+
var.folder != "" ? join("", ["&folder=", var.folder]) : "",
81+
var.open_recent ? "&openRecent" : "",
82+
"&url=${data.coder_workspace.me.access_url}",
83+
# NOTE: There is a protocol whitelist for the token replacement, so this will only work with the protocols hardcoded in the front-end.
84+
# (https://github.com/coder/coder/blob/6ba4b5bbc95e2e528d7f5b1e31fffa200ae1a6db/site/src/modules/apps/apps.ts#L18)
85+
"&token=$SESSION_TOKEN",
86+
])
87+
}
88+
89+
output "ide_uri" {
90+
value = coder_app.vscode-desktop.url
91+
description = "IDE URI."
92+
}

0 commit comments

Comments
 (0)