Commit cca87c3
feat: set-up subcommand — MVP scaffold + lifecycle hooks (PR-6a) (#36)
Implements the core value of `set-up` per
`docs/subcommand-specs/set-up/SPEC.md`: convert an already-running container
into a DevContainer by applying configuration + image metadata and executing
lifecycle hooks, emitting a single-line JSON result on stdout.
## CLI surface
`deacon set-up --container-id <id> [--config <path>] [--skip-post-create]
[--skip-non-blocking-commands] [--remote-env NAME=VALUE]...
[--include-configuration] [--include-merged-configuration]
[--container-data-folder <path>]`
## What this PR includes
- `--container-id` resolution + inspect validation. Missing container fails
with the upstream-aligned summary `"Dev container not found."`
- Optional `--config` load via `ConfigLoader::load_with_extends` (extends
chain honored per CLAUDE.md). Missing path fails with
`"Dev container config (<path>) not found."`
- Image-metadata extraction from the container's `devcontainer.metadata`
label. Tolerates BOTH the JSON-array form (PR-2 / #27) and the
single-object form for older images.
- Config merge: file config wins over image metadata on scalar fields
(spec §4 `mergeConfiguration(config.config, imageMetadata)`).
- Variable substitution for both `configuration` and `mergedConfiguration`.
- Lifecycle hook execution via `ContainerLifecycle` (onCreate →
updateContent → postCreate → postStart → postAttach), gated by
`--skip-post-create` (skips ALL phases per spec §2) and
`--skip-non-blocking-commands` (stops after the configured `waitFor`).
- JSON output per spec §10: `{outcome: "success", configuration?,
mergedConfiguration?}`. `containerId` is intentionally excluded (spec
§16 design decision).
## Deferred to PR-6b
- `/etc/environment` + `/etc/profile` root-side patches with system markers
under `/var/devcontainer/`
- Dotfiles installer with target-path marker (would reuse
`crates/deacon/src/commands/up/dotfiles.rs`)
- A second substitution pass against the live container environment
(`${containerEnv:VAR}`) — current pass uses the configured
`container_env`, not a live `docker exec` env probe
These are spec §5 phases 3a and 3c; both marked "best-effort" with graceful
fallback on failure. Splitting them out keeps PR-6a reviewable.
## Tests
14 new unit tests in `set_up::tests`:
- `--remote-env` parsing (accepts `NAME=VALUE`, rejects malformed input)
- `--config` loading (default when absent, error when missing path)
- Image-metadata label parsing (missing → None, array form, single-object
form, invalid JSON → error)
- Config merging (file wins over metadata; file-only when no metadata)
- Argument defaults and JSON-result shape (outcome field, optional fields)
Verification:
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets -- -D warnings`
- `cargo test -p deacon --lib` → 227 pass
- `make test-nextest-fast` → 1927 pass (no regression)
Refs: issue #34 (Tier 1 progress tracker), plan
`/home/vscode/.claude/plans/let-s-come-up-with-recursive-sutherland.md`
(PR-6 sequencing rationale).
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>1 parent a53328a commit cca87c3
3 files changed
Lines changed: 719 additions & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
517 | 517 | | |
518 | 518 | | |
519 | 519 | | |
520 | | - | |
| 520 | + | |
521 | 521 | | |
522 | 522 | | |
523 | 523 | | |
| |||
537 | 537 | | |
538 | 538 | | |
539 | 539 | | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
540 | 575 | | |
541 | 576 | | |
542 | 577 | | |
| |||
1402 | 1437 | | |
1403 | 1438 | | |
1404 | 1439 | | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
1405 | 1471 | | |
1406 | 1472 | | |
1407 | 1473 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
13 | 13 | | |
14 | 14 | | |
15 | 15 | | |
| 16 | + | |
| 17 | + | |
16 | 18 | | |
17 | 19 | | |
18 | 20 | | |
| |||
0 commit comments