Skip to content

Commit b839ad5

Browse files
committed
Merge branch 'main' into brett-429/add-debuggin-to-the-plugin
2 parents 5fbc7a7 + 1400383 commit b839ad5

File tree

10 files changed

+395
-252
lines changed

10 files changed

+395
-252
lines changed

.prettierrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
],
1010
"options": {
1111
"printWidth": 80,
12-
"proseWrap": "always"
12+
"proseWrap": "preserve"
1313
}
1414
}
1515
]

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
## Unreleased
44

5+
## [v1.7.0](https://github.com/coder/vscode-coder/releases/tag/v1.7.0) (2025-04-03)
6+
7+
### Added
8+
9+
- Add new `/openDevContainer` path, similar to the `/open` path, except this
10+
allows connecting to a dev container inside a workspace. For now, the dev
11+
container must already be running for this to work.
12+
13+
### Fixed
14+
15+
- When not using token authentication, avoid setting `undefined` for the token
16+
header, as Node will throw an error when headers are undefined. Now, we will
17+
not set any header at all.
18+
19+
## [v1.6.0](https://github.com/coder/vscode-coder/releases/tag/v1.6.0) (2025-04-01)
20+
21+
### Added
22+
23+
- Add support for Coder inbox.
24+
525
## [v1.5.0](https://github.com/coder/vscode-coder/releases/tag/v1.5.0) (2025-03-20)
626

727
### Fixed

README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Coder Remote
22

33
[![Visual Studio Marketplace](https://vsmarketplacebadges.dev/version/coder.coder-remote.svg)](https://marketplace.visualstudio.com/items?itemName=coder.coder-remote)
4+
[![Open VSX Version](https://img.shields.io/open-vsx/v/coder/coder-remote)](https://open-vsx.org/extension/coder/coder-remote)
45
[!["Join us on
56
Discord"](https://badgen.net/discord/online-members/coder)](https://coder.com/chat?utm_source=github.com/coder/vscode-coder&utm_medium=github&utm_campaign=readme.md)
67

@@ -13,11 +14,11 @@ workspaces with a single click.
1314
- Works in air-gapped or restricted networks. Just connect to your Coder
1415
deployment!
1516
- Supports multiple editors: VS Code, Cursor, and Windsurf.
16-
> [!NOTE]
17-
> The extension builds on VSCode provided implementations of SSH. Make sure
18-
> you have the correct ssh extension installed for your editor
19-
> (ms-vscode-remote.remote-ssh or codeium.windsurf-remote-openssh for
20-
> windsurf)
17+
18+
> [!NOTE]
19+
> The extension builds on VS Code-provided implementations of SSH. Make
20+
> sure you have the correct SSH extension installed for your editor
21+
> (`ms-vscode-remote.remote-ssh` or `codeium.windsurf-remote-openssh` for Windsurf).
2122
2223
![Demo](https://github.com/coder/vscode-coder/raw/main/demo.gif?raw=true)
2324

@@ -26,19 +27,18 @@ workspaces with a single click.
2627
Launch VS Code Quick Open (Ctrl+P), paste the following command, and press
2728
enter.
2829

29-
```text
30+
```shell
3031
ext install coder.coder-remote
3132
```
3233

3334
Alternatively, manually install the VSIX from the
3435
[latest release](https://github.com/coder/vscode-coder/releases/latest).
3536

36-
#### Variables Reference
37+
### Variables Reference
3738

38-
Coder uses
39-
${userHome} from VS Code's
39+
Coder uses `${userHome}` from VS Code's
4040
[variables reference](https://code.visualstudio.com/docs/editor/variables-reference).
41-
Use this when formatting paths in the Coder extension settings rather than ~ or
42-
$HOME.
41+
Use this when formatting paths in the Coder extension settings rather than `~`
42+
or `$HOME`.
4343

4444
Example: ${userHome}/foo/bar.baz

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
"displayName": "Coder",
55
"description": "Open any workspace with a single click.",
66
"repository": "https://github.com/coder/vscode-coder",
7-
"version": "1.5.0",
7+
"version": "1.7.0",
88
"engines": {
99
"vscode": "^1.73.0"
1010
},
@@ -303,13 +303,13 @@
303303
"utf-8-validate": "^6.0.5",
304304
"vitest": "^0.34.6",
305305
"vscode-test": "^1.5.0",
306-
"webpack": "^5.94.0",
306+
"webpack": "^5.98.0",
307307
"webpack-cli": "^5.1.4"
308308
},
309309
"dependencies": {
310-
"axios": "1.7.7",
310+
"axios": "1.8.4",
311311
"date-fns": "^3.6.0",
312-
"eventsource": "^3.0.5",
312+
"eventsource": "^3.0.6",
313313
"find-process": "^1.4.7",
314314
"jsonc-parser": "^3.3.1",
315315
"memfs": "^4.9.3",

src/api.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import { getProxyForUrl } from "./proxy"
1313
import { Storage } from "./storage"
1414
import { expandPath } from "./util"
1515

16+
export const coderSessionTokenHeader = "Coder-Session-Token"
17+
1618
/**
1719
* Return whether the API will need a token for authorization.
1820
* If mTLS is in use (as specified by the cert or key files being set) then
@@ -242,14 +244,15 @@ export async function waitForBuild(
242244
const baseUrl = new URL(baseUrlRaw)
243245
const proto = baseUrl.protocol === "https:" ? "wss:" : "ws:"
244246
const socketUrlRaw = `${proto}//${baseUrl.host}${path}`
247+
const token = restClient.getAxiosInstance().defaults.headers.common[coderSessionTokenHeader] as string | undefined
245248
const socket = new ws.WebSocket(new URL(socketUrlRaw), {
246-
headers: {
247-
"Coder-Session-Token": restClient.getAxiosInstance().defaults.headers.common["Coder-Session-Token"] as
248-
| string
249-
| undefined,
250-
},
251-
followRedirects: true,
252249
agent: agent,
250+
followRedirects: true,
251+
headers: token
252+
? {
253+
[coderSessionTokenHeader]: token,
254+
}
255+
: undefined,
253256
})
254257
socket.binaryType = "nodebuffer"
255258
socket.on("message", (data) => {

src/commands.ts

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { makeCoderSdk, needToken } from "./api"
66
import { extractAgents } from "./api-helper"
77
import { CertificateError } from "./error"
88
import { Storage } from "./storage"
9-
import { AuthorityPrefix, toSafeHost } from "./util"
9+
import { toRemoteAuthority, toSafeHost } from "./util"
1010
import { OpenableTreeItem } from "./workspacesProvider"
1111

1212
export class Commands {
@@ -499,6 +499,26 @@ export class Commands {
499499
await openWorkspace(baseUrl, workspaceOwner, workspaceName, workspaceAgent, folderPath, openRecent)
500500
}
501501

502+
/**
503+
* Open a devcontainer from a workspace belonging to the currently logged-in deployment.
504+
*
505+
* Throw if not logged into a deployment.
506+
*/
507+
public async openDevContainer(...args: string[]): Promise<void> {
508+
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL
509+
if (!baseUrl) {
510+
throw new Error("You are not logged in")
511+
}
512+
513+
const workspaceOwner = args[0] as string
514+
const workspaceName = args[1] as string
515+
const workspaceAgent = undefined // args[2] is reserved, but we do not support multiple agents yet.
516+
const devContainerName = args[3] as string
517+
const devContainerFolder = args[4] as string
518+
519+
await openDevContainer(baseUrl, workspaceOwner, workspaceName, workspaceAgent, devContainerName, devContainerFolder)
520+
}
521+
502522
/**
503523
* Update the current workspace. If there is no active workspace connection,
504524
* this is a no-op.
@@ -536,10 +556,7 @@ async function openWorkspace(
536556
) {
537557
// A workspace can have multiple agents, but that's handled
538558
// when opening a workspace unless explicitly specified.
539-
let remoteAuthority = `ssh-remote+${AuthorityPrefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}`
540-
if (workspaceAgent) {
541-
remoteAuthority += `.${workspaceAgent}`
542-
}
559+
const remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent)
543560

544561
let newWindow = true
545562
// Open in the existing window if no workspaces are open.
@@ -598,3 +615,32 @@ async function openWorkspace(
598615
reuseWindow: !newWindow,
599616
})
600617
}
618+
619+
async function openDevContainer(
620+
baseUrl: string,
621+
workspaceOwner: string,
622+
workspaceName: string,
623+
workspaceAgent: string | undefined,
624+
devContainerName: string,
625+
devContainerFolder: string,
626+
) {
627+
const remoteAuthority = toRemoteAuthority(baseUrl, workspaceOwner, workspaceName, workspaceAgent)
628+
629+
const devContainer = Buffer.from(JSON.stringify({ containerName: devContainerName }), "utf-8").toString("hex")
630+
const devContainerAuthority = `attached-container+${devContainer}@${remoteAuthority}`
631+
632+
let newWindow = true
633+
if (!vscode.workspace.workspaceFolders?.length) {
634+
newWindow = false
635+
}
636+
637+
await vscode.commands.executeCommand(
638+
"vscode.openFolder",
639+
vscode.Uri.from({
640+
scheme: "vscode-remote",
641+
authority: devContainerAuthority,
642+
path: devContainerFolder,
643+
}),
644+
newWindow,
645+
)
646+
}

src/extension.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,61 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
130130
await storage.configureCli(toSafeHost(url), url, token)
131131

132132
vscode.commands.executeCommand("coder.open", owner, workspace, agent, folder, openRecent)
133+
} else if (uri.path === "/openDevContainer") {
134+
const workspaceOwner = params.get("owner")
135+
const workspaceName = params.get("workspace")
136+
const workspaceAgent = params.get("agent")
137+
const devContainerName = params.get("devContainerName")
138+
const devContainerFolder = params.get("devContainerFolder")
139+
140+
if (!workspaceOwner) {
141+
throw new Error("workspace owner must be specified as a query parameter")
142+
}
143+
144+
if (!workspaceName) {
145+
throw new Error("workspace name must be specified as a query parameter")
146+
}
147+
148+
if (!devContainerName) {
149+
throw new Error("dev container name must be specified as a query parameter")
150+
}
151+
152+
if (!devContainerFolder) {
153+
throw new Error("dev container folder must be specified as a query parameter")
154+
}
155+
156+
// We are not guaranteed that the URL we currently have is for the URL
157+
// this workspace belongs to, or that we even have a URL at all (the
158+
// queries will default to localhost) so ask for it if missing.
159+
// Pre-populate in case we do have the right URL so the user can just
160+
// hit enter and move on.
161+
const url = await commands.maybeAskUrl(params.get("url"), storage.getUrl())
162+
if (url) {
163+
restClient.setHost(url)
164+
await storage.setUrl(url)
165+
} else {
166+
throw new Error("url must be provided or specified as a query parameter")
167+
}
168+
169+
// If the token is missing we will get a 401 later and the user will be
170+
// prompted to sign in again, so we do not need to ensure it is set now.
171+
// For non-token auth, we write a blank token since the `vscodessh`
172+
// command currently always requires a token file. However, if there is
173+
// a query parameter for non-token auth go ahead and use it anyway; all
174+
// that really matters is the file is created.
175+
const token = needToken() ? params.get("token") : (params.get("token") ?? "")
176+
177+
// Store on disk to be used by the cli.
178+
await storage.configureCli(toSafeHost(url), url, token)
179+
180+
vscode.commands.executeCommand(
181+
"coder.openDevContainer",
182+
workspaceOwner,
183+
workspaceName,
184+
workspaceAgent,
185+
devContainerName,
186+
devContainerFolder,
187+
)
133188
} else {
134189
throw new Error(`Unknown path ${uri.path}`)
135190
}
@@ -142,6 +197,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
142197
vscode.commands.registerCommand("coder.login", commands.login.bind(commands))
143198
vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands))
144199
vscode.commands.registerCommand("coder.open", commands.open.bind(commands))
200+
vscode.commands.registerCommand("coder.openDevContainer", commands.openDevContainer.bind(commands))
145201
vscode.commands.registerCommand("coder.openFromSidebar", commands.openFromSidebar.bind(commands))
146202
vscode.commands.registerCommand("coder.workspace.update", commands.updateWorkspace.bind(commands))
147203
vscode.commands.registerCommand("coder.createWorkspace", commands.createWorkspace.bind(commands))

src/inbox.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { Workspace, GetInboxNotificationResponse } from "coder/site/src/api/type
33
import { ProxyAgent } from "proxy-agent"
44
import * as vscode from "vscode"
55
import { WebSocket } from "ws"
6+
import { coderSessionTokenHeader } from "./api"
67
import { errToStr } from "./api-helper"
78
import { getMemoryLogger } from "./memoryLogger"
89
import { type Storage } from "./storage"
@@ -49,14 +50,15 @@ export class Inbox implements vscode.Disposable {
4950
logger.debug(`Connecting to inbox WebSocket at: ${socketUrl}`)
5051

5152
const coderSessionTokenHeader = "Coder-Session-Token"
53+
const token = restClient.getAxiosInstance().defaults.headers.common[coderSessionTokenHeader] as string | undefined
5254
this.#socket = new WebSocket(new URL(socketUrl), {
53-
followRedirects: true,
5455
agent: httpAgent,
55-
headers: {
56-
[coderSessionTokenHeader]: restClient.getAxiosInstance().defaults.headers.common[coderSessionTokenHeader] as
57-
| string
58-
| undefined,
59-
},
56+
followRedirects: true,
57+
headers: token
58+
? {
59+
[coderSessionTokenHeader]: token,
60+
}
61+
: undefined,
6062
})
6163

6264
this.#socket.on("open", () => {

src/util.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,19 @@ export function parseRemoteAuthority(authority: string): AuthorityParts | null {
6161
}
6262
}
6363

64+
export function toRemoteAuthority(
65+
baseUrl: string,
66+
workspaceOwner: string,
67+
workspaceName: string,
68+
workspaceAgent: string | undefined,
69+
): string {
70+
let remoteAuthority = `ssh-remote+${AuthorityPrefix}.${toSafeHost(baseUrl)}--${workspaceOwner}--${workspaceName}`
71+
if (workspaceAgent) {
72+
remoteAuthority += `.${workspaceAgent}`
73+
}
74+
return remoteAuthority
75+
}
76+
6477
/**
6578
* Given a URL, return the host in a format that is safe to write.
6679
*/

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy