feat(project): show create progress like build and deploy - #2174
Conversation
`project create` printed a flat line per step while `build` and `deploy` render the live step list #2163 introduced. It now uses the same `runWithProgress` driver, so a TTY gets the spinner and per-step ✓, and the non-TTY and --json paths keep the previous plain output byte for byte. Every event the create generator yields is already a `step`, so nothing else had to change. The bare interactive `project create` still opens the TUI wizard.
The step list create now runs under can show a live tail of the running step's output, but create's subprocesses sent their chunks only to the debug log, so the tail was always empty and create looked flatter than build and deploy. `npm install` (CDK app and scaffolded runtime), `uv sync`, and container lockfile generation now stream through withOutputEvents plus createLineSplitter, the same bridge the CDK backend uses for synth. Chunks still reach the debug log whole; the splitter reassembles them into lines for display and flushes an unterminated trailing chunk when the process exits. `git init` stays plain since it prints nothing worth tailing. Draining the generator on a real create yields 3 step events and 133 output events, including "added 320 packages, and audited 341 packages in 6s" and "Creating virtual environment at: .venv". A cached npm install finishes in milliseconds and flushes at the end, so the tail is only visible on a cold cache or a slow step -- and on failure, where the driver keeps it in scrollback.
The rebase onto refactor picked up #2170, which renamed the hello-world-python template shortcut, so the new streaming test referenced a constant that no longer exists.
|
Claude Security Review: no high-confidence findings. (run) |
There was a problem hiding this comment.
AgentCore Harness Review
Verdict: Looks good
Small, focused change that brings project create in line with build/deploy by driving progress through runWithProgress, and factors a runStreaming helper so the three npm install / uv sync / lockfile-generation steps stream subprocess output as output events for the live tail.
- The
runStreaminghelper insrc/core/project/manager.tsxis a faithful factoring of the existingwithOutputEvents(...) + createLineSplitter + finally(flush)pattern insrc/core/project/backends/cdk.ts, including the.finally(() => lines.flush())to drain unterminated trailing chunks. interactive: ctx.require(JsonKey) ? false : undefinedinsrc/handlers/project/create/index.tsmatches the shape used bybuild/deployand correctly forces the plain path only under--json, otherwise deferring torunWithProgress's TTY autodetection.ProjectEvent = ProgressEvent, so passing the generator straight torunWithProgressis type-safe.- The other caller of
projectManager.create— the wizard inscreen.tsx— already filters onevent.type === "step", so the newoutputevents are silently ignored there. No regression. - The added test uses a real temp directory and mocks only at the runner boundary (the true I/O boundary), and covers the important edge case of flushing an unterminated trailing chunk on process exit.
No telemetry gap: the sibling build/deploy handlers don't instrument here either, so this isn't a new omission.
Nothing blocking — good to merge.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## refactor #2174 +/- ##
============================================
- Coverage 97.12% 97.07% -0.05%
============================================
Files 535 535
Lines 36844 36878 +34
============================================
+ Hits 35783 35800 +17
- Misses 1061 1078 +17 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
npm prints nothing at all while its stderr is piped, so the progress tail stayed empty for the first ~5.8s of a ~6.5s install. Neither --progress=true nor a real PTY helps: the flag is ignored when piped, and npm's TTY output is a textless spinner. Its HTTP log is the only per-package progress it will emit, so --loglevel=http is now parsed back into package names -- 'resolving aws-cdk-lib' rather than 'npm http fetch GET 200 https://registry.npmjs.org/aws-cdk-lib 34ms'. First readable line lands at 540ms instead of 5869ms, at no measurable cost. uv sync needs none of this; it already prints for people.
|
Claude Security Review: no high-confidence findings. (run) |
…g test The four functions in npmProgress.ts were each used once, and tarballVersion parsed a version out of a tarball name only to print it in a scrolling tail. Collapsed to a single function beside the flag that makes npm talk, dropping the file: the version, the URL parsing, and the decodeURIComponent guard all go away, since npm's only encoding is %2f for a scope slash. Also drops the create streaming test, which asserted that a mocked runner's chunks reach the generator -- plumbing the type system already pins.
|
Claude Security Review: no high-confidence findings. (run) |
runStreaming duplicated run rather than replacing it, leaving two methods doing one job and one caller -- git init -- still on the old path because it prints little worth tailing. That is not worth a duplicate method: run now streams, every caller yields it, and git init's one line shows like any other.
|
Claude Security Review: no high-confidence findings. (run) |
Hweinstock
left a comment
There was a problem hiding this comment.
Thanks for fixing this!
| return this.runner(command, { | ||
| cwd, | ||
| onOutput: (chunk) => { | ||
| this.logger.debug(chunk); |
There was a problem hiding this comment.
OOS here since I think its existing behavior, but this is dumping raw output (with ANSI codes) into the logs which are not human readable.
Ex.
{
"level": "debug",
"msg": "\u001b[32m+\u001b[39m \u001b[1ms3transfer\u001b[0m\u001b[2m==0.19.2\u001b[0m\n \u001b[32m+\u001b[39m \u001b[1msix\u001b[0m\u001b[2m==1.17.0\u001b[0m\n \u001b[32m+",
"time": 1788392133886,
"cliSessionId": "cebc9a0c-9985-400f-b5e7-28c9221ce9cd",
"version": "1.0.0",
"module": "projectManager"
}
{
"level": "debug",
"msg": "\u001b[39m \u001b[1msse-starlette\u001b[0m\u001b[2m==3.4.8\u001b[0m",
"time": 1788392133887,
"cliSessionId": "cebc9a0c-9985-400f-b5e7-28c9221ce9cd",
"version": "1.0.0",
"module": "projectManager"
}
{
"level": "debug",
"msg": "\n ",
"time": 1788392133887,
"cliSessionId": "cebc9a0c-9985-400f-b5e7-28c9221ce9cd",
"version": "1.0.0",
"module": "projectManager"
}
{
"level": "debug",
"msg": "\u001b[32m",
"time": 1788392133887,
"cliSessionId": "cebc9a0c-9985-400f-b5e7-28c9221ce9cd",
"version": "1.0.0",
"module": "projectManager"
}
There was a problem hiding this comment.
Ahhhh this is a good call out. I can fix this in a follow up. I don't think that this was pre existing.
| for await (const event of config.projectManager.create(createInput)) { | ||
| if (event.type === "step") config.io.stderr.write(`${event.message}\n`); | ||
| } | ||
| // Same driver as build and deploy: a live step list in a TTY, and the previous plain |
There was a problem hiding this comment.
nit: i don't feel like this comment is relevant.
project createprinted a plain line per step whilebuildanddeployrendered a livestep list. This runs create under the same driver, so all three look the same.
createnow goes throughrunWithProgress, the same driverbuildanddeployuse.as
outputevents, so the tail under the running step shows real progress instead ofsitting empty.
Non-TTY and
--jsonkeep the previous plain line-per-step output, byte for byte.Testing
bun test src— 2789 pass, 0 failbun run typecheck,bunx oxlint src,bunx prettier --check srccleanproject createin a TTY: spinner, ✓ marks, and the output tail all render;measured no event-loop stall over 150ms across the run
--jsonand piped stderr produce output identical to before, and that a failingstep renders
✕with its tail preserved