This repository contains devcontainer features
whose install scripts are written in Amber and compiled to bash.
The compiled bash is checked in and published to ghcr.io/nicoviii/devcontainer-features/<id>.
lib/*.ab— shared Amber modules (root check, apt handling, download, checksum verification, install helpers). Imported by every feature.src/<id>/— one directory per feature:install.ab— the install script source of truthinstall.sh— generated byamber build, do not edit by handdevcontainer-feature.json— feature metadata and optionsREADME.md— generated bydevcontainer features generate-docs, do not edit by handNOTES.md— optional, hand-written; its content is included in the generated READMECHANGELOG.md— hand-written, Keep a Changelog format
test/<id>/— test scenarios (scenarios.json) plus one<scenario-name>.shper scenario.
- Generated files must stay in sync. After editing any
.abfile, rebuild; after editingdevcontainer-feature.jsonorNOTES.md, regenerate docs. CI fails if the committedinstall.shor README files differ from what a fresh build/generation produces. lib/changes affect every feature. Amber inlines imports, so editinglib/*.abrequires rebuilding allsrc/*/install.shfiles.- Use Amber 0.6.0-alpha (the version CI uses; also pinned in
.devcontainer/devcontainer.json). A different compiler version produces different output and trips the CI drift check.
Run from the repository root (VS Code tasks exist for all of these, see .vscode/tasks.json):
# Check all Amber sources
set -e; for f in $(find ./lib ./src -name '*.ab'); do echo "$f"; amber check "$f"; done
# Rebuild all generated install.sh files
set -e; for f in $(find ./src -name '*.ab'); do echo "$f"; amber build "$f"; done
# Regenerate feature READMEs
(cd ./src && devcontainer features generate-docs -n "NicoVIII/devcontainer-features")
# Test one feature (requires docker; add --filter <scenario substring> to narrow further)
devcontainer features test --skip-autogenerated -f <id>- Inside
$ ... $command blocks,{expr}interpolates, so literal$and{must be escaped as\$and\{. - Interpolated values end up unquoted in the compiled bash — avoid passing untrusted or whitespace-containing values into command blocks without quoting them in the command text.
- A trailing
?propagates failure;mainblocks exit on the first failed?call.
- Feature options are read as uppercase environment variables (option
version→read_param("VERSION", ...)). - Most features install a tool and have a
versionoption;"latest"is resolved via the GitHub API where supported (oxlint does not support it — see issue #25). The exception isclaude-mount, which installs no tool: it declares a bindmountsentry in itsdevcontainer-feature.jsonand itsinstall.abonly symlinks the mount into the remote user's home. It has noversionoption. - Test scenarios follow the naming
<os>-<variant>fordebian/ubuntu×default/latest/specific. The-specificscenarios intentionally pin an older version than the current release so the assertion proves version selection works. Features without aversionoption (e.g.claude-mount) use plain<os>scenario names. - In test scripts, wrap piped assertions for the
checkhelper inbash -c, e.g.check "just version" bash -c "just --version | grep -F '1.50.0'"— a bare pipe would apply tocheck's output instead of the command.
.github/workflows/ci.ymlchecks/builds Amber, verifies no drift in generated files, then runs per-feature test jobs (viareusable-feature.yml) only for features whose paths changed.- Release flow: in
src/<id>/, bumpversionindevcontainer-feature.json, move theUnreleasedchangelog section to the new version (CI reads the changelog section for the release notes), commit, then push a tag named<id>-v<version>. CI tests, publishes the feature to ghcr.io, and creates a GitHub release. - Keeping an
Unreleasedsection inCHANGELOG.mdbetween releases is expected.
- Copy the structure of an existing feature under
src/<id>/andtest/<id>/. - Add the feature to the
detect-changesfilters and add a per-feature job inci.yml(copy an existing one), plus the picker entry in.vscode/tasks.json. - Build, regenerate docs, and add test scenarios before tagging a
<id>-v0.1.0release.