Skip to content

feat: set-up — wire dotfiles installer (PR-6b)#43

Merged
pofallon merged 3 commits into
mainfrom
pr6b-set-up-dotfiles
May 25, 2026
Merged

feat: set-up — wire dotfiles installer (PR-6b)#43
pofallon merged 3 commits into
mainfrom
pr6b-set-up-dotfiles

Conversation

@pofallon
Copy link
Copy Markdown
Contributor

Re-opened replacement for #37 — the original PR was auto-closed when its base branch pr6-set-up-subcommand (#36) was deleted on merge. Branch content unchanged; base retargeted to main so the squashed PR-6a commit on main reconciles cleanly.

See #37 for the original PR description and reviews.

🤖 Generated with Claude Code

pofallon and others added 3 commits May 25, 2026 14:13
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>
Follow-up to PR-6a (#36). Adds the three `--dotfiles-*` flags from spec §2
to the `set-up` CLI surface and threads them as a `DotfilesConfig` into
`ContainerLifecycle`. The lifecycle helper already implements clone +
auto-detected installer (`install.sh` / `bootstrap` / `setup` / `script/*`)
+ target-path marker for idempotency — set-up just needed to opt in.

## New flags

- `--dotfiles-repository <URL or owner/repo>`
- `--dotfiles-install-command <string>` (overrides auto-detection)
- `--dotfiles-target-path <path>` (defaults to `~/dotfiles`)

No repository → no clone. The lifecycle helper's marker-at-target-path
guard provides idempotency across re-runs (spec §6, §16 design decision).

## Tests

3 new unit tests in `set_up::tests::build_dotfiles_config_*`:

- `returns_none_when_no_repository` — no repo URL means no opt-in, even if
  the other flags are set
- `forwards_all_three_fields` — all flags round-trip into `DotfilesConfig`
- `leaves_defaults_to_lifecycle_helper` — `target_path` / `install_command`
  stay `None` so the helper computes its standard defaults

Verification:
- `cargo fmt --all -- --check`
- `cargo clippy --all-targets -- -D warnings`
- `cargo test -p deacon --lib set_up` → 17 pass (14 from PR-6a + 3 new)

## Deferred to PR-6c

- `/etc/environment` + `/etc/profile` root-side patches with system markers
  under `/var/devcontainer/` (spec §5 phase 3a; net-new code that needs
  root exec + heredoc plumbing)
- Live-container `${containerEnv:VAR}` second-pass substitution

Refs: issue #34 (Tier 1 progress tracker); PR-6a (#36).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
# Conflicts:
#	crates/deacon/src/cli.rs
#	crates/deacon/src/commands/set_up.rs
@pofallon pofallon merged commit 40c17cc into main May 25, 2026
8 checks passed
@pofallon pofallon deleted the pr6b-set-up-dotfiles branch May 25, 2026 19:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant