mirror of
https://github.com/coder/registry.git
synced 2026-06-03 13:08:14 +00:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| eb38bc3092 | |||
| 93e6094b1b | |||
| 6ec506e9b6 | |||
| b794b1edd9 | |||
| 94e41d3780 |
@@ -14,7 +14,7 @@ Automatically install [code-server](https://github.com/coder/code-server) in a w
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -29,7 +29,7 @@ module "code-server" {
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
install_version = "4.106.3"
|
||||
}
|
||||
@@ -43,7 +43,7 @@ Install the Dracula theme from [OpenVSX](https://open-vsx.org/):
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = [
|
||||
"dracula-theme.theme-dracula"
|
||||
@@ -61,7 +61,7 @@ Configure VS Code's [settings.json](https://code.visualstudio.com/docs/getstarte
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula"]
|
||||
settings = {
|
||||
@@ -78,7 +78,7 @@ Just run code-server in the background, don't fetch it from GitHub:
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
||||
}
|
||||
@@ -92,7 +92,7 @@ You can pass additional command-line arguments to code-server using the `additio
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
additional_args = "--disable-workspace-trust"
|
||||
}
|
||||
@@ -108,7 +108,7 @@ Run an existing copy of code-server if found, otherwise download from GitHub:
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
use_cached = true
|
||||
extensions = ["dracula-theme.theme-dracula", "ms-azuretools.vscode-docker"]
|
||||
@@ -121,7 +121,7 @@ Just run code-server in the background, don't fetch it from GitHub:
|
||||
module "code-server" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/code-server/coder"
|
||||
version = "1.4.2"
|
||||
version = "1.4.3"
|
||||
agent_id = coder_agent.example.id
|
||||
offline = true
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ variable "settings" {
|
||||
default = {}
|
||||
}
|
||||
|
||||
variable "machine-settings" {
|
||||
variable "machine_settings" {
|
||||
type = any
|
||||
description = "A map of template level machine settings to apply to code-server. This will be overwritten at each container start."
|
||||
default = {}
|
||||
@@ -167,7 +167,7 @@ resource "coder_script" "code-server" {
|
||||
INSTALL_PREFIX : var.install_prefix,
|
||||
// This is necessary otherwise the quotes are stripped!
|
||||
SETTINGS : replace(jsonencode(var.settings), "\"", "\\\""),
|
||||
MACHINE_SETTINGS : replace(jsonencode(var.machine-settings), "\"", "\\\""),
|
||||
MACHINE_SETTINGS : replace(jsonencode(var.machine_settings), "\"", "\\\""),
|
||||
OFFLINE : var.offline,
|
||||
USE_CACHED : var.use_cached,
|
||||
USE_CACHED_EXTENSIONS : var.use_cached_extensions,
|
||||
|
||||
@@ -18,7 +18,7 @@ Under the hood, this module uses the [coder dotfiles](https://coder.com/docs/v2/
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -31,7 +31,7 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
```
|
||||
@@ -42,7 +42,7 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
}
|
||||
@@ -54,14 +54,14 @@ module "dotfiles" {
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
}
|
||||
|
||||
module "dotfiles-root" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
user = "root"
|
||||
dotfiles_uri = module.dotfiles.dotfiles_uri
|
||||
@@ -76,7 +76,7 @@ You can set a default dotfiles repository for all users by setting the `default_
|
||||
module "dotfiles" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/dotfiles/coder"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.example.id
|
||||
default_dotfiles_uri = "https://github.com/coder/dotfiles"
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@ describe("dotfiles", async () => {
|
||||
"git@github.com:coder/dotfiles.git",
|
||||
"git://github.com/coder/dotfiles.git",
|
||||
"ssh://git@github.com/coder/dotfiles.git",
|
||||
"ssh://git@bitbucket.example.org:7999/~myusername/dotfiles.git",
|
||||
];
|
||||
for (const url of validUrls) {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
|
||||
@@ -40,7 +40,7 @@ variable "default_dotfiles_uri" {
|
||||
validation {
|
||||
condition = (
|
||||
var.default_dotfiles_uri == "" ||
|
||||
can(regex("^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@-]+$", var.default_dotfiles_uri))
|
||||
can(regex("^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@~-]+$", var.default_dotfiles_uri))
|
||||
)
|
||||
error_message = "Must be a valid dotfiles repository URL (https, git@, or git://) without special characters."
|
||||
}
|
||||
@@ -55,7 +55,7 @@ variable "dotfiles_uri" {
|
||||
condition = (
|
||||
var.dotfiles_uri == null ||
|
||||
var.dotfiles_uri == "" ||
|
||||
can(regex("^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@-]+$", var.dotfiles_uri))
|
||||
can(regex("^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@~-]+$", var.dotfiles_uri))
|
||||
)
|
||||
error_message = "Must be a valid dotfiles repository URL (https, git@, or git://) without special characters."
|
||||
}
|
||||
@@ -102,7 +102,7 @@ data "coder_parameter" "dotfiles_uri" {
|
||||
icon = "/icon/dotfiles.svg"
|
||||
|
||||
validation {
|
||||
regex = "^$|^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@-]+$"
|
||||
regex = "^$|^(https?://|ssh://|git@|git://)[a-zA-Z0-9._/:@~-]+$"
|
||||
error = "Must be a valid dotfiles repository URL (https, git@, or git://) without special characters."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ tags: [ai, agents, development, multiplexer]
|
||||
|
||||
# Mux
|
||||
|
||||
Automatically install and run [Mux](https://github.com/coder/mux) in a Coder workspace. By default, the module installs `mux@next` from npm (with a fallback to downloading the npm tarball if npm is unavailable). Mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces.
|
||||
Automatically install and run [Mux](https://github.com/coder/mux) in a Coder workspace. By default, the module auto-detects an available package manager (`npm`, `pnpm`, or `bun`) to install `mux@next` (with a fallback to downloading the npm tarball if none is found). You can also force a specific package manager via `package_manager` and point to a custom registry with `registry_url`. Mux is a desktop application for parallel agentic development that enables developers to run multiple AI agents simultaneously across isolated workspaces.
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@@ -37,7 +37,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
}
|
||||
```
|
||||
@@ -48,7 +48,7 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
# Default is "latest"; set to a specific version to pin
|
||||
install_version = "0.4.0"
|
||||
@@ -63,9 +63,24 @@ Start Mux with `mux server --add-project /path/to/project`:
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
add-project = "/path/to/project"
|
||||
add_project = "/path/to/project"
|
||||
}
|
||||
```
|
||||
|
||||
### Pass Arbitrary `mux server` Arguments
|
||||
|
||||
Use `additional_arguments` to append additional arguments to `mux server`.
|
||||
The module parses quoted values, so grouped arguments remain intact.
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -75,12 +90,40 @@ module "mux" {
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
port = 8080
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Package Manager
|
||||
|
||||
Force a specific package manager instead of auto-detection:
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
package_manager = "pnpm" # or "npm", "bun"
|
||||
}
|
||||
```
|
||||
|
||||
### Custom Registry
|
||||
|
||||
Use a private or mirrored npm registry:
|
||||
|
||||
```tf
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
registry_url = "https://npm.pkg.github.com"
|
||||
}
|
||||
```
|
||||
|
||||
### Use Cached Installation
|
||||
|
||||
Run an existing copy of Mux if found, otherwise install from npm:
|
||||
@@ -89,7 +132,7 @@ Run an existing copy of Mux if found, otherwise install from npm:
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
use_cached = true
|
||||
}
|
||||
@@ -103,7 +146,7 @@ Run without installing from the network (requires Mux to be pre-installed):
|
||||
module "mux" {
|
||||
count = data.coder_workspace.me.start_count
|
||||
source = "registry.coder.com/coder/mux/coder"
|
||||
version = "1.1.0"
|
||||
version = "1.3.1"
|
||||
agent_id = coder_agent.main.id
|
||||
install = false
|
||||
}
|
||||
@@ -117,4 +160,6 @@ module "mux" {
|
||||
|
||||
- Mux is currently in preview and you may encounter bugs
|
||||
- Requires internet connectivity for agent operations (unless `install` is set to false)
|
||||
- Installs `mux@next` from npm by default (falls back to the npm tarball if npm is unavailable)
|
||||
- Auto-detects `npm`, `pnpm`, or `bun` by default; set `package_manager` to force a specific one
|
||||
- Installs `mux@next` from the npm registry by default; set `registry_url` to use a private or mirrored registry
|
||||
- Falls back to a direct tarball download when no package manager is found
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
import {
|
||||
executeScriptInContainer,
|
||||
execContainer,
|
||||
findResourceInstance,
|
||||
readFileContainer,
|
||||
removeContainer,
|
||||
runContainer,
|
||||
runTerraformApply,
|
||||
runTerraformInit,
|
||||
testRequiredVariables,
|
||||
@@ -30,7 +35,7 @@ describe("mux", async () => {
|
||||
}
|
||||
expect(output.exitCode).toBe(0);
|
||||
const expectedLines = [
|
||||
"📥 npm not found; downloading tarball from npm registry...",
|
||||
"📥 No package manager found; downloading tarball from registry...",
|
||||
"🥳 mux has been installed in /tmp/mux",
|
||||
"🚀 Starting mux server on port 4000...",
|
||||
"Check logs at /tmp/mux.log!",
|
||||
@@ -40,6 +45,57 @@ describe("mux", async () => {
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it("parses custom additional_arguments", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
install: false,
|
||||
log_path: "/tmp/mux.log",
|
||||
additional_arguments:
|
||||
"--open-mode pinned --add-project '/workspaces/my repo'",
|
||||
});
|
||||
|
||||
const instance = findResourceInstance(state, "coder_script");
|
||||
const id = await runContainer("alpine/curl");
|
||||
|
||||
try {
|
||||
const setup = await execContainer(id, [
|
||||
"sh",
|
||||
"-c",
|
||||
`apk add --no-cache bash >/dev/null
|
||||
mkdir -p /tmp/mux
|
||||
cat <<'EOF' > /tmp/mux/mux
|
||||
#!/usr/bin/env sh
|
||||
i=1
|
||||
for arg in "$@"; do
|
||||
echo "arg$i=$arg"
|
||||
i=$((i + 1))
|
||||
done
|
||||
EOF
|
||||
chmod +x /tmp/mux/mux`,
|
||||
]);
|
||||
expect(setup.exitCode).toBe(0);
|
||||
|
||||
const output = await execContainer(id, ["sh", "-c", instance.script]);
|
||||
if (output.exitCode !== 0) {
|
||||
console.log("STDOUT:\n" + output.stdout);
|
||||
console.log("STDERR:\n" + output.stderr);
|
||||
}
|
||||
expect(output.exitCode).toBe(0);
|
||||
|
||||
await execContainer(id, ["sh", "-c", "sleep 1"]);
|
||||
const log = await readFileContainer(id, "/tmp/mux.log");
|
||||
expect(log).toContain("arg1=server");
|
||||
expect(log).toContain("arg2=--port");
|
||||
expect(log).toContain("arg3=4000");
|
||||
expect(log).toContain("arg4=--open-mode");
|
||||
expect(log).toContain("arg5=pinned");
|
||||
expect(log).toContain("arg6=--add-project");
|
||||
expect(log).toContain("arg7=/workspaces/my repo");
|
||||
} finally {
|
||||
await removeContainer(id);
|
||||
}
|
||||
}, 60000);
|
||||
|
||||
it("runs with npm present", async () => {
|
||||
const state = await runTerraformApply(import.meta.dir, {
|
||||
agent_id: "foo",
|
||||
@@ -55,7 +111,7 @@ describe("mux", async () => {
|
||||
expect(output.exitCode).toBe(0);
|
||||
const expectedLines = [
|
||||
"📦 Installing mux via npm into /tmp/mux...",
|
||||
"⏭️ Skipping npm lifecycle scripts with --ignore-scripts",
|
||||
"⏭️ Skipping lifecycle scripts with --ignore-scripts",
|
||||
"🥳 mux has been installed in /tmp/mux",
|
||||
"🚀 Starting mux server on port 4000...",
|
||||
"Check logs at /tmp/mux.log!",
|
||||
|
||||
@@ -49,18 +49,41 @@ variable "log_path" {
|
||||
default = "/tmp/mux.log"
|
||||
}
|
||||
|
||||
variable "add-project" {
|
||||
variable "add_project" {
|
||||
type = string
|
||||
description = "Optional path to add/open as a project in Mux on startup."
|
||||
default = null
|
||||
}
|
||||
|
||||
variable "additional_arguments" {
|
||||
type = string
|
||||
description = "Additional command-line arguments to pass to `mux server` (for example: `--add-project /path --open-mode pinned`)."
|
||||
default = ""
|
||||
}
|
||||
|
||||
variable "install_version" {
|
||||
type = string
|
||||
description = "The version or dist-tag of Mux to install."
|
||||
default = "next"
|
||||
}
|
||||
|
||||
variable "package_manager" {
|
||||
type = string
|
||||
description = "Package manager to install Mux. 'auto' detects npm, pnpm, or bun (falling back to tarball download). Set to 'npm', 'pnpm', or 'bun' to force a specific one."
|
||||
default = "auto"
|
||||
validation {
|
||||
condition = contains(["auto", "npm", "pnpm", "bun"], var.package_manager)
|
||||
error_message = "The 'package_manager' variable must be one of: 'auto', 'npm', 'pnpm', 'bun'."
|
||||
}
|
||||
}
|
||||
|
||||
variable "registry_url" {
|
||||
type = string
|
||||
description = "The npm-compatible registry URL to install Mux from. Override this for private registries or mirrors."
|
||||
default = "https://registry.npmjs.org"
|
||||
}
|
||||
|
||||
|
||||
variable "share" {
|
||||
type = string
|
||||
default = "owner"
|
||||
@@ -131,6 +154,7 @@ resource "random_password" "mux_auth_token" {
|
||||
|
||||
locals {
|
||||
mux_auth_token = random_password.mux_auth_token.result
|
||||
registry_url = trimsuffix(var.registry_url, "/")
|
||||
}
|
||||
|
||||
resource "coder_script" "mux" {
|
||||
@@ -141,11 +165,14 @@ resource "coder_script" "mux" {
|
||||
VERSION : var.install_version,
|
||||
PORT : var.port,
|
||||
LOG_PATH : var.log_path,
|
||||
ADD_PROJECT : var.add-project == null ? "" : var.add-project,
|
||||
ADD_PROJECT : var.add_project == null ? "" : var.add_project,
|
||||
ADDITIONAL_ARGUMENTS : var.additional_arguments,
|
||||
INSTALL_PREFIX : var.install_prefix,
|
||||
OFFLINE : !var.install,
|
||||
USE_CACHED : var.use_cached,
|
||||
AUTH_TOKEN : local.mux_auth_token,
|
||||
PACKAGE_MANAGER : var.package_manager,
|
||||
REGISTRY_URL : local.registry_url,
|
||||
})
|
||||
run_on_start = true
|
||||
|
||||
|
||||
@@ -79,6 +79,20 @@ run "auth_token_in_url" {
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_additional_arguments" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
additional_arguments = "--open-mode pinned --add-project '/workspaces/my repo'"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "--open-mode pinned --add-project '/workspaces/my repo'")
|
||||
error_message = "mux launch script must include the configured additional arguments"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_version" {
|
||||
command = plan
|
||||
|
||||
@@ -107,3 +121,96 @@ run "use_cached_only_success" {
|
||||
use_cached = true
|
||||
}
|
||||
}
|
||||
|
||||
# Custom package_manager should appear in generated script
|
||||
run "custom_package_manager_npm" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
package_manager = "npm"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "PM_CMD=\"npm\"")
|
||||
error_message = "mux script must set PM_CMD to the configured package manager"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_package_manager_pnpm" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
package_manager = "pnpm"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "PM_CMD=\"pnpm\"")
|
||||
error_message = "mux script must set PM_CMD to the configured package manager"
|
||||
}
|
||||
}
|
||||
|
||||
run "custom_package_manager_bun" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
package_manager = "bun"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "PM_CMD=\"bun\"")
|
||||
error_message = "mux script must set PM_CMD to the configured package manager"
|
||||
}
|
||||
}
|
||||
|
||||
# Invalid package_manager should fail validation
|
||||
run "invalid_package_manager" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
package_manager = "yarn"
|
||||
}
|
||||
|
||||
expect_failures = [
|
||||
var.package_manager
|
||||
]
|
||||
}
|
||||
|
||||
# Custom registry_url should appear in generated script
|
||||
run "custom_registry_url" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
registry_url = "https://npm.example.com"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "https://npm.example.com")
|
||||
error_message = "mux script must use the configured registry URL"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = !strcontains(resource.coder_script.mux.script, "registry.npmjs.org")
|
||||
error_message = "mux script must not contain hardcoded registry.npmjs.org when custom registry is set"
|
||||
}
|
||||
}
|
||||
|
||||
# registry_url trailing slash should be stripped
|
||||
run "registry_url_trailing_slash" {
|
||||
command = plan
|
||||
|
||||
variables {
|
||||
agent_id = "foo"
|
||||
registry_url = "https://npm.example.com/"
|
||||
}
|
||||
|
||||
assert {
|
||||
condition = strcontains(resource.coder_script.mux.script, "https://npm.example.com/mux/")
|
||||
error_message = "registry URL trailing slash must be stripped to avoid double slashes"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,22 @@ function run_mux() {
|
||||
if [ -n "${ADD_PROJECT}" ]; then
|
||||
set -- "$@" --add-project "${ADD_PROJECT}"
|
||||
fi
|
||||
|
||||
# Parse additional user-supplied server arguments while preserving quoted groups.
|
||||
if [ -n "${ADDITIONAL_ARGUMENTS}" ]; then
|
||||
local parsed_additional_arguments
|
||||
if ! parsed_additional_arguments="$(printf "%s\n" "${ADDITIONAL_ARGUMENTS}" | xargs -n1 printf "%s\n" 2> /dev/null)"; then
|
||||
echo "❌ Failed to parse additional_arguments. Ensure quotes are balanced."
|
||||
exit 1
|
||||
fi
|
||||
while IFS= read -r parsed_arg; do
|
||||
[ -n "$parsed_arg" ] || continue
|
||||
set -- "$@" "$parsed_arg"
|
||||
done << EOF
|
||||
$${parsed_additional_arguments}
|
||||
EOF
|
||||
fi
|
||||
|
||||
echo "🚀 Starting mux server on port $port_value..."
|
||||
echo "Check logs at ${LOG_PATH}!"
|
||||
MUX_SERVER_AUTH_TOKEN="$auth_token_value" PORT="$port_value" "$MUX_BINARY" "$@" > "${LOG_PATH}" 2>&1 &
|
||||
@@ -38,7 +54,7 @@ fi
|
||||
|
||||
# If there is no cached install OR we don't want to use a cached install
|
||||
if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
|
||||
printf "$${BOLD}Installing mux from npm...\n"
|
||||
printf "$${BOLD}Installing mux...\n"
|
||||
|
||||
# Clean up from other install (in case install prefix changed).
|
||||
if [ -n "$CODER_SCRIPT_BIN_DIR" ] && [ -e "$CODER_SCRIPT_BIN_DIR/mux" ]; then
|
||||
@@ -47,41 +63,76 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
|
||||
|
||||
mkdir -p "$(dirname "$MUX_BINARY")"
|
||||
|
||||
if command -v npm > /dev/null 2>&1; then
|
||||
echo "📦 Installing mux via npm into ${INSTALL_PREFIX}..."
|
||||
# Determine which package manager to use
|
||||
PM_CMD=""
|
||||
if [ "${PACKAGE_MANAGER}" = "auto" ]; then
|
||||
for pm in npm pnpm bun; do
|
||||
if command -v "$pm" > /dev/null 2>&1; then
|
||||
PM_CMD="$pm"
|
||||
break
|
||||
fi
|
||||
done
|
||||
else
|
||||
PM_CMD="${PACKAGE_MANAGER}"
|
||||
if ! command -v "$PM_CMD" > /dev/null 2>&1; then
|
||||
echo "❌ Configured package manager '${PACKAGE_MANAGER}' not found on PATH"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "$PM_CMD" ]; then
|
||||
echo "📦 Installing mux via $PM_CMD into ${INSTALL_PREFIX}..."
|
||||
NPM_WORKDIR="${INSTALL_PREFIX}/npm"
|
||||
mkdir -p "$NPM_WORKDIR"
|
||||
cd "$NPM_WORKDIR" || exit 1
|
||||
if [ ! -f package.json ]; then
|
||||
echo '{}' > package.json
|
||||
fi
|
||||
echo "⏭️ Skipping npm lifecycle scripts with --ignore-scripts"
|
||||
echo "⏭️ Skipping lifecycle scripts with --ignore-scripts"
|
||||
PKG="mux"
|
||||
if [ -z "${VERSION}" ] || [ "${VERSION}" = "latest" ]; then
|
||||
PKG_SPEC="$PKG@latest"
|
||||
else
|
||||
PKG_SPEC="$PKG@${VERSION}"
|
||||
fi
|
||||
if ! npm install --no-audit --no-fund --omit=dev --ignore-scripts "$PKG_SPEC"; then
|
||||
echo "❌ Failed to install mux via npm"
|
||||
INSTALL_OK=true
|
||||
case "$PM_CMD" in
|
||||
npm)
|
||||
if ! npm install --no-audit --no-fund --omit=dev --ignore-scripts --registry "${REGISTRY_URL}" "$PKG_SPEC"; then
|
||||
INSTALL_OK=false
|
||||
fi
|
||||
;;
|
||||
pnpm)
|
||||
if ! pnpm add --ignore-scripts --registry "${REGISTRY_URL}" "$PKG_SPEC"; then
|
||||
INSTALL_OK=false
|
||||
fi
|
||||
;;
|
||||
bun)
|
||||
if ! bun add --ignore-scripts --registry "${REGISTRY_URL}" "$PKG_SPEC"; then
|
||||
INSTALL_OK=false
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
if [ "$INSTALL_OK" != true ]; then
|
||||
echo "❌ Failed to install mux via $PM_CMD"
|
||||
exit 1
|
||||
fi
|
||||
# Determine the installed binary path
|
||||
BIN_DIR="$NPM_WORKDIR/node_modules/.bin"
|
||||
CANDIDATE="$BIN_DIR/mux"
|
||||
if [ ! -f "$CANDIDATE" ]; then
|
||||
echo "❌ Could not locate mux binary after npm install"
|
||||
echo "❌ Could not locate mux binary after $PM_CMD install"
|
||||
exit 1
|
||||
fi
|
||||
chmod +x "$CANDIDATE" || true
|
||||
ln -sf "$CANDIDATE" "$MUX_BINARY"
|
||||
else
|
||||
echo "📥 npm not found; downloading tarball from npm registry..."
|
||||
echo "📥 No package manager found; downloading tarball from registry..."
|
||||
VERSION_TO_USE="${VERSION}"
|
||||
if [ -z "$VERSION_TO_USE" ]; then
|
||||
VERSION_TO_USE="next"
|
||||
fi
|
||||
META_URL="https://registry.npmjs.org/mux/$VERSION_TO_USE"
|
||||
META_URL="${REGISTRY_URL}/mux/$VERSION_TO_USE"
|
||||
META_JSON="$(curl -fsSL "$META_URL" || true)"
|
||||
if [ -z "$META_JSON" ]; then
|
||||
echo "❌ Failed to fetch npm metadata: $META_URL"
|
||||
@@ -120,7 +171,7 @@ if [ ! -f "$MUX_BINARY" ] || [ "${USE_CACHED}" != true ]; then
|
||||
echo "❌ Could not determine version for mux"
|
||||
exit 1
|
||||
fi
|
||||
TARBALL_URL="https://registry.npmjs.org/mux/-/mux-$VERSION_TO_USE.tgz"
|
||||
TARBALL_URL="${REGISTRY_URL}/mux/-/mux-$VERSION_TO_USE.tgz"
|
||||
fi
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
TAR_PATH="$TMP_DIR/mux.tgz"
|
||||
|
||||
@@ -11,6 +11,34 @@ set -euo pipefail
|
||||
#
|
||||
# This script only validates changed modules. Documentation and template changes are ignored.
|
||||
|
||||
# Validates that Terraform variable names use underscores (snake_case) instead
|
||||
# of hyphens. Hyphens are technically valid but deprecated and non-idiomatic.
|
||||
# See: https://developer.hashicorp.com/terraform/language/values/variables
|
||||
validate_variable_names() {
|
||||
local dir="$1"
|
||||
local found_issues=0
|
||||
|
||||
while IFS= read -r tf_file; do
|
||||
while IFS= read -r match; do
|
||||
local line_num
|
||||
line_num=$(echo "$match" | cut -d: -f1)
|
||||
local line_content
|
||||
line_content=$(echo "$match" | cut -d: -f2-)
|
||||
local var_name
|
||||
var_name=$(echo "$line_content" | sed -n 's/.*variable "\([^"]*\)".*/\1/p')
|
||||
|
||||
if [[ -n "$var_name" ]]; then
|
||||
echo " ERROR: $tf_file:$line_num"
|
||||
echo " Variable \"$var_name\" contains a hyphen."
|
||||
echo " Rename to \"${var_name//-/_}\" (use underscores instead of hyphens)."
|
||||
found_issues=$((found_issues + 1))
|
||||
fi
|
||||
done < <(grep -n 'variable "[^"]*-[^"]*"' "$tf_file" 2> /dev/null || true)
|
||||
done < <(find "$dir" -name '*.tf' -type f | sort)
|
||||
|
||||
return "$found_issues"
|
||||
}
|
||||
|
||||
validate_terraform_directory() {
|
||||
local dir="$1"
|
||||
echo "Running \`terraform validate\` in $dir"
|
||||
@@ -91,6 +119,16 @@ main() {
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "==> Validating Terraform variable names use snake_case..."
|
||||
for dir in $subdirs; do
|
||||
if test -f "$dir/main.tf"; then
|
||||
if ! validate_variable_names "$dir"; then
|
||||
status=1
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
exit $status
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user