From cd6f2443fb0c158050dbdd01fe61cb7dd2da6a1d Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 17 Apr 2025 11:28:31 +0500 Subject: [PATCH 1/3] feat(windsurf): add Windsurf Editor module --- windsurf/README.md | 37 ++++++++++++++++++ windsurf/main.test.ts | 88 +++++++++++++++++++++++++++++++++++++++++++ windsurf/main.tf | 62 ++++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+) create mode 100644 windsurf/README.md create mode 100644 windsurf/main.test.ts create mode 100644 windsurf/main.tf diff --git a/windsurf/README.md b/windsurf/README.md new file mode 100644 index 00000000..93f25ebb --- /dev/null +++ b/windsurf/README.md @@ -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" +} +``` diff --git a/windsurf/main.test.ts b/windsurf/main.test.ts new file mode 100644 index 00000000..b02eb9fd --- /dev/null +++ b/windsurf/main.test.ts @@ -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.cursor_url.value).toBe( + "cursor://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", + openRecent: "false", + }); + expect(state.outputs.cursor_url.value).toBe( + "cursor://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.cursor_url.value).toBe( + "cursor://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 === "cursor", + ); + + expect(coder_app).not.toBeNull(); + expect(coder_app?.instances.length).toBe(1); + expect(coder_app?.instances[0].attributes.order).toBe(22); + }); +}); diff --git a/windsurf/main.tf b/windsurf/main.tf new file mode 100644 index 00000000..1d836d7e --- /dev/null +++ b/windsurf/main.tf @@ -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." +} From 467d3f3b10f56ebb4cd3d5dbdb7384c124621e77 Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 24 Apr 2025 01:43:21 +0500 Subject: [PATCH 2/3] add icon --- .icons/windsurf.svg | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .icons/windsurf.svg diff --git a/.icons/windsurf.svg b/.icons/windsurf.svg new file mode 100644 index 00000000..a7684d4c --- /dev/null +++ b/.icons/windsurf.svg @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From a7c863549abfa81e1675a7807f78d546854f9fbd Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Thu, 24 Apr 2025 01:52:53 +0500 Subject: [PATCH 3/3] fix tests --- windsurf/main.test.ts | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/windsurf/main.test.ts b/windsurf/main.test.ts index b02eb9fd..a158962a 100644 --- a/windsurf/main.test.ts +++ b/windsurf/main.test.ts @@ -43,10 +43,10 @@ describe("windsurf", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", folder: "/foo/bar", - open_recent: "true", + open_recent: true, }); - expect(state.outputs.cursor_url.value).toBe( - "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + 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", ); }); @@ -54,31 +54,31 @@ describe("windsurf", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", folder: "/foo/bar", - openRecent: "false", + open_recent: false, }); - expect(state.outputs.cursor_url.value).toBe( - "cursor://coder.coder-remote/open?owner=default&workspace=default&folder=/foo/bar&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + 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", + open_recent: true, }); - expect(state.outputs.cursor_url.value).toBe( - "cursor://coder.coder-remote/open?owner=default&workspace=default&openRecent&url=https://mydeployment.coder.com&token=$SESSION_TOKEN", + 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", + order: 22, }); const coder_app = state.resources.find( - (res) => res.type === "coder_app" && res.name === "cursor", + (res) => res.type === "coder_app" && res.name === "windsurf", ); expect(coder_app).not.toBeNull();