Skip to content

feat(devcontainers-cli): add devcontainers-cli module #425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 16, 2025
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
}
```
70 changes: 70 additions & 0 deletions devcontainers-cli/main.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { describe, expect, it } from "bun:test";
import {
execContainer,
executeScriptInContainer,
findResourceInstance,
runContainer,
runTerraformApply,
runTerraformInit,
testRequiredVariables,
type TerraformState,
} from "../test";

const executeScriptInContainerWithNPM = async (
state: TerraformState,
image: string,
shell = "sh",
): Promise<{
exitCode: number;
stdout: string[];
stderr: string[];
}> => {
const instance = findResourceInstance(state, "coder_script");
const id = await runContainer(image);
const respPipx = await execContainer(id, [shell, "-c", "apk add nodejs npm"]);
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 npm", 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([
"Installing @devcontainers/cli ...",
"npm is not installed, please install npm first",
]);
});

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

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

expect(output.stdout[0]).toEqual("Installing @devcontainers/cli ...");
expect(output.stdout[1]).toEqual(
"Running npm install -g @devcontainers/cli ...",
);
expect(output.stdout[4]).toEqual(
"🥳 @devcontainers/cli has been installed !",
);
});
});
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
}
22 changes: 22 additions & 0 deletions devcontainers-cli/run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env sh

echo "Installing @devcontainers/cli ..."

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

# If npm is not installed, we should skip
if ! command -v npm > /dev/null 2>&1; then
echo "npm is not installed, please install npm first"
exit 1
fi

# If @devcontainers/cli is not installed, we should install it
echo "Running npm install -g @devcontainers/cli ..."
npm install -g @devcontainers/cli \
&& echo "🥳 @devcontainers/cli has been installed !"

exit 0