tubeless 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/dist/cli/cli-command.d.ts +2 -2
  2. package/dist/cli/cli-command.js +28 -45
  3. package/dist/cli/cli-command.js.map +1 -1
  4. package/dist/cli/cli-pipeline-command.js +1 -1
  5. package/dist/cli/cli-pipeline-command.js.map +1 -1
  6. package/dist/core/child-execution.js +65 -4
  7. package/dist/core/child-execution.js.map +1 -1
  8. package/dist/core/child-progress.d.ts +6 -0
  9. package/dist/core/child-progress.js +48 -0
  10. package/dist/core/child-progress.js.map +1 -0
  11. package/dist/core/mapped-child-progress.d.ts +4 -2
  12. package/dist/core/mapped-child-progress.js.map +1 -1
  13. package/dist/core/pipeline-steps.d.ts +3 -3
  14. package/dist/core/pipeline-steps.js +12 -23
  15. package/dist/core/pipeline-steps.js.map +1 -1
  16. package/dist/core/pipeline-types.d.ts +8 -1
  17. package/dist/core/pipeline-types.js.map +1 -1
  18. package/dist/render/render.js +2 -6
  19. package/dist/render/render.js.map +1 -1
  20. package/dist/reporter/interactive-reporter.d.ts +1 -0
  21. package/dist/reporter/interactive-reporter.js +50 -15
  22. package/dist/reporter/interactive-reporter.js.map +1 -1
  23. package/dist/reporter/live-ticker-worker.js +8 -42
  24. package/dist/reporter/live-ticker-worker.js.map +1 -1
  25. package/dist/reporter/live-ticker.d.ts +10 -0
  26. package/dist/reporter/live-ticker.js +50 -47
  27. package/dist/reporter/live-ticker.js.map +1 -1
  28. package/dist/run-store/run-store-ndjson.js +8 -2
  29. package/dist/run-store/run-store-ndjson.js.map +1 -1
  30. package/dist/run-store/run-store.js +14 -33
  31. package/dist/run-store/run-store.js.map +1 -1
  32. package/dist/studio/run-store-ui-client-source.js +1 -1
  33. package/dist/studio/run-store-ui-client.d.ts +0 -1
  34. package/dist/studio/run-store-ui-client.js +5 -35
  35. package/dist/studio/run-store-ui-client.js.map +1 -1
  36. package/dist/studio/run-store-ui-page.d.ts +1 -1
  37. package/dist/studio/run-store-ui-page.js +1 -1
  38. package/dist/studio/run-store-ui-page.js.map +1 -1
  39. package/dist/tracing/tracing-internal.js +31 -5
  40. package/dist/tracing/tracing-internal.js.map +1 -1
  41. package/dist/workbench/pipeline-module.d.ts +2 -12
  42. package/dist/workbench/pipeline-module.js +4 -11
  43. package/dist/workbench/pipeline-module.js.map +1 -1
  44. package/dist/workbench/workbench-project-loader.js +1 -1
  45. package/dist/workbench/workbench-project-loader.js.map +1 -1
  46. package/dist/workbench/workbench-ui.js +1 -1
  47. package/dist/workbench/workbench-ui.js.map +1 -1
  48. package/docs/agent-guide.md +8 -0
  49. package/docs/api-reference.md +12 -12
  50. package/docs/api-report.json +12 -12
  51. package/docs/child-pipeline-composition.md +51 -19
  52. package/docs/cli.md +25 -0
  53. package/docs/recipes.md +2 -2
  54. package/examples/catalog/tubeless.project.ts +6 -0
  55. package/examples/fan-out-progress.ts +16 -2
  56. package/package.json +2 -1
@@ -57,10 +57,17 @@ export interface PipelineExecutionContext<TOptions extends object> extends Pipel
57
57
  * Optional indented detail row under a parent step's progress.
58
58
  * Domain-agnostic: mapped children, nested work units, per-file status, etc.
59
59
  */
60
- export type PipelineStepProgressDetailStatus = "completed" | "failed" | "pending" | "running" | "skipped";
60
+ export type PipelineStepProgressDetailStatus = "cancelled" | "completed" | "failed" | "pending" | "running" | "skipped";
61
61
  export interface PipelineStepProgressDetail {
62
62
  /** Stable identity for the row (item key, path, job id, …). */
63
63
  id: string;
64
+ /** Optional display name; identity remains `id`. */
65
+ name?: string;
66
+ /** Indentation relative to direct child rows (0). Rows are in tree preorder. */
67
+ depth?: number;
68
+ /** Inner work counts, independent of the parent's lifecycle count. */
69
+ completed?: number;
70
+ total?: number;
64
71
  /** Short status text shown after the id. */
65
72
  label?: string;
66
73
  /** Controls the detail row symbol. Defaults to `running`. */
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline-types.js","sourceRoot":"","sources":["../../src/core/pipeline-types.ts"],"names":[],"mappings":"AAqcA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC","sourcesContent":["import type { RUN_MODEL_VERSION } from \"./pipeline-ids.js\";\nimport type {\n PipelineTraceAttributes,\n PipelineTraceContext,\n PipelineTracingOptions,\n} from \"../tracing/tracing.js\";\n\nexport interface PipelineLogger {\n error(message?: unknown, ...optionalParams: unknown[]): void;\n log(message?: unknown, ...optionalParams: unknown[]): void;\n warn(message?: unknown, ...optionalParams: unknown[]): void;\n}\n\nexport interface PipelineContext {\n cwd: string;\n /** Hook sets run in order and failures are isolated per set. */\n hooks?: PipelineHooks | readonly PipelineHooks[];\n log: PipelineLogger;\n /** Timestamp source for persisted run records. Defaults to `Date.now`. */\n now?: () => number;\n /** Optional caller-owned parent run identity for nested or resumed orchestration. */\n parentRunId?: string;\n /** Optional caller-owned run identity. A unique ID is generated when omitted. */\n runId?: string;\n signal?: AbortSignal;\n sleep?: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n /** Optional structured lifecycle tracing configuration for this run. */\n tracing?: PipelineTracingOptions;\n}\n\nexport interface PipelineRunControls<\n TStepId extends string = string,\n TTargetId extends string = string,\n> {\n continueOnError?: boolean;\n dryRun?: boolean;\n /**\n * Run only these steps; omitted or undefined runs every step. An empty array\n * is invalid and fails planning/execution instead of silently running nothing.\n */\n stepIds?: readonly TStepId[];\n /**\n * Run these declared pipeline targets and the upstream work required to\n * reach them. Required inputs and failure gates are included recursively;\n * optional-only inputs are not. Cannot be combined with exact `stepIds`\n * filtering.\n */\n targets?: readonly TTargetId[];\n}\n\n/** Domain input plus built-in controls. Child `mapOptions` still returns this mix. */\nexport type PipelineRunOptions<\n TOptions extends object = object,\n TStepId extends string = string,\n TTargetId extends string = string,\n> = TOptions & PipelineRunControls<TStepId, TTargetId>;\nexport interface PipelineRuntime extends PipelineContext {\n now: () => number;\n sleep: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n}\n\nexport interface PipelineExecutionContext<TOptions extends object> extends PipelineRuntime {\n dryRun: boolean;\n options: TOptions;\n /** Stable identity for this execution, whether or not tracing is configured. */\n runId: string;\n /** Parent execution identity when this run was started by another run. */\n parentRunId?: string;\n /** Stable identities for this traced run; absent unless `context.tracing` is configured. */\n trace?: PipelineTraceContext;\n}\n\n/**\n * Optional indented detail row under a parent step's progress.\n * Domain-agnostic: mapped children, nested work units, per-file status, etc.\n */\nexport type PipelineStepProgressDetailStatus =\n | \"completed\"\n | \"failed\"\n | \"pending\"\n | \"running\"\n | \"skipped\";\n\nexport interface PipelineStepProgressDetail {\n /** Stable identity for the row (item key, path, job id, …). */\n id: string;\n /** Short status text shown after the id. */\n label?: string;\n /** Controls the detail row symbol. Defaults to `running`. */\n status?: PipelineStepProgressDetailStatus;\n}\n\nexport interface PipelineStepProgress {\n /** Work completed so far. Values outside the reported total are allowed but renderers may clamp them. */\n completed: number;\n /** Total work when known. Omit for indeterminate progress. */\n total?: number;\n /** Optional short status shown next to the step. Prefer one-line summaries. */\n message?: string;\n /**\n * Optional multi-line detail rows for capable reporters (interactive TTY).\n * Keep the parent `message` as a summary; put per-item status here so high\n * concurrency does not collapse onto one truncated line.\n */\n details?: readonly PipelineStepProgressDetail[];\n}\n\nexport interface PipelineStepContext<\n TOptions extends object,\n> extends PipelineExecutionContext<TOptions> {\n /** Stable identity for this execution of the current step. */\n attemptId: string;\n /** Emit a retry/attempt trace event for this step when tracing is enabled. */\n reportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;\n /** Publish the latest progress snapshot for this step. */\n reportProgress(progress: PipelineStepProgress): void;\n}\n\nexport interface RemoteStepAdapter<TOptions extends object, TPayload, TResult> {\n /** Presentation only. The kernel never switches on this. */\n readonly engine: string;\n /** Function name, workflow type, URL, queue. Presentation only. */\n readonly target?: string;\n invoke(payload: TPayload, context: PipelineStepContext<TOptions>): Promise<TResult>;\n}\n\nexport type PipelineErrorPhase = \"definition\" | \"execution\" | \"finalization\" | \"planning\";\n\nexport type PipelineErrorKind =\n | \"cancellation\"\n | \"child\"\n | \"definition\"\n | \"finalization\"\n | \"selection\"\n | \"step\"\n | \"validation\";\n\n/** Stable package-owned codes for pipeline errors. */\nexport type PipelineErrorCode =\n | \"TUBELESS_CHILD_FAILED\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CYCLE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE\"\n | \"TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_PIPELINE_ID_BLANK\"\n | \"TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT\"\n | \"TUBELESS_DEFINITION_STEP_ID_BLANK\"\n | \"TUBELESS_DEFINITION_STEP_ID_RESERVED\"\n | \"TUBELESS_DEFINITION_STEP_IDS_DUPLICATE\"\n | \"TUBELESS_DEFINITION_STEP_NAME_BLANK\"\n | \"TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH\"\n | \"TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_TARGETS_DUPLICATE\"\n | \"TUBELESS_FINALIZATION_CANCELLED\"\n | \"TUBELESS_FINALIZATION_FAILED\"\n | \"TUBELESS_FINAL_RESULT_VALIDATION_FAILED\"\n | \"TUBELESS_OPTIONS_VALIDATION_FAILED\"\n | \"TUBELESS_PLANNING_SELECTION_CONFLICT\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_STEP_UNKNOWN\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_TARGET_UNDECLARED\"\n | \"TUBELESS_PLANNING_TARGET_UNKNOWN\"\n | \"TUBELESS_RUN_CANCELLED\"\n | \"TUBELESS_STEP_OUTPUT_VALIDATION_FAILED\"\n | \"TUBELESS_STEP_FAILED\";\n\n/** One dependency-free Standard Schema issue normalized for reports and traces. */\nexport interface PipelineValidationIssue {\n message: string;\n path?: readonly (number | string)[];\n}\n\n/** Bounded diagnostics for a failed or cancelled runtime fan-out. */\nexport interface PipelineFanOutFailure {\n /** Original input position, including when the key is truncated. */\n index: number;\n /** At most 1024 UTF-16 code units; do not rerun by key when keyTruncated is true. */\n key: string;\n keyTruncated: boolean;\n cancelled: boolean;\n error: PipelineErrorCause;\n}\n\nexport interface PipelineFanOutDiagnostics {\n /** Failed started items, in input order; at most 32 entries. */\n failures: readonly PipelineFanOutFailure[];\n /** Total failed started items, including omitted diagnostics. */\n failureCount: number;\n omittedFailureCount: number;\n /** Scheduler failure is separate and never assigned an item key. */\n schedulerError?: PipelineErrorCause;\n}\n\nexport interface PipelineError {\n /** Present for aggregated fan-out failures; absent for setup errors. */\n fanOut?: PipelineFanOutDiagnostics;\n /** Bounded, JSON-safe snapshot of the thrown error's cause chain. */\n cause?: PipelineErrorCause;\n code: PipelineErrorCode;\n kind: PipelineErrorKind;\n message: string;\n phase: PipelineErrorPhase;\n /** Present when a Standard Schema rejected a boundary value. */\n issues?: readonly PipelineValidationIssue[];\n stepId?: string;\n /** Machine-readable code copied from the original thrown error, when present. */\n sourceCode?: string;\n stack?: string;\n}\n\n/** Dependency-free subset of the Standard Schema V1 protocol. */\nexport interface StandardSchemaV1<TInput = unknown, TOutput = TInput> {\n readonly \"~standard\": StandardSchemaV1Props<TInput, TOutput>;\n}\n\nexport interface StandardSchemaV1Props<TInput = unknown, TOutput = TInput> {\n readonly types?: { readonly input: TInput; readonly output: TOutput };\n readonly validate: (\n value: unknown,\n options?: { readonly libraryOptions?: Record<string, unknown> }\n ) => StandardSchemaV1Result<TOutput> | Promise<StandardSchemaV1Result<TOutput>>;\n readonly vendor: string;\n readonly version: 1;\n}\n\nexport type StandardSchemaV1Result<TOutput> =\n | { readonly issues?: undefined; readonly value: TOutput }\n | { readonly issues: readonly StandardSchemaV1Issue[] };\n\nexport interface StandardSchemaV1Issue {\n readonly message: string;\n readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[];\n}\n\n/** Infer the accepted input type from a Standard Schema without importing its package. */\nexport type InferSchemaInput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"input\"];\n\n/** Infer the validated output type from a Standard Schema without importing its package. */\nexport type InferSchemaOutput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"output\"];\n\nexport interface PipelineErrorCause {\n cause?: PipelineErrorCause;\n message: string;\n name?: string;\n /** Machine-readable code copied from this cause, when present. */\n sourceCode?: string;\n}\n\ninterface PipelineStepReportBase {\n /** Present when the step started an execution attempt. */\n attemptId?: string;\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n description?: string;\n /** Wall-clock time when this terminal report was recorded. */\n finishedAtMs: number;\n /** Wall-clock time when execution began; absent when the step never started. */\n startedAtMs?: number;\n}\n\nexport interface PipelineStepCompleteReport extends PipelineStepReportBase {\n attemptId: string;\n startedAtMs: number;\n status: \"completed\";\n}\n\nexport interface PipelineStepSkippedReport extends PipelineStepReportBase {\n /** Dependency that blocked this step, when applicable. */\n dependencyId?: string;\n /** Human-readable skip detail, especially for policy and fail-fast skips. */\n message?: string;\n reason: PipelineStepSkipReason;\n status: \"skipped\";\n}\n\nexport interface PipelineStepCancelledReport extends PipelineStepReportBase {\n error: PipelineError;\n status: \"cancelled\";\n}\n\nexport interface PipelineStepFailedReport extends PipelineStepReportBase {\n attemptId: string;\n error: PipelineError;\n startedAtMs: number;\n status: \"failed\";\n}\n\n/** Terminal state recorded for one step after a run. */\nexport type PipelineStepReport =\n | PipelineStepCancelledReport\n | PipelineStepFailedReport\n | PipelineStepSkippedReport\n | PipelineStepCompleteReport;\n\nexport type PipelineStepReportStatus = PipelineStepReport[\"status\"];\n\n/** Terminal disposition of a completed run record. */\nexport type PipelineRunStatus = \"cancelled\" | \"completed\" | \"failed\";\n\n/** Versioned public record returned for one pipeline execution. */\nexport interface PipelineRun<TResult = unknown> {\n pipelineId: string;\n dryRun: boolean;\n errors: PipelineError[];\n finalized: boolean;\n finishedAtMs: number;\n parentRunId?: string;\n runId: string;\n startedAtMs: number;\n status: PipelineRunStatus;\n steps: PipelineStepReport[];\n value?: TResult;\n version: typeof RUN_MODEL_VERSION;\n}\n\n/**\n * Why a step did not run. Built-in structural reasons keep dependency semantics;\n * `\"policy\"` is an intentional skip from `step.skip()` (still unlocks dependents).\n */\nexport type PipelineStepSkipReason =\n | \"dry-run\"\n | \"failed-dependency\"\n | \"fail-fast\"\n | \"filtered\"\n | \"policy\"\n | \"unmet-dependency\";\n\n/**\n * Decision from an optional `skip` predicate.\n * - falsy / `false` → run the step\n * - non-empty string → skip with that message (`reason: \"policy\"`); the step's\n * published output is `undefined`\n * - `{ reason, value? }` → skip and optionally publish `value` for dependents\n * (`value` may be intentionally `undefined` if passed explicitly)\n *\n * Policy skips unlock required dependents. Because a bare-string skip (or a\n * `{ reason }` without `value`) publishes `undefined`, any step that declares\n * `skip` is typed so dependents see `TOut | undefined`. Prefer returning\n * `{ reason, value }` on every skip path when dependents need a real output.\n */\nexport type StepSkipDecision<TOut = unknown> =\n | false\n | null\n | undefined\n | string\n | { reason: string; value?: TOut };\n\n/** One observable status in a step's planned → running → terminal lifecycle. */\nexport type PipelineStepStatus =\n | { pipelineId: string; status: \"planned\"; step: PipelinePlanStep }\n | {\n attemptId: string;\n pipelineId: string;\n progress?: PipelineStepProgress;\n status: \"running\";\n step: PipelinePlanStep;\n }\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCancelledReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepFailedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepSkippedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCompleteReport);\n\nexport type PipelineStepLifecycleStatus = PipelineStepStatus[\"status\"];\n\nexport type PipelineStepPlannedEvent = Extract<PipelineStepStatus, { status: \"planned\" }>;\ntype PipelineStepRunningStatus = Extract<PipelineStepStatus, { status: \"running\" }>;\nexport type PipelineStepStartEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress?: undefined;\n};\nexport type PipelineStepProgressEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress: PipelineStepProgress;\n};\nexport type PipelineStepCancelledEvent = Extract<PipelineStepStatus, { status: \"cancelled\" }>;\nexport type PipelineStepFailedEvent = Extract<PipelineStepStatus, { status: \"failed\" }>;\nexport type PipelineStepSkippedEvent = Extract<PipelineStepStatus, { status: \"skipped\" }>;\nexport type PipelineStepCompleteEvent = Extract<PipelineStepStatus, { status: \"completed\" }>;\n\nexport interface PipelineHooks<TResult = unknown> {\n onFinalizeComplete?(event: { durationMs: number; pipelineId: string; value: TResult }): void;\n onFinalizeError?(event: { durationMs: number; error: PipelineError; pipelineId: string }): void;\n onFinalizeStart?(event: { pipelineId: string }): void;\n onPipelineComplete?(event: PipelineRun<TResult>): void;\n onPipelineStart?(event: PipelinePlan): void;\n onStepCancel?(event: PipelineStepCancelledEvent): void;\n onStepFail?(event: PipelineStepFailedEvent): void;\n onStepPlan?(event: PipelineStepPlannedEvent): void;\n onStepProgress?(event: PipelineStepProgressEvent): void;\n onStepSkip?(event: PipelineStepSkippedEvent): void;\n onStepStart?(event: PipelineStepStartEvent): void;\n onStepStatus?(event: PipelineStepStatus): void;\n onStepComplete?(event: PipelineStepCompleteEvent): void;\n}\n\n/** Machine-readable explanation of why a planned step was included or omitted. */\nexport type PipelineStepSelectionReason =\n | { kind: \"all\" }\n | { kind: \"exact\" }\n | { kind: \"target\"; targetId: string }\n | { dependentId: string; kind: \"required-dependency\"; targetId: string }\n | { dependentId: string; kind: \"failure-gate\"; targetId: string }\n | { dependentId: string; kind: \"optional-only\"; targetId: string }\n | { kind: \"outside-target-closure\" }\n | { kind: \"not-selected\" };\n\nexport interface PipelinePlanStep {\n dependencies: string[];\n description?: string;\n /** How this step behaves when the pipeline is run with `dryRun: true`. */\n dryRun: \"custom\" | \"run\" | \"skip\";\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n /** Static child structure when this opaque step executes another pipeline. */\n nestedPipeline?: {\n /** One child execution or one child execution per runtime item. */\n mode: \"single\" | \"for-each\";\n pipelineId: string;\n /** All declared child step ids; runtime selection may execute only a subset. */\n stepIds: readonly string[];\n };\n /** Static remote adapter metadata when this opaque step calls an external engine. */\n remote?: {\n engine: string;\n target?: string;\n };\n optionalDependencies: string[];\n /** True when the step's runtime policy may elect not to run it. */\n runtimeSkipPossible: boolean;\n selected: boolean;\n /** Stable reasons for selection or omission; shared prerequisites can have several. */\n selectionReasons: readonly PipelineStepSelectionReason[];\n skipAfterFailureOf: string[];\n skipReason?: PipelineStepSkipReason;\n}\n\nexport interface PipelinePlan {\n dryRun: boolean;\n errors: PipelineError[];\n ok: boolean;\n pipelineId: string;\n steps: PipelinePlanStep[];\n}\n\n/** Layout directions for generated Mermaid flowcharts. */\nexport const PIPELINE_MERMAID_DIRECTIONS = [\"BT\", \"LR\", \"RL\", \"TB\", \"TD\"] as const;\nexport type PipelineMermaidDirection = (typeof PIPELINE_MERMAID_DIRECTIONS)[number];\n\nexport interface PipelineMermaidOptions {\n /** Mermaid flowchart direction. Defaults to top-down (`TD`). */\n direction?: PipelineMermaidDirection;\n /** Append each operational description to its node label. Defaults to false. */\n includeDescriptions?: boolean;\n}\n\nexport interface Pipeline<\n TOptions extends object,\n TResult,\n TStepId extends string = string,\n TTargetId extends string = TStepId,\n> {\n readonly id: string;\n /** Stable definition-order step ids for discovery surfaces such as CLI help. */\n readonly stepIds: readonly TStepId[];\n /** Stable declared goal ids that support dependency-aware target execution. */\n readonly targetIds: readonly TTargetId[];\n plan(controls?: PipelineRunControls<TStepId, TTargetId>): PipelinePlan;\n run(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<PipelineRun<TResult>>;\n runOrThrow(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<TResult>;\n /** Generate a static Mermaid flowchart without running or planning the pipeline. */\n toMermaid(options?: PipelineMermaidOptions): string;\n}\n"]}
1
+ {"version":3,"file":"pipeline-types.js","sourceRoot":"","sources":["../../src/core/pipeline-types.ts"],"names":[],"mappings":"AA6cA,0DAA0D;AAC1D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAU,CAAC","sourcesContent":["import type { RUN_MODEL_VERSION } from \"./pipeline-ids.js\";\nimport type {\n PipelineTraceAttributes,\n PipelineTraceContext,\n PipelineTracingOptions,\n} from \"../tracing/tracing.js\";\n\nexport interface PipelineLogger {\n error(message?: unknown, ...optionalParams: unknown[]): void;\n log(message?: unknown, ...optionalParams: unknown[]): void;\n warn(message?: unknown, ...optionalParams: unknown[]): void;\n}\n\nexport interface PipelineContext {\n cwd: string;\n /** Hook sets run in order and failures are isolated per set. */\n hooks?: PipelineHooks | readonly PipelineHooks[];\n log: PipelineLogger;\n /** Timestamp source for persisted run records. Defaults to `Date.now`. */\n now?: () => number;\n /** Optional caller-owned parent run identity for nested or resumed orchestration. */\n parentRunId?: string;\n /** Optional caller-owned run identity. A unique ID is generated when omitted. */\n runId?: string;\n signal?: AbortSignal;\n sleep?: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n /** Optional structured lifecycle tracing configuration for this run. */\n tracing?: PipelineTracingOptions;\n}\n\nexport interface PipelineRunControls<\n TStepId extends string = string,\n TTargetId extends string = string,\n> {\n continueOnError?: boolean;\n dryRun?: boolean;\n /**\n * Run only these steps; omitted or undefined runs every step. An empty array\n * is invalid and fails planning/execution instead of silently running nothing.\n */\n stepIds?: readonly TStepId[];\n /**\n * Run these declared pipeline targets and the upstream work required to\n * reach them. Required inputs and failure gates are included recursively;\n * optional-only inputs are not. Cannot be combined with exact `stepIds`\n * filtering.\n */\n targets?: readonly TTargetId[];\n}\n\n/** Domain input plus built-in controls. Child `mapOptions` still returns this mix. */\nexport type PipelineRunOptions<\n TOptions extends object = object,\n TStepId extends string = string,\n TTargetId extends string = string,\n> = TOptions & PipelineRunControls<TStepId, TTargetId>;\nexport interface PipelineRuntime extends PipelineContext {\n now: () => number;\n sleep: (durationMs: number, signal?: AbortSignal) => Promise<void>;\n}\n\nexport interface PipelineExecutionContext<TOptions extends object> extends PipelineRuntime {\n dryRun: boolean;\n options: TOptions;\n /** Stable identity for this execution, whether or not tracing is configured. */\n runId: string;\n /** Parent execution identity when this run was started by another run. */\n parentRunId?: string;\n /** Stable identities for this traced run; absent unless `context.tracing` is configured. */\n trace?: PipelineTraceContext;\n}\n\n/**\n * Optional indented detail row under a parent step's progress.\n * Domain-agnostic: mapped children, nested work units, per-file status, etc.\n */\nexport type PipelineStepProgressDetailStatus =\n | \"cancelled\"\n | \"completed\"\n | \"failed\"\n | \"pending\"\n | \"running\"\n | \"skipped\";\n\nexport interface PipelineStepProgressDetail {\n /** Stable identity for the row (item key, path, job id, …). */\n id: string;\n /** Optional display name; identity remains `id`. */\n name?: string;\n /** Indentation relative to direct child rows (0). Rows are in tree preorder. */\n depth?: number;\n /** Inner work counts, independent of the parent's lifecycle count. */\n completed?: number;\n total?: number;\n /** Short status text shown after the id. */\n label?: string;\n /** Controls the detail row symbol. Defaults to `running`. */\n status?: PipelineStepProgressDetailStatus;\n}\n\nexport interface PipelineStepProgress {\n /** Work completed so far. Values outside the reported total are allowed but renderers may clamp them. */\n completed: number;\n /** Total work when known. Omit for indeterminate progress. */\n total?: number;\n /** Optional short status shown next to the step. Prefer one-line summaries. */\n message?: string;\n /**\n * Optional multi-line detail rows for capable reporters (interactive TTY).\n * Keep the parent `message` as a summary; put per-item status here so high\n * concurrency does not collapse onto one truncated line.\n */\n details?: readonly PipelineStepProgressDetail[];\n}\n\nexport interface PipelineStepContext<\n TOptions extends object,\n> extends PipelineExecutionContext<TOptions> {\n /** Stable identity for this execution of the current step. */\n attemptId: string;\n /** Emit a retry/attempt trace event for this step when tracing is enabled. */\n reportAttempt(attempt: number, attributes?: PipelineTraceAttributes): void;\n /** Publish the latest progress snapshot for this step. */\n reportProgress(progress: PipelineStepProgress): void;\n}\n\nexport interface RemoteStepAdapter<TOptions extends object, TPayload, TResult> {\n /** Presentation only. The kernel never switches on this. */\n readonly engine: string;\n /** Function name, workflow type, URL, queue. Presentation only. */\n readonly target?: string;\n invoke(payload: TPayload, context: PipelineStepContext<TOptions>): Promise<TResult>;\n}\n\nexport type PipelineErrorPhase = \"definition\" | \"execution\" | \"finalization\" | \"planning\";\n\nexport type PipelineErrorKind =\n | \"cancellation\"\n | \"child\"\n | \"definition\"\n | \"finalization\"\n | \"selection\"\n | \"step\"\n | \"validation\";\n\n/** Stable package-owned codes for pipeline errors. */\nexport type PipelineErrorCode =\n | \"TUBELESS_CHILD_FAILED\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_CYCLE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE\"\n | \"TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_PIPELINE_ID_BLANK\"\n | \"TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT\"\n | \"TUBELESS_DEFINITION_STEP_ID_BLANK\"\n | \"TUBELESS_DEFINITION_STEP_ID_RESERVED\"\n | \"TUBELESS_DEFINITION_STEP_IDS_DUPLICATE\"\n | \"TUBELESS_DEFINITION_STEP_NAME_BLANK\"\n | \"TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH\"\n | \"TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS\"\n | \"TUBELESS_DEFINITION_TARGETS_DUPLICATE\"\n | \"TUBELESS_FINALIZATION_CANCELLED\"\n | \"TUBELESS_FINALIZATION_FAILED\"\n | \"TUBELESS_FINAL_RESULT_VALIDATION_FAILED\"\n | \"TUBELESS_OPTIONS_VALIDATION_FAILED\"\n | \"TUBELESS_PLANNING_SELECTION_CONFLICT\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_STEP_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_STEP_UNKNOWN\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_DUPLICATE\"\n | \"TUBELESS_PLANNING_TARGET_SELECTION_EMPTY\"\n | \"TUBELESS_PLANNING_TARGET_UNDECLARED\"\n | \"TUBELESS_PLANNING_TARGET_UNKNOWN\"\n | \"TUBELESS_RUN_CANCELLED\"\n | \"TUBELESS_STEP_OUTPUT_VALIDATION_FAILED\"\n | \"TUBELESS_STEP_FAILED\";\n\n/** One dependency-free Standard Schema issue normalized for reports and traces. */\nexport interface PipelineValidationIssue {\n message: string;\n path?: readonly (number | string)[];\n}\n\n/** Bounded diagnostics for a failed or cancelled runtime fan-out. */\nexport interface PipelineFanOutFailure {\n /** Original input position, including when the key is truncated. */\n index: number;\n /** At most 1024 UTF-16 code units; do not rerun by key when keyTruncated is true. */\n key: string;\n keyTruncated: boolean;\n cancelled: boolean;\n error: PipelineErrorCause;\n}\n\nexport interface PipelineFanOutDiagnostics {\n /** Failed started items, in input order; at most 32 entries. */\n failures: readonly PipelineFanOutFailure[];\n /** Total failed started items, including omitted diagnostics. */\n failureCount: number;\n omittedFailureCount: number;\n /** Scheduler failure is separate and never assigned an item key. */\n schedulerError?: PipelineErrorCause;\n}\n\nexport interface PipelineError {\n /** Present for aggregated fan-out failures; absent for setup errors. */\n fanOut?: PipelineFanOutDiagnostics;\n /** Bounded, JSON-safe snapshot of the thrown error's cause chain. */\n cause?: PipelineErrorCause;\n code: PipelineErrorCode;\n kind: PipelineErrorKind;\n message: string;\n phase: PipelineErrorPhase;\n /** Present when a Standard Schema rejected a boundary value. */\n issues?: readonly PipelineValidationIssue[];\n stepId?: string;\n /** Machine-readable code copied from the original thrown error, when present. */\n sourceCode?: string;\n stack?: string;\n}\n\n/** Dependency-free subset of the Standard Schema V1 protocol. */\nexport interface StandardSchemaV1<TInput = unknown, TOutput = TInput> {\n readonly \"~standard\": StandardSchemaV1Props<TInput, TOutput>;\n}\n\nexport interface StandardSchemaV1Props<TInput = unknown, TOutput = TInput> {\n readonly types?: { readonly input: TInput; readonly output: TOutput };\n readonly validate: (\n value: unknown,\n options?: { readonly libraryOptions?: Record<string, unknown> }\n ) => StandardSchemaV1Result<TOutput> | Promise<StandardSchemaV1Result<TOutput>>;\n readonly vendor: string;\n readonly version: 1;\n}\n\nexport type StandardSchemaV1Result<TOutput> =\n | { readonly issues?: undefined; readonly value: TOutput }\n | { readonly issues: readonly StandardSchemaV1Issue[] };\n\nexport interface StandardSchemaV1Issue {\n readonly message: string;\n readonly path?: readonly (PropertyKey | { readonly key: PropertyKey })[];\n}\n\n/** Infer the accepted input type from a Standard Schema without importing its package. */\nexport type InferSchemaInput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"input\"];\n\n/** Infer the validated output type from a Standard Schema without importing its package. */\nexport type InferSchemaOutput<TSchema extends StandardSchemaV1> = NonNullable<\n TSchema[\"~standard\"][\"types\"]\n>[\"output\"];\n\nexport interface PipelineErrorCause {\n cause?: PipelineErrorCause;\n message: string;\n name?: string;\n /** Machine-readable code copied from this cause, when present. */\n sourceCode?: string;\n}\n\ninterface PipelineStepReportBase {\n /** Present when the step started an execution attempt. */\n attemptId?: string;\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n description?: string;\n /** Wall-clock time when this terminal report was recorded. */\n finishedAtMs: number;\n /** Wall-clock time when execution began; absent when the step never started. */\n startedAtMs?: number;\n}\n\nexport interface PipelineStepCompleteReport extends PipelineStepReportBase {\n attemptId: string;\n startedAtMs: number;\n status: \"completed\";\n}\n\nexport interface PipelineStepSkippedReport extends PipelineStepReportBase {\n /** Dependency that blocked this step, when applicable. */\n dependencyId?: string;\n /** Human-readable skip detail, especially for policy and fail-fast skips. */\n message?: string;\n reason: PipelineStepSkipReason;\n status: \"skipped\";\n}\n\nexport interface PipelineStepCancelledReport extends PipelineStepReportBase {\n error: PipelineError;\n status: \"cancelled\";\n}\n\nexport interface PipelineStepFailedReport extends PipelineStepReportBase {\n attemptId: string;\n error: PipelineError;\n startedAtMs: number;\n status: \"failed\";\n}\n\n/** Terminal state recorded for one step after a run. */\nexport type PipelineStepReport =\n | PipelineStepCancelledReport\n | PipelineStepFailedReport\n | PipelineStepSkippedReport\n | PipelineStepCompleteReport;\n\nexport type PipelineStepReportStatus = PipelineStepReport[\"status\"];\n\n/** Terminal disposition of a completed run record. */\nexport type PipelineRunStatus = \"cancelled\" | \"completed\" | \"failed\";\n\n/** Versioned public record returned for one pipeline execution. */\nexport interface PipelineRun<TResult = unknown> {\n pipelineId: string;\n dryRun: boolean;\n errors: PipelineError[];\n finalized: boolean;\n finishedAtMs: number;\n parentRunId?: string;\n runId: string;\n startedAtMs: number;\n status: PipelineRunStatus;\n steps: PipelineStepReport[];\n value?: TResult;\n version: typeof RUN_MODEL_VERSION;\n}\n\n/**\n * Why a step did not run. Built-in structural reasons keep dependency semantics;\n * `\"policy\"` is an intentional skip from `step.skip()` (still unlocks dependents).\n */\nexport type PipelineStepSkipReason =\n | \"dry-run\"\n | \"failed-dependency\"\n | \"fail-fast\"\n | \"filtered\"\n | \"policy\"\n | \"unmet-dependency\";\n\n/**\n * Decision from an optional `skip` predicate.\n * - falsy / `false` → run the step\n * - non-empty string → skip with that message (`reason: \"policy\"`); the step's\n * published output is `undefined`\n * - `{ reason, value? }` → skip and optionally publish `value` for dependents\n * (`value` may be intentionally `undefined` if passed explicitly)\n *\n * Policy skips unlock required dependents. Because a bare-string skip (or a\n * `{ reason }` without `value`) publishes `undefined`, any step that declares\n * `skip` is typed so dependents see `TOut | undefined`. Prefer returning\n * `{ reason, value }` on every skip path when dependents need a real output.\n */\nexport type StepSkipDecision<TOut = unknown> =\n | false\n | null\n | undefined\n | string\n | { reason: string; value?: TOut };\n\n/** One observable status in a step's planned → running → terminal lifecycle. */\nexport type PipelineStepStatus =\n | { pipelineId: string; status: \"planned\"; step: PipelinePlanStep }\n | {\n attemptId: string;\n pipelineId: string;\n progress?: PipelineStepProgress;\n status: \"running\";\n step: PipelinePlanStep;\n }\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCancelledReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepFailedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepSkippedReport)\n | ({ pipelineId: string; step: PipelinePlanStep } & PipelineStepCompleteReport);\n\nexport type PipelineStepLifecycleStatus = PipelineStepStatus[\"status\"];\n\nexport type PipelineStepPlannedEvent = Extract<PipelineStepStatus, { status: \"planned\" }>;\ntype PipelineStepRunningStatus = Extract<PipelineStepStatus, { status: \"running\" }>;\nexport type PipelineStepStartEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress?: undefined;\n};\nexport type PipelineStepProgressEvent = Omit<PipelineStepRunningStatus, \"progress\"> & {\n progress: PipelineStepProgress;\n};\nexport type PipelineStepCancelledEvent = Extract<PipelineStepStatus, { status: \"cancelled\" }>;\nexport type PipelineStepFailedEvent = Extract<PipelineStepStatus, { status: \"failed\" }>;\nexport type PipelineStepSkippedEvent = Extract<PipelineStepStatus, { status: \"skipped\" }>;\nexport type PipelineStepCompleteEvent = Extract<PipelineStepStatus, { status: \"completed\" }>;\n\nexport interface PipelineHooks<TResult = unknown> {\n onFinalizeComplete?(event: { durationMs: number; pipelineId: string; value: TResult }): void;\n onFinalizeError?(event: { durationMs: number; error: PipelineError; pipelineId: string }): void;\n onFinalizeStart?(event: { pipelineId: string }): void;\n onPipelineComplete?(event: PipelineRun<TResult>): void;\n onPipelineStart?(event: PipelinePlan): void;\n onStepCancel?(event: PipelineStepCancelledEvent): void;\n onStepFail?(event: PipelineStepFailedEvent): void;\n onStepPlan?(event: PipelineStepPlannedEvent): void;\n onStepProgress?(event: PipelineStepProgressEvent): void;\n onStepSkip?(event: PipelineStepSkippedEvent): void;\n onStepStart?(event: PipelineStepStartEvent): void;\n onStepStatus?(event: PipelineStepStatus): void;\n onStepComplete?(event: PipelineStepCompleteEvent): void;\n}\n\n/** Machine-readable explanation of why a planned step was included or omitted. */\nexport type PipelineStepSelectionReason =\n | { kind: \"all\" }\n | { kind: \"exact\" }\n | { kind: \"target\"; targetId: string }\n | { dependentId: string; kind: \"required-dependency\"; targetId: string }\n | { dependentId: string; kind: \"failure-gate\"; targetId: string }\n | { dependentId: string; kind: \"optional-only\"; targetId: string }\n | { kind: \"outside-target-closure\" }\n | { kind: \"not-selected\" };\n\nexport interface PipelinePlanStep {\n dependencies: string[];\n description?: string;\n /** How this step behaves when the pipeline is run with `dryRun: true`. */\n dryRun: \"custom\" | \"run\" | \"skip\";\n id: string;\n /** Optional human-facing display name. Stable machine identity remains `id`. */\n name?: string;\n /** Static child structure when this opaque step executes another pipeline. */\n nestedPipeline?: {\n /** One child execution or one child execution per runtime item. */\n mode: \"single\" | \"for-each\";\n pipelineId: string;\n /** All declared child step ids; runtime selection may execute only a subset. */\n stepIds: readonly string[];\n };\n /** Static remote adapter metadata when this opaque step calls an external engine. */\n remote?: {\n engine: string;\n target?: string;\n };\n optionalDependencies: string[];\n /** True when the step's runtime policy may elect not to run it. */\n runtimeSkipPossible: boolean;\n selected: boolean;\n /** Stable reasons for selection or omission; shared prerequisites can have several. */\n selectionReasons: readonly PipelineStepSelectionReason[];\n skipAfterFailureOf: string[];\n skipReason?: PipelineStepSkipReason;\n}\n\nexport interface PipelinePlan {\n dryRun: boolean;\n errors: PipelineError[];\n ok: boolean;\n pipelineId: string;\n steps: PipelinePlanStep[];\n}\n\n/** Layout directions for generated Mermaid flowcharts. */\nexport const PIPELINE_MERMAID_DIRECTIONS = [\"BT\", \"LR\", \"RL\", \"TB\", \"TD\"] as const;\nexport type PipelineMermaidDirection = (typeof PIPELINE_MERMAID_DIRECTIONS)[number];\n\nexport interface PipelineMermaidOptions {\n /** Mermaid flowchart direction. Defaults to top-down (`TD`). */\n direction?: PipelineMermaidDirection;\n /** Append each operational description to its node label. Defaults to false. */\n includeDescriptions?: boolean;\n}\n\nexport interface Pipeline<\n TOptions extends object,\n TResult,\n TStepId extends string = string,\n TTargetId extends string = TStepId,\n> {\n readonly id: string;\n /** Stable definition-order step ids for discovery surfaces such as CLI help. */\n readonly stepIds: readonly TStepId[];\n /** Stable declared goal ids that support dependency-aware target execution. */\n readonly targetIds: readonly TTargetId[];\n plan(controls?: PipelineRunControls<TStepId, TTargetId>): PipelinePlan;\n run(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<PipelineRun<TResult>>;\n runOrThrow(\n options: TOptions,\n controls?: PipelineRunControls<TStepId, TTargetId>,\n context?: Partial<PipelineContext>\n ): Promise<TResult>;\n /** Generate a static Mermaid flowchart without running or planning the pipeline. */\n toMermaid(options?: PipelineMermaidOptions): string;\n}\n"]}
@@ -2,13 +2,9 @@ import { formatPipelineError } from "../core/pipeline-diagnostics.js";
2
2
  function renderJson(value, pretty = false) {
3
3
  return JSON.stringify(value, null, pretty ? 2 : undefined);
4
4
  }
5
- /** True when the caller asked for machine-readable JSON output. */
6
- function isJsonRenderOptions(options) {
7
- return options.format === "json";
8
- }
9
5
  /** Render one structured diagnostic for a person or a machine consumer. */
10
6
  export function renderPipelineError(error, options = {}) {
11
- if (isJsonRenderOptions(options))
7
+ if (options.format === "json")
12
8
  return renderJson(error, options.pretty);
13
9
  return formatPipelineError(error);
14
10
  }
@@ -70,7 +66,7 @@ function renderHumanPlan(plan, options) {
70
66
  }
71
67
  /** Render an execution plan without recomputing selection or dependency provenance. */
72
68
  export function renderPipelinePlan(plan, options = {}) {
73
- if (isJsonRenderOptions(options))
69
+ if (options.format === "json")
74
70
  return renderJson(plan, options.pretty);
75
71
  return renderHumanPlan(plan, options);
76
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n PipelineError,\n PipelinePlan,\n PipelinePlanStep,\n PipelineStepSelectionReason,\n} from \"../core/pipeline.js\";\nimport { formatPipelineError } from \"../core/pipeline-diagnostics.js\";\n\nexport interface PipelineHumanRenderOptions {\n /** Human-readable output. This is the default format. */\n format?: \"human\";\n}\n\nexport interface PipelineJsonRenderOptions {\n /** Machine-readable output containing the original structured fields. */\n format: \"json\";\n /** Indent JSON output by two spaces. Defaults to false for stream-friendly output. */\n pretty?: boolean;\n}\n\nexport type PipelineRenderOptions = PipelineHumanRenderOptions | PipelineJsonRenderOptions;\n\nexport type PipelinePlanRenderOptions =\n | (PipelineHumanRenderOptions & {\n /** Include structured selection provenance and runtime-skip annotations. Defaults to true. */\n explain?: boolean;\n })\n | PipelineJsonRenderOptions;\n\nfunction renderJson<T>(value: T, pretty = false): string {\n return JSON.stringify(value, null, pretty ? 2 : undefined);\n}\n\n/** True when the caller asked for machine-readable JSON output. */\nfunction isJsonRenderOptions(\n options: PipelineRenderOptions | PipelinePlanRenderOptions\n): options is PipelineJsonRenderOptions {\n return options.format === \"json\";\n}\n\n/** Render one structured diagnostic for a person or a machine consumer. */\nexport function renderPipelineError(\n error: PipelineError,\n options: PipelineRenderOptions = {}\n): string {\n if (isJsonRenderOptions(options)) return renderJson(error, options.pretty);\n return formatPipelineError(error);\n}\n\nfunction describeRemote(step: PipelinePlanStep, planDryRun: boolean): string {\n if (!step.remote) return \"\";\n const target = step.remote.target ? ` (${step.remote.target})` : \"\";\n const label = `remote ${step.remote.engine}${target}`;\n const contactsEngine =\n planDryRun && step.dryRun === \"run\" && step.selected && step.skipReason === undefined;\n return contactsEngine ? ` -> ${label}; dry-run contacts engine` : ` -> ${label}`;\n}\n\nfunction describeSelectionReason(reason: PipelineStepSelectionReason): string | undefined {\n switch (reason.kind) {\n case \"all\":\n return undefined;\n case \"exact\":\n return \"exact selection\";\n case \"target\":\n return `target ${reason.targetId}`;\n case \"required-dependency\":\n return `required by ${reason.dependentId} for target ${reason.targetId}`;\n case \"failure-gate\":\n return `failure gate for ${reason.dependentId} for target ${reason.targetId}`;\n case \"optional-only\":\n return `optional-only input to ${reason.dependentId} for target ${reason.targetId}`;\n case \"outside-target-closure\":\n return \"outside target closure\";\n case \"not-selected\":\n return \"not selected\";\n }\n}\n\n/** Human-readable plan options (the JSON member excluded). */\ntype HumanPlanRenderOptions = Exclude<PipelinePlanRenderOptions, PipelineJsonRenderOptions>;\n\n/** Human-readable plan body; the JSON branch is handled by the caller. */\nfunction renderHumanPlan(plan: PipelinePlan, options: HumanPlanRenderOptions): string {\n const explain = options.explain !== false;\n const lines = [\n `Pipeline ${plan.pipelineId}: plan (ok=${plan.ok}, dryRun=${plan.dryRun}, steps=${plan.steps.length})`,\n ];\n for (const step of plan.steps) {\n const displayName = step.name ? `${step.name} [${step.id}]` : step.id;\n const annotations = explain\n ? step.selectionReasons\n .map(describeSelectionReason)\n .filter((detail): detail is string => detail !== undefined)\n : [];\n if (explain && !step.skipReason && step.runtimeSkipPossible) {\n annotations.unshift(\"policy may skip\");\n }\n const disposition = `${step.skipReason ? `skip: ${step.skipReason}` : \"run\"}${\n annotations.length > 0 ? ` (${annotations.join(\"; \")})` : \"\"\n }`;\n const nested = step.nestedPipeline\n ? ` -> ${step.nestedPipeline.mode === \"for-each\" ? \"fan-out\" : \"child\"} pipeline ${step.nestedPipeline.pipelineId} (${step.nestedPipeline.stepIds.length} declared steps)`\n : \"\";\n const remote = describeRemote(step, plan.dryRun);\n lines.push(\n ` - ${displayName}: ${disposition}${nested}${remote}${step.description ? ` - ${step.description}` : \"\"}`\n );\n }\n for (const error of plan.errors) {\n lines.push(` ! ${renderPipelineError(error)}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Render an execution plan without recomputing selection or dependency provenance. */\nexport function renderPipelinePlan(\n plan: PipelinePlan,\n options: PipelinePlanRenderOptions = {}\n): string {\n if (isJsonRenderOptions(options)) return renderJson(plan, options.pretty);\n return renderHumanPlan(plan, options);\n}\n"]}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../src/render/render.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAuBtE,SAAS,UAAU,CAAI,KAAQ,EAAE,MAAM,GAAG,KAAK;IAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;AAC7D,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,mBAAmB,CACjC,KAAoB,EACpB,OAAO,GAA0B,EAAE;IAEnC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,UAAU,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACxE,OAAO,mBAAmB,CAAC,KAAK,CAAC,CAAC;AACpC,CAAC;AAED,SAAS,cAAc,CAAC,IAAsB,EAAE,UAAmB;IACjE,IAAI,CAAC,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACpE,MAAM,KAAK,GAAG,UAAU,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;IACtD,MAAM,cAAc,GAClB,UAAU,IAAI,IAAI,CAAC,MAAM,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,CAAC;IACxF,OAAO,cAAc,CAAC,CAAC,CAAC,OAAO,KAAK,2BAA2B,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC;AACnF,CAAC;AAED,SAAS,uBAAuB,CAAC,MAAmC;IAClE,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,KAAK;YACR,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,iBAAiB,CAAC;QAC3B,KAAK,QAAQ;YACX,OAAO,UAAU,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,KAAK,qBAAqB;YACxB,OAAO,eAAe,MAAM,CAAC,WAAW,eAAe,MAAM,CAAC,QAAQ,EAAE,CAAC;QAC3E,KAAK,cAAc;YACjB,OAAO,oBAAoB,MAAM,CAAC,WAAW,eAAe,MAAM,CAAC,QAAQ,EAAE,CAAC;QAChF,KAAK,eAAe;YAClB,OAAO,0BAA0B,MAAM,CAAC,WAAW,eAAe,MAAM,CAAC,QAAQ,EAAE,CAAC;QACtF,KAAK,wBAAwB;YAC3B,OAAO,wBAAwB,CAAC;QAClC,KAAK,cAAc;YACjB,OAAO,cAAc,CAAC;IAC1B,CAAC;AACH,CAAC;AAKD,0EAA0E;AAC1E,SAAS,eAAe,CAAC,IAAkB,EAAE,OAA+B;IAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,KAAK,KAAK,CAAC;IAC1C,MAAM,KAAK,GAAG;QACZ,YAAY,IAAI,CAAC,UAAU,cAAc,IAAI,CAAC,EAAE,YAAY,IAAI,CAAC,MAAM,WAAW,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG;KACvG,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC;QACtE,MAAM,WAAW,GAAG,OAAO;YACzB,CAAC,CAAC,IAAI,CAAC,gBAAgB;iBAClB,GAAG,CAAC,uBAAuB,CAAC;iBAC5B,MAAM,CAAC,CAAC,MAAM,EAAoB,EAAE,CAAC,MAAM,KAAK,SAAS,CAAC;YAC/D,CAAC,CAAC,EAAE,CAAC;QACP,IAAI,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,mBAAmB,EAAE,CAAC;YAC5D,WAAW,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACzC,CAAC;QACD,MAAM,WAAW,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,KAAK,GACzE,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAC5D,EAAE,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc;YAChC,CAAC,CAAC,OAAO,IAAI,CAAC,cAAc,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,aAAa,IAAI,CAAC,cAAc,CAAC,UAAU,KAAK,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,MAAM,kBAAkB;YAC1K,CAAC,CAAC,EAAE,CAAC;QACP,MAAM,MAAM,GAAG,cAAc,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QACjD,KAAK,CAAC,IAAI,CACR,OAAO,WAAW,KAAK,WAAW,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAC1G,CAAC;IACJ,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC,OAAO,mBAAmB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,uFAAuF;AACvF,MAAM,UAAU,kBAAkB,CAChC,IAAkB,EAClB,OAAO,GAA8B,EAAE;IAEvC,IAAI,OAAO,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IACvE,OAAO,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACxC,CAAC","sourcesContent":["import type {\n PipelineError,\n PipelinePlan,\n PipelinePlanStep,\n PipelineStepSelectionReason,\n} from \"../core/pipeline.js\";\nimport { formatPipelineError } from \"../core/pipeline-diagnostics.js\";\n\nexport interface PipelineHumanRenderOptions {\n /** Human-readable output. This is the default format. */\n format?: \"human\";\n}\n\nexport interface PipelineJsonRenderOptions {\n /** Machine-readable output containing the original structured fields. */\n format: \"json\";\n /** Indent JSON output by two spaces. Defaults to false for stream-friendly output. */\n pretty?: boolean;\n}\n\nexport type PipelineRenderOptions = PipelineHumanRenderOptions | PipelineJsonRenderOptions;\n\nexport type PipelinePlanRenderOptions =\n | (PipelineHumanRenderOptions & {\n /** Include structured selection provenance and runtime-skip annotations. Defaults to true. */\n explain?: boolean;\n })\n | PipelineJsonRenderOptions;\n\nfunction renderJson<T>(value: T, pretty = false): string {\n return JSON.stringify(value, null, pretty ? 2 : undefined);\n}\n\n/** Render one structured diagnostic for a person or a machine consumer. */\nexport function renderPipelineError(\n error: PipelineError,\n options: PipelineRenderOptions = {}\n): string {\n if (options.format === \"json\") return renderJson(error, options.pretty);\n return formatPipelineError(error);\n}\n\nfunction describeRemote(step: PipelinePlanStep, planDryRun: boolean): string {\n if (!step.remote) return \"\";\n const target = step.remote.target ? ` (${step.remote.target})` : \"\";\n const label = `remote ${step.remote.engine}${target}`;\n const contactsEngine =\n planDryRun && step.dryRun === \"run\" && step.selected && step.skipReason === undefined;\n return contactsEngine ? ` -> ${label}; dry-run contacts engine` : ` -> ${label}`;\n}\n\nfunction describeSelectionReason(reason: PipelineStepSelectionReason): string | undefined {\n switch (reason.kind) {\n case \"all\":\n return undefined;\n case \"exact\":\n return \"exact selection\";\n case \"target\":\n return `target ${reason.targetId}`;\n case \"required-dependency\":\n return `required by ${reason.dependentId} for target ${reason.targetId}`;\n case \"failure-gate\":\n return `failure gate for ${reason.dependentId} for target ${reason.targetId}`;\n case \"optional-only\":\n return `optional-only input to ${reason.dependentId} for target ${reason.targetId}`;\n case \"outside-target-closure\":\n return \"outside target closure\";\n case \"not-selected\":\n return \"not selected\";\n }\n}\n\n/** Human-readable plan options (the JSON member excluded). */\ntype HumanPlanRenderOptions = Exclude<PipelinePlanRenderOptions, PipelineJsonRenderOptions>;\n\n/** Human-readable plan body; the JSON branch is handled by the caller. */\nfunction renderHumanPlan(plan: PipelinePlan, options: HumanPlanRenderOptions): string {\n const explain = options.explain !== false;\n const lines = [\n `Pipeline ${plan.pipelineId}: plan (ok=${plan.ok}, dryRun=${plan.dryRun}, steps=${plan.steps.length})`,\n ];\n for (const step of plan.steps) {\n const displayName = step.name ? `${step.name} [${step.id}]` : step.id;\n const annotations = explain\n ? step.selectionReasons\n .map(describeSelectionReason)\n .filter((detail): detail is string => detail !== undefined)\n : [];\n if (explain && !step.skipReason && step.runtimeSkipPossible) {\n annotations.unshift(\"policy may skip\");\n }\n const disposition = `${step.skipReason ? `skip: ${step.skipReason}` : \"run\"}${\n annotations.length > 0 ? ` (${annotations.join(\"; \")})` : \"\"\n }`;\n const nested = step.nestedPipeline\n ? ` -> ${step.nestedPipeline.mode === \"for-each\" ? \"fan-out\" : \"child\"} pipeline ${step.nestedPipeline.pipelineId} (${step.nestedPipeline.stepIds.length} declared steps)`\n : \"\";\n const remote = describeRemote(step, plan.dryRun);\n lines.push(\n ` - ${displayName}: ${disposition}${nested}${remote}${step.description ? ` - ${step.description}` : \"\"}`\n );\n }\n for (const error of plan.errors) {\n lines.push(` ! ${renderPipelineError(error)}`);\n }\n return lines.join(\"\\n\");\n}\n\n/** Render an execution plan without recomputing selection or dependency provenance. */\nexport function renderPipelinePlan(\n plan: PipelinePlan,\n options: PipelinePlanRenderOptions = {}\n): string {\n if (options.format === \"json\") return renderJson(plan, options.pretty);\n return renderHumanPlan(plan, options);\n}\n"]}
@@ -4,6 +4,7 @@ export type PipelineReporterMode = "auto" | "interactive" | "plain";
4
4
  export type ResolvedPipelineReporterMode = Exclude<PipelineReporterMode, "auto">;
5
5
  export interface ReporterOutput {
6
6
  readonly columns?: number;
7
+ readonly rows?: number;
7
8
  /**
8
9
  * File descriptor for live frames. When set, spinner, elapsed time, and shimmer
9
10
  * paint on a worker thread so they keep moving during CPU-bound steps. Defaults
@@ -53,25 +53,32 @@ function renderProgress(progress, width, unicode) {
53
53
  const bar = `${unicode ? "█".repeat(filled) : "=".repeat(filled)}${unicode ? "░".repeat(width - filled) : "-".repeat(width - filled)}`;
54
54
  return `[${bar}] ${Math.round(ratio * 100)}% ${formatCount(completed)}/${formatCount(total)}${message}`;
55
55
  }
56
- function renderProgressDetail(detail, theme, spinner) {
57
- const status = detail.status ?? "running";
56
+ function renderProgressDetail(detail, theme, spinner, progressBarWidth, parentStatus) {
57
+ const status = (detail.status ?? "running") === "running" && parentStatus !== "running"
58
+ ? parentStatus
59
+ : (detail.status ?? "running");
58
60
  const symbol = status === "completed"
59
61
  ? theme.styled.complete(theme.symbols.complete)
60
62
  : status === "failed"
61
63
  ? theme.styled.fail(theme.symbols.fail)
62
- : status === "skipped"
64
+ : status === "skipped" || status === "cancelled"
63
65
  ? theme.styled.skip(theme.symbols.skip)
64
66
  : status === "pending"
65
67
  ? theme.styled.description(theme.symbols.pending)
66
68
  : theme.styled.start(spinner);
67
- const id = safeTerminalText(detail.id);
68
- const labelText = detail.label ? safeTerminalText(detail.label) : "";
69
- const running = status !== "completed" && status !== "failed" && status !== "skipped" && status !== "pending";
69
+ const id = safeTerminalText(detail.name ?? detail.id);
70
+ const label = detail.label ? safeTerminalText(detail.label) : "";
71
+ const labelText = status === "cancelled" ? `cancelled${label ? `: ${label}` : ""}` : label;
72
+ const running = status === "running";
70
73
  const body = labelText ? `${id} ${labelText}` : id;
71
74
  const paintedBody = running && theme.colorEnabled
72
75
  ? shimmerToken(body)
73
76
  : `${id}${labelText ? ` ${theme.styled.description(labelText)}` : ""}`;
74
- return ` ${symbol} ${paintedBody}`;
77
+ const depth = Math.max(0, Math.min(32, Math.floor(safeNumber(detail.depth ?? 0))));
78
+ const progress = detail.completed === undefined
79
+ ? ""
80
+ : ` ${renderProgress({ completed: detail.completed, total: detail.total }, progressBarWidth, theme.capabilities.unicode)}`;
81
+ return `${" ".repeat(depth + 2)}${symbol} ${paintedBody}${progress}`;
75
82
  }
76
83
  function renderStep(state, theme, spinner, progressBarWidth) {
77
84
  const { step } = state;
@@ -87,12 +94,6 @@ function renderStep(state, theme, spinner, progressBarWidth) {
87
94
  const lines = [
88
95
  ` ${theme.styled.start(spinner)} ${shimmerToken(displayName)}${progress}${elapsed}`,
89
96
  ];
90
- const details = state.progress?.details;
91
- if (details && details.length > 0) {
92
- for (const detail of details) {
93
- lines.push(renderProgressDetail(detail, theme, spinner));
94
- }
95
- }
96
97
  return lines;
97
98
  }
98
99
  case "completed":
@@ -162,6 +163,9 @@ function createInteractiveReporter(options, output) {
162
163
  }
163
164
  for (const state of steps.values()) {
164
165
  lines.push(...renderStep(state, theme, SPINNER_TOKEN, progressBarWidth));
166
+ for (const detail of state.details ?? []) {
167
+ lines.push(renderProgressDetail(detail, theme, SPINNER_TOKEN, progressBarWidth, state.status));
168
+ }
165
169
  }
166
170
  if (finalize.status === "running") {
167
171
  lines.push(` ${theme.styled.start(SPINNER_TOKEN)} ${shimmerToken("finalize")}`);
@@ -196,7 +200,37 @@ function createInteractiveReporter(options, output) {
196
200
  const lines = frameLines();
197
201
  if (lines.length === 0)
198
202
  return;
199
- ensureTicker().setLines(lines);
203
+ const rows = output.rows;
204
+ if (rows === undefined || rows < 2 || lines.length < rows) {
205
+ ensureTicker().setLines(lines);
206
+ return;
207
+ }
208
+ if (result) {
209
+ // A finished tree can scroll normally; never try to erase beyond the viewport.
210
+ ensureTicker().setLines([]);
211
+ ensureTicker().writeLog(`${lines.join("\n")}\n`);
212
+ return;
213
+ }
214
+ const ellipsis = theme.capabilities.unicode ? "…" : "...";
215
+ if (rows < 5) {
216
+ ensureTicker().setLines([lines[0], ` ${ellipsis} ${lines.length - 1} rows omitted`].slice(0, rows - 1));
217
+ return;
218
+ }
219
+ const available = rows - 4;
220
+ let focus = 0;
221
+ for (const [index, line] of lines.entries()) {
222
+ if (line.includes(SPINNER_TOKEN))
223
+ focus = index;
224
+ }
225
+ const start = Math.max(1, Math.min(lines.length - available, focus - Math.floor(available / 2)));
226
+ const end = start + available;
227
+ const visible = [lines[0]];
228
+ if (start > 1)
229
+ visible.push(` ${ellipsis} ${start - 1} rows above`);
230
+ visible.push(...lines.slice(start, end));
231
+ if (end < lines.length)
232
+ visible.push(` ${ellipsis} ${lines.length - end} rows below`);
233
+ ensureTicker().setLines(visible);
200
234
  };
201
235
  const flushProgress = () => {
202
236
  if (trailingFlush !== undefined) {
@@ -271,6 +305,7 @@ function createInteractiveReporter(options, output) {
271
305
  steps.set(event.step.id, {
272
306
  ...event,
273
307
  progress,
308
+ details: progress?.details,
274
309
  startedAtMs: previous?.startedAtMs ?? Date.now(),
275
310
  });
276
311
  const now = Date.now();
@@ -292,7 +327,7 @@ function createInteractiveReporter(options, output) {
292
327
  }
293
328
  if (event.status !== "planned")
294
329
  flushProgress();
295
- steps.set(event.step.id, event);
330
+ steps.set(event.step.id, { ...event, details: previous?.details });
296
331
  redraw();
297
332
  },
298
333
  onFinalizeStart: () => {
@@ -1 +1 @@
1
- {"version":3,"file":"interactive-reporter.js","sourceRoot":"","sources":["../../src/reporter/interactive-reporter.ts"],"names":[],"mappings":"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","sourcesContent":["import { format } from \"node:util\";\nimport type {\n PipelineError,\n PipelineHooks,\n PipelineLogger,\n PipelinePlan,\n PipelineRun,\n PipelineStepProgress,\n PipelineStepStatus,\n} from \"../core/pipeline.js\";\nimport { hasVisibleStepProgress } from \"../core/progress.js\";\nimport {\n createLiveTicker,\n elapsedToken,\n shimmerToken,\n SPINNER_TOKEN,\n type LiveTicker,\n} from \"./live-ticker.js\";\nimport {\n createReporterTheme,\n createRunReporter,\n formatDurationMs,\n type ReporterTheme,\n type RunReporterConfig,\n} from \"./reporter.js\";\n\nexport type PipelineReporterMode = \"auto\" | \"interactive\" | \"plain\";\nexport type ResolvedPipelineReporterMode = Exclude<PipelineReporterMode, \"auto\">;\n\nexport interface ReporterOutput {\n readonly columns?: number;\n /**\n * File descriptor for live frames. When set, spinner, elapsed time, and shimmer\n * paint on a worker thread so they keep moving during CPU-bound steps. Defaults\n * to stdout's fd when `output` is `process.stdout`.\n */\n readonly fd?: number;\n readonly isTTY?: boolean;\n write(chunk: string): unknown;\n}\n\nexport interface PipelineReporterConfig extends RunReporterConfig {\n /** Auto selects the interactive renderer only for a capable, non-CI TTY. */\n mode?: PipelineReporterMode;\n /** Terminal stream used by the interactive renderer. Defaults to stdout. */\n output?: ReporterOutput;\n /** Spinner redraw cadence. Defaults to 80ms. */\n refreshIntervalMs?: number;\n /** Filled/empty character width for determinate progress. Defaults to 20. */\n progressBarWidth?: number;\n}\n\nexport interface PipelineReporterOptions extends PipelineReporterConfig {\n log: PipelineLogger;\n}\n\nexport interface PipelineReporterController<TResult = unknown> {\n readonly hooks: PipelineHooks<TResult>;\n readonly log: PipelineLogger;\n readonly mode: ResolvedPipelineReporterMode;\n /** Stop redraws and restore terminal state. Safe to call more than once. */\n dispose(): void;\n}\n\ntype StepState = PipelineStepStatus & {\n /** Wall-clock ms from Date.now() when the step entered the running state. */\n startedAtMs?: number;\n};\n\ntype FinalizeState =\n | { status: \"idle\" }\n | { status: \"running\" }\n | { durationMs: number; status: \"completed\" }\n | { durationMs: number; error: PipelineError; status: \"failed\" };\n\nconst TERMINAL_OSC = /(?:\\u001B\\]|\\u009D)[\\s\\S]*?(?:\\u0007|\\u001B\\\\|\\u009C)/g;\nconst TERMINAL_STRING = /(?:\\u001B[P_X^]|\\u0090|\\u0098|\\u009E|\\u009F)[\\s\\S]*?(?:\\u001B\\\\|\\u009C)/g;\nconst TERMINAL_CSI = /(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]/g;\nconst TERMINAL_ESCAPE = /\\u001B[@-_]/g;\nconst TERMINAL_CONTROL = /[\\u0000-\\u0009\\u000B-\\u001F\\u007F-\\u009F]/g;\n\nfunction autoInteractiveAllowed(output: ReporterOutput, config: PipelineReporterConfig): boolean {\n const isTTY = config.terminal?.isTTY ?? output.isTTY === true;\n const ci = process.env.CI;\n const isCI = ci !== undefined && ci !== \"\" && ci !== \"0\" && ci !== \"false\";\n return isTTY && process.env.TERM !== \"dumb\" && !isCI;\n}\n\nfunction resolveMode(\n output: ReporterOutput,\n config: PipelineReporterConfig\n): ResolvedPipelineReporterMode {\n if (config.mode === \"interactive\") return \"interactive\";\n if (config.mode === \"plain\") return \"plain\";\n return autoInteractiveAllowed(output, config) ? \"interactive\" : \"plain\";\n}\n\nfunction safeNumber(value: number): number {\n return Number.isFinite(value) ? value : 0;\n}\n\nfunction stripTerminalControls(value: string): string {\n return value\n .replace(TERMINAL_OSC, \"\")\n .replace(TERMINAL_STRING, \"\")\n .replace(TERMINAL_CSI, \"\")\n .replace(TERMINAL_ESCAPE, \"\")\n .replace(TERMINAL_CONTROL, \"\");\n}\n\nfunction safeTerminalText(value: string): string {\n return stripTerminalControls(value.replace(/\\s+/g, \" \")).trim();\n}\n\nfunction safeTerminalLog(value: string): string {\n return stripTerminalControls(value.replace(/\\r\\n?/g, \"\\n\").replaceAll(\"\\t\", \" \"));\n}\n\nfunction formatCount(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(1);\n}\n\nfunction renderProgress(progress: PipelineStepProgress, width: number, unicode: boolean): string {\n // Parent line keeps the summary message only; per-item work uses `details`.\n const message = progress.message ? ` ${safeTerminalText(progress.message)}` : \"\";\n if (progress.total === undefined || safeNumber(progress.total) <= 0) {\n return `${formatCount(safeNumber(progress.completed))}${message}`;\n }\n const completed = safeNumber(progress.completed);\n const total = safeNumber(progress.total);\n const ratio = Math.max(0, Math.min(1, completed / total));\n const filled = Math.round(ratio * width);\n const bar = `${unicode ? \"█\".repeat(filled) : \"=\".repeat(filled)}${\n unicode ? \"░\".repeat(width - filled) : \"-\".repeat(width - filled)\n }`;\n return `[${bar}] ${Math.round(ratio * 100)}% ${formatCount(completed)}/${formatCount(total)}${message}`;\n}\n\nfunction renderProgressDetail(\n detail: NonNullable<PipelineStepProgress[\"details\"]>[number],\n theme: ReporterTheme,\n spinner: string\n): string {\n const status = detail.status ?? \"running\";\n const symbol =\n status === \"completed\"\n ? theme.styled.complete(theme.symbols.complete)\n : status === \"failed\"\n ? theme.styled.fail(theme.symbols.fail)\n : status === \"skipped\"\n ? theme.styled.skip(theme.symbols.skip)\n : status === \"pending\"\n ? theme.styled.description(theme.symbols.pending)\n : theme.styled.start(spinner);\n const id = safeTerminalText(detail.id);\n const labelText = detail.label ? safeTerminalText(detail.label) : \"\";\n const running =\n status !== \"completed\" && status !== \"failed\" && status !== \"skipped\" && status !== \"pending\";\n const body = labelText ? `${id} ${labelText}` : id;\n const paintedBody =\n running && theme.colorEnabled\n ? shimmerToken(body)\n : `${id}${labelText ? ` ${theme.styled.description(labelText)}` : \"\"}`;\n return ` ${symbol} ${paintedBody}`;\n}\n\nfunction renderStep(\n state: StepState,\n theme: ReporterTheme,\n spinner: string,\n progressBarWidth: number\n): string[] {\n const { step } = state;\n const displayName = safeTerminalText(step.name ?? step.id);\n switch (state.status) {\n case \"running\": {\n const progress =\n state.progress && hasVisibleStepProgress(state.progress)\n ? ` ${renderProgress(state.progress, progressBarWidth, theme.capabilities.unicode)}`\n : \"\";\n const elapsed =\n state.startedAtMs !== undefined\n ? ` ${theme.styled.duration(elapsedToken(state.startedAtMs))}`\n : \"\";\n const lines = [\n ` ${theme.styled.start(spinner)} ${shimmerToken(displayName)}${progress}${elapsed}`,\n ];\n const details = state.progress?.details;\n if (details && details.length > 0) {\n for (const detail of details) {\n lines.push(renderProgressDetail(detail, theme, spinner));\n }\n }\n return lines;\n }\n case \"completed\":\n return [\n ` ${theme.styled.complete(theme.symbols.complete)} ${displayName} ${theme.styled.duration(\n `(${formatDurationMs(state.finishedAtMs - state.startedAtMs)})`\n )}`,\n ];\n case \"failed\":\n return [\n ` ${theme.styled.fail(theme.symbols.fail)} ${displayName}: ${safeTerminalText(\n state.error.message\n )}`,\n ];\n case \"cancelled\":\n return [\n ` ${theme.styled.skip(theme.symbols.skip)} ${displayName}: cancelled: ${safeTerminalText(\n state.error.message\n )}`,\n ];\n case \"skipped\":\n return [\n ` ${theme.styled.skip(theme.symbols.skip)} ${displayName} ${theme.styled.duration(\n `(${state.message ?? state.reason})`\n )}`,\n ];\n case \"planned\":\n return [\n ` ${theme.styled.description(theme.symbols.pending)} ${displayName} ${theme.styled.description(\"waiting\")}`,\n ];\n }\n}\n\nfunction reporterOutputFd(output: ReporterOutput): number | undefined {\n const stdoutFd = output === process.stdout ? process.stdout.fd : undefined;\n for (const fd of [output.fd, stdoutFd]) {\n if (fd !== undefined && Number.isInteger(fd) && fd >= 0) return fd;\n }\n return undefined;\n}\n\nfunction createInteractiveReporter<TResult>(\n options: PipelineReporterOptions,\n output: ReporterOutput\n): PipelineReporterController<TResult> {\n const terminalIsTTY = options.terminal?.isTTY ?? output.isTTY === true;\n const theme = createReporterTheme({\n ...options,\n terminal: { isTTY: terminalIsTTY, ...options.terminal },\n });\n const progressBarWidth = Math.max(4, Math.floor(options.progressBarWidth ?? 20));\n // ~12.5 fps keeps braille spinners smooth without flooding the terminal.\n const refreshIntervalMs = Math.max(16, Math.floor(options.refreshIntervalMs ?? 80));\n const steps = new Map<string, StepState>();\n let plan: PipelinePlan | undefined;\n let result: PipelineRun<TResult> | undefined;\n let finalize: FinalizeState = { status: \"idle\" };\n let lastProgressRedrawAt = Number.NEGATIVE_INFINITY;\n let progressDirty = false;\n let disposed = false;\n let ticker: LiveTicker | undefined;\n let trailingFlush: ReturnType<typeof setTimeout> | undefined;\n let exitListener: (() => void) | undefined;\n\n const frameLines = (): string[] => {\n if (!plan) return [];\n const lines: string[] = [];\n if (options.logPlan !== false) {\n const header = result\n ? options.logSummary === false\n ? `Pipeline ${result.pipelineId}`\n : `Pipeline ${result.pipelineId}: done in ${formatDurationMs(result.finishedAtMs - result.startedAtMs)} (status=${result.status}, steps=${result.steps.length}, errors=${result.errors.length})`\n : `Pipeline ${plan.pipelineId} (${plan.steps.length} steps, dryRun=${plan.dryRun})`;\n lines.push(\n result?.status === \"completed\"\n ? theme.styled.complete(header)\n : result\n ? theme.styled.fail(header)\n : theme.styled.pipeline(header)\n );\n }\n for (const state of steps.values()) {\n lines.push(...renderStep(state, theme, SPINNER_TOKEN, progressBarWidth));\n }\n if (finalize.status === \"running\") {\n lines.push(` ${theme.styled.start(SPINNER_TOKEN)} ${shimmerToken(\"finalize\")}`);\n } else if (finalize.status === \"completed\") {\n lines.push(\n ` ${theme.styled.complete(theme.symbols.complete)} finalize ${theme.styled.duration(\n `(${formatDurationMs(finalize.durationMs)})`\n )}`\n );\n } else if (finalize.status === \"failed\") {\n lines.push(\n ` ${theme.styled.fail(theme.symbols.fail)} finalize: ${safeTerminalText(\n finalize.error.message\n )}`\n );\n }\n return lines;\n };\n\n const ensureTicker = (): LiveTicker => {\n if (!ticker) {\n ticker = createLiveTicker({\n color: theme.colorEnabled,\n columns: output.columns,\n getColumns: () => output.columns,\n fd: reporterOutputFd(output),\n refreshIntervalMs,\n unicode: theme.capabilities.unicode,\n write: (chunk) => {\n output.write(chunk);\n },\n });\n }\n return ticker;\n };\n\n const redraw = (): void => {\n if (disposed) return;\n const lines = frameLines();\n if (lines.length === 0) return;\n ensureTicker().setLines(lines);\n };\n\n const flushProgress = (): void => {\n if (trailingFlush !== undefined) {\n clearTimeout(trailingFlush);\n trailingFlush = undefined;\n }\n if (!progressDirty) return;\n progressDirty = false;\n lastProgressRedrawAt = Date.now();\n redraw();\n };\n\n const dispose = (): void => {\n if (disposed) return;\n disposed = true;\n if (trailingFlush !== undefined) {\n clearTimeout(trailingFlush);\n trailingFlush = undefined;\n }\n ticker?.dispose();\n ticker = undefined;\n if (exitListener) process.off(\"exit\", exitListener);\n exitListener = undefined;\n };\n\n const writeLog = (level: \"error\" | \"log\" | \"warn\", message?: unknown, ...args: unknown[]) => {\n const rendered = message === undefined && args.length === 0 ? \"\" : format(message, ...args);\n const safeRendered = safeTerminalLog(rendered);\n const prefix =\n level === \"error\"\n ? `${theme.styled.fail(theme.symbols.fail)} `\n : level === \"warn\"\n ? `${theme.styled.skip(\"!\")} `\n : \"\";\n const text = `${prefix}${safeRendered}\\n`;\n if (disposed) {\n output.write(text);\n return;\n }\n ensureTicker().writeLog(text);\n progressDirty = false;\n lastProgressRedrawAt = Date.now();\n redraw();\n };\n\n const log: PipelineLogger = {\n error: (message, ...args) => writeLog(\"error\", message, ...args),\n log: (message, ...args) => writeLog(\"log\", message, ...args),\n warn: (message, ...args) => writeLog(\"warn\", message, ...args),\n };\n\n const hooks: PipelineHooks<TResult> = {\n onPipelineStart: (nextPlan) => {\n plan = nextPlan;\n for (const step of nextPlan.steps) {\n steps.set(step.id, { pipelineId: nextPlan.pipelineId, status: \"planned\", step });\n }\n if (output === process.stdout) {\n exitListener = dispose;\n process.once(\"exit\", exitListener);\n }\n redraw();\n },\n onStepStatus: (event) => {\n const previous = steps.get(event.step.id);\n if (event.status === \"running\") {\n // Empty/non-visible progress only refreshes the frame; retain the last\n // visible progress snapshot while still publishing one running state.\n const progress =\n event.progress && hasVisibleStepProgress(event.progress)\n ? event.progress\n : previous?.status === \"running\"\n ? previous.progress\n : undefined;\n steps.set(event.step.id, {\n ...event,\n progress,\n startedAtMs: previous?.startedAtMs ?? Date.now(),\n });\n const now = Date.now();\n if (now - lastProgressRedrawAt >= refreshIntervalMs) {\n progressDirty = false;\n lastProgressRedrawAt = now;\n redraw();\n } else {\n progressDirty = true;\n trailingFlush ??= setTimeout(\n () => {\n trailingFlush = undefined;\n if (!disposed) flushProgress();\n },\n Math.max(0, refreshIntervalMs - (now - lastProgressRedrawAt))\n );\n trailingFlush.unref?.();\n }\n return;\n }\n if (event.status !== \"planned\") flushProgress();\n steps.set(event.step.id, event);\n redraw();\n },\n onFinalizeStart: () => {\n finalize = { status: \"running\" };\n redraw();\n },\n onFinalizeComplete: ({ durationMs }) => {\n finalize = { durationMs, status: \"completed\" };\n redraw();\n },\n onFinalizeError: ({ durationMs, error }) => {\n finalize = { durationMs, error, status: \"failed\" };\n redraw();\n },\n onPipelineComplete: (nextResult) => {\n result = nextResult;\n redraw();\n dispose();\n },\n };\n\n return { dispose, hooks, log, mode: \"interactive\" };\n}\n\n/** Create the plain or interactive reporter selected by the configured terminal mode. */\nexport function createPipelineReporter<TResult = unknown>(\n options: PipelineReporterOptions\n): PipelineReporterController<TResult> {\n const output = options.output ?? process.stdout;\n const mode = resolveMode(output, options);\n if (mode === \"interactive\") {\n return createInteractiveReporter(options, output);\n }\n return {\n dispose: () => undefined,\n hooks: createRunReporter<TResult>(options),\n log: options.log,\n mode: \"plain\",\n };\n}\n"]}
1
+ {"version":3,"file":"interactive-reporter.js","sourceRoot":"","sources":["../../src/reporter/interactive-reporter.ts"],"names":[],"mappings":"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","sourcesContent":["import { format } from \"node:util\";\nimport type {\n PipelineError,\n PipelineHooks,\n PipelineLogger,\n PipelinePlan,\n PipelineRun,\n PipelineStepProgress,\n PipelineStepStatus,\n} from \"../core/pipeline.js\";\nimport { hasVisibleStepProgress } from \"../core/progress.js\";\nimport {\n createLiveTicker,\n elapsedToken,\n shimmerToken,\n SPINNER_TOKEN,\n type LiveTicker,\n} from \"./live-ticker.js\";\nimport {\n createReporterTheme,\n createRunReporter,\n formatDurationMs,\n type ReporterTheme,\n type RunReporterConfig,\n} from \"./reporter.js\";\n\nexport type PipelineReporterMode = \"auto\" | \"interactive\" | \"plain\";\nexport type ResolvedPipelineReporterMode = Exclude<PipelineReporterMode, \"auto\">;\n\nexport interface ReporterOutput {\n readonly columns?: number;\n readonly rows?: number;\n /**\n * File descriptor for live frames. When set, spinner, elapsed time, and shimmer\n * paint on a worker thread so they keep moving during CPU-bound steps. Defaults\n * to stdout's fd when `output` is `process.stdout`.\n */\n readonly fd?: number;\n readonly isTTY?: boolean;\n write(chunk: string): unknown;\n}\n\nexport interface PipelineReporterConfig extends RunReporterConfig {\n /** Auto selects the interactive renderer only for a capable, non-CI TTY. */\n mode?: PipelineReporterMode;\n /** Terminal stream used by the interactive renderer. Defaults to stdout. */\n output?: ReporterOutput;\n /** Spinner redraw cadence. Defaults to 80ms. */\n refreshIntervalMs?: number;\n /** Filled/empty character width for determinate progress. Defaults to 20. */\n progressBarWidth?: number;\n}\n\nexport interface PipelineReporterOptions extends PipelineReporterConfig {\n log: PipelineLogger;\n}\n\nexport interface PipelineReporterController<TResult = unknown> {\n readonly hooks: PipelineHooks<TResult>;\n readonly log: PipelineLogger;\n readonly mode: ResolvedPipelineReporterMode;\n /** Stop redraws and restore terminal state. Safe to call more than once. */\n dispose(): void;\n}\n\ntype StepState = PipelineStepStatus & {\n /** Wall-clock ms from Date.now() when the step entered the running state. */\n startedAtMs?: number;\n details?: PipelineStepProgress[\"details\"];\n};\n\ntype FinalizeState =\n | { status: \"idle\" }\n | { status: \"running\" }\n | { durationMs: number; status: \"completed\" }\n | { durationMs: number; error: PipelineError; status: \"failed\" };\n\nconst TERMINAL_OSC = /(?:\\u001B\\]|\\u009D)[\\s\\S]*?(?:\\u0007|\\u001B\\\\|\\u009C)/g;\nconst TERMINAL_STRING = /(?:\\u001B[P_X^]|\\u0090|\\u0098|\\u009E|\\u009F)[\\s\\S]*?(?:\\u001B\\\\|\\u009C)/g;\nconst TERMINAL_CSI = /(?:\\u001B\\[|\\u009B)[0-?]*[ -/]*[@-~]/g;\nconst TERMINAL_ESCAPE = /\\u001B[@-_]/g;\nconst TERMINAL_CONTROL = /[\\u0000-\\u0009\\u000B-\\u001F\\u007F-\\u009F]/g;\n\nfunction autoInteractiveAllowed(output: ReporterOutput, config: PipelineReporterConfig): boolean {\n const isTTY = config.terminal?.isTTY ?? output.isTTY === true;\n const ci = process.env.CI;\n const isCI = ci !== undefined && ci !== \"\" && ci !== \"0\" && ci !== \"false\";\n return isTTY && process.env.TERM !== \"dumb\" && !isCI;\n}\n\nfunction resolveMode(\n output: ReporterOutput,\n config: PipelineReporterConfig\n): ResolvedPipelineReporterMode {\n if (config.mode === \"interactive\") return \"interactive\";\n if (config.mode === \"plain\") return \"plain\";\n return autoInteractiveAllowed(output, config) ? \"interactive\" : \"plain\";\n}\n\nfunction safeNumber(value: number): number {\n return Number.isFinite(value) ? value : 0;\n}\n\nfunction stripTerminalControls(value: string): string {\n return value\n .replace(TERMINAL_OSC, \"\")\n .replace(TERMINAL_STRING, \"\")\n .replace(TERMINAL_CSI, \"\")\n .replace(TERMINAL_ESCAPE, \"\")\n .replace(TERMINAL_CONTROL, \"\");\n}\n\nfunction safeTerminalText(value: string): string {\n return stripTerminalControls(value.replace(/\\s+/g, \" \")).trim();\n}\n\nfunction safeTerminalLog(value: string): string {\n return stripTerminalControls(value.replace(/\\r\\n?/g, \"\\n\").replaceAll(\"\\t\", \" \"));\n}\n\nfunction formatCount(value: number): string {\n return Number.isInteger(value) ? String(value) : value.toFixed(1);\n}\n\nfunction renderProgress(progress: PipelineStepProgress, width: number, unicode: boolean): string {\n // Parent line keeps the summary message only; per-item work uses `details`.\n const message = progress.message ? ` ${safeTerminalText(progress.message)}` : \"\";\n if (progress.total === undefined || safeNumber(progress.total) <= 0) {\n return `${formatCount(safeNumber(progress.completed))}${message}`;\n }\n const completed = safeNumber(progress.completed);\n const total = safeNumber(progress.total);\n const ratio = Math.max(0, Math.min(1, completed / total));\n const filled = Math.round(ratio * width);\n const bar = `${unicode ? \"█\".repeat(filled) : \"=\".repeat(filled)}${\n unicode ? \"░\".repeat(width - filled) : \"-\".repeat(width - filled)\n }`;\n return `[${bar}] ${Math.round(ratio * 100)}% ${formatCount(completed)}/${formatCount(total)}${message}`;\n}\n\nfunction renderProgressDetail(\n detail: NonNullable<PipelineStepProgress[\"details\"]>[number],\n theme: ReporterTheme,\n spinner: string,\n progressBarWidth: number,\n parentStatus: PipelineStepStatus[\"status\"]\n): string {\n const status =\n (detail.status ?? \"running\") === \"running\" && parentStatus !== \"running\"\n ? parentStatus\n : (detail.status ?? \"running\");\n const symbol =\n status === \"completed\"\n ? theme.styled.complete(theme.symbols.complete)\n : status === \"failed\"\n ? theme.styled.fail(theme.symbols.fail)\n : status === \"skipped\" || status === \"cancelled\"\n ? theme.styled.skip(theme.symbols.skip)\n : status === \"pending\"\n ? theme.styled.description(theme.symbols.pending)\n : theme.styled.start(spinner);\n const id = safeTerminalText(detail.name ?? detail.id);\n const label = detail.label ? safeTerminalText(detail.label) : \"\";\n const labelText = status === \"cancelled\" ? `cancelled${label ? `: ${label}` : \"\"}` : label;\n const running = status === \"running\";\n const body = labelText ? `${id} ${labelText}` : id;\n const paintedBody =\n running && theme.colorEnabled\n ? shimmerToken(body)\n : `${id}${labelText ? ` ${theme.styled.description(labelText)}` : \"\"}`;\n const depth = Math.max(0, Math.min(32, Math.floor(safeNumber(detail.depth ?? 0))));\n const progress =\n detail.completed === undefined\n ? \"\"\n : ` ${renderProgress(\n { completed: detail.completed, total: detail.total },\n progressBarWidth,\n theme.capabilities.unicode\n )}`;\n return `${\" \".repeat(depth + 2)}${symbol} ${paintedBody}${progress}`;\n}\n\nfunction renderStep(\n state: StepState,\n theme: ReporterTheme,\n spinner: string,\n progressBarWidth: number\n): string[] {\n const { step } = state;\n const displayName = safeTerminalText(step.name ?? step.id);\n switch (state.status) {\n case \"running\": {\n const progress =\n state.progress && hasVisibleStepProgress(state.progress)\n ? ` ${renderProgress(state.progress, progressBarWidth, theme.capabilities.unicode)}`\n : \"\";\n const elapsed =\n state.startedAtMs !== undefined\n ? ` ${theme.styled.duration(elapsedToken(state.startedAtMs))}`\n : \"\";\n const lines = [\n ` ${theme.styled.start(spinner)} ${shimmerToken(displayName)}${progress}${elapsed}`,\n ];\n return lines;\n }\n case \"completed\":\n return [\n ` ${theme.styled.complete(theme.symbols.complete)} ${displayName} ${theme.styled.duration(\n `(${formatDurationMs(state.finishedAtMs - state.startedAtMs)})`\n )}`,\n ];\n case \"failed\":\n return [\n ` ${theme.styled.fail(theme.symbols.fail)} ${displayName}: ${safeTerminalText(\n state.error.message\n )}`,\n ];\n case \"cancelled\":\n return [\n ` ${theme.styled.skip(theme.symbols.skip)} ${displayName}: cancelled: ${safeTerminalText(\n state.error.message\n )}`,\n ];\n case \"skipped\":\n return [\n ` ${theme.styled.skip(theme.symbols.skip)} ${displayName} ${theme.styled.duration(\n `(${state.message ?? state.reason})`\n )}`,\n ];\n case \"planned\":\n return [\n ` ${theme.styled.description(theme.symbols.pending)} ${displayName} ${theme.styled.description(\"waiting\")}`,\n ];\n }\n}\n\nfunction reporterOutputFd(output: ReporterOutput): number | undefined {\n const stdoutFd = output === process.stdout ? process.stdout.fd : undefined;\n for (const fd of [output.fd, stdoutFd]) {\n if (fd !== undefined && Number.isInteger(fd) && fd >= 0) return fd;\n }\n return undefined;\n}\n\nfunction createInteractiveReporter<TResult>(\n options: PipelineReporterOptions,\n output: ReporterOutput\n): PipelineReporterController<TResult> {\n const terminalIsTTY = options.terminal?.isTTY ?? output.isTTY === true;\n const theme = createReporterTheme({\n ...options,\n terminal: { isTTY: terminalIsTTY, ...options.terminal },\n });\n const progressBarWidth = Math.max(4, Math.floor(options.progressBarWidth ?? 20));\n // ~12.5 fps keeps braille spinners smooth without flooding the terminal.\n const refreshIntervalMs = Math.max(16, Math.floor(options.refreshIntervalMs ?? 80));\n const steps = new Map<string, StepState>();\n let plan: PipelinePlan | undefined;\n let result: PipelineRun<TResult> | undefined;\n let finalize: FinalizeState = { status: \"idle\" };\n let lastProgressRedrawAt = Number.NEGATIVE_INFINITY;\n let progressDirty = false;\n let disposed = false;\n let ticker: LiveTicker | undefined;\n let trailingFlush: ReturnType<typeof setTimeout> | undefined;\n let exitListener: (() => void) | undefined;\n\n const frameLines = (): string[] => {\n if (!plan) return [];\n const lines: string[] = [];\n if (options.logPlan !== false) {\n const header = result\n ? options.logSummary === false\n ? `Pipeline ${result.pipelineId}`\n : `Pipeline ${result.pipelineId}: done in ${formatDurationMs(result.finishedAtMs - result.startedAtMs)} (status=${result.status}, steps=${result.steps.length}, errors=${result.errors.length})`\n : `Pipeline ${plan.pipelineId} (${plan.steps.length} steps, dryRun=${plan.dryRun})`;\n lines.push(\n result?.status === \"completed\"\n ? theme.styled.complete(header)\n : result\n ? theme.styled.fail(header)\n : theme.styled.pipeline(header)\n );\n }\n for (const state of steps.values()) {\n lines.push(...renderStep(state, theme, SPINNER_TOKEN, progressBarWidth));\n for (const detail of state.details ?? []) {\n lines.push(\n renderProgressDetail(detail, theme, SPINNER_TOKEN, progressBarWidth, state.status)\n );\n }\n }\n if (finalize.status === \"running\") {\n lines.push(` ${theme.styled.start(SPINNER_TOKEN)} ${shimmerToken(\"finalize\")}`);\n } else if (finalize.status === \"completed\") {\n lines.push(\n ` ${theme.styled.complete(theme.symbols.complete)} finalize ${theme.styled.duration(\n `(${formatDurationMs(finalize.durationMs)})`\n )}`\n );\n } else if (finalize.status === \"failed\") {\n lines.push(\n ` ${theme.styled.fail(theme.symbols.fail)} finalize: ${safeTerminalText(\n finalize.error.message\n )}`\n );\n }\n return lines;\n };\n\n const ensureTicker = (): LiveTicker => {\n if (!ticker) {\n ticker = createLiveTicker({\n color: theme.colorEnabled,\n columns: output.columns,\n getColumns: () => output.columns,\n fd: reporterOutputFd(output),\n refreshIntervalMs,\n unicode: theme.capabilities.unicode,\n write: (chunk) => {\n output.write(chunk);\n },\n });\n }\n return ticker;\n };\n\n const redraw = (): void => {\n if (disposed) return;\n const lines = frameLines();\n if (lines.length === 0) return;\n const rows = output.rows;\n if (rows === undefined || rows < 2 || lines.length < rows) {\n ensureTicker().setLines(lines);\n return;\n }\n if (result) {\n // A finished tree can scroll normally; never try to erase beyond the viewport.\n ensureTicker().setLines([]);\n ensureTicker().writeLog(`${lines.join(\"\\n\")}\\n`);\n return;\n }\n const ellipsis = theme.capabilities.unicode ? \"…\" : \"...\";\n if (rows < 5) {\n ensureTicker().setLines(\n [lines[0]!, ` ${ellipsis} ${lines.length - 1} rows omitted`].slice(0, rows - 1)\n );\n return;\n }\n const available = rows - 4;\n let focus = 0;\n for (const [index, line] of lines.entries()) {\n if (line.includes(SPINNER_TOKEN)) focus = index;\n }\n const start = Math.max(\n 1,\n Math.min(lines.length - available, focus - Math.floor(available / 2))\n );\n const end = start + available;\n const visible = [lines[0]!];\n if (start > 1) visible.push(` ${ellipsis} ${start - 1} rows above`);\n visible.push(...lines.slice(start, end));\n if (end < lines.length) visible.push(` ${ellipsis} ${lines.length - end} rows below`);\n ensureTicker().setLines(visible);\n };\n\n const flushProgress = (): void => {\n if (trailingFlush !== undefined) {\n clearTimeout(trailingFlush);\n trailingFlush = undefined;\n }\n if (!progressDirty) return;\n progressDirty = false;\n lastProgressRedrawAt = Date.now();\n redraw();\n };\n\n const dispose = (): void => {\n if (disposed) return;\n disposed = true;\n if (trailingFlush !== undefined) {\n clearTimeout(trailingFlush);\n trailingFlush = undefined;\n }\n ticker?.dispose();\n ticker = undefined;\n if (exitListener) process.off(\"exit\", exitListener);\n exitListener = undefined;\n };\n\n const writeLog = (level: \"error\" | \"log\" | \"warn\", message?: unknown, ...args: unknown[]) => {\n const rendered = message === undefined && args.length === 0 ? \"\" : format(message, ...args);\n const safeRendered = safeTerminalLog(rendered);\n const prefix =\n level === \"error\"\n ? `${theme.styled.fail(theme.symbols.fail)} `\n : level === \"warn\"\n ? `${theme.styled.skip(\"!\")} `\n : \"\";\n const text = `${prefix}${safeRendered}\\n`;\n if (disposed) {\n output.write(text);\n return;\n }\n ensureTicker().writeLog(text);\n progressDirty = false;\n lastProgressRedrawAt = Date.now();\n redraw();\n };\n\n const log: PipelineLogger = {\n error: (message, ...args) => writeLog(\"error\", message, ...args),\n log: (message, ...args) => writeLog(\"log\", message, ...args),\n warn: (message, ...args) => writeLog(\"warn\", message, ...args),\n };\n\n const hooks: PipelineHooks<TResult> = {\n onPipelineStart: (nextPlan) => {\n plan = nextPlan;\n for (const step of nextPlan.steps) {\n steps.set(step.id, { pipelineId: nextPlan.pipelineId, status: \"planned\", step });\n }\n if (output === process.stdout) {\n exitListener = dispose;\n process.once(\"exit\", exitListener);\n }\n redraw();\n },\n onStepStatus: (event) => {\n const previous = steps.get(event.step.id);\n if (event.status === \"running\") {\n // Empty/non-visible progress only refreshes the frame; retain the last\n // visible progress snapshot while still publishing one running state.\n const progress =\n event.progress && hasVisibleStepProgress(event.progress)\n ? event.progress\n : previous?.status === \"running\"\n ? previous.progress\n : undefined;\n steps.set(event.step.id, {\n ...event,\n progress,\n details: progress?.details,\n startedAtMs: previous?.startedAtMs ?? Date.now(),\n });\n const now = Date.now();\n if (now - lastProgressRedrawAt >= refreshIntervalMs) {\n progressDirty = false;\n lastProgressRedrawAt = now;\n redraw();\n } else {\n progressDirty = true;\n trailingFlush ??= setTimeout(\n () => {\n trailingFlush = undefined;\n if (!disposed) flushProgress();\n },\n Math.max(0, refreshIntervalMs - (now - lastProgressRedrawAt))\n );\n trailingFlush.unref?.();\n }\n return;\n }\n if (event.status !== \"planned\") flushProgress();\n steps.set(event.step.id, { ...event, details: previous?.details });\n redraw();\n },\n onFinalizeStart: () => {\n finalize = { status: \"running\" };\n redraw();\n },\n onFinalizeComplete: ({ durationMs }) => {\n finalize = { durationMs, status: \"completed\" };\n redraw();\n },\n onFinalizeError: ({ durationMs, error }) => {\n finalize = { durationMs, error, status: \"failed\" };\n redraw();\n },\n onPipelineComplete: (nextResult) => {\n result = nextResult;\n redraw();\n dispose();\n },\n };\n\n return { dispose, hooks, log, mode: \"interactive\" };\n}\n\n/** Create the plain or interactive reporter selected by the configured terminal mode. */\nexport function createPipelineReporter<TResult = unknown>(\n options: PipelineReporterOptions\n): PipelineReporterController<TResult> {\n const output = options.output ?? process.stdout;\n const mode = resolveMode(output, options);\n if (mode === \"interactive\") {\n return createInteractiveReporter(options, output);\n }\n return {\n dispose: () => undefined,\n hooks: createRunReporter<TResult>(options),\n log: options.log,\n mode: \"plain\",\n };\n}\n"]}
@@ -1,11 +1,6 @@
1
1
  import { writeSync } from "node:fs";
2
2
  import { parentPort, workerData } from "node:worker_threads";
3
- import { currentSpinner, paintLiveLines, SHIMMER_TOKEN_START, SPINNER_TOKEN, } from "./live-ticker.js";
4
- const ANSI = {
5
- clearDown: "\u001B[J",
6
- hideCursor: "\u001B[?25l",
7
- showCursor: "\u001B[?25h",
8
- };
3
+ import { TickerFrame, currentSpinner, paintLiveLines, SHIMMER_TOKEN_START, SPINNER_TOKEN, } from "./live-ticker.js";
9
4
  const port = parentPort;
10
5
  if (port === null) {
11
6
  throw new Error("live-ticker-worker must run as a worker thread");
@@ -16,48 +11,20 @@ const data = workerData;
16
11
  const handshake = new Int32Array(data.handshakeBuffer);
17
12
  let columns = data.columns;
18
13
  let lines = [];
19
- let frameLineCount = 0;
20
- let cursorHidden = false;
14
+ const frame = new TickerFrame((chunk) => writeSync(data.fd, chunk));
21
15
  let announcedReady = false;
22
16
  function publishFrame() {
23
- Atomics.store(handshake, 1, frameLineCount);
17
+ Atomics.store(handshake, 1, frame.frameLineCount);
24
18
  }
25
19
  function announceReady() {
26
20
  publishFrame();
27
21
  if (announcedReady || port === null)
28
22
  return;
29
23
  announcedReady = true;
30
- port.postMessage({ type: "ready", frameLineCount });
31
- }
32
- function write(chunk) {
33
- writeSync(data.fd, chunk);
34
- }
35
- function hideCursor() {
36
- if (cursorHidden)
37
- return;
38
- write(ANSI.hideCursor);
39
- cursorHidden = true;
40
- }
41
- function showCursor() {
42
- if (!cursorHidden)
43
- return;
44
- write(ANSI.showCursor);
45
- cursorHidden = false;
46
- }
47
- function clearFrame() {
48
- if (frameLineCount === 0)
49
- return;
50
- write(`\u001B[${frameLineCount}F${ANSI.clearDown}`);
51
- frameLineCount = 0;
24
+ port.postMessage({ type: "ready", frameLineCount: frame.frameLineCount });
52
25
  }
53
26
  function redraw() {
54
- hideCursor();
55
- clearFrame();
56
- if (lines.length !== 0) {
57
- const painted = paintLiveLines(lines, currentSpinner(data.unicode, data.refreshIntervalMs), Date.now(), columns, data.color);
58
- write(`${painted.join("\n")}\n`);
59
- frameLineCount = painted.length;
60
- }
27
+ frame.redraw(paintLiveLines(lines, currentSpinner(data.unicode, data.refreshIntervalMs), Date.now(), columns, data.color));
61
28
  publishFrame();
62
29
  }
63
30
  function done() {
@@ -77,9 +44,8 @@ const timer = setInterval(() => {
77
44
  }, data.refreshIntervalMs);
78
45
  port.on("message", (msg) => {
79
46
  if (msg.type === "log") {
80
- clearFrame();
81
- write(msg.text);
82
- frameLineCount = 0;
47
+ frame.clear();
48
+ writeSync(data.fd, msg.text);
83
49
  publishFrame();
84
50
  Atomics.add(handshake, 2, 1);
85
51
  announceReady();
@@ -99,7 +65,7 @@ port.on("message", (msg) => {
99
65
  if (msg.lines)
100
66
  lines = msg.lines;
101
67
  redraw();
102
- showCursor();
68
+ frame.showCursor();
103
69
  done();
104
70
  port.close();
105
71
  }
@@ -1 +1 @@
1
- {"version":3,"file":"live-ticker-worker.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeSync } from \"node:fs\";\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport {\n currentSpinner,\n paintLiveLines,\n SHIMMER_TOKEN_START,\n SPINNER_TOKEN,\n} from \"./live-ticker.js\";\n\nconst ANSI = {\n clearDown: \"\\u001B[J\",\n hideCursor: \"\\u001B[?25l\",\n showCursor: \"\\u001B[?25h\",\n} as const;\n\ninterface LiveTickerWorkerData {\n color: boolean;\n columns?: number;\n fd: number;\n handshakeBuffer: SharedArrayBuffer;\n refreshIntervalMs: number;\n unicode: boolean;\n}\n\ntype TickerWorkerMessage =\n | { columns?: number; lines: string[]; type: \"lines\" }\n | { text: string; type: \"log\" }\n | { columns?: number; lines: string[]; type: \"stop\" };\n\nconst port = parentPort;\nif (port === null) {\n throw new Error(\"live-ticker-worker must run as a worker thread\");\n}\n\n// SAFETY: createWorkerTicker always posts this exact workerData shape\n// (color, columns, fd, handshakeBuffer, refreshIntervalMs, unicode).\nconst data = workerData as LiveTickerWorkerData;\nconst handshake = new Int32Array(data.handshakeBuffer);\nlet columns = data.columns;\nlet lines: string[] = [];\nlet frameLineCount = 0;\nlet cursorHidden = false;\nlet announcedReady = false;\n\nfunction publishFrame(): void {\n Atomics.store(handshake, 1, frameLineCount);\n}\n\nfunction announceReady(): void {\n publishFrame();\n if (announcedReady || port === null) return;\n announcedReady = true;\n port.postMessage({ type: \"ready\", frameLineCount });\n}\n\nfunction write(chunk: string): void {\n writeSync(data.fd, chunk);\n}\n\nfunction hideCursor(): void {\n if (cursorHidden) return;\n write(ANSI.hideCursor);\n cursorHidden = true;\n}\n\nfunction showCursor(): void {\n if (!cursorHidden) return;\n write(ANSI.showCursor);\n cursorHidden = false;\n}\n\nfunction clearFrame(): void {\n if (frameLineCount === 0) return;\n write(`\\u001B[${frameLineCount}F${ANSI.clearDown}`);\n frameLineCount = 0;\n}\n\nfunction redraw(): void {\n hideCursor();\n clearFrame();\n if (lines.length !== 0) {\n const painted = paintLiveLines(\n lines,\n currentSpinner(data.unicode, data.refreshIntervalMs),\n Date.now(),\n columns,\n data.color\n );\n write(`${painted.join(\"\\n\")}\\n`);\n frameLineCount = painted.length;\n }\n publishFrame();\n}\n\nfunction done(): void {\n Atomics.store(handshake, 0, 1);\n Atomics.notify(handshake, 0);\n}\n\nconst beat = (): void => {\n Atomics.add(handshake, 3, 1);\n};\n\nbeat();\nconst timer = setInterval(() => {\n beat();\n if (!lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START))) {\n return;\n }\n redraw();\n}, data.refreshIntervalMs);\n\nport.on(\"message\", (msg: TickerWorkerMessage) => {\n if (msg.type === \"log\") {\n clearFrame();\n write(msg.text);\n frameLineCount = 0;\n publishFrame();\n Atomics.add(handshake, 2, 1);\n announceReady();\n port.postMessage({ type: \"ack\", kind: \"log\" });\n return;\n }\n if (msg.columns !== undefined) columns = msg.columns;\n if (msg.type === \"lines\") {\n lines = msg.lines;\n redraw();\n announceReady();\n return;\n }\n if (msg.type === \"stop\") {\n clearInterval(timer);\n if (msg.lines) lines = msg.lines;\n redraw();\n showCursor();\n done();\n port.close();\n }\n});\n"]}
1
+ {"version":3,"file":"live-ticker-worker.js","sourceRoot":"","sources":["../../src/reporter/live-ticker-worker.ts"],"names":[],"mappings":"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","sourcesContent":["import { writeSync } from \"node:fs\";\nimport { parentPort, workerData } from \"node:worker_threads\";\nimport {\n TickerFrame,\n currentSpinner,\n paintLiveLines,\n SHIMMER_TOKEN_START,\n SPINNER_TOKEN,\n} from \"./live-ticker.js\";\n\ninterface LiveTickerWorkerData {\n color: boolean;\n columns?: number;\n fd: number;\n handshakeBuffer: SharedArrayBuffer;\n refreshIntervalMs: number;\n unicode: boolean;\n}\n\ntype TickerWorkerMessage =\n | { columns?: number; lines: string[]; type: \"lines\" }\n | { text: string; type: \"log\" }\n | { columns?: number; lines: string[]; type: \"stop\" };\n\nconst port = parentPort;\nif (port === null) {\n throw new Error(\"live-ticker-worker must run as a worker thread\");\n}\n\n// SAFETY: createWorkerTicker always posts this exact workerData shape\n// (color, columns, fd, handshakeBuffer, refreshIntervalMs, unicode).\nconst data = workerData as LiveTickerWorkerData;\nconst handshake = new Int32Array(data.handshakeBuffer);\nlet columns = data.columns;\nlet lines: string[] = [];\nconst frame = new TickerFrame((chunk) => writeSync(data.fd, chunk));\nlet announcedReady = false;\n\nfunction publishFrame(): void {\n Atomics.store(handshake, 1, frame.frameLineCount);\n}\n\nfunction announceReady(): void {\n publishFrame();\n if (announcedReady || port === null) return;\n announcedReady = true;\n port.postMessage({ type: \"ready\", frameLineCount: frame.frameLineCount });\n}\n\nfunction redraw(): void {\n frame.redraw(\n paintLiveLines(\n lines,\n currentSpinner(data.unicode, data.refreshIntervalMs),\n Date.now(),\n columns,\n data.color\n )\n );\n publishFrame();\n}\n\nfunction done(): void {\n Atomics.store(handshake, 0, 1);\n Atomics.notify(handshake, 0);\n}\n\nconst beat = (): void => {\n Atomics.add(handshake, 3, 1);\n};\n\nbeat();\nconst timer = setInterval(() => {\n beat();\n if (!lines.some((line) => line.includes(SPINNER_TOKEN) || line.includes(SHIMMER_TOKEN_START))) {\n return;\n }\n redraw();\n}, data.refreshIntervalMs);\n\nport.on(\"message\", (msg: TickerWorkerMessage) => {\n if (msg.type === \"log\") {\n frame.clear();\n writeSync(data.fd, msg.text);\n publishFrame();\n Atomics.add(handshake, 2, 1);\n announceReady();\n port.postMessage({ type: \"ack\", kind: \"log\" });\n return;\n }\n if (msg.columns !== undefined) columns = msg.columns;\n if (msg.type === \"lines\") {\n lines = msg.lines;\n redraw();\n announceReady();\n return;\n }\n if (msg.type === \"stop\") {\n clearInterval(timer);\n if (msg.lines) lines = msg.lines;\n redraw();\n frame.showCursor();\n done();\n port.close();\n }\n});\n"]}
@@ -19,6 +19,16 @@ export interface LiveTickerOptions {
19
19
  workerUrl?: URL;
20
20
  supervisorUrl?: URL;
21
21
  }
22
+ /** Cursor and frame ownership shared by inline and worker tickers. */
23
+ export declare class TickerFrame {
24
+ private readonly write;
25
+ frameLineCount: number;
26
+ private cursorHidden;
27
+ constructor(write: (chunk: string) => void, frameLineCount?: number);
28
+ showCursor(): void;
29
+ clear(): void;
30
+ redraw(lines: readonly string[]): void;
31
+ }
22
32
  export declare function currentSpinner(unicode: boolean, refreshIntervalMs: number): string;
23
33
  /** Inline interval for tests; worker thread when `fd` is a real TTY/pipe. */
24
34
  export declare function createLiveTicker(options: LiveTickerOptions): LiveTicker;