Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
acd5edffe7 | ||
|
|
4dcab99cb0 | ||
|
|
50a946df0f | ||
|
|
8a0ac3435c | ||
|
|
438c904567 |
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@@ -17,6 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: coder/coder/.github/actions/setup-tf@main
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
@@ -29,6 +30,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0 # Needed to get tags
|
||||
- uses: coder/coder/.github/actions/setup-tf@main
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: latest
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
} from "../test";
|
||||
import { describe } from "bun:test";
|
||||
import { runTerraformInit, testRequiredVariables } from "../test";
|
||||
|
||||
describe("coder-login", async () => {
|
||||
await runTerraformInit(import.meta.dir);
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { type Server, serve } from "bun";
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
createJSONResponse,
|
||||
@@ -9,7 +10,6 @@ import {
|
||||
testRequiredVariables,
|
||||
writeCoder,
|
||||
} from "../test";
|
||||
import { Server, serve } from "bun";
|
||||
|
||||
describe("github-upload-public-key", async () => {
|
||||
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 () => {
|
||||
const { instance, id, server } = await setupContainer();
|
||||
await writeCoder(id, "echo foo");
|
||||
let exec = await execContainer(id, [
|
||||
|
||||
const url = server.url.toString().slice(0, -1);
|
||||
const exec = await execContainer(id, [
|
||||
"env",
|
||||
"CODER_ACCESS_URL=" + server.url.toString().slice(0, -1),
|
||||
"GITHUB_API_URL=" + server.url.toString().slice(0, -1),
|
||||
`CODER_ACCESS_URL=${url}`,
|
||||
`GITHUB_API_URL=${url}`,
|
||||
"CODER_OWNER_SESSION_TOKEN=foo",
|
||||
"CODER_EXTERNAL_AUTH_ID=github",
|
||||
"bash",
|
||||
@@ -42,10 +44,12 @@ describe("github-upload-public-key", async () => {
|
||||
const { instance, id, server } = await setupContainer();
|
||||
// use keyword to make server return a existing key
|
||||
await writeCoder(id, "echo findkey");
|
||||
let exec = await execContainer(id, [
|
||||
|
||||
const url = server.url.toString().slice(0, -1);
|
||||
const exec = await execContainer(id, [
|
||||
"env",
|
||||
"CODER_ACCESS_URL=" + server.url.toString().slice(0, -1),
|
||||
"GITHUB_API_URL=" + server.url.toString().slice(0, -1),
|
||||
`CODER_ACCESS_URL=${url}`,
|
||||
`GITHUB_API_URL=${url}`,
|
||||
"CODER_OWNER_SESSION_TOKEN=foo",
|
||||
"CODER_EXTERNAL_AUTH_ID=github",
|
||||
"bash",
|
||||
@@ -95,7 +99,7 @@ const setupServer = async (): Promise<Server> => {
|
||||
}
|
||||
|
||||
// case: key already exists
|
||||
if (req.headers.get("Authorization") == "Bearer findkey") {
|
||||
if (req.headers.get("Authorization") === "Bearer findkey") {
|
||||
return createJSONResponse([
|
||||
{
|
||||
key: "foo",
|
||||
|
||||
@@ -14,7 +14,7 @@ This module adds a JetBrains Gateway Button to open any workspace with a single
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -32,7 +32,7 @@ module "jetbrains_gateway" {
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -46,7 +46,7 @@ module "jetbrains_gateway" {
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
@@ -61,7 +61,7 @@ module "jetbrains_gateway" {
|
||||
```tf
|
||||
module "jetbrains_gateway" {
|
||||
source = "registry.coder.com/modules/jetbrains-gateway/coder"
|
||||
version = "1.0.13"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
agent_name = "example"
|
||||
folder = "/home/coder/example"
|
||||
|
||||
@@ -14,6 +14,26 @@ describe("jetbrains-gateway", async () => {
|
||||
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 () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
|
||||
@@ -243,6 +243,7 @@ data "coder_parameter" "jetbrains_ide" {
|
||||
}
|
||||
|
||||
data "coder_workspace" "me" {}
|
||||
data "coder_workspace_owner" "me" {}
|
||||
|
||||
resource "coder_app" "gateway" {
|
||||
agent_id = var.agent_id
|
||||
@@ -254,6 +255,8 @@ resource "coder_app" "gateway" {
|
||||
url = join("", [
|
||||
"jetbrains-gateway://connect#type=coder&workspace=",
|
||||
data.coder_workspace.me.name,
|
||||
"&owner=",
|
||||
data.coder_workspace_owner.me.name,
|
||||
"&agent=",
|
||||
var.agent_name,
|
||||
"&folder=",
|
||||
|
||||
@@ -116,7 +116,7 @@ EOF`;
|
||||
const proxies = ["foo", "bar", "baz"]
|
||||
.map((r) => `https://${user}:@${fakeFrogApi}/go/${r}`)
|
||||
.join(",");
|
||||
expect(proxyEnv["value"]).toEqual(proxies);
|
||||
expect(proxyEnv.value).toEqual(proxies);
|
||||
|
||||
const coderScript = findResourceInstance(state, "coder_script");
|
||||
expect(coderScript.script).toContain(
|
||||
|
||||
@@ -151,7 +151,7 @@ EOF`;
|
||||
const proxies = ["foo", "bar", "baz"]
|
||||
.map((r) => `https://${user}:${token}@${fakeFrogApi}/go/${r}`)
|
||||
.join(",");
|
||||
expect(proxyEnv["value"]).toEqual(proxies);
|
||||
expect(proxyEnv.value).toEqual(proxies);
|
||||
|
||||
const coderScript = findResourceInstance(state, "coder_script");
|
||||
expect(coderScript.script).toContain(
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
execContainer,
|
||||
executeScriptInContainer,
|
||||
findResourceInstance,
|
||||
runContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
findResourceInstance,
|
||||
runContainer,
|
||||
TerraformState,
|
||||
execContainer,
|
||||
type TerraformState,
|
||||
} from "../test";
|
||||
|
||||
// executes the coder script after installing pip
|
||||
const executeScriptInContainerWithPip = async (
|
||||
state: TerraformState,
|
||||
image: string,
|
||||
shell: string = "sh",
|
||||
shell = "sh",
|
||||
): Promise<{
|
||||
exitCode: number;
|
||||
stdout: string[];
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import { describe } from "bun:test";
|
||||
import { runTerraformInit, testRequiredVariables } from "../test";
|
||||
|
||||
describe("nodejs", async () => {
|
||||
|
||||
@@ -1,13 +1,9 @@
|
||||
import { readableStreamToText, spawn } from "bun";
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
runContainer,
|
||||
execContainer,
|
||||
findResourceInstance,
|
||||
} from "../test";
|
||||
|
||||
describe("personalize", async () => {
|
||||
|
||||
@@ -72,7 +72,7 @@ executed`,
|
||||
it("formats execution with milliseconds", async () => {
|
||||
await assertSlackMessage({
|
||||
command: "echo test",
|
||||
format: `$COMMAND took $DURATION`,
|
||||
format: "$COMMAND took $DURATION",
|
||||
durationMS: 150,
|
||||
output: "echo test took 150ms",
|
||||
});
|
||||
@@ -81,7 +81,7 @@ executed`,
|
||||
it("formats execution with seconds", async () => {
|
||||
await assertSlackMessage({
|
||||
command: "echo test",
|
||||
format: `$COMMAND took $DURATION`,
|
||||
format: "$COMMAND took $DURATION",
|
||||
durationMS: 15000,
|
||||
output: "echo test took 15.0s",
|
||||
});
|
||||
@@ -90,7 +90,7 @@ executed`,
|
||||
it("formats execution with minutes", async () => {
|
||||
await assertSlackMessage({
|
||||
command: "echo test",
|
||||
format: `$COMMAND took $DURATION`,
|
||||
format: "$COMMAND took $DURATION",
|
||||
durationMS: 120000,
|
||||
output: "echo test took 2m 0.0s",
|
||||
});
|
||||
@@ -99,7 +99,7 @@ executed`,
|
||||
it("formats execution with hours", async () => {
|
||||
await assertSlackMessage({
|
||||
command: "echo test",
|
||||
format: `$COMMAND took $DURATION`,
|
||||
format: "$COMMAND took $DURATION",
|
||||
durationMS: 60000 * 60,
|
||||
output: "echo test took 1hr 0m 0.0s",
|
||||
});
|
||||
|
||||
30
test.ts
30
test.ts
@@ -1,6 +1,6 @@
|
||||
import { readableStreamToText, spawn } from "bun";
|
||||
import { afterEach, expect, it } from "bun:test";
|
||||
import { readFile, unlink } from "fs/promises";
|
||||
import { expect, it } from "bun:test";
|
||||
import { readFile, unlink } from "node:fs/promises";
|
||||
|
||||
export const runContainer = async (
|
||||
image: string,
|
||||
@@ -21,7 +21,8 @@ export const runContainer = async (
|
||||
"-c",
|
||||
init,
|
||||
]);
|
||||
let containerID = await readableStreamToText(proc.stdout);
|
||||
|
||||
const containerID = await readableStreamToText(proc.stdout);
|
||||
const exitCode = await proc.exited;
|
||||
if (exitCode !== 0) {
|
||||
throw new Error(containerID);
|
||||
@@ -36,7 +37,7 @@ export const runContainer = async (
|
||||
export const executeScriptInContainer = async (
|
||||
state: TerraformState,
|
||||
image: string,
|
||||
shell: string = "sh",
|
||||
shell = "sh",
|
||||
): Promise<{
|
||||
exitCode: number;
|
||||
stdout: string[];
|
||||
@@ -116,6 +117,9 @@ export interface CoderScriptAttributes {
|
||||
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
|
||||
* 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,
|
||||
type: T,
|
||||
name?: string,
|
||||
// if type is "coder_script" return CoderScriptAttributes
|
||||
): T extends "coder_script"
|
||||
? CoderScriptAttributes
|
||||
: Record<string, string> => {
|
||||
): ResourceInstance<T> => {
|
||||
const resource = state.resources.find(
|
||||
(resource) =>
|
||||
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`,
|
||||
);
|
||||
}
|
||||
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);
|
||||
varNames.forEach((varName) => {
|
||||
for (const varName of varNames) {
|
||||
// Ensures that every variable provided is required!
|
||||
it("missing variable " + varName, async () => {
|
||||
it(`missing variable: ${varName}`, async () => {
|
||||
const localVars: TerraformVariables = {};
|
||||
varNames.forEach((otherVarName) => {
|
||||
for (const otherVarName of varNames) {
|
||||
if (otherVarName !== varName) {
|
||||
localVars[otherVarName] = vars[otherVarName];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await runTerraformApply(dir, localVars);
|
||||
@@ -181,7 +183,7 @@ export const testRequiredVariables = <TVars extends TerraformVariables>(
|
||||
}
|
||||
throw new Error(`${varName} is not a required variable!`);
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "esnext",
|
||||
"module": "nodenext",
|
||||
// If we were just compiling for the tests, we could safely target ESNext at
|
||||
// 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,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"moduleResolution": "nodenext",
|
||||
"moduleResolution": "node",
|
||||
"types": ["bun-types"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ This module lets you authenticate with [Hashicorp Vault](https://www.vaultprojec
|
||||
```tf
|
||||
module "vault" {
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
vault_addr = "https://vault.example.com"
|
||||
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
|
||||
module "vault" {
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
vault_addr = "https://vault.example.com"
|
||||
vault_jwt_auth_path = "oidc"
|
||||
@@ -56,7 +56,7 @@ data "coder_workspace_owner" "me" {}
|
||||
|
||||
module "vault" {
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
vault_addr = "https://vault.example.com"
|
||||
vault_jwt_role = data.coder_workspace_owner.me.groups[0]
|
||||
@@ -68,7 +68,7 @@ module "vault" {
|
||||
```tf
|
||||
module "vault" {
|
||||
source = "registry.coder.com/modules/vault-jwt/coder"
|
||||
version = "1.0.19"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
vault_addr = "https://vault.example.com"
|
||||
vault_jwt_role = "coder" # The Vault role to use for authentication
|
||||
|
||||
@@ -51,7 +51,7 @@ install() {
|
||||
printf "Failed to determine the latest Vault version.\n"
|
||||
return 1
|
||||
fi
|
||||
VAULT_CLI_VERSION=$${VAULT_CLI_VERSION}
|
||||
VAULT_CLI_VERSION=$${LATEST_VERSION}
|
||||
fi
|
||||
|
||||
# Check if the vault CLI is installed and has the correct version
|
||||
|
||||
@@ -22,7 +22,7 @@ describe("vscode-desktop", async () => {
|
||||
);
|
||||
|
||||
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();
|
||||
@@ -79,7 +79,7 @@ describe("vscode-desktop", async () => {
|
||||
});
|
||||
|
||||
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();
|
||||
|
||||
@@ -14,7 +14,7 @@ Automatically install [Visual Studio Code Server](https://code.visualstudio.com/
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.14"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
accept_license = true
|
||||
}
|
||||
@@ -29,7 +29,7 @@ module "vscode-web" {
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.14"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
install_prefix = "/home/coder/.vscode-web"
|
||||
folder = "/home/coder"
|
||||
@@ -42,7 +42,7 @@ module "vscode-web" {
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.14"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["github.copilot", "ms-python.python", "ms-toolsai.jupyter"]
|
||||
accept_license = true
|
||||
@@ -56,7 +56,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
|
||||
```tf
|
||||
module "vscode-web" {
|
||||
source = "registry.coder.com/modules/vscode-web/coder"
|
||||
version = "1.0.14"
|
||||
version = "1.0.20"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula"]
|
||||
settings = {
|
||||
|
||||
@@ -72,27 +72,25 @@ for extension in "$${EXTENSIONLIST[@]}"; do
|
||||
output=$($VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to install extension: $extension: $output"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "${AUTO_INSTALL_EXTENSIONS}" = true ]; then
|
||||
if ! command -v jq > /dev/null; then
|
||||
echo "jq is required to install extensions from a workspace file."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
WORKSPACE_DIR="$HOME"
|
||||
if [ -n "${FOLDER}" ]; then
|
||||
WORKSPACE_DIR="${FOLDER}"
|
||||
fi
|
||||
|
||||
WORKSPACE_DIR="$HOME"
|
||||
if [ -n "${FOLDER}" ]; then
|
||||
WORKSPACE_DIR="${FOLDER}"
|
||||
fi
|
||||
|
||||
if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then
|
||||
printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR"
|
||||
extensions=$(jq -r '.recommendations[]' "$WORKSPACE_DIR"/.vscode/extensions.json)
|
||||
for extension in $extensions; do
|
||||
$VSCODE_WEB "$EXTENSION_ARG" --install-extension "$extension" --force
|
||||
done
|
||||
if [ -f "$WORKSPACE_DIR/.vscode/extensions.json" ]; then
|
||||
printf "🧩 Installing extensions from %s/.vscode/extensions.json...\n" "$WORKSPACE_DIR"
|
||||
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
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
TerraformState,
|
||||
type TerraformState,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
|
||||
Reference in New Issue
Block a user