Compare commits

...

5 Commits

Author SHA1 Message Date
Muhammad Atif Ali
acd5edffe7 fix(vault-jwt): fix vault CLI installation (#311) 2024-10-16 02:04:28 +05:00
Muhammad Atif Ali
4dcab99cb0 fix(vscode-web): remove exit if extension installation fails (#318) 2024-10-15 22:53:36 +05:00
Muhammad Atif Ali
50a946df0f chore: explicitly setup terraform (#319) 2024-10-15 18:48:15 +05:00
Asher
8a0ac3435c Add owner to Gateway link (#310)
Without this, it is not possible to reliably connect to another user's
workspace (for admins, mainly) when duplicate workspace names are
involved.
2024-10-07 21:16:01 -08:00
Michael Smith
438c904567 chore: cleanup all test files (#293)
## Changes made
- Removed all unused imports, and made sure type imports were labeled
correctly
- Updated all comparisons to be more strict
- Simplified loops to remove unneeded closure functions
- Removed all explicit `any` types
- Updated how strings were defined to follow general TypeScript best
practices

## Notes
- We definitely want some kind of linting setup for this repo. I'm going
to bring this up when Blueberry has its next team meeting next week
2024-09-27 15:35:47 -04:00
24 changed files with 102 additions and 82 deletions

View File

@@ -17,6 +17,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: coder/coder/.github/actions/setup-tf@main
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest
@@ -29,6 +30,7 @@ jobs:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with: with:
fetch-depth: 0 # Needed to get tags fetch-depth: 0 # Needed to get tags
- uses: coder/coder/.github/actions/setup-tf@main
- uses: oven-sh/setup-bun@v2 - uses: oven-sh/setup-bun@v2
with: with:
bun-version: latest bun-version: latest

View File

@@ -1,6 +1,5 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
executeScriptInContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,

View File

@@ -1,6 +1,5 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
executeScriptInContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,

View File

@@ -1,10 +1,5 @@
import { describe, expect, it } from "bun:test"; import { describe } from "bun:test";
import { import { runTerraformInit, testRequiredVariables } from "../test";
executeScriptInContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
} from "../test";
describe("coder-login", async () => { describe("coder-login", async () => {
await runTerraformInit(import.meta.dir); await runTerraformInit(import.meta.dir);

View File

@@ -1,6 +1,5 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
executeScriptInContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,

View File

@@ -1,6 +1,5 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
executeScriptInContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,

View File

@@ -1,3 +1,4 @@
import { type Server, serve } from "bun";
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
createJSONResponse, createJSONResponse,
@@ -9,7 +10,6 @@ import {
testRequiredVariables, testRequiredVariables,
writeCoder, writeCoder,
} from "../test"; } from "../test";
import { Server, serve } from "bun";
describe("github-upload-public-key", async () => { describe("github-upload-public-key", async () => {
await runTerraformInit(import.meta.dir); await runTerraformInit(import.meta.dir);
@@ -21,10 +21,12 @@ describe("github-upload-public-key", async () => {
it("creates new key if one does not exist", async () => { it("creates new key if one does not exist", async () => {
const { instance, id, server } = await setupContainer(); const { instance, id, server } = await setupContainer();
await writeCoder(id, "echo foo"); await writeCoder(id, "echo foo");
let exec = await execContainer(id, [
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env", "env",
"CODER_ACCESS_URL=" + server.url.toString().slice(0, -1), `CODER_ACCESS_URL=${url}`,
"GITHUB_API_URL=" + server.url.toString().slice(0, -1), `GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo", "CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github", "CODER_EXTERNAL_AUTH_ID=github",
"bash", "bash",
@@ -42,10 +44,12 @@ describe("github-upload-public-key", async () => {
const { instance, id, server } = await setupContainer(); const { instance, id, server } = await setupContainer();
// use keyword to make server return a existing key // use keyword to make server return a existing key
await writeCoder(id, "echo findkey"); await writeCoder(id, "echo findkey");
let exec = await execContainer(id, [
const url = server.url.toString().slice(0, -1);
const exec = await execContainer(id, [
"env", "env",
"CODER_ACCESS_URL=" + server.url.toString().slice(0, -1), `CODER_ACCESS_URL=${url}`,
"GITHUB_API_URL=" + server.url.toString().slice(0, -1), `GITHUB_API_URL=${url}`,
"CODER_OWNER_SESSION_TOKEN=foo", "CODER_OWNER_SESSION_TOKEN=foo",
"CODER_EXTERNAL_AUTH_ID=github", "CODER_EXTERNAL_AUTH_ID=github",
"bash", "bash",
@@ -95,7 +99,7 @@ const setupServer = async (): Promise<Server> => {
} }
// case: key already exists // case: key already exists
if (req.headers.get("Authorization") == "Bearer findkey") { if (req.headers.get("Authorization") === "Bearer findkey") {
return createJSONResponse([ return createJSONResponse([
{ {
key: "foo", key: "foo",

View File

@@ -14,7 +14,7 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.13" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
@@ -32,7 +32,7 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.13" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
@@ -46,7 +46,7 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.13" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"
@@ -61,7 +61,7 @@ module "jetbrains_gateway" {
```tf ```tf
module "jetbrains_gateway" { module "jetbrains_gateway" {
source = "registry.coder.com/modules/jetbrains-gateway/coder" source = "registry.coder.com/modules/jetbrains-gateway/coder"
version = "1.0.13" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
agent_name = "example" agent_name = "example"
folder = "/home/coder/example" folder = "/home/coder/example"

View File

@@ -14,6 +14,26 @@ describe("jetbrains-gateway", async () => {
folder: "/home/foo", folder: "/home/foo",
}); });
it("should create a link with the default values", async () => {
const state = await runTerraformApply(import.meta.dir, {
// These are all required.
agent_id: "foo",
agent_name: "foo",
folder: "/home/coder",
});
expect(state.outputs.url.value).toBe(
"jetbrains-gateway://connect#type=coder&workspace=default&owner=default&agent=foo&folder=/home/coder&url=https://mydeployment.coder.com&token=$SESSION_TOKEN&ide_product_code=IU&ide_build_number=241.14494.240&ide_download_link=https://download.jetbrains.com/idea/ideaIU-2024.1.tar.gz",
);
const coder_app = state.resources.find(
(res) => res.type === "coder_app" && res.name === "gateway",
);
expect(coder_app).not.toBeNull();
expect(coder_app?.instances.length).toBe(1);
expect(coder_app?.instances[0].attributes.order).toBeNull();
});
it("default to first ide", async () => { it("default to first ide", async () => {
const state = await runTerraformApply(import.meta.dir, { const state = await runTerraformApply(import.meta.dir, {
agent_id: "foo", agent_id: "foo",

View File

@@ -243,6 +243,7 @@ data "coder_parameter" "jetbrains_ide" {
} }
data "coder_workspace" "me" {} data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}
resource "coder_app" "gateway" { resource "coder_app" "gateway" {
agent_id = var.agent_id agent_id = var.agent_id
@@ -254,6 +255,8 @@ resource "coder_app" "gateway" {
url = join("", [ url = join("", [
"jetbrains-gateway://connect#type=coder&workspace=", "jetbrains-gateway://connect#type=coder&workspace=",
data.coder_workspace.me.name, data.coder_workspace.me.name,
"&owner=",
data.coder_workspace_owner.me.name,
"&agent=", "&agent=",
var.agent_name, var.agent_name,
"&folder=", "&folder=",

View File

@@ -116,7 +116,7 @@ EOF`;
const proxies = ["foo", "bar", "baz"] const proxies = ["foo", "bar", "baz"]
.map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`) .map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`)
.join(","); .join(",");
expect(proxyEnv["value"]).toEqual(proxies); expect(proxyEnv.value).toEqual(proxies);
const coderScript = findResourceInstance(state, "coder_script"); const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain( expect(coderScript.script).toContain(

View File

@@ -151,7 +151,7 @@ EOF`;
const proxies = ["foo", "bar", "baz"] const proxies = ["foo", "bar", "baz"]
.map((r) => `https://${user}:${token}@${fakeFrogApi}/go/${r}`) .map((r) => `https://${user}:${token}@${fakeFrogApi}/go/${r}`)
.join(","); .join(",");
expect(proxyEnv["value"]).toEqual(proxies); expect(proxyEnv.value).toEqual(proxies);
const coderScript = findResourceInstance(state, "coder_script"); const coderScript = findResourceInstance(state, "coder_script");
expect(coderScript.script).toContain( expect(coderScript.script).toContain(

View File

@@ -1,20 +1,20 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
execContainer,
executeScriptInContainer, executeScriptInContainer,
findResourceInstance,
runContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,
findResourceInstance, type TerraformState,
runContainer,
TerraformState,
execContainer,
} from "../test"; } from "../test";
// executes the coder script after installing pip // executes the coder script after installing pip
const executeScriptInContainerWithPip = async ( const executeScriptInContainerWithPip = async (
state: TerraformState, state: TerraformState,
image: string, image: string,
shell: string = "sh", shell = "sh",
): Promise<{ ): Promise<{
exitCode: number; exitCode: number;
stdout: string[]; stdout: string[];

View File

@@ -1,4 +1,4 @@
import { describe, expect, it } from "bun:test"; import { describe } from "bun:test";
import { runTerraformInit, testRequiredVariables } from "../test"; import { runTerraformInit, testRequiredVariables } from "../test";
describe("nodejs", async () => { describe("nodejs", async () => {

View File

@@ -1,13 +1,9 @@
import { readableStreamToText, spawn } from "bun";
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
executeScriptInContainer, executeScriptInContainer,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,
runContainer,
execContainer,
findResourceInstance,
} from "../test"; } from "../test";
describe("personalize", async () => { describe("personalize", async () => {

View File

@@ -72,7 +72,7 @@ executed`,
it("formats execution with milliseconds", async () => { it("formats execution with milliseconds", async () => {
await assertSlackMessage({ await assertSlackMessage({
command: "echo test", command: "echo test",
format: `$COMMAND took $DURATION`, format: "$COMMAND took $DURATION",
durationMS: 150, durationMS: 150,
output: "echo test took 150ms", output: "echo test took 150ms",
}); });
@@ -81,7 +81,7 @@ executed`,
it("formats execution with seconds", async () => { it("formats execution with seconds", async () => {
await assertSlackMessage({ await assertSlackMessage({
command: "echo test", command: "echo test",
format: `$COMMAND took $DURATION`, format: "$COMMAND took $DURATION",
durationMS: 15000, durationMS: 15000,
output: "echo test took 15.0s", output: "echo test took 15.0s",
}); });
@@ -90,7 +90,7 @@ executed`,
it("formats execution with minutes", async () => { it("formats execution with minutes", async () => {
await assertSlackMessage({ await assertSlackMessage({
command: "echo test", command: "echo test",
format: `$COMMAND took $DURATION`, format: "$COMMAND took $DURATION",
durationMS: 120000, durationMS: 120000,
output: "echo test took 2m 0.0s", output: "echo test took 2m 0.0s",
}); });
@@ -99,7 +99,7 @@ executed`,
it("formats execution with hours", async () => { it("formats execution with hours", async () => {
await assertSlackMessage({ await assertSlackMessage({
command: "echo test", command: "echo test",
format: `$COMMAND took $DURATION`, format: "$COMMAND took $DURATION",
durationMS: 60000 * 60, durationMS: 60000 * 60,
output: "echo test took 1hr 0m 0.0s", output: "echo test took 1hr 0m 0.0s",
}); });

30
test.ts
View File

@@ -1,6 +1,6 @@
import { readableStreamToText, spawn } from "bun"; import { readableStreamToText, spawn } from "bun";
import { afterEach, expect, it } from "bun:test"; import { expect, it } from "bun:test";
import { readFile, unlink } from "fs/promises"; import { readFile, unlink } from "node:fs/promises";
export const runContainer = async ( export const runContainer = async (
image: string, image: string,
@@ -21,7 +21,8 @@ export const runContainer = async (
"-c", "-c",
init, init,
]); ]);
let containerID = await readableStreamToText(proc.stdout);
const containerID = await readableStreamToText(proc.stdout);
const exitCode = await proc.exited; const exitCode = await proc.exited;
if (exitCode !== 0) { if (exitCode !== 0) {
throw new Error(containerID); throw new Error(containerID);
@@ -36,7 +37,7 @@ export const runContainer = async (
export const executeScriptInContainer = async ( export const executeScriptInContainer = async (
state: TerraformState, state: TerraformState,
image: string, image: string,
shell: string = "sh", shell = "sh",
): Promise<{ ): Promise<{
exitCode: number; exitCode: number;
stdout: string[]; stdout: string[];
@@ -116,6 +117,9 @@ export interface CoderScriptAttributes {
url: string; url: string;
} }
export type ResourceInstance<T extends string = string> =
T extends "coder_script" ? CoderScriptAttributes : Record<string, string>;
/** /**
* finds the first instance of the given resource type in the given state. If * finds the first instance of the given resource type in the given state. If
* name is specified, it will only find the instance with the given name. * name is specified, it will only find the instance with the given name.
@@ -124,10 +128,7 @@ export const findResourceInstance = <T extends string>(
state: TerraformState, state: TerraformState,
type: T, type: T,
name?: string, name?: string,
// if type is "coder_script" return CoderScriptAttributes ): ResourceInstance<T> => {
): T extends "coder_script"
? CoderScriptAttributes
: Record<string, string> => {
const resource = state.resources.find( const resource = state.resources.find(
(resource) => (resource) =>
resource.type === type && (name ? resource.name === name : true), resource.type === type && (name ? resource.name === name : true),
@@ -140,7 +141,8 @@ export const findResourceInstance = <T extends string>(
`Resource ${type} has ${resource.instances.length} instances`, `Resource ${type} has ${resource.instances.length} instances`,
); );
} }
return resource.instances[0].attributes as any;
return resource.instances[0].attributes as ResourceInstance<T>;
}; };
/** /**
@@ -157,15 +159,15 @@ export const testRequiredVariables = <TVars extends TerraformVariables>(
}); });
const varNames = Object.keys(vars); const varNames = Object.keys(vars);
varNames.forEach((varName) => { for (const varName of varNames) {
// Ensures that every variable provided is required! // Ensures that every variable provided is required!
it("missing variable " + varName, async () => { it(`missing variable: ${varName}`, async () => {
const localVars: TerraformVariables = {}; const localVars: TerraformVariables = {};
varNames.forEach((otherVarName) => { for (const otherVarName of varNames) {
if (otherVarName !== varName) { if (otherVarName !== varName) {
localVars[otherVarName] = vars[otherVarName]; localVars[otherVarName] = vars[otherVarName];
} }
}); }
try { try {
await runTerraformApply(dir, localVars); await runTerraformApply(dir, localVars);
@@ -181,7 +183,7 @@ export const testRequiredVariables = <TVars extends TerraformVariables>(
} }
throw new Error(`${varName} is not a required variable!`); throw new Error(`${varName} is not a required variable!`);
}); });
}); }
}; };
/** /**

View File

@@ -1,10 +1,14 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "esnext", // If we were just compiling for the tests, we could safely target ESNext at
"module": "nodenext", // all times, but just because we've been starting to add more runtime logic
// files to some of the modules, erring on the side of caution by having a
// older compilation target
"target": "ES6",
"module": "ESNext",
"strict": true, "strict": true,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"moduleResolution": "nodenext", "moduleResolution": "node",
"types": ["bun-types"] "types": ["bun-types"]
} }
} }

View File

@@ -15,7 +15,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec
```tf ```tf
module "vault" { module "vault" {
source = "registry.coder.com/modules/vault-jwt/coder" source = "registry.coder.com/modules/vault-jwt/coder"
version = "1.0.19" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com" vault_addr = "https://vault.example.com"
vault_jwt_role = "coder" # The Vault role to use for authentication vault_jwt_role = "coder" # The Vault role to use for authentication
@@ -41,7 +41,7 @@ curl -H "X-Vault-Token: ${VAULT_TOKEN}" -X GET "${VAULT_ADDR}/v1/coder/secrets/d
```tf ```tf
module "vault" { module "vault" {
source = "registry.coder.com/modules/vault-jwt/coder" source = "registry.coder.com/modules/vault-jwt/coder"
version = "1.0.19" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com" vault_addr = "https://vault.example.com"
vault_jwt_auth_path = "oidc" vault_jwt_auth_path = "oidc"
@@ -56,7 +56,7 @@ data "coder_workspace_owner" "me" {}
module "vault" { module "vault" {
source = "registry.coder.com/modules/vault-jwt/coder" source = "registry.coder.com/modules/vault-jwt/coder"
version = "1.0.19" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com" vault_addr = "https://vault.example.com"
vault_jwt_role = data.coder_workspace_owner.me.groups[0] vault_jwt_role = data.coder_workspace_owner.me.groups[0]
@@ -68,7 +68,7 @@ module "vault" {
```tf ```tf
module "vault" { module "vault" {
source = "registry.coder.com/modules/vault-jwt/coder" source = "registry.coder.com/modules/vault-jwt/coder"
version = "1.0.19" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
vault_addr = "https://vault.example.com" vault_addr = "https://vault.example.com"
vault_jwt_role = "coder" # The Vault role to use for authentication vault_jwt_role = "coder" # The Vault role to use for authentication

View File

@@ -51,7 +51,7 @@ install() {
printf "Failed to determine the latest Vault version.\n" printf "Failed to determine the latest Vault version.\n"
return 1 return 1
fi fi
VAULT_CLI_VERSION=$${VAULT_CLI_VERSION} VAULT_CLI_VERSION=$${LATEST_VERSION}
fi fi
# Check if the vault CLI is installed and has the correct version # Check if the vault CLI is installed and has the correct version

View File

@@ -22,7 +22,7 @@ describe("vscode-desktop", async () => {
); );
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type == "coder_app" && res.name == "vscode", (res) => res.type === "coder_app" && res.name === "vscode",
); );
expect(coder_app).not.toBeNull(); expect(coder_app).not.toBeNull();
@@ -79,7 +79,7 @@ describe("vscode-desktop", async () => {
}); });
const coder_app = state.resources.find( const coder_app = state.resources.find(
(res) => res.type == "coder_app" && res.name == "vscode", (res) => res.type === "coder_app" && res.name === "vscode",
); );
expect(coder_app).not.toBeNull(); expect(coder_app).not.toBeNull();

View File

@@ -14,7 +14,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
```tf ```tf
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.14" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
accept_license = true accept_license = true
} }
@@ -29,7 +29,7 @@ module "vscode-web" {
```tf ```tf
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.14" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
install_prefix = "/home/coder/.vscode-web" install_prefix = "/home/coder/.vscode-web"
folder = "/home/coder" folder = "/home/coder"
@@ -42,7 +42,7 @@ module "vscode-web" {
```tf ```tf
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.14" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"] extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
accept_license = true accept_license = true
@@ -56,7 +56,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
```tf ```tf
module "vscode-web" { module "vscode-web" {
source = "registry.coder.com/modules/vscode-web/coder" source = "registry.coder.com/modules/vscode-web/coder"
version = "1.0.14" version = "1.0.20"
agent_id = coder_agent.example.id agent_id = coder_agent.example.id
extensions = ["dracula-theme.theme-dracula"] extensions = ["dracula-theme.theme-dracula"]
settings = { settings = {

View File

@@ -72,27 +72,25 @@ for extension in "$${EXTENSIONLIST[@]}"; do
output=$($VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force) output=$($VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force)
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "Failed to install extension: $extension: $output" echo "Failed to install extension: $extension: $output"
exit 1
fi fi
done done
if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then
if ! command -v jq > /dev/null; then if ! command -v jq > /dev/null; then
echo "jq is required to install extensions from a workspace file." echo "jq is required to install extensions from a workspace file."
exit 0 else
fi WORKSPACE_DIR="$HOME"
if [ -n "${FOLDER}" ]; then
WORKSPACE_DIR="${FOLDER}"
fi
WORKSPACE_DIR="$HOME" if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then
if [ -n "${FOLDER}" ]; then printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR"
WORKSPACE_DIR="${FOLDER}" extensions=$(jq -r '.recommendations[]' "$WORKSPACE_DIR"/.vscode/extensions.json)
fi for extension in $extensions; do
$VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force
if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then done
printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR" fi
extensions=$(jq -r '.recommendations[]' "$WORKSPACE_DIR"/.vscode/extensions.json)
for extension in $extensions; do
$VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force
done
fi fi
fi fi

View File

@@ -1,6 +1,6 @@
import { describe, expect, it } from "bun:test"; import { describe, expect, it } from "bun:test";
import { import {
TerraformState, type TerraformState,
runTerraformApply, runTerraformApply,
runTerraformInit, runTerraformInit,
testRequiredVariables, testRequiredVariables,