From 96c0ca7cce7144d665e270ec95641e798f7d18b2 Mon Sep 17 00:00:00 2001 From: Atif Ali Date: Sat, 13 Dec 2025 12:54:13 +0000 Subject: [PATCH 1/2] fix(zed): fix settings JSON parsing with base64 encoding The previous implementation used quote escaping which produced invalid JSON: SETTINGS_JSON='{"theme":"dark"}' This broke jq parsing because the escaped quotes are not valid JSON. Changed to use base64 encoding internally: settings_b64 = base64encode(var.settings) SETTINGS_JSON="$(echo -n "${SETTINGS_B64}" | base64 -d)" User interface remains the same - pass JSON via jsonencode() or heredoc. Also added end-to-end container tests and fixed existing tests to use override_data for workspace mocking. --- registry/coder/modules/zed/main.test.ts | 138 +++++++++++++++------- registry/coder/modules/zed/main.tf | 7 +- registry/coder/modules/zed/zed.tftest.hcl | 74 ++++++++++++ 3 files changed, 175 insertions(+), 44 deletions(-) diff --git a/registry/coder/modules/zed/main.test.ts b/registry/coder/modules/zed/main.test.ts index 124137505..9a39b47b1 100644 --- a/registry/coder/modules/zed/main.test.ts +++ b/registry/coder/modules/zed/main.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "bun:test"; import { + execContainer, + findResourceInstance, + removeContainer, + runContainer, runTerraformApply, runTerraformInit, testRequiredVariables, @@ -12,66 +16,114 @@ describe("zed", async () => { agent_id: "foo", }); - it("default output", async () => { + it("creates settings file with correct JSON", async () => { + const settings = { + theme: "One Dark", + buffer_font_size: 14, + vim_mode: true, + telemetry: { + diagnostics: false, + metrics: false, + }, + // Test special characters: single quotes, backslashes, URLs + message: "it's working", + path: "C:\\Users\\test", + api_url: "https://api.example.com/v1?token=abc&user=test", + }; + const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", + settings: JSON.stringify(settings), }); - expect(state.outputs.zed_url.value).toBe("zed://ssh/default.coder"); - const coder_app = state.resources.find( - (res) => res.type === "coder_app" && res.name === "zed", - ); + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer("alpine:latest"); - expect(coder_app).not.toBeNull(); - expect(coder_app?.instances.length).toBe(1); - expect(coder_app?.instances[0].attributes.order).toBeNull(); - }); + try { + const result = await execContainer(id, ["sh", "-c", instance.script]); + expect(result.exitCode).toBe(0); - it("adds folder", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - folder: "/foo/bar", - }); - expect(state.outputs.zed_url.value).toBe("zed://ssh/default.coder/foo/bar"); - }); + const catResult = await execContainer(id, [ + "cat", + "/root/.config/zed/settings.json", + ]); + expect(catResult.exitCode).toBe(0); - it("expect order to be set", async () => { - const state = await runTerraformApply(import.meta.dir, { - agent_id: "foo", - order: "22", - }); + const written = JSON.parse(catResult.stdout.trim()); + expect(written).toEqual(settings); + } finally { + await removeContainer(id); + } + }, 30000); - const coder_app = state.resources.find( - (res) => res.type === "coder_app" && res.name === "zed", - ); + it("merges settings with existing file when jq available", async () => { + const existingSettings = { + theme: "Solarized Dark", + vim_mode: true, + }; - expect(coder_app).not.toBeNull(); - expect(coder_app?.instances.length).toBe(1); - expect(coder_app?.instances[0].attributes.order).toBe(22); - }); + const newSettings = { + theme: "One Dark", + buffer_font_size: 14, + }; - it("expect display_name to be set", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - display_name: "Custom Zed", + settings: JSON.stringify(newSettings), }); - const coder_app = state.resources.find( - (res) => res.type === "coder_app" && res.name === "zed", - ); + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer("alpine:latest"); - expect(coder_app).not.toBeNull(); - expect(coder_app?.instances.length).toBe(1); - expect(coder_app?.instances[0].attributes.display_name).toBe("Custom Zed"); - }); + try { + // Install jq and create existing settings file + await execContainer(id, ["apk", "add", "--no-cache", "jq"]); + await execContainer(id, ["mkdir", "-p", "/root/.config/zed"]); + await execContainer(id, [ + "sh", + "-c", + `echo '${JSON.stringify(existingSettings)}' > /root/.config/zed/settings.json`, + ]); + + const result = await execContainer(id, ["sh", "-c", instance.script]); + expect(result.exitCode).toBe(0); + + const catResult = await execContainer(id, [ + "cat", + "/root/.config/zed/settings.json", + ]); + expect(catResult.exitCode).toBe(0); + + const merged = JSON.parse(catResult.stdout.trim()); + expect(merged.theme).toBe("One Dark"); // overwritten + expect(merged.buffer_font_size).toBe(14); // added + expect(merged.vim_mode).toBe(true); // preserved + } finally { + await removeContainer(id); + } + }, 30000); - it("adds agent_name to hostname", async () => { + it("exits early with empty settings", async () => { const state = await runTerraformApply(import.meta.dir, { agent_id: "foo", - agent_name: "myagent", + settings: "", }); - expect(state.outputs.zed_url.value).toBe( - "zed://ssh/myagent.default.default.coder", - ); - }); + + const instance = findResourceInstance(state, "coder_script"); + const id = await runContainer("alpine:latest"); + + try { + const result = await execContainer(id, ["sh", "-c", instance.script]); + expect(result.exitCode).toBe(0); + + // Settings file should not be created + const catResult = await execContainer(id, [ + "cat", + "/root/.config/zed/settings.json", + ]); + expect(catResult.exitCode).not.toBe(0); + } finally { + await removeContainer(id); + } + }, 30000); }); diff --git a/registry/coder/modules/zed/main.tf b/registry/coder/modules/zed/main.tf index 745254be0..414c3c0e9 100644 --- a/registry/coder/modules/zed/main.tf +++ b/registry/coder/modules/zed/main.tf @@ -65,6 +65,7 @@ locals { owner_name = lower(data.coder_workspace_owner.me.name) agent_name = lower(var.agent_name) hostname = var.agent_name != "" ? "${local.agent_name}.${local.workspace_name}.${local.owner_name}.coder" : "${local.workspace_name}.coder" + settings_b64 = var.settings != "" ? base64encode(var.settings) : "" } resource "coder_script" "zed_settings" { @@ -75,7 +76,11 @@ resource "coder_script" "zed_settings" { script = <<-EOT #!/usr/bin/env bash set -eu - SETTINGS_JSON='${replace(var.settings, "\"", "\\\"")}' + SETTINGS_B64='${local.settings_b64}' + if [ -z "$${SETTINGS_B64}" ]; then + exit 0 + fi + SETTINGS_JSON="$(echo -n "$${SETTINGS_B64}" | base64 -d)" if [ -z "$${SETTINGS_JSON}" ] || [ "$${SETTINGS_JSON}" = "{}" ]; then exit 0 fi diff --git a/registry/coder/modules/zed/zed.tftest.hcl b/registry/coder/modules/zed/zed.tftest.hcl index 508b65503..339f6876f 100644 --- a/registry/coder/modules/zed/zed.tftest.hcl +++ b/registry/coder/modules/zed/zed.tftest.hcl @@ -5,6 +5,20 @@ run "default_output" { agent_id = "foo" } + override_data { + target = data.coder_workspace.me + values = { + name = "default" + } + } + + override_data { + target = data.coder_workspace_owner.me + values = { + name = "default" + } + } + assert { condition = output.zed_url == "zed://ssh/default.coder" error_message = "zed_url did not match expected default URL" @@ -19,6 +33,20 @@ run "adds_folder" { folder = "/foo/bar" } + override_data { + target = data.coder_workspace.me + values = { + name = "default" + } + } + + override_data { + target = data.coder_workspace_owner.me + values = { + name = "default" + } + } + assert { condition = output.zed_url == "zed://ssh/default.coder/foo/bar" error_message = "zed_url did not include provided folder path" @@ -33,8 +61,54 @@ run "adds_agent_name" { agent_name = "myagent" } + override_data { + target = data.coder_workspace.me + values = { + name = "default" + } + } + + override_data { + target = data.coder_workspace_owner.me + values = { + name = "default" + } + } + assert { condition = output.zed_url == "zed://ssh/myagent.default.default.coder" error_message = "zed_url did not include agent_name in hostname" } } + +run "settings_base64_encoding" { + command = apply + + variables { + agent_id = "foo" + settings = jsonencode({ + theme = "dark" + fontSize = 14 + }) + } + + # Verify settings are base64 encoded (eyJ = base64 prefix for JSON starting with {") + assert { + condition = can(regex("SETTINGS_B64='eyJ", coder_script.zed_settings.script)) + error_message = "settings should be base64 encoded in the script" + } +} + +run "empty_settings" { + command = apply + + variables { + agent_id = "foo" + settings = "" + } + + assert { + condition = can(regex("SETTINGS_B64=''", coder_script.zed_settings.script)) + error_message = "empty settings should result in empty SETTINGS_B64" + } +} From f198e6ba0af018049d3cbb033ca9b42e7b6a1cdf Mon Sep 17 00:00:00 2001 From: Muhammad Atif Ali Date: Tue, 16 Dec 2025 14:41:00 +0500 Subject: [PATCH 2/2] bump version Signed-off-by: Muhammad Atif Ali --- registry/coder/modules/zed/README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/registry/coder/modules/zed/README.md b/registry/coder/modules/zed/README.md index 60e72f6c3..cffb71448 100644 --- a/registry/coder/modules/zed/README.md +++ b/registry/coder/modules/zed/README.md @@ -19,7 +19,7 @@ Zed is a high-performance, multiplayer code editor from the creators of Atom and module "zed" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/zed/coder" - version = "1.1.3" + version = "1.1.4" agent_id = coder_agent.main.id } ``` @@ -32,7 +32,7 @@ module "zed" { module "zed" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/zed/coder" - version = "1.1.3" + version = "1.1.4" agent_id = coder_agent.main.id folder = "/home/coder/project" } @@ -44,7 +44,7 @@ module "zed" { module "zed" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/zed/coder" - version = "1.1.3" + version = "1.1.4" agent_id = coder_agent.main.id display_name = "Zed Editor" order = 1 @@ -57,7 +57,7 @@ module "zed" { module "zed" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/zed/coder" - version = "1.1.3" + version = "1.1.4" agent_id = coder_agent.main.id agent_name = coder_agent.example.name } @@ -73,7 +73,7 @@ You can declaratively set/merge settings with the `settings` input. Provide a JS module "zed" { count = data.coder_workspace.me.start_count source = "registry.coder.com/coder/zed/coder" - version = "1.1.3" + version = "1.1.4" agent_id = coder_agent.main.id settings = jsonencode({ @@ -85,6 +85,7 @@ module "zed" { env = {} } + } }) }