diff --git a/packages/server/src/server/routes/settings.ts b/packages/server/src/server/routes/settings.ts index 5d18e4fd..1d6fe13a 100644 --- a/packages/server/src/server/routes/settings.ts +++ b/packages/server/src/server/routes/settings.ts @@ -1,6 +1,6 @@ import { FastifyInstance } from "fastify" import { z } from "zod" -import { probeBinaryVersion } from "../../workspaces/spawn" +import { probeBinaryVersion } from "../../workspaces/runtime" import type { SettingsService } from "../../settings/service" import type { Logger } from "../../logger" import { sanitizeConfigDoc, sanitizeConfigOwner } from "../../settings/public-config" diff --git a/packages/server/src/workspaces/__tests__/spawn.test.ts b/packages/server/src/workspaces/__tests__/spawn.test.ts deleted file mode 100644 index 8266dac9..00000000 --- a/packages/server/src/workspaces/__tests__/spawn.test.ts +++ /dev/null @@ -1,70 +0,0 @@ -import assert from "node:assert/strict" -import { describe, it } from "node:test" - -import { buildWindowsSpawnSpec, parseWslUncPath, resolveWslWorkingDirectory } from "../spawn" - -describe("parseWslUncPath", () => { - it("parses WSL UNC paths into distro and linux path", () => { - assert.deepEqual(parseWslUncPath(String.raw`\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`), { - distro: "Ubuntu", - linuxPath: "/home/dev/.opencode/bin/opencode", - }) - }) - - it("supports the legacy wsl$ UNC prefix", () => { - assert.deepEqual(parseWslUncPath(String.raw`\\wsl$\Ubuntu\home\dev`), { - distro: "Ubuntu", - linuxPath: "/home/dev", - }) - }) -}) - -describe("resolveWslWorkingDirectory", () => { - it("keeps WSL workspace folders in the same distro", () => { - assert.equal( - resolveWslWorkingDirectory(String.raw`\\wsl.localhost\Ubuntu\home\dev\workspace`, "Ubuntu"), - "/home/dev/workspace", - ) - }) - - it("maps Windows drive paths into /mnt when launching through WSL", () => { - assert.equal(resolveWslWorkingDirectory(String.raw`C:\Users\dev\workspace`, "Ubuntu"), "/mnt/c/Users/dev/workspace") - }) - - it("rejects WSL workspace folders from a different distro", () => { - assert.equal(resolveWslWorkingDirectory(String.raw`\\wsl.localhost\Debian\home\dev\workspace`, "Ubuntu"), null) - }) -}) - -describe("buildWindowsSpawnSpec", () => { - it("wraps WSL binaries with wsl.exe and propagates required env vars", () => { - const spec = buildWindowsSpawnSpec( - String.raw`\\wsl.localhost\Ubuntu\home\dev\.opencode\bin\opencode`, - ["serve", "--port", "0"], - { - cwd: String.raw`\\wsl.localhost\Ubuntu\home\dev\workspace`, - env: { - OPENCODE_CONFIG_DIR: String.raw`C:\Users\dev\AppData\Roaming\CodeNomad\opencode-config`, - CODENOMAD_INSTANCE_ID: "workspace-123", - OPENCODE_SERVER_PASSWORD: "secret", - }, - propagateEnvKeys: ["OPENCODE_CONFIG_DIR", "CODENOMAD_INSTANCE_ID", "OPENCODE_SERVER_PASSWORD"], - }, - ) - - assert.equal(spec.command, "wsl.exe") - assert.deepEqual(spec.args, [ - "--distribution", - "Ubuntu", - "--cd", - "/home/dev/workspace", - "--exec", - "/home/dev/.opencode/bin/opencode", - "serve", - "--port", - "0", - ]) - assert.equal(spec.cwd, undefined) - assert.equal(spec.env?.WSLENV, "OPENCODE_CONFIG_DIR/p:CODENOMAD_INSTANCE_ID:OPENCODE_SERVER_PASSWORD") - }) -}) diff --git a/packages/server/src/workspaces/runtime.ts b/packages/server/src/workspaces/runtime.ts index 3b52ca39..1269f0b7 100644 --- a/packages/server/src/workspaces/runtime.ts +++ b/packages/server/src/workspaces/runtime.ts @@ -4,7 +4,98 @@ import path from "path" import { EventBus } from "../events/bus" import { LogLevel, WorkspaceLogEntry } from "../api-types" import { Logger } from "../logger" -import { buildSpawnSpec } from "./spawn" + +export const WINDOWS_CMD_EXTENSIONS = new Set([".cmd", ".bat"]) +export const WINDOWS_POWERSHELL_EXTENSIONS = new Set([".ps1"]) + +const VERSION_REGEX = /([0-9]+\.[0-9]+\.[0-9A-Za-z.-]+)/ + +export function buildSpawnSpec(binaryPath: string, args: string[]) { + if (process.platform !== "win32") { + return { command: binaryPath, args, options: {} as const } + } + + const extension = path.extname(binaryPath).toLowerCase() + + if (WINDOWS_CMD_EXTENSIONS.has(extension)) { + const comspec = process.env.ComSpec || "cmd.exe" + // cmd.exe requires the full command as a single string. + // Using the ""