Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.

feat(windsurf): add Windsurf Editor module #446

Merged
merged 4 commits into from
Apr 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .icons/windsurf.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 37 additions & 0 deletions windsurf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
display_name: Windsurf Editor
description: Add a one-click button to launch Windsurf Editor
icon: ../.icons/windsurf.svg
maintainer_github: coder
verified: true
tags: [ide, windsurf, helper, ai]
---

# Windsurf Editor

Add a button to open any workspace with a single click in Windsurf Editor.

Uses the [Coder Remote VS Code Extension](https://github.com/coder/vscode-coder).

```tf
module "windsurf" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/windsurf/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
}
```

## Examples

### Open in a specific directory

```tf
module "windsurf" {
count = data.coder_workspace.me.start_count
source = "registry.coder.com/modules/windsurf/coder"
version = "1.0.0"
agent_id = coder_agent.example.id
folder = "/home/coder/project"
}
```
88 changes: 88 additions & 0 deletions windsurf/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import { describe, expect, it } from "bun:test";
import {
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";

describe("windsurf", async () => {
await runTerraformInit(import.meta.dir);

testRequiredVariables(import.meta.dir, {
agent_id: "foo",
});

it("default output", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
});
expect(state.outputs.windsurf_url.value).toBe(
"windsurf://coder.coder-remote/open?owner=default&workspace=default&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);

const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "windsurf",
);

expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});

it("adds folder", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
});
expect(state.outputs.windsurf_url.value).toBe(
"windsurf://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});

it("adds folder and open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: true,
});
expect(state.outputs.windsurf_url.value).toBe(
"windsurf://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});

it("adds folder but not open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
folder: "/foo/bar",
open_recent: false,
});
expect(state.outputs.windsurf_url.value).toBe(
"windsurf://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});

it("adds open_recent", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
open_recent: true,
});
expect(state.outputs.windsurf_url.value).toBe(
"windsurf://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN",
);
});

it("expect order to be set", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
order: 22,
});

const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "windsurf",
);

expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBe(22);
});
});
62 changes: 62 additions & 0 deletions windsurf/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 0.23"
}
}
}

variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}

variable "folder" {
type = string
description = "The folder to open in Cursor IDE."
default = ""
}

variable "open_recent" {
type = bool
description = "Open the most recent workspace or folder. Falls back to the folder if there is no recent workspace or folder to open."
default = false
}

variable "order" {
type = number
description = "The order determines the position of app in the UI presentation. The lowest order is shown first and apps with equal order are sorted by name (ascending order)."
default = null
}

data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

resource "coder_app" "windsurf" {
agent_id = var.agent_id
external = true
icon = "/icon/windsurf.svg"
slug = "windsurf"
display_name = "Windsurf Editor"
order = var.order
url = join("", [
"windsurf://coder.coder-remote/open",
"?owner=",
data.coder_workspace_owner.me.name,
"&workspace=",
data.coder_workspace.me.name,
var.folder != "" ? join("", ["&folder=", var.folder]) : "",
var.open_recent ? "&openRecent" : "",
"&url=",
data.coder_workspace.me.access_url,
"&token=$SESSION_TOKEN",
])
}

output "windsurf_url" {
value = coder_app.windsurf.url
description = "Windsurf Editor URL."
}