feat(cli): implement up, down, status, logs, doctor, and sync#22
feat(cli): implement up, down, status, logs, doctor, and sync#22wax911 wants to merge 16 commits into
Conversation
- Deno 2.x project structure with deno.json and task definitions - JSR dependencies: @cliffy/command, @std/assert, @std/testing, @std/yaml, @std/dotenv, @std/fs, @std/path - Full CLI command tree with stubs for all 15 issues - Shared interfaces (ProcessRunner, config types, ExitCode) for parallel work - FakeProcessRunner with recording, pre-programmed responses, and dry-run support - CI pipeline: fmt, lint, typecheck, test, coverage, and cross-platform build - .gitignore for generated and environment-specific files
- Default config values with sensible defaults - Deep merge for 5-layer config resolution (defaults -> base -> profile -> local -> local-profile) - Filesystem discovery (.stackctl, .stackctl.<profile>, .stackctl.local, .stackctl.local.<profile>) - Post-merge validation returning all errors at once - Template generation with inline comments, --detect, --preset, --profile, --force, --dry-run - STACKCTL_PROFILE env var support - 43 config tests + existing 15 = 58 passing - CLI init command wired to real implementation
Port of tools/generate_stacks.py from AniTrend/local-stack to idiomatic Deno TypeScript: - File discovery: walks repo root, finds docker-compose.yml/yaml files with x-stack metadata - Fragment loading: optional swarm.fragment.yml deep-merge per service - Compose deep merge (dict recursive, array replacement, scalar override) - Service transforms: strip compose-only keys (container_name, restart, build), inject logging defaults, rewrite env_file and bind-mount paths to repo-root relative - Named volume collection (external: true), default traefik-public overlay network - YAML output with header comment, --dry-run support - CLI generate command wired to real implementation - 60 compose tests + 58 existing = 118 passing
- composeOverrideMerge: scalars replace, maps merge, sequences append (distinct from fragment merge which replaces arrays) - loadOverrideFile: load YAML override from relative/absolute path - applyOverrides: load and apply chain of override files to base compose - Override integration in generateStacks via GenerateOptions.overrides - 26 tests covering all merge rules, file loading, edge cases - CLI generate command accepts --override flag
- Variable interpolation: ${VAR}, ${VAR-default}, ${VAR:-default}, $VAR, $$
- Variable scope resolution: shell env -> env_file(s) -> service.environment
- Deep interpolation through all string values in compose structures
- Path absolutization for env_file and bind-mount paths
- Strict mode (fail on unresolved) and non-strict mode (leave as-is with warnings)
- CLI pipeline: resolveConfig -> generateStacks -> renderStack -> output
- 49 comprehensive tests covering all interpolation forms and edge cases
Covers config migration, command mapping, profiles, overrides, rollback, troubleshooting, and behavior differences.
- Add composite action at .github/actions/setup-stackctl/action.yml - Support linux-x64, linux-arm64, macos-x64, macos-arm64 - Download from GitHub Releases, verify SHA256, cache in tool cache - Resolve latest version via GitHub API, accept explicit versions - Add PATH integration for subsequent workflow steps - Document CI usage in docs/migration.md Closes #11
- Add RealProcessRunner using Deno.Command with dry-run and signal forwarding - Add Docker CLI integration module (deploy, rm, services, ps, logs, info, swarm) - Add full sync pipeline: config -> discover -> generate -> render -> deploy - Wire CLI commands: up, down, status, logs, doctor, sync - Replace all issue #6 stubs with real implementations - Add 31 new tests (22 docker + 9 sync) all using FakeProcessRunner
- deno.json: add build:* tasks with Deno.compile for 4 targets - .github/workflows/release.yml: build matrix, SHA256 checksums, GitHub Releases - .github/workflows/ci.yml: update build stage to use renamed tasks
- Wire Cliffy CompletionsCommand for bash/zsh/fish/powershell completions - Add detailed descriptions to all CLI commands (2-3 sentences each) - Add .example() calls for every command with realistic usage patterns - Update deno.json import map with @cliffy/command/completions
Implements config-first, change-aware stack reload: - reloadStacks() in src/compose/reload.ts with SHA-256 checksum comparison - CLI wiring in src/cli/mod.ts with --skip-generate, --follow-logs, --dry-run - 19 unit tests covering dry-run, unchanged detection, deployment, error handling - Only deploys stacks whose rendered output has changed Ref: #9
- Add EnvExample, EnvDiff, CreateResult, BatchCreateResult types - Implement discoverEnvExamples with profile-driven discovery - Implement createEnvFromExample with dry-run and force support - Implement diffEnvFiles for key comparison - Add batchCreateEnvs helper for bulk operations - Wire env list, create, diff subcommands to CLI - Add 30 unit tests: discovery, creation, diff, batch ops Issue: #14
Implement encrypt, decrypt, deploy, clean, and check subcommands for managing SOPS-encrypted dotenv files with age keys. All operations go through the ProcessRunner interface enabling dry-run and test faking. - Add ToolingStatus, EncryptResult, DecryptResult, DeployResult, CleanResult types - Implement checkTooling() for sops/age availability detection - Implement resolveAgeKey() with config file, env var, and CLI flag resolution - Implement discoverEncryptedFiles() / discoverDecryptedFiles() for file discovery - Implement encryptFile() / decryptFile() with --dry-run support - Implement deploySecrets() for decrypting and creating Docker secrets - Implement cleanTempFiles() for removing .tmp and stray decrypted files - Add ageKeyFile and secretsDir to SecretsConfig - Wire all secrets subcommands in CLI with RealProcessRunner - Add 42 comprehensive tests using FakeProcessRunner Ref: #7
# Conflicts: # src/cli/mod.ts
wax911
left a comment
There was a problem hiding this comment.
Review notes against #6:
-
The PR body describes the sync pipeline as
config -> discover -> generate -> render -> deploy. That is wrong forsync.syncmust only generate into a temporary directory and compare against committed canonical stack files. It must not render and must never deploy. -
The command surface diverges from the accepted plan.
uplists flags such as--follow-logs,--detach, and--prune, while the issue required compatibility with--no-logs,--dry-run,--skip-generate,--allow-unrendered,--stacks, and--profile. New flags are fine later, but compatibility flags must be present and tested first. -
doctorneeds to validate the render path anddocker compose configfor rendered files. Do not reduce it to only Docker availability/Swarm checks. -
logsmust support default configured services when no service names are passed and prefix output per service. Verify this behavior is present and covered by tests. -
External command execution needs to stay fully behind the process runner with argument arrays only. No shell-concatenated user input should appear anywhere in Docker command paths.
Please correct sync first. A deploy-capable sync command is a hard safety regression.
- Add baseConfigPath, profileConfigPath, localConfigPath fields to ResolvedConfig - Populate config path fields in resolveConfig / load.ts - Implement PlanJsonOutput interface with stable shape: operation, config (layers), stacks, steps, warnings, encryptedInputs, cleanupActions - plan never mutates files (all generation uses dryRun=true in-memory) - plan secrets deploy shows encryptedInputs and cleanupActions without decrypting - Report resolved config layers: base config path, profile overlay, local override - CLI plan command wired with human-readable and --json output - 16 tests covering structure, JSON shape, resolved layers, safety (never-mutate)
wax911
left a comment
There was a problem hiding this comment.
Follow-up review after the push:
Good: this PR is now draft, which is appropriate while the lower layers are still moving.
Still unresolved:
-
The PR still targets
mainand includes a large cumulative diff. Retarget it to the preceding implementation branch once the stack order is finalized. -
The PR body still describes
syncasconfig -> discover -> generate -> render -> deploy. That is still unsafe.syncmust beconfig -> generate into temp -> diff against committed stacks -> exit with drift status. No render, no deploy. -
The command flag list in the PR body still diverges from the compatibility contract. Keep
--no-logs,--dry-run,--skip-generate,--allow-unrendered,--stacks, and--profileas first-class compatibility flags before adding extras such as--detachand--prune.
Leave this draft until the body and implementation are corrected. The sync/deploy wording alone is enough to block readiness.
Closes #6