mirror of
https://github.com/coder/registry.git
synced 2026-06-02 20:48:14 +00:00
docs: standardize module data layout under ~/.coder-modules (#869)
This commit is contained in:
@@ -21,6 +21,84 @@ bun test main.test.ts # Run single TS test (from
|
|||||||
- **Templates**: `registry/[ns]/templates/[name]/` with `main.tf`, `README.md`
|
- **Templates**: `registry/[ns]/templates/[name]/` with `main.tf`, `README.md`
|
||||||
- **Validation**: `cmd/readmevalidation/` (Go) validates structure/frontmatter; URLs must be relative, not absolute
|
- **Validation**: `cmd/readmevalidation/` (Go) validates structure/frontmatter; URLs must be relative, not absolute
|
||||||
|
|
||||||
|
## Module Data Layout
|
||||||
|
|
||||||
|
All runtime data a module writes on the workspace MUST live under a single per-module root:
|
||||||
|
|
||||||
|
```
|
||||||
|
$HOME/.coder-modules/<namespace>/<module-name>/
|
||||||
|
```
|
||||||
|
|
||||||
|
For a Coder-owned module named `claude-code`, the root is `$HOME/.coder-modules/coder/claude-code/`.
|
||||||
|
|
||||||
|
Within that root, use these standard subdirectories:
|
||||||
|
|
||||||
|
| Subdirectory | Purpose | Example |
|
||||||
|
| ------------ | ----------------------------------------- | ----------------------------------------------------------- |
|
||||||
|
| `logs/` | Output from install, start, or any script | `$HOME/.coder-modules/coder/claude-code/logs/install.log` |
|
||||||
|
| `scripts/` | Scripts materialized at runtime (if any) | `$HOME/.coder-modules/coder/claude-code/scripts/install.sh` |
|
||||||
|
|
||||||
|
- Name log files after the script that produced them (`install.sh` writes to `logs/install.log`, `start.sh` writes to `logs/start.log`).
|
||||||
|
- Always `mkdir -p` the target directory before writing; do not assume it exists.
|
||||||
|
- Do not write module runtime data to `$HOME` directly, to ad-hoc paths like `~/.<module>-module/`, or to `/tmp/` for anything that must survive the session.
|
||||||
|
- Tool-specific data (config files, caches, state, etc.) lives wherever the tool expects; only standardize paths the module itself controls.
|
||||||
|
- READMEs and tests should reference paths under this root so troubleshooting has one place to look.
|
||||||
|
- New modules MUST follow this layout. Existing modules should migrate to it when they are next touched.
|
||||||
|
|
||||||
|
## Use `coder-utils` for Script Orchestration
|
||||||
|
|
||||||
|
For any new module that runs scripts (or when reworking an existing one), use the [`coder-utils`](registry/coder/modules/coder-utils) module to orchestrate `pre_install`, `install`, `post_install`, and `start` scripts instead of hand-rolling `coder_script` resources.
|
||||||
|
|
||||||
|
- `coder-utils` handles script ordering via `coder exp sync`, materializes scripts under `module_directory/scripts/` (e.g., `install.sh`, `start.sh`), and writes logs to `module_directory/logs/` automatically, which aligns with the Module Data Layout above.
|
||||||
|
- Set `module_directory = "$HOME/.coder-modules/<namespace>/<module-name>"` so the standard root, `scripts/`, and `logs/` subdirectories fall out for free.
|
||||||
|
|
||||||
|
### Passing scripts to `coder-utils`
|
||||||
|
|
||||||
|
Store each script as a `.tftpl` file under `scripts/`. Render it at **plan time** in a `locals` block using `templatefile()`, then pass the rendered string directly to the `coder-utils` module.
|
||||||
|
|
||||||
|
**Encoding rules for template variables:**
|
||||||
|
|
||||||
|
| Value type | Terraform side | Template (`.tftpl`) side |
|
||||||
|
| ------------------------------------- | ----------------------------------- | ---------------------------------------------- |
|
||||||
|
| String / path | pass as-is | `ARG_FOO='${ARG_FOO}'` |
|
||||||
|
| Boolean | `tostring(var.foo)` | `ARG_FOO='${ARG_FOO}'` |
|
||||||
|
| Free-form string (may contain quotes) | `base64encode(var.foo)` | `ARG_FOO=$(echo -n '${ARG_FOO}' \| base64 -d)` |
|
||||||
|
| Object / list (JSON) | `base64encode(jsonencode(var.foo))` | `ARG_FOO=$(echo -n '${ARG_FOO}' \| base64 -d)` |
|
||||||
|
|
||||||
|
In `.tftpl` files, write literal bash `$` as `$$` (e.g., `$${HOME}`) so Terraform does not treat them as template interpolations.
|
||||||
|
|
||||||
|
```tf
|
||||||
|
locals {
|
||||||
|
install_script = templatefile("${path.module}/scripts/install.sh.tftpl", {
|
||||||
|
ARG_FOO = var.foo
|
||||||
|
ARG_BAR = var.bar
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
module "coder_utils" {
|
||||||
|
source = "registry.coder.com/coder/coder-utils/coder"
|
||||||
|
version = "0.0.1"
|
||||||
|
|
||||||
|
agent_id = var.agent_id
|
||||||
|
module_directory = "$HOME/.coder-modules/<namespace>/<module-name>"
|
||||||
|
display_name_prefix = "My Module"
|
||||||
|
icon = var.icon
|
||||||
|
pre_install_script = var.pre_install_script
|
||||||
|
install_script = local.install_script
|
||||||
|
post_install_script = var.post_install_script
|
||||||
|
start_script = var.start_script # optional; omit if the module does not start a process
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Always expose the `scripts` output as a pass-through so upstream modules can serialize their own `coder_script` resources behind this module's install pipeline:
|
||||||
|
|
||||||
|
```tf
|
||||||
|
output "scripts" {
|
||||||
|
description = "Ordered list of coder exp sync names produced by this module, in run order."
|
||||||
|
value = module.coder_utils.scripts
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
- Every module MUST have `.tftest.hcl` tests; optional `main.test.ts` for container/script tests
|
- Every module MUST have `.tftest.hcl` tests; optional `main.test.ts` for container/script tests
|
||||||
@@ -31,6 +109,28 @@ bun test main.test.ts # Run single TS test (from
|
|||||||
- **Do NOT include input/output variable tables in module or template READMEs.** The registry automatically generates these from the Terraform source (e.g., variable and output blocks in `main.tf`). Adding them to the README is redundant and creates maintenance drift.
|
- **Do NOT include input/output variable tables in module or template READMEs.** The registry automatically generates these from the Terraform source (e.g., variable and output blocks in `main.tf`). Adding them to the README is redundant and creates maintenance drift.
|
||||||
- Usage examples (e.g., a `module "..." { }` block) are encouraged, but not tables enumerating inputs/outputs.
|
- Usage examples (e.g., a `module "..." { }` block) are encouraged, but not tables enumerating inputs/outputs.
|
||||||
|
|
||||||
|
### Variable and output conventions
|
||||||
|
|
||||||
|
Order variable blocks: `description` → `type` → `default` → `validation` → `sensitive`.
|
||||||
|
|
||||||
|
```tf
|
||||||
|
variable "api_key" {
|
||||||
|
description = "API key for the service."
|
||||||
|
type = string
|
||||||
|
default = ""
|
||||||
|
sensitive = true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Mark variables and outputs that hold secrets or tokens `sensitive = true`.
|
||||||
|
- Every `output` block must have a `description`.
|
||||||
|
- Use `count = condition ? 1 : 0` for optional singleton resources. Reserve `for_each` for maps/sets where resource identity matters.
|
||||||
|
|
||||||
|
### `.tftest.hcl` test commands
|
||||||
|
|
||||||
|
- Use `command = plan` only for assertions on **input-derived values** (variables, locals computed from inputs).
|
||||||
|
- Use `command = apply` for **computed attributes** (resource IDs, anything the provider generates), and for nested blocks of set type (they cannot be indexed with `[0]` under `plan`).
|
||||||
|
|
||||||
## PR Review Checklist
|
## PR Review Checklist
|
||||||
|
|
||||||
- Version bumped via `.github/scripts/version-bump.sh` if module changed (patch=bugfix, minor=feature, major=breaking)
|
- Version bumped via `.github/scripts/version-bump.sh` if module changed (patch=bugfix, minor=feature, major=breaking)
|
||||||
|
|||||||
Reference in New Issue
Block a user