From d0ed6f01ae5d8b6c980bd9f4ddc1584e3dcb7bde Mon Sep 17 00:00:00 2001 From: Samuel K Date: Wed, 14 Oct 2026 18:45:54 -0600 Subject: [PATCH 1/5] feat(desktop): select external MicroSandbox for new providers --- .../main/__tests__/ipc-provider-jobs.test.ts | 16 ++ .../components/provider/ProviderWizard.svelte | 21 +- .../provider/ProviderWizard.test.ts | 204 ++++++++++++++++++ .../external-runtime-rollout.mdx | 14 +- 4 files changed, 243 insertions(+), 12 deletions(-) create mode 100644 desktop/src/renderer/src/lib/components/provider/ProviderWizard.test.ts diff --git a/desktop/src/main/__tests__/ipc-provider-jobs.test.ts b/desktop/src/main/__tests__/ipc-provider-jobs.test.ts index 8a2cffd621..47fe8043a0 100644 --- a/desktop/src/main/__tests__/ipc-provider-jobs.test.ts +++ b/desktop/src/main/__tests__/ipc-provider-jobs.test.ts @@ -235,6 +235,22 @@ describe("provider job lifecycle over IPC", () => { expect(providerJobs.get("docker")?.activity).toBe("installing") }) + it("adds and initializes the external Microsandbox preset without selecting a default", async () => { + const { cli, providerJobs } = setup() + const source = "github.com/devsy-org/devsy-provider-microsandbox@v0.1.8" + + await invoke("provider_add", { name: "microsandbox", source }) + await invoke("provider_init", { name: "microsandbox" }) + + expect(cli.runStreaming.mock.calls.map(([args]) => args)).toEqual([ + ["provider", "add", source, "--use=false", "--name", "microsandbox"], + ["provider", "init", "microsandbox"], + ]) + expect(cli.run).not.toHaveBeenCalled() + expect(cli.runRaw).not.toHaveBeenCalled() + expect(providerJobs.get("microsandbox")).toBeUndefined() + }) + it("releases an abandoned job so the card stops spinning", async () => { const { providerJobs } = setup(() => ({ lines: [], code: 0 })) diff --git a/desktop/src/renderer/src/lib/components/provider/ProviderWizard.svelte b/desktop/src/renderer/src/lib/components/provider/ProviderWizard.svelte index b51969730b..67d6ca9971 100644 --- a/desktop/src/renderer/src/lib/components/provider/ProviderWizard.svelte +++ b/desktop/src/renderer/src/lib/components/provider/ProviderWizard.svelte @@ -31,11 +31,17 @@ import { isCommandSuccess } from "$lib/utils/log-parser.js" import type { ProviderOption } from "$lib/types/index.js" import type { UnlistenFn } from "$lib/ipc/types.js" -const PRESETS = [ +type ProviderPreset = { name: string; description: string; source?: string } + +const PRESETS: ProviderPreset[] = [ { name: "docker", description: "Local Docker containers" }, { name: "podman", description: "Local Podman containers" }, { name: "apple", description: "Apple containers (macOS 26+, Apple silicon)" }, - { name: "microsandbox", description: "Hardware-isolated microVMs" }, + { + name: "microsandbox", + source: "github.com/devsy-org/devsy-provider-microsandbox@v0.1.8", + description: "Hardware-isolated microVMs", + }, { name: "lima", description: "Linux VMs via Lima (macOS)" }, { name: "orbstack", @@ -49,6 +55,10 @@ const PRESETS = [ { name: "digitalocean", description: "DigitalOcean Droplets" }, ] +function presetSource(preset: ProviderPreset): string { + return preset.source ?? preset.name +} + type Step = "select" | "configure" | "initialize" | "complete" type StepState = "pending" | "active" | "complete" | "error" @@ -196,7 +206,8 @@ async function handleSelect() { error = "Provider source is required" return } - const name = customName.trim() || src + const preset = PRESETS.find((item) => presetSource(item) === src) + const name = customName.trim() || preset?.name || src if (nameExists(name)) { error = `Provider "${name}" already exists. Choose a different name.` return @@ -381,9 +392,9 @@ function handleDone() {