You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
refactor: unify commands as generators with HumanRenderer factory, remove stdout plumbing (#416)
## Summary
Unifies all command functions as async generators, introduces a
`HumanRenderer` factory for stateful rendering, and removes direct
stdout/stderr plumbing from commands. This is Phase 6b of the output
convergence plan.
### 1. Unify all command functions as async generators
Every command `func` is now an `async *func` generator. Commands yield
`CommandOutput<T>` values instead of writing to stdout directly.
- Commands that produce output yield via `commandOutput(data)`
- Void generators (e.g. `auth/token`) are valid no-op generators
- The `buildCommand` wrapper iterates the generator and routes output
through the `OutputConfig` pipeline
### 2. Brand `CommandOutput` with Symbol discriminant
Replace duck-typing (`"data" in value`) with a `COMMAND_OUTPUT_BRAND`
Symbol. Prevents false positives from raw API responses that have a
`data` property.
- `commandOutput<T>(data)` factory creates branded values
- `isCommandOutput()` checks the Symbol instead of structural shape
- All command files migrated to use `commandOutput()`
### 3. Move hints from yield to generator return value
Hints (footer text like "Detected from .env.local") are no longer part
of the yielded `CommandOutput`. Generators `return { hint }` after their
final yield.
- New `CommandReturn` type: `{ hint?: string }`
- Wrapper uses manual `.next()` iteration to capture the return value
- Hints are suppressed in JSON mode
### 4. `HumanRenderer` factory pattern for `OutputConfig.human`
`OutputConfig.human` is now a factory function `() => HumanRenderer<T>`
instead of a plain `(data: T) => string`. This enables stateful
rendering (e.g., streaming tables) without module-level singletons.
- `HumanRenderer<T>` has `render(data: T): string` and optional
`finalize(hint?: string): string`
- `stateless(fn)` helper wraps plain formatters for commands that don't
need state
- `createLogRenderer()` replaces the old module-level
`streamingTable`/`streamingHeaderEmitted` singletons in `log/list.ts`
- The `buildCommand` wrapper resolves the factory once per invocation,
passes the renderer through iteration, and calls `finalize()` after the
generator completes
### 5. Remove stdout/stderr plumbing from commands
Commands no longer receive or pass `stdout`/`stderr` Writer references.
All diagnostic/interactive output routes through consola logger (→
stderr), keeping stdout reserved for structured command output.
- `HandlerContext` and `DispatchOptions` no longer carry `stdout`
- `runInteractiveLogin` uses logger instead of Writer params
- Follow-mode banners, diagnostics, and QR code display all use logger
- `displayTraceLogs` → `formatTraceLogs` (returns string)
- `formatFooter()` helper extracted from `writeFooter()`
### 6. `log/list.ts` streaming refactor
The most complex command — 685 lines with follow-mode streaming, trace
filtering, and JSONL output — is fully converted:
- `createLogRenderer()` factory manages streaming table state
per-invocation
- `render()` emits table header on first non-empty batch, rows per batch
- `finalize(hint?)` closes the table footer and appends hint text
- Empty-state hints render as primary text (not muted footer)
- `jsonl` flag on `LogListResult` controls JSONL vs array serialization
- `executeSingleFetch`/`executeTraceSingleFetch` return `FetchResult`
with separate `result` and `hint` fields
### 7. `auth/login` yield pattern
Interactive OAuth login now yields `LoginResult` values through the
generator instead of writing to stdout/stderr directly:
- Token-based login: yields success result, returns hint
- OAuth flow: yields QR code + URL, progress dots, and final result
- `runInteractiveLogin` returns `LoginResult` values consumed by the
generator
**Remaining stdout usage (intentional):**
- `auth/token`: raw stdout for pipe compatibility (`sentry auth token |
pbcopy`)
- `help.ts`: help text to stdout (like `git --help`)
- `trace/logs.ts`: `process.stdout.write` (pending OutputConfig
migration)
## Test plan
- 1656 tests pass, 0 fail, 15753 assertions across 55 files
- Typecheck clean, lint clean (375 files)
- E2E tests pass (103 pass, 3 skip)
- All CI checks green including Seer Code Review and Cursor BugBot
---------
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
0 commit comments