This is a Deno 2.x project. There is no Node.js, no npm, no package.json. All dependencies come from JSR. Do not use npm install, node, or Node-ecosystem tools. Do not add node_modules or a package.json.
Entrypoint: src/main.ts
Package name on JSR: @anitrend/stackctl
| Task | Command |
|---|---|
| Cache deps | deno task cache |
| Type-check | deno task check |
| Format | deno task fmt |
| Check format (CI) | deno task fmt:check |
| Lint | deno task lint |
| Run all tests | deno task test |
| Coverage report | deno task coverage (run after test with --coverage=.coverage) |
| Build binary | deno task build |
| Cross-compile | deno task build:linux:x64, :linux:arm64, :darwin:x64, :darwin:arm64 |
CI-mandated order: deno task cache -> fmt:check -> lint -> check (type-check) -> test -> coverage
Tests require permissions. The test task bakes them in, but if calling deno test directly you need:
deno test --allow-read --allow-write --allow-env --allow-run --allow-sys
- Line width: 100 chars
- Indent: 2 spaces, no tabs
- Quotes: double quotes, not single
- Prose wrap: always
- Lint rules:
recommendedtag, withno-unused-varsexcluded - Compiler:
strict: true,noUnusedLocals: true,noUnusedParameters: true
These are enforced by CI. Match the repo style; the formatter is the authority.
src/
cli/mod.ts — All CLI commands (one large file, ~1600 lines). Uses @cliffy/command.
config/ — Config loading, merging, validation, init. Exports from mod.ts.
compose/ — Docker Compose discovery, generation, merging, transform, reload, sync, plan.
render/ — ${VAR} interpolation in compose YAML.
docker/ — Docker / Swarm CLI wrappers (stack deploy, rm, ps, services, swarm status, etc.).
secrets/ — SOPS + age integration for encrypting .env files.
env/ — .env file scaffolding (list, create, diff, materialize from profile presets, audit).
process/ — ProcessRunner interface + RealProcessRunner. All external tool calls go through this.
testing/ — FakeProcessRunner for unit tests, plus helper factories.
main.ts — Bootstraps CLI.
version.ts — VERSION constant. Updated by CI on release.
- Built-in defaults (
src/config/defaults.ts) .stackctl(base).stackctl.<profile>(profile overlay).stackctl.local(local overrides, gitignored).stackctl.local.<profile>(local profile overlay)
Explicit --override files are applied after all config layers but before rendering.
Config merges use: scalars replaced, maps deep-merged, sequences appended.
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Drift or validation failure |
| 2 | User config error |
| 3 | Missing dependency (Docker, sops, age) |
| 4 | Unexpected / internal error |
- Test files are co-located:
foo_test.tsnext tofoo.ts. - Use
FakeProcessRunnerfromsrc/testing/fakes.tsinstead of calling real tools. Pre-program responses withFakeProcessRunnerBuilder. The fake throws on unknown commands — every test must configure all commands its code path will invoke. - Use
@std/assertand@std/testing(from JSR) for assertions. - Coverage threshold: minimum 80% line coverage for
src/. Check withdeno task coverage. - The CI workflow (
ci.yml) runs on pushes/PRs tomainanddevbranches.
- Binary compiled via
deno compilewith permissions baked in:--allow-read --allow-write --allow-env --allow-sys --allow-run=git,docker,sops,age,age-keygen,shred,rm - Release workflow triggers on
v*tags. Cross-compiles for 4 targets (linux/macOS, x64/arm64), packages tarballs, generates SHA256 checksums. - Version bump: after a release,
version-bump.ymlupdates bothdeno.json("version"field) andsrc/version.ts(VERSIONconstant). If changing version manually, update both. release-drafter-config.ymlauto-labels PRs by branch prefix.
See CONTRIBUTING.md for full details. Quick reference:
- Branch:
<type>/<issue-number>-<short-description>(e.g.,feat/1208-implement-override-merging) - Commit:
<type>(<scope>): <summary>(e.g.,feat(config): add profile overlay discovery) - PR title: same format as commits, linked to the relevant issue
- Valid types:
feat,fix,dependencies,chore,docs,refactor,test,build,ci,revert - Valid scopes:
config,generate,render,cli,docker,secrets,project,env,compose,process
When creating a branch automatically, select the prefix based on the change intent. The release drafter autolabeler maps each branch prefix to a changelog label:
| What you are doing | Branch prefix | Auto-label applied |
|---|---|---|
| Adding a new feature or capability | feat |
:star2: feature |
| Fixing a bug | fix |
:adhesive_bandage: bug fix |
| Updating dependencies (Renovate, manual) | dependencies |
:dagger: dependencies |
| Changing build system or tooling | build |
:dagger: dependencies |
| Routine maintenance, chores | chore |
:wrench: enhancement |
| Documentation only | docs |
:books: docs |
| Refactoring without behavior change | refactor |
:hammer_and_wrench: refactor |
| Adding or fixing tests | test |
:test_tube: testing |
| CI/CD configuration | ci |
:construction_worker: ci |
| Reverting a previous change | revert |
:rewind: revert |
If no issue number exists, use <type>/<short-description> and link to an issue once created.
AGENTS.md(this file) anddocs/must be kept up to date with code changes. If you add, remove, or change behavior, update them in the same PR.docs/migration.mddocuments the migration path from the legacystackctl.shscript to the standalonestackctlbinary. Keep it in sync with CLI changes that affect documented commands or config fields.
- This project is in early development. All commands are listed as "Planned" in
README.md; check actual implementation status insrc/cli/mod.tsbefore assuming a command exists. - Permissions for the compiled binary are defined in
src/main.tsshebang and the CI build step. Do not change permissions without updating both. - Git-ignored files:
.stackctl.local,.stackctl.local.*,*.env(except.env.example),*.env.enc,age-key.txt,age.key,dist/,.rendered/,.coverage/,cov/.
A full codemap is available at codemap.md in the project root.
Before working on any task, read codemap.md to understand:
- Project architecture and entry points
- Directory responsibilities and design patterns
- Data flow and integration points between modules
For deep work on a specific folder, also read that folder's codemap.md.