---
description: Develop a light-process node inside a VS Code dev container that mirrors its production Docker image, with automatic `.env` loading. Use this skill whenever the user runs or asks about `light node dev`, wants to code a node in the same image used at runtime, generates or refreshes a `.devcontainer/devcontainer.json` from `.node.json`, hits "code command not found" when opening, needs to rebuild the container after changing `setup`, or wants environment variables loaded into the container without copy-pasting. Also trigger when the user mentions devcontainers, container-based development, "iterate on a node", `.env` files, `env-file`, auto-detect env vars, secrets in container, loading environment variables, or `${localEnv:...}` placeholders in this repo, even if they do not name the command explicitly.
---

# Develop a node in VS Code

`light node dev <dir>` reads `<dir>/.node.json`, generates `<dir>/.devcontainer/devcontainer.json` from it, then opens VS Code on `<dir>`. The result: the node is edited and run inside the exact image used in production by light-run, with no host toolchain required.

The point is parity. A node ships as a container, so coding in that container catches "works on my machine" bugs early: missing system libs, Node or Python version mismatch, native binding ABI, glibc/musl differences. Without this, the dev would need every node's toolchain installed locally.

## Commands

```bash
light node dev <dir>                       # generate (if missing) and open VS Code
light node dev <dir> --force               # regenerate even if devcontainer.json exists
light node dev <dir> --env-file <path>     # use a specific .env file (override auto-detection)
```

When the prompt appears, press `Enter` or `Space` to open VS Code, or `q` / `Esc` to quit without opening. If a `.env` file is in play, the CLI prints its absolute path right before the prompt so the choice can be validated before VS Code spawns.

## How devcontainer.json is built

Every field is derived from `.node.json` plus the auto-detected `.env`. Nothing else is added.

| devcontainer field | source | notes |
|---|---|---|
| `name` | `.node.json:name` | falls back to `"node"` |
| `image` | `.node.json:image` | required, errors out if missing |
| `workspaceFolder` | `.node.json:workdir` | falls back to `/app` |
| `workspaceMount` | derived from `workdir` | bind mount of `${localWorkspaceFolder}` to `workdir` |
| `postCreateCommand` | `.node.json:setup` | joined with ` && `, omitted if `setup` is empty |
| `remoteEnv` | `.node.json:env` | each name maps to `${localEnv:NAME}` |
| `runArgs` | first `.env` found walking up, or `--env-file` flag | `["--env-file", "<abs path>"]`, omitted when no file is found |
| `customizations.vscode.extensions` | `image` prefix | auto-picked, see table below |

### Auto-picked VS Code extensions

`pickExtensions(image)` matches the lowercased image string. Custom or registry-prefixed images (for example `ghcr.io/org/python:3.12`) do not match and produce no extensions. Add them by hand after generation if needed.

| Image pattern | Extension |
|---|---|
| starts with `node:` or contains `-node:` / `-node-` | `dbaeumer.vscode-eslint` |
| starts with `python:` | `ms-python.python` |
| starts with `openjdk:`, `eclipse-temurin:`, `maven:`, `gradle:` | `vscjava.vscode-java-pack` |
| starts with `rust:` | `rust-lang.rust-analyzer` |
| starts with `golang:` or `go:` | `golang.go` |
| anything else | none |

## Loading environment variables

Two mechanisms work side by side. Use them for different shapes of data.

### `.env` auto-detection (bulk)

The CLI walks up from the node folder, up to 5 levels (node dir, workflow root, parent, grandparent, great-grandparent), and picks the first `.env` it finds. The closest one wins, so a node-local `.env` overrides a workflow-root `.env`. When found, the generated `devcontainer.json` gets:

```json
"runArgs": ["--env-file", "/abs/path/to/.env"]
```

Docker reads that file at container start and injects every variable into the container's environment. No host shell export required, no copy-pasting var names. Pass `--env-file <path>` to skip auto-detection and pin a specific file. The CLI errors out before doing anything else if the path does not exist.

The `.env` should be gitignored. Docker reads it on the host at runtime, so the secrets never enter the committed `devcontainer.json`.

### `.node.json:env` array (host-shell vars)

Names listed in `.node.json`'s `env` array become `${localEnv:NAME}` entries in `remoteEnv`. They are read from the host shell that launched VS Code, not from a file. Use this for one-off vars that live in your shell session (a fresh OAuth token, a CI-injected secret) but are not in any `.env`.

If both mechanisms target the same variable, the `.env` value wins because Docker applies `--env-file` to the container itself, while `remoteEnv` only scopes to the VS Code remote server process.

## Behavior when devcontainer.json already exists

| State | Default behavior | With `--force` |
|---|---|---|
| File present and valid JSON | Open as-is, no overwrite | Overwrite from `.node.json` and current `.env` |
| File present but malformed JSON | Abort with parse error | Overwrite from `.node.json` and current `.env` |
| File missing | Generate from `.node.json` and current `.env` | Same as default |

Without `--force`, a stale devcontainer.json keeps using the env file recorded at the last generation. After moving or renaming a `.env`, regenerate with `--force` so the new path lands in `runArgs`. After regenerating, VS Code reuses the existing built container; pick up new `setup` or new env values by running **Dev Containers: Rebuild Container** from the VS Code Command Palette.

## Troubleshooting

| Symptom | Fix |
|---|---|
| `No .node.json in <dir>. Run 'light init --node' first.` | Run `light init --node` or change to the right folder. |
| `.node.json has no 'image'.` | Add `"image": "node:20-slim"` (or similar) to `.node.json`. |
| `Failed to parse existing devcontainer.json` | Re-run with `--force` to regenerate, or fix the JSON manually. |
| `--env-file: <path> not found.` | The path passed to `--env-file` does not exist. Check spelling and absolute vs relative resolution. |
| Container does not see vars from `.env` | Confirm the summary shows `env-file: <path>`. If `(none)`, the file was not found in the 5-level walk; pass `--env-file` explicitly or move the file closer to the node. |
| Vars updated in `.env` but container still has old values | VS Code is reusing the running container. Run **Dev Containers: Rebuild Container**. |
| Wrong `.env` was picked (a parent one instead of node-local) | The closest one should win. Place the node-local `.env` directly in the node folder, or pass `--env-file` to override. |
| `'code' command not found on PATH.` | In VS Code, run **Shell Command: Install 'code' command in PATH** from the Command Palette. |
| Setup did not run after `--force` | VS Code reuses the existing container. Use **Dev Containers: Rebuild Container**. |
| Wrong or missing extensions for a custom image | Edit `customizations.vscode.extensions` in the generated file by hand. |

## Related commands

| Command | Purpose |
|---|---|
| `light init --node` | Scaffold a new node folder with a starter `.node.json`. |
| `light node info <dir>` | Show the node's metadata, schema, and incoming links. |
| `light node schema <dir>` | Edit the input/output JSON Schema interactively. |
| `light node helpers <dir>` | Regenerate `lp.d.ts` from the schema after editing `.node.json` by hand. |
| `light node register <dir>` | Add the node folder to its parent `workflow.json`. |

Implementation reference: `src/cli/node-dev.ts`.
