From e5b706015902b497ae3ea2df4769aac0c49ecab2 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Mon, 29 Sep 2025 17:42:14 +0300 Subject: [PATCH 1/2] Add support to CODER_BINARY_DESTINATION environment variable --- CHANGELOG.md | 4 ++++ package.json | 2 +- src/core/pathResolver.test.ts | 27 +++++++++++++++++++++++++-- src/core/pathResolver.ts | 11 +++++++---- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35649a76..e9da9987 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +### Added + +- Support for `CODER_BINARY_DESTINATION` environment variable to set CLI download location (overridden by extension setting `coder.binaryDestination` if configured). + ## [v1.11.0](https://github.com/coder/vscode-coder/releases/tag/v1.11.0) 2025-09-24 ### Changed diff --git a/package.json b/package.json index 41878cc9..5c1ab016 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "default": "" }, "coder.binaryDestination": { - "markdownDescription": "The full path of the directory into which the Coder CLI will be downloaded. Defaults to the extension's global storage directory.", + "markdownDescription": "The full path of the directory into which the Coder CLI will be downloaded. Defaults to the value of `CODER_BINARY_DESTINATION` if not set, otherwise the extension's global storage directory.", "type": "string", "default": "" }, diff --git a/src/core/pathResolver.test.ts b/src/core/pathResolver.test.ts index 8216a547..3c331a26 100644 --- a/src/core/pathResolver.test.ts +++ b/src/core/pathResolver.test.ts @@ -1,5 +1,5 @@ import * as path from "path"; -import { describe, it, expect, beforeEach } from "vitest"; +import { describe, it, expect, beforeEach, vi } from "vitest"; import { MockConfigurationProvider } from "../__mocks__/testHelpers"; import { PathResolver } from "./pathResolver"; @@ -11,6 +11,7 @@ describe("PathResolver", () => { let mockConfig: MockConfigurationProvider; beforeEach(() => { + vi.unstubAllEnvs(); pathResolver = new PathResolver(basePath, codeLogPath); mockConfig = new MockConfigurationProvider(); }); @@ -32,6 +33,7 @@ describe("PathResolver", () => { }); it("should use default path when custom destination is empty or whitespace", () => { + vi.stubEnv("CODER_BINARY_DESTINATION", " "); mockConfig.set("coder.binaryDestination", " "); expect(pathResolver.getBinaryCachePath("deployment")).toBe( path.join(basePath, "deployment", "bin"), @@ -41,7 +43,28 @@ describe("PathResolver", () => { it("should normalize custom paths", () => { mockConfig.set("coder.binaryDestination", "/custom/../binary/./path"); expect(pathResolver.getBinaryCachePath("deployment")).toBe( - path.normalize("/custom/../binary/./path"), + "/binary/path", + ); + }); + + it("should use CODER_BINARY_DESTINATION environment variable with proper precedence", () => { + // Use the global storage when the environment variable and setting are unset/blank + vi.stubEnv("CODER_BINARY_DESTINATION", ""); + mockConfig.set("coder.binaryDestination", ""); + expect(pathResolver.getBinaryCachePath("deployment")).toBe( + path.join(basePath, "deployment", "bin"), + ); + + // Test environment variable takes precedence over global storage + vi.stubEnv("CODER_BINARY_DESTINATION", " /env/binary/path "); + expect(pathResolver.getBinaryCachePath("deployment")).toBe( + "/env/binary/path", + ); + + // Test setting takes precedence over environment variable + mockConfig.set("coder.binaryDestination", " /setting/path "); + expect(pathResolver.getBinaryCachePath("deployment")).toBe( + "/setting/path", ); }); }); diff --git a/src/core/pathResolver.ts b/src/core/pathResolver.ts index 6c1ee7ef..514e64fb 100644 --- a/src/core/pathResolver.ts +++ b/src/core/pathResolver.ts @@ -28,11 +28,14 @@ export class PathResolver { * The caller must ensure this directory exists before use. */ public getBinaryCachePath(label: string): string { - const configPath = vscode.workspace + const settingPath = vscode.workspace .getConfiguration() - .get("coder.binaryDestination"); - return configPath && configPath.trim().length > 0 - ? path.normalize(configPath) + .get("coder.binaryDestination") + ?.trim(); + const binaryPath = + settingPath || process.env.CODER_BINARY_DESTINATION?.trim(); + return binaryPath + ? path.normalize(binaryPath) : path.join(this.getGlobalConfigDir(label), "bin"); } From d9e651adcde6f447ebc600baca8af7e9b1a1c15c Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Wed, 1 Oct 2025 10:49:35 +0300 Subject: [PATCH 2/2] Trim headerCommand and defaultUrl settings and their env variables --- src/commands.ts | 9 ++++++--- src/extension.ts | 4 +++- src/headers.test.ts | 4 ++-- src/headers.ts | 9 ++++----- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/commands.ts b/src/commands.ts index 914adbfc..b9dcf10d 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -102,10 +102,13 @@ export class Commands { * CODER_URL or enter a new one. Undefined means the user aborted. */ private async askURL(selection?: string): Promise { - const defaultURL = - vscode.workspace.getConfiguration().get("coder.defaultUrl") ?? ""; + const defaultURL = vscode.workspace + .getConfiguration() + .get("coder.defaultUrl") + ?.trim(); const quickPick = vscode.window.createQuickPick(); - quickPick.value = selection || defaultURL || process.env.CODER_URL || ""; + quickPick.value = + selection || defaultURL || process.env.CODER_URL?.trim() || ""; quickPick.placeholder = "https://example.coder.com"; quickPick.title = "Enter the URL of your Coder deployment."; diff --git a/src/extension.ts b/src/extension.ts index bd8a09c6..678ea3b7 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -423,7 +423,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { // Handle autologin, if not already logged in. const cfg = vscode.workspace.getConfiguration(); if (cfg.get("coder.autologin") === true) { - const defaultUrl = cfg.get("coder.defaultUrl") || process.env.CODER_URL; + const defaultUrl = + cfg.get("coder.defaultUrl")?.trim() || + process.env.CODER_URL?.trim(); if (defaultUrl) { vscode.commands.executeCommand( "coder.login", diff --git a/src/headers.test.ts b/src/headers.test.ts index 84c39d36..6f2933a3 100644 --- a/src/headers.test.ts +++ b/src/headers.test.ts @@ -123,9 +123,9 @@ describe("getHeaderCommand", () => { expect(getHeaderCommand(config)).toBeUndefined(); }); - it("should return undefined if coder.headerCommand is not a string", () => { + it("should return undefined if coder.headerCommand is a blank string", () => { const config = { - get: () => 1234, + get: () => " ", } as unknown as WorkspaceConfiguration; expect(getHeaderCommand(config)).toBeUndefined(); diff --git a/src/headers.ts b/src/headers.ts index d259c9e1..1aad4258 100644 --- a/src/headers.ts +++ b/src/headers.ts @@ -19,11 +19,10 @@ export function getHeaderCommand( config: WorkspaceConfiguration, ): string | undefined { const cmd = - config.get("coder.headerCommand") || process.env.CODER_HEADER_COMMAND; - if (!cmd || typeof cmd !== "string") { - return undefined; - } - return cmd; + config.get("coder.headerCommand")?.trim() || + process.env.CODER_HEADER_COMMAND?.trim(); + + return cmd ? cmd : undefined; } export function getHeaderArgs(config: WorkspaceConfiguration): string[] {