Add Cursor IDE module with Terraform support
This commit introduces the Cursor IDE module, allowing users to add a one-click button for launching Cursor IDE into their workspaces. The module is designed to work with Terraform, making it straightforward to manage and configure through infrastructure as code. - Adds a comprehensive README for setting up the module. - Includes example configurations for typical usage scenarios. - Provides automated tests to ensure module reliability. - Defines Terraform resources and variables to handle essential configurations like agent ID, folder locations, and order in the UI.pull/290/head
							parent
							
								
									831f64da56
								
							
						
					
					
						commit
						f07bc2ff11
					
				@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					---
 | 
				
			||||||
 | 
					display_name: Cursor IDE
 | 
				
			||||||
 | 
					description: Add a one-click button to launch Cursor IDE
 | 
				
			||||||
 | 
					icon: ../.icons/cursor.svg
 | 
				
			||||||
 | 
					maintainer_github: coder
 | 
				
			||||||
 | 
					verified: true
 | 
				
			||||||
 | 
					tags: [ide, cursor, helper]
 | 
				
			||||||
 | 
					---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Cursor IDE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Add a button to open any workspace with a single click in Cursor IDE.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Uses the [Coder Remote VS Code Extension](https://github.com/coder/cursor-coder).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```tf
 | 
				
			||||||
 | 
					module "cursor" {
 | 
				
			||||||
 | 
					  source   = "registry.coder.com/modules/cursor/coder"
 | 
				
			||||||
 | 
					  version  = "1.0.15"
 | 
				
			||||||
 | 
					  agent_id = coder_agent.example.id
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Examples
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Open in a specific directory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```tf
 | 
				
			||||||
 | 
					module "cursor" {
 | 
				
			||||||
 | 
					  source   = "registry.coder.com/modules/cursor/coder"
 | 
				
			||||||
 | 
					  version  = "1.0.15"
 | 
				
			||||||
 | 
					  agent_id = coder_agent.example.id
 | 
				
			||||||
 | 
					  folder   = "/home/coder/project"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
@ -0,0 +1,89 @@
 | 
				
			|||||||
 | 
					import { describe, expect, it } from "bun:test";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  executeScriptInContainer,
 | 
				
			||||||
 | 
					  runTerraformApply,
 | 
				
			||||||
 | 
					  runTerraformInit,
 | 
				
			||||||
 | 
					  testRequiredVariables,
 | 
				
			||||||
 | 
					} from "../test";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe("cursor", 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.cursor_url.value).toBe(
 | 
				
			||||||
 | 
					      "cursor://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 == "cursor",
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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.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 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);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -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" "vscode" {
 | 
				
			||||||
 | 
					  agent_id     = var.agent_id
 | 
				
			||||||
 | 
					  external     = true
 | 
				
			||||||
 | 
					  icon         = "/icon/code.svg"
 | 
				
			||||||
 | 
					  slug         = "vscode"
 | 
				
			||||||
 | 
					  display_name = "VS Code Desktop"
 | 
				
			||||||
 | 
					  order        = var.order
 | 
				
			||||||
 | 
					  url = join("", [
 | 
				
			||||||
 | 
					    "vscode://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 "vscode_url" {
 | 
				
			||||||
 | 
					  value       = coder_app.vscode.url
 | 
				
			||||||
 | 
					  description = "VS Code Desktop URL."
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
					Loading…
					
					
				
		Reference in New Issue