refactor: update VS Code Web settings handling to merge with existing settings

- Changed test description to reflect merging behavior of settings.
- Updated Terraform variable description to clarify merging of settings.
- Implemented a new function in the run script to merge settings using jq or Python3.
- Adjusted the settings file creation logic to merge new settings with existing ones.
- Updated README to reflect changes in settings configuration and merging requirements.
This commit is contained in:
DevelopmentCats 2026-02-24 13:26:19 -06:00
parent 08bd84c529
commit 8ec817e33c
4 changed files with 70 additions and 15 deletions

View File

@ -51,9 +51,9 @@ module "vscode-web" {
}
```
### Pre-configure Settings
### Pre-configure Machine Settings
Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settings-json-file) file:
Configure VS Code's [Machine settings.json](https://code.visualstudio.com/docs/getstarted/settings#_settings-json-file). These settings are merged with any existing machine settings on startup:
```tf
module "vscode-web" {
@ -69,6 +69,8 @@ module "vscode-web" {
}
```
> **Note:** Merging settings requires `jq` or `python3`. If neither is available, existing machine settings will be preserved. User settings configured through the VS Code UI are stored in browser local storage and will not persist across different browsers or devices.
### Open an existing workspace on startup
To open an existing workspace on startup the `workspace` parameter can be used to represent a path on disk to a `code-workspace` file.

View File

@ -500,7 +500,7 @@ chmod +x /usr/local/bin/code`,
expect(parentDirResult.stdout).toContain("Machine");
});
it("does not overwrite existing settings file", async () => {
it("merges settings with existing settings file", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo",
accept_license: true,
@ -510,7 +510,15 @@ chmod +x /usr/local/bin/code`,
const containerId = await runContainer("ubuntu:22.04");
cleanupContainers.push(containerId);
// Create a mock code CLI
// Install jq and create mock code CLI
await execContainer(containerId, ["apt-get", "update", "-qq"]);
await execContainer(containerId, [
"apt-get",
"install",
"-y",
"-qq",
"jq",
]);
await execContainer(containerId, [
"bash",
"-c",
@ -532,17 +540,18 @@ chmod +x /usr/local/bin/code`,
await execContainer(containerId, ["bash", "-c", script.script]);
// Check that existing settings file was NOT overwritten
// Check that settings were merged (both existing and new should be present)
const settingsResult = await execContainer(containerId, [
"cat",
"/root/.vscode-server/data/Machine/settings.json",
]);
expect(settingsResult.exitCode).toBe(0);
// Should contain existing setting, not the new one
// Should contain both existing and new settings
expect(settingsResult.stdout).toContain("existing.setting");
expect(settingsResult.stdout).toContain("existing_value");
expect(settingsResult.stdout).not.toContain("new.setting");
expect(settingsResult.stdout).toContain("new.setting");
expect(settingsResult.stdout).toContain("new_value");
});
it("creates valid JSON settings file", async () => {

View File

@ -99,7 +99,7 @@ variable "group" {
variable "settings" {
type = any
description = "A map of settings to apply to VS Code web."
description = "A map of settings to apply to VS Code Web's Machine settings. These settings are merged with any existing machine settings on startup."
default = {}
}
@ -167,6 +167,10 @@ variable "commit_id" {
data "coder_workspace_owner" "me" {}
data "coder_workspace" "me" {}
locals {
settings_b64 = var.settings != {} ? base64encode(jsonencode(var.settings)) : ""
}
resource "coder_script" "vscode-web" {
agent_id = var.agent_id
display_name = "VS Code Web"
@ -177,8 +181,7 @@ resource "coder_script" "vscode-web" {
INSTALL_PREFIX : var.install_prefix,
EXTENSIONS : join(",", var.extensions),
TELEMETRY_LEVEL : var.telemetry_level,
// This is necessary otherwise the quotes are stripped!
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
SETTINGS_B64 : local.settings_b64,
OFFLINE : var.offline,
USE_CACHED : var.use_cached,
DISABLE_TRUST : var.disable_trust,

View File

@ -5,6 +5,47 @@ RESET='\033[0m'
CODE='\033[36;40;1m'
EXTENSIONS=("${EXTENSIONS}")
# Merge settings from module with existing settings file
# Uses jq if available, falls back to Python3 for deep merge
merge_settings() {
local new_settings="$1"
local settings_file="$2"
if [ -z "$new_settings" ] || [ "$new_settings" = "{}" ]; then
return 0
fi
if [ ! -f "$settings_file" ]; then
mkdir -p "$(dirname "$settings_file")"
printf '%s\n' "$new_settings" > "$settings_file"
printf "Creating settings file...\n"
return 0
fi
local tmpfile
tmpfile="$(mktemp)"
if command -v jq > /dev/null 2>&1; then
if jq -s '.[0] * .[1]' "$settings_file" <(printf '%s\n' "$new_settings") > "$tmpfile" 2> /dev/null; then
mv "$tmpfile" "$settings_file"
printf "Merging settings...\n"
return 0
fi
fi
if command -v python3 > /dev/null 2>&1; then
if python3 -c "import json,sys;m=lambda a,b:{**a,**{k:m(a[k],v)if k in a and type(a[k])==type(v)==dict else v for k,v in b.items()}};print(json.dumps(m(json.load(open(sys.argv[1])),json.loads(sys.argv[2])),indent=2))" "$settings_file" "$new_settings" > "$tmpfile" 2> /dev/null; then
mv "$tmpfile" "$settings_file"
printf "Merging settings...\n"
return 0
fi
fi
rm -f "$tmpfile"
printf "Warning: Could not merge settings. Keeping existing settings.\n"
return 0
}
# Set extension directory argument
EXTENSION_ARG=""
if [ -n "${EXTENSIONS_DIR}" ]; then
@ -251,11 +292,11 @@ install_extensions() {
fi
}
# Create settings file if it doesn't exist
if [ ! -f ~/.vscode-server/data/Machine/settings.json ]; then
printf "Creating settings file...\n"
mkdir -p ~/.vscode-server/data/Machine
echo "${SETTINGS}" > ~/.vscode-server/data/Machine/settings.json
# Apply machine settings (merge with existing if present)
SETTINGS_B64='${SETTINGS_B64}'
if [ -n "$SETTINGS_B64" ]; then
SETTINGS_JSON="$(echo -n "$SETTINGS_B64" | base64 -d)"
merge_settings "$SETTINGS_JSON" ~/.vscode-server/data/Machine/settings.json
fi
# Determine which command to use