Skip to content
This repository was archived by the owner on May 15, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .icons/devcontainers.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 22 additions & 0 deletions devcontainers-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
display_name: devcontainers-cli
description: devcontainers-cli module provides an easy way to install @devcontainers/cli into a workspace
icon: ../.icons/devcontainers.svg
verified: true
maintainer_github: coder
tags: [devcontainers]
---

# devcontainers-cli

The devcontainers-cli module provides an easy way to install @devcontainers/cli into a workspace. It can be used within any workspace as it runs only if
@devcontainers/cli is not installed yet.
npm is required and should be installed in order for the module to work.

```tf
module "devcontainers-cli" {
source = "registry.coder.com/modules/devcontainers-cli/coder"
version = "release/claude-code/1.0.32"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does release/claude-code/1.0.32 exist or is it an example?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one is enforced by the update-version.sh script - not sure if @matifali has more info here on why.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be release/devcontainers-cli/1.0.0.

agent_id = coder_agent.example.id
}
```
110 changes: 110 additions & 0 deletions devcontainers-cli/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { describe, expect, it } from "bun:test";
import {
execContainer,
executeScriptInContainer,
findResourceInstance,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
type TerraformState,
} from "../test";

const executeScriptInContainerWithPackageManager = async (
state: TerraformState,
image: string,
packageManager: string,
shell = "sh",
): Promise<{
exitCode: number;
stdout: string[];
stderr: string[];
}> => {
const instance = findResourceInstance(state, "coder_script");
const id = await runContainer(image);

// Install the specified package manager
if (packageManager === "npm") {
await execContainer(id, [shell, "-c", "apk add nodejs npm"]);
} else if (packageManager === "pnpm") {
await execContainer(id, [
shell,
"-c",
"apk add nodejs npm && npm install -g pnpm",
]);
} else if (packageManager === "yarn") {
await execContainer(id, [
shell,
"-c",
"apk add nodejs npm && npm install -g yarn",
]);
}

const resp = await execContainer(id, [shell, "-c", instance.script]);
const stdout = resp.stdout.trim().split("\n");
const stderr = resp.stderr.trim().split("\n");
return {
exitCode: resp.exitCode,
stdout,
stderr,
};
};

describe("devcontainers-cli", async () => {
await runTerraformInit(import.meta.dir);

testRequiredVariables(import.meta.dir, {
agent_id: "some-agent-id",
});

it("misses all package managers", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "some-agent-id",
});
const output = await executeScriptInContainer(state, "alpine");
expect(output.exitCode).toBe(1);
expect(output.stdout).toEqual([

Check failure on line 66 in devcontainers-cli/main.test.ts

View workflow job for this annotation

GitHub Actions / test

error: expect(received).toEqual(expected)

[ + "Docker is required." - "No supported package manager (npm, pnpm, yarn) is installed. Please install one first." ] - Expected - 1 + Received + 1 at <anonymous> (/home/runner/work/modules/modules/devcontainers-cli/main.test.ts:66:27)
"No supported package manager (npm, pnpm, yarn) is installed. Please install one first.",
]);
});

it("installs devcontainers-cli with npm", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "some-agent-id",
});

const output = await executeScriptInContainerWithPackageManager(
state,
"alpine",
"npm",
);
expect(output.exitCode).toBe(0);

Check failure on line 81 in devcontainers-cli/main.test.ts

View workflow job for this annotation

GitHub Actions / test

error: expect(received).toBe(expected)

Expected: 0 Received: 1 at <anonymous> (/home/runner/work/modules/modules/devcontainers-cli/main.test.ts:81:29)

expect(output.stdout[0]).toEqual(
"Installing @devcontainers/cli using npm ...",
);
expect(output.stdout[output.stdout.length - 1]).toEqual(
"🥳 @devcontainers/cli has been installed into /usr/local/bin/devcontainer!",
);
});

it("installs devcontainers-cli with yarn", async () => {
const state = await runTerraformApply(import.meta.dir, {
agent_id: "some-agent-id",
});

const output = await executeScriptInContainerWithPackageManager(
state,
"alpine",
"yarn",
);
expect(output.exitCode).toBe(0);

Check failure on line 101 in devcontainers-cli/main.test.ts

View workflow job for this annotation

GitHub Actions / test

error: expect(received).toBe(expected)

Expected: 0 Received: 1 at <anonymous> (/home/runner/work/modules/modules/devcontainers-cli/main.test.ts:101:29)

expect(output.stdout[0]).toEqual(
"Installing @devcontainers/cli using yarn ...",
);
expect(output.stdout[output.stdout.length - 1]).toEqual(
"🥳 @devcontainers/cli has been installed into /usr/local/bin/devcontainer!",
);
});
});
23 changes: 23 additions & 0 deletions devcontainers-cli/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
terraform {
required_version = ">= 1.0"

required_providers {
coder = {
source = "coder/coder"
version = ">= 0.17"
}
}
}

variable "agent_id" {
type = string
description = "The ID of a Coder agent."
}

resource "coder_script" "devcontainers-cli" {
agent_id = var.agent_id
display_name = "devcontainers-cli"
icon = "/icon/devcontainers.svg"
script = templatefile("${path.module}/run.sh", {})
run_on_start = true
}
38 changes: 38 additions & 0 deletions devcontainers-cli/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env sh

# If @devcontainers/cli is already installed, we can skip
if command -v devcontainer > /dev/null 2>&1; then
echo "🥳 @devcontainers/cli is already installed"
exit 0
fi

# Check if docker is installed
if ! command -v docker > /dev/null 2>&1; then
echo "Docker is required."
exit 1
fi

# Determine the package manager to use: npm, pnpm, or yarn
if command -v pnpm > /dev/null 2>&1; then
PACKAGE_MANAGER="pnpm"
elif command -v yarn > /dev/null 2>&1; then
PACKAGE_MANAGER="yarn"
elif command -v npm > /dev/null 2>&1; then
PACKAGE_MANAGER="npm"
else
echo "No supported package manager (npm, pnpm, yarn) is installed. Please install one first."
exit 1
fi

echo "Installing @devcontainers/cli using $PACKAGE_MANAGER ..."

# Install @devcontainers/cli using the selected package manager
if [ "$PACKAGE_MANAGER" = "npm" ] || [ "$PACKAGE_MANAGER" = "pnpm" ]; then
$PACKAGE_MANAGER install -g @devcontainers/cli \
&& echo "🥳 @devcontainers/cli has been installed into $(which devcontainer)!"
elif [ "$PACKAGE_MANAGER" = "yarn" ]; then
$PACKAGE_MANAGER global add @devcontainers/cli \
&& echo "🥳 @devcontainers/cli has been installed into $(which devcontainer)!"
fi

exit 0
Loading