feat(shell): auto-build missing devcontainer with live build log#7
Merged
Conversation
Add a reusable rich-Live-based spinner (`dcode._progress`) that pins to the bottom of the terminal while subprocess output scrolls above it. * `with_spinner(label)` — bare context manager for short non-streaming work (used to wrap the VS Code editor launch in `run_dcode` so users see a brief loader while `code` connects to the running window). * `run_streaming(argv, *, label, console=...)` — runs a subprocess with PIPE stdout/stderr, pumps each stderr line through `console.print(... markup=False, highlight=False)` from a daemon thread while the spinner stays pinned. Returns a `StreamedResult` dataclass with returncode, full captured stdout (for downstream parsing), full captured stderr, and an `error` field for pre-launch OSError. KeyboardInterrupt is propagated after best-effort terminate. `core.run_dcode` now wraps editor launches in the spinner and captures the editor's output, only printing it on non-zero exit so stray VS Code messages don't trample the spinner. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… metadata When `dcode shell` runs against a project whose devcontainer has never been built, prompt to build it via the official `@devcontainers/cli` (the same Node CLI VS Code's Dev Containers extension uses under the hood). The build's stderr scrolls live above a pinned spinner via `dcode._progress.run_streaming`, and we trust the `containerId` returned by `devcontainer up` directly (no docker-label re-lookup, which would mismatch on WSL). If the CLI is not installed, prompt to install it via the upstream `install.sh` (which bundles its own Node runtime into `~/.devcontainers`, no host Node required). Install download progress also streams beneath the spinner. If the user declines, exit with a clear hint pointing at the curl/npm install commands and at `dcode <path>` as a fallback. Auto-build always prompts and never runs without an interactive TTY. Also fix a related correctness bug: `_resolve_exec_user` only read `remoteUser` / `containerUser` from the project's devcontainer.json, which meant images that set the user via metadata (e.g. `mcr.microsoft.com/devcontainers/javascript-node` → `remoteUser: node`) landed the user as root. We now also read the container's `devcontainer.metadata` Docker label, walking layers per-key in reverse to match devcontainers/cli's `mergeConfiguration` precedence, then prefer `remoteUser` over `containerUser`. `devcontainer.json` stays the highest-precedence layer. `dcode doctor` gains a `Dev Containers CLI` check in the Container panel: ok with version when found on PATH or at `~/.devcontainers/bin/devcontainer`, warn with the curl install hint otherwise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two coupled improvements to
dcode shellplus a reusable progress UXhelper.
1.
dcode shellbuilds a missing devcontainer on demandToday,
dcode shellagainst a brand-new project just exits with nodevcontainer found running. With this PR, it offers to build & start
the container for you so you don't have to open VS Code first:
Uses the official
@devcontainers/cli(the same Node CLI VS Code's DevContainers extension drives under the hood), so the resulting container
carries the same
devcontainer.local_folder/devcontainer.config_file/devcontainer.metadatalabels VS Codeexpects — open the project in VS Code later and it'll attach to the
same container.
If the CLI isn't installed, dcode offers to fetch the upstream
install.sh(which bundles its own Node.js, no host Node required).Decline → exit with a clear hint, no auto-fallback to VS Code polling
(unreliable:
docker ps … runningis not a readiness signal becauselifecycle hooks may still be in progress).
After
devcontainer upsucceeds we trust the returnedcontainerIddirectly and don't re-call
find_container— that would mismatchon WSL because the labels VS Code expects are Windows paths but
devcontainer upwrites WSL paths.2. Resolve
remoteUserfrom the image'sdevcontainer.metadatalabelCoupled correctness fix that was triggered originally on WSL but
affects all platforms:
_resolve_exec_useronly readremoteUser/containerUserfrom the project'sdevcontainer.json,which meant images that set the user via metadata (e.g.
mcr.microsoft.com/devcontainers/javascript-node→remoteUser: node) landed users as root because no-uwas passedto
docker exec.We now also read the container's
devcontainer.metadataDocker labelwritten by devcontainers/cli. The merge mirrors the upstream
mergeConfigurationsemantics: per-key reverse walk of metadatalayers (last entry wins for each of
remoteUser/containerUser),then
remoteUseris preferred overcontainerUser. The localdevcontainer.jsonstays the highest-precedence layer (defensiveduplication for containers that predate the metadata label).
3. Reusable progress spinner with live streaming output
New
dcode._progressmodule:with_spinner(label)— bare context manager for short, non-streamingwork. Used to wrap the VS Code editor launch in
run_dcodeso userssee a brief loader for
dcode .too.run_streaming(argv, *, label, console=...)— runs a subprocess withPIPE stdout/stderr, pumps each stderr line through
console.print(... markup=False, highlight=False)from a daemonthread while the rich
Livespinner stays pinned at the bottom.Returns a
StreamedResultdataclass withreturncode, fullcaptured
stdout(for downstream JSON parsing), full capturedstderr, and anerrorfield for pre-launchOSError.KeyboardInterrupt is propagated after best-effort terminate. Gracefully
degrades on non-TTY consoles (Live becomes a one-shot status line and
stderr lines still pass through).
Used by
devcontainer_cli.up()(build logs),devcontainer_cli.install_cli()(installer download/extract), and
core.run_dcode()(editor launch).dcode doctorNew check in the Container panel:
Dev Containers CLI: /usr/local/bin/devcontainer (0.86.0)Dev Containers CLI: not on PATH or at ~/.devcontainers/bin/devcontainer …with the curl install hint.
Tests
318 passing, lint clean.
tests/test_progress.py— 12 new tests coveringrun_streaming(real subprocess for integration coverage of capture + streaming),
exit-code propagation, env/cwd passthrough, OSError handling,
markup-safety, supplied-console routing.
tests/test_devcontainer_cli.py— 24 tests coveringfind_cli/cli_version/install_cli/up/install_hintwithmocked
_progress.run_streamingandurllib.request.urlopen,including object-shaped legacy metadata labels and last-entry-wins
semantics edge cases.
tests/test_shell.py— extended withTestPromptYesNo,TestObtainOrInstallCli,TestBuildMissingContainer,TestRunShellMissingBuild(accept/decline/build-failure/metadata-derived user), plus
TestInspectContainerMetadataand
TestResolveExecUserfor the metadata-label fix.tests/test_doctor.py—TestCheckDevcontainerCliplus stubupdates to existing fixtures.
handling and editor launch.
Commits
Two file-level commits, each independently buildable:
feat: live progress spinner with streaming subprocess output—_progressmodule + tests +core.run_dcodeeditor wrapper.feat(shell): auto-build missing devcontainer; resolve user from image metadata— everything else; uses_progressfrom chore: audit cleanup (WSL fixes, hatch-vcs, ruff, CI, release-please) #1.Out of scope
devcontainer.jsonchanges (always reusesexisting container if present).
devcontainer upsupports.long-running CLI invocations get the live spinner).