tubeless 0.2.2 → 0.2.4
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.
- package/README.md +52 -52
- package/dist/cli/cli-types.d.ts +1 -1
- package/dist/cli/cli-types.js.map +1 -1
- package/dist/cli/cli.d.ts +2 -4
- package/dist/cli/cli.js +1 -3
- package/dist/cli/cli.js.map +1 -1
- package/dist/core/child-execution.js +9 -4
- package/dist/core/child-execution.js.map +1 -1
- package/dist/core/lifecycle.d.ts +1 -1
- package/dist/core/lifecycle.js +2 -0
- package/dist/core/lifecycle.js.map +1 -1
- package/dist/core/pipeline-compiler.d.ts +17 -0
- package/dist/core/pipeline-compiler.js +44 -0
- package/dist/core/pipeline-compiler.js.map +1 -0
- package/dist/core/pipeline-definition-validation.d.ts +4 -0
- package/dist/core/pipeline-definition-validation.js +101 -0
- package/dist/core/pipeline-definition-validation.js.map +1 -0
- package/dist/core/pipeline-definition.d.ts +28 -0
- package/dist/core/pipeline-definition.js +2 -0
- package/dist/core/pipeline-definition.js.map +1 -0
- package/dist/core/pipeline-disposition.d.ts +26 -0
- package/dist/core/pipeline-disposition.js +44 -0
- package/dist/core/pipeline-disposition.js.map +1 -0
- package/dist/core/pipeline-errors.d.ts +8 -0
- package/dist/core/pipeline-errors.js +20 -0
- package/dist/core/pipeline-errors.js.map +1 -0
- package/dist/core/pipeline-execute.d.ts +3 -1
- package/dist/core/pipeline-execute.js +136 -396
- package/dist/core/pipeline-execute.js.map +1 -1
- package/dist/core/pipeline-finalizer.d.ts +18 -0
- package/dist/core/pipeline-finalizer.js +37 -0
- package/dist/core/pipeline-finalizer.js.map +1 -0
- package/dist/core/pipeline-graph.d.ts +23 -0
- package/dist/core/pipeline-graph.js +135 -0
- package/dist/core/pipeline-graph.js.map +1 -0
- package/dist/core/pipeline-identity.d.ts +5 -0
- package/dist/core/pipeline-identity.js +11 -0
- package/dist/core/pipeline-identity.js.map +1 -0
- package/dist/core/pipeline-mermaid.d.ts +4 -0
- package/dist/core/pipeline-mermaid.js +56 -0
- package/dist/core/pipeline-mermaid.js.map +1 -0
- package/dist/core/pipeline-plan.d.ts +5 -94
- package/dist/core/pipeline-plan.js +5 -392
- package/dist/core/pipeline-plan.js.map +1 -1
- package/dist/core/pipeline-run-state.d.ts +49 -0
- package/dist/core/pipeline-run-state.js +284 -0
- package/dist/core/pipeline-run-state.js.map +1 -0
- package/dist/core/pipeline-step-executor.d.ts +20 -0
- package/dist/core/pipeline-step-executor.js +52 -0
- package/dist/core/pipeline-step-executor.js.map +1 -0
- package/dist/core/pipeline-step-metadata.d.ts +4 -0
- package/dist/core/pipeline-step-metadata.js +5 -0
- package/dist/core/pipeline-step-metadata.js.map +1 -0
- package/dist/core/pipeline-steps.d.ts +1 -1
- package/dist/core/pipeline-steps.js +1 -1
- package/dist/core/pipeline-steps.js.map +1 -1
- package/dist/core/pipeline-types.d.ts +17 -8
- package/dist/core/pipeline-types.js +8 -0
- package/dist/core/pipeline-types.js.map +1 -1
- package/dist/core/pipeline-validation.d.ts +6 -0
- package/dist/core/pipeline-validation.js +41 -0
- package/dist/core/pipeline-validation.js.map +1 -0
- package/dist/core/pipeline.d.ts +8 -9
- package/dist/core/pipeline.js +9 -6
- package/dist/core/pipeline.js.map +1 -1
- package/dist/node/node.d.ts +0 -3
- package/dist/node/node.js +0 -3
- package/dist/node/node.js.map +1 -1
- package/dist/reporter/reporter-entry.d.ts +1 -2
- package/dist/reporter/reporter-entry.js +1 -2
- package/dist/reporter/reporter-entry.js.map +1 -1
- package/dist/run-store/run-store-codec.d.ts +4 -0
- package/dist/run-store/run-store-codec.js +31 -0
- package/dist/run-store/run-store-codec.js.map +1 -0
- package/dist/run-store/run-store-ndjson.js +2 -295
- package/dist/run-store/run-store-ndjson.js.map +1 -1
- package/dist/run-store/run-store-sqlite.js +41 -38
- package/dist/run-store/run-store-sqlite.js.map +1 -1
- package/dist/run-store/run-store.d.ts +4 -3
- package/dist/run-store/run-store.js +38 -195
- package/dist/run-store/run-store.js.map +1 -1
- package/dist/studio/run-store-ui-client-app.d.ts +12 -0
- package/dist/studio/run-store-ui-client-app.js +224 -0
- package/dist/studio/run-store-ui-client-app.js.map +1 -0
- package/dist/studio/run-store-ui-client-browser.d.ts +1 -0
- package/dist/studio/run-store-ui-client-browser.js +2 -0
- package/dist/studio/run-store-ui-client-commands.d.ts +18 -0
- package/dist/studio/run-store-ui-client-commands.js +98 -0
- package/dist/studio/run-store-ui-client-commands.js.map +1 -0
- package/dist/studio/run-store-ui-client-form.d.ts +11 -0
- package/dist/studio/run-store-ui-client-form.js +54 -0
- package/dist/studio/run-store-ui-client-form.js.map +1 -0
- package/dist/studio/run-store-ui-client-modals.d.ts +20 -0
- package/dist/studio/run-store-ui-client-modals.js +154 -0
- package/dist/studio/run-store-ui-client-modals.js.map +1 -0
- package/dist/studio/run-store-ui-client-model.d.ts +19 -0
- package/dist/studio/run-store-ui-client-model.js +189 -0
- package/dist/studio/run-store-ui-client-model.js.map +1 -0
- package/dist/studio/run-store-ui-client-runs.d.ts +24 -0
- package/dist/studio/run-store-ui-client-runs.js +78 -0
- package/dist/studio/run-store-ui-client-runs.js.map +1 -0
- package/dist/studio/run-store-ui-client-shared.d.ts +22 -0
- package/dist/studio/run-store-ui-client-shared.js +63 -0
- package/dist/studio/run-store-ui-client-shared.js.map +1 -0
- package/dist/studio/run-store-ui-client-transport.d.ts +25 -0
- package/dist/studio/run-store-ui-client-transport.js +317 -0
- package/dist/studio/run-store-ui-client-transport.js.map +1 -0
- package/dist/studio/run-store-ui-client.d.ts +2 -14
- package/dist/studio/run-store-ui-client.js +9 -1360
- package/dist/studio/run-store-ui-client.js.map +1 -1
- package/dist/studio/run-store-ui-page.d.ts +2 -2
- package/dist/studio/run-store-ui-page.js +7 -71
- package/dist/studio/run-store-ui-page.js.map +1 -1
- package/dist/studio/run-store-ui.d.ts +1 -1
- package/dist/studio/run-store-ui.js +43 -25
- package/dist/studio/run-store-ui.js.map +1 -1
- package/dist/tracing/tracing-codec.d.ts +5 -0
- package/dist/tracing/tracing-codec.js +13 -0
- package/dist/tracing/tracing-codec.js.map +1 -0
- package/dist/tracing/tracing-constants.d.ts +4 -0
- package/dist/tracing/tracing-constants.js +5 -0
- package/dist/tracing/tracing-constants.js.map +1 -0
- package/dist/tracing/tracing-contracts.d.ts +31 -0
- package/dist/tracing/tracing-contracts.js +2 -0
- package/dist/tracing/tracing-contracts.js.map +1 -0
- package/dist/tracing/tracing-internal.d.ts +1 -1
- package/dist/tracing/tracing-internal.js +116 -81
- package/dist/tracing/tracing-internal.js.map +1 -1
- package/dist/tracing/tracing-schema.d.ts +809 -0
- package/dist/tracing/tracing-schema.js +429 -0
- package/dist/tracing/tracing-schema.js.map +1 -0
- package/dist/tracing/tracing.d.ts +2 -54
- package/dist/tracing/tracing.js.map +1 -1
- package/dist/tracing/wire-schema.d.ts +50 -0
- package/dist/tracing/wire-schema.js +193 -0
- package/dist/tracing/wire-schema.js.map +1 -0
- package/dist/workbench/pipeline-module.js +4 -4
- package/dist/workbench/pipeline-module.js.map +1 -1
- package/dist/workbench/workbench-entry.d.ts +2 -0
- package/dist/workbench/workbench-entry.js +3 -0
- package/dist/workbench/workbench-entry.js.map +1 -0
- package/dist/workbench/workbench-history.js +8 -1
- package/dist/workbench/workbench-history.js.map +1 -1
- package/dist/workbench/workbench-ui.js +39 -43
- package/dist/workbench/workbench-ui.js.map +1 -1
- package/docs/README.md +22 -15
- package/docs/agent-guide.md +62 -68
- package/docs/agent-skills.md +59 -0
- package/docs/api-reference.md +14 -178
- package/docs/api-report.json +13 -215
- package/docs/child-pipeline-composition.md +139 -193
- package/docs/cli.md +86 -64
- package/docs/comparison.md +64 -62
- package/docs/concepts.md +210 -281
- package/docs/getting-started.md +61 -49
- package/docs/llms.txt +8 -8
- package/docs/recipes.md +42 -45
- package/docs/remote-step-composition.md +69 -74
- package/docs/studio.md +110 -73
- package/examples/catalog/pipelines/enrich.ts +2 -2
- package/examples/catalog/scripts/enrich.ts +1 -1
- package/examples/catalog/scripts/import.ts +1 -1
- package/examples/catalog/scripts/publish.ts +1 -1
- package/examples/catalog/tubeless.project.ts +6 -3
- package/examples/cli-job.ts +1 -1
- package/examples/host-embedding.ts +4 -5
- package/examples/live-tui.ts +1 -1
- package/examples/peloton.ts +1 -1
- package/examples/remote-steps.ts +8 -5
- package/examples/tracing.ts +96 -5
- package/package.json +17 -64
- package/skills/tubeless/SKILL.md +85 -0
- package/skills/tubeless-make-pipeline/SKILL.md +126 -0
- package/dist/node/env.d.ts +0 -1
- package/dist/node/env.js +0 -8
- package/dist/node/env.js.map +0 -1
- package/dist/node/file-utils.d.ts +0 -3
- package/dist/node/file-utils.js +0 -17
- package/dist/node/file-utils.js.map +0 -1
- package/dist/node/paths.d.ts +0 -8
- package/dist/node/paths.js +0 -19
- package/dist/node/paths.js.map +0 -1
- package/dist/reporter/prompt-select.d.ts +0 -80
- package/dist/reporter/prompt-select.js +0 -197
- package/dist/reporter/prompt-select.js.map +0 -1
- package/dist/studio/run-store-ui-client-source.d.ts +0 -1
- package/dist/studio/run-store-ui-client-source.js +0 -1
- package/dist/tracing/tracing-json.d.ts +0 -11
- package/dist/tracing/tracing-json.js +0 -8
- package/dist/tracing/tracing-json.js.map +0 -1
- package/dist/tracing/tracing-otel.d.ts +0 -35
- package/dist/tracing/tracing-otel.js +0 -60
- package/dist/tracing/tracing-otel.js.map +0 -1
- package/dist/workbench/workbench-studio.d.ts +0 -29
- package/dist/workbench/workbench-studio.js +0 -61
- package/dist/workbench/workbench-studio.js.map +0 -1
- package/examples/catalog/tubeless.studio.ts +0 -20
- package/examples/local-observability.ts +0 -105
- package/examples/rendering.ts +0 -34
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tubeless
|
|
3
|
+
description: Author, modify, or review typed Tubeless pipelines, pipeline-backed CLI commands, and project catalogs in TypeScript projects. Use for dependency modeling, failure and skip policies, dry runs, child pipelines, and pipeline tests.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Author Tubeless pipelines
|
|
7
|
+
|
|
8
|
+
Use the project's installed Tubeless version and public package imports. Keep
|
|
9
|
+
domain logic in ordinary functions and make orchestration explicit in the graph.
|
|
10
|
+
|
|
11
|
+
## Find the matching documentation
|
|
12
|
+
|
|
13
|
+
Read the consumer's package manifest, installed Tubeless version, and existing
|
|
14
|
+
pipeline conventions first. The installed `tubeless` package includes `docs/`
|
|
15
|
+
and `examples/`; locate it through the project's package manager. Within the
|
|
16
|
+
Tubeless source repository, use those directories at the repository root.
|
|
17
|
+
|
|
18
|
+
Read `docs/agent-guide.md`, then the smallest matching example in
|
|
19
|
+
`docs/recipes.md`. These paths are relative to the **Tubeless package**, not
|
|
20
|
+
this installed skill or the consumer's repository. If package docs are absent,
|
|
21
|
+
start with the [public documentation index](https://tubeless.io/llms.txt),
|
|
22
|
+
[agent guide](https://tubeless.io/docs/agent-guide.md), and
|
|
23
|
+
[recipes](https://tubeless.io/docs/recipes.md). Confirm newer examples against
|
|
24
|
+
the installed declarations before using them; do not silently upgrade Tubeless.
|
|
25
|
+
|
|
26
|
+
## Authoring decisions
|
|
27
|
+
|
|
28
|
+
- Use `createSteps<TDomainOptions>()` per pipeline. Domain options contain
|
|
29
|
+
business inputs; pass built-in controls separately to `run(options, controls?)`.
|
|
30
|
+
- Give steps stable kebab-case IDs and descriptions of their domain work.
|
|
31
|
+
`name` is an optional display label. Return values from steps and consume
|
|
32
|
+
inferred dependency outputs instead of sharing mutable state.
|
|
33
|
+
- Use `dependsOn` for required outputs, `optionalDependsOn` for expected
|
|
34
|
+
absence, and `skipAfterFailureOf` for a failure gate without a required value.
|
|
35
|
+
Keep publication dependent on successful validation.
|
|
36
|
+
- Use `step.skippable` for an intentional successful omission. Handle its
|
|
37
|
+
`T | undefined` output explicitly; do not turn exceptions into skips.
|
|
38
|
+
- Set `dryRun: "skip"` on writes and other external side effects, or supply a
|
|
39
|
+
side-effect-free typed preview handler. Unmarked steps still run in dry runs.
|
|
40
|
+
- Use `requireOutputs` when the final result requires specific outputs. A plain
|
|
41
|
+
finalizer is appropriate when partial results are valid domain results.
|
|
42
|
+
- Use `runOrThrow` for callers expecting a successful value, `run` for callers
|
|
43
|
+
inspecting structured failures or partial results, and `plan(controls)` when
|
|
44
|
+
no work should execute. Plans do not validate domain input.
|
|
45
|
+
- Use `context.log`, forward `context.signal`, use `context.sleep` for waits,
|
|
46
|
+
and report progress for long loops. Resolve relative paths from `context.cwd`.
|
|
47
|
+
- Branch on structured error `code`, `phase`, and `kind`, not message text.
|
|
48
|
+
|
|
49
|
+
## Add only the capabilities the workflow needs
|
|
50
|
+
|
|
51
|
+
Read the corresponding package recipe before using these features:
|
|
52
|
+
|
|
53
|
+
- `fromPipeline` for an independently useful child workflow;
|
|
54
|
+
`forEachPipeline` for runtime fan-out with stable keys and bounded concurrency.
|
|
55
|
+
Use ordinary helpers or `runConcurrent` for lightweight work without child
|
|
56
|
+
lifecycle reporting.
|
|
57
|
+
- `createSteps(optionsSchema)`, `outputSchema`, or `resultSchema` for runtime
|
|
58
|
+
validation at untrusted boundaries. Reuse the project's Standard Schema
|
|
59
|
+
implementation; core needs no schema dependency.
|
|
60
|
+
- `definePipelineCommand` from `tubeless/workbench` for pipeline-backed scripts.
|
|
61
|
+
Built-in `--step` / `--target` flags map to `stepIds` / `targets`. Do not
|
|
62
|
+
redeclare built-in flags. Read `docs/cli.md` for option mapping.
|
|
63
|
+
- Use `pipelines/<name>.ts` for definitions and `scripts/<name>.ts` for command
|
|
64
|
+
wrappers when introducing a layout. Register commands explicitly in
|
|
65
|
+
`tubeless.project.ts`; adapt the package's `examples/catalog/` without copying
|
|
66
|
+
unrelated example IDs. Preserve existing consumer conventions.
|
|
67
|
+
- Keep storage and Studio optional. Read `docs/studio.md` before adding them;
|
|
68
|
+
read the composition guides before adding child or remote execution.
|
|
69
|
+
- Implement concrete JSON or telemetry adapters in the application against
|
|
70
|
+
`PipelineTraceExporter`; adapt the package's `examples/tracing.ts`. Use
|
|
71
|
+
`composeTraceExporters` for multiple trace destinations. Finished traces
|
|
72
|
+
can be read with `tubeless history --trace` or `tubeless ui --trace`; recorded
|
|
73
|
+
contents are not redacted.
|
|
74
|
+
|
|
75
|
+
## Verify
|
|
76
|
+
|
|
77
|
+
Typecheck the consumer and run focused tests. Use `createPipelineTestRuntime`
|
|
78
|
+
from `tubeless/testing` for deterministic observation and cancellation, with
|
|
79
|
+
fake I/O for side effects. Check failure gates, skipped outputs, and dry-run
|
|
80
|
+
behavior. Generate diagrams with `pipeline.toMermaid()` when useful.
|
|
81
|
+
|
|
82
|
+
Within the Tubeless source repository, also follow its `AGENTS.md` and run
|
|
83
|
+
`make check`. For repository evaluations, write `solution.ts` in a disposable
|
|
84
|
+
directory and compile with `bun run eval:agent --`; do not execute model-written
|
|
85
|
+
submissions in the repository. Follow `docs/agent-evaluations.md` for assessment.
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: tubeless-make-pipeline
|
|
3
|
+
description: Convert existing TypeScript or JavaScript scripts, jobs, service workflows, or ETL code into thoughtfully structured, typed Tubeless pipelines. Use when asked to extract a pipeline from existing code, replace ad hoc orchestration, or organize a large workflow into typed steps while preserving behavior.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Make a Tubeless pipeline from existing code
|
|
7
|
+
|
|
8
|
+
Implement the conversion in the user's project. Preserve observable behavior and
|
|
9
|
+
reuse domain functions while making data flow, ordering, and failure policy
|
|
10
|
+
visible in a typed graph.
|
|
11
|
+
|
|
12
|
+
## Ground the design in the existing code
|
|
13
|
+
|
|
14
|
+
Read the entrypoint, its callers, relevant helpers, tests, and package manifest.
|
|
15
|
+
Trace inputs, outputs, external effects, error handling, retries, cancellation,
|
|
16
|
+
and any shared mutable state. Identify ordering requirements that are currently
|
|
17
|
+
implicit, such as validation before publication or writes inside a transaction.
|
|
18
|
+
Preserve transaction boundaries, cleanup, and resource ownership when splitting
|
|
19
|
+
work. A transaction or critical section can remain inside one step.
|
|
20
|
+
|
|
21
|
+
Identify the installed Tubeless version. Read `docs/agent-guide.md` and the
|
|
22
|
+
smallest matching `docs/recipes.md` example from that installed package, or from
|
|
23
|
+
the package root when working in Tubeless itself. These are package paths, not
|
|
24
|
+
paths relative to this skill. If unavailable, use the
|
|
25
|
+
[agent guide](https://tubeless.io/docs/agent-guide.md) and
|
|
26
|
+
[recipe index](https://tubeless.io/docs/recipes.md), checking APIs against the
|
|
27
|
+
installed declarations. If Tubeless is missing, add it using the project's
|
|
28
|
+
package manager and dependency conventions. Avoid unrelated upgrades.
|
|
29
|
+
|
|
30
|
+
Before editing, briefly explain the proposed steps, their inputs and outputs,
|
|
31
|
+
and which steps can write externally. Resolve uncertainties from code and tests;
|
|
32
|
+
ask only when a missing domain decision affects correctness.
|
|
33
|
+
|
|
34
|
+
## Choose meaningful boundaries
|
|
35
|
+
|
|
36
|
+
Create steps around domain outcomes an operator would recognize: load records,
|
|
37
|
+
validate a batch, enrich records, build an artifact, publish it. A useful step
|
|
38
|
+
has a coherent output and a reason to appear in a plan or failure report.
|
|
39
|
+
|
|
40
|
+
- Keep small parsing, formatting, and mapping functions as helpers inside a
|
|
41
|
+
step. Do not make every function call a step or hide the entire old script in
|
|
42
|
+
one step when it contains distinct stages.
|
|
43
|
+
- Build dependencies from consumed values and required ordering. Do not rely
|
|
44
|
+
on declaration order. Keep independent work independent, but preserve the
|
|
45
|
+
original concurrency, rate limits, and ordering unless a change is intended.
|
|
46
|
+
- Use `dependsOn` when a value or successful prerequisite is required. Use
|
|
47
|
+
`optionalDependsOn` only when absence is a valid case. Use
|
|
48
|
+
`skipAfterFailureOf` when a failed or cancelled prerequisite must block work
|
|
49
|
+
without requiring its output. Optional input alone is not a failure gate.
|
|
50
|
+
- Replace shared mutable accumulators with returned step outputs. Reuse
|
|
51
|
+
domain types and helpers; avoid a generic bag of optional fields passed
|
|
52
|
+
through every step.
|
|
53
|
+
- Extract a child pipeline only when that workflow is independently useful.
|
|
54
|
+
Choose `forEachPipeline` for per-item lifecycle reporting; use bounded helper
|
|
55
|
+
concurrency when that reporting is unnecessary. Read the matching recipe
|
|
56
|
+
before introducing either.
|
|
57
|
+
|
|
58
|
+
## Keep the graph typed
|
|
59
|
+
|
|
60
|
+
Use one `createSteps<TDomainOptions>()` builder and one `definePipeline` per
|
|
61
|
+
pipeline. Import from `tubeless` and its public subpaths. Keep business inputs
|
|
62
|
+
in domain options; built-in run controls belong in the separate controls argument.
|
|
63
|
+
|
|
64
|
+
Give each step a stable literal kebab-case ID and an operational description.
|
|
65
|
+
Let `dependsOn: [step]` infer input types from returned outputs. Preserve
|
|
66
|
+
inference in step collections; do not widen IDs to `string` or use `any`, broad
|
|
67
|
+
records, or type assertions to bypass graph errors. Add explicit output types
|
|
68
|
+
where they define a domain contract, rather than annotating every intermediate.
|
|
69
|
+
|
|
70
|
+
Parse untrusted inputs as `unknown` and validate at the boundary with existing
|
|
71
|
+
project validators. Use Standard Schema support when it fits existing schemas;
|
|
72
|
+
TypeScript alone does not validate external data.
|
|
73
|
+
|
|
74
|
+
Use `requireOutputs` for a result that requires completed steps. If dry runs,
|
|
75
|
+
intentional skips, or best-effort execution legitimately produce partial
|
|
76
|
+
results, model that result explicitly and narrow missing values. Do not default
|
|
77
|
+
a missing required output to an empty array, `false`, or a fake success value.
|
|
78
|
+
|
|
79
|
+
## Preserve operational behavior
|
|
80
|
+
|
|
81
|
+
- Mark filesystem writes, database mutations, publication, and other external
|
|
82
|
+
side effects with `dryRun: "skip"` or a typed, side-effect-free `dryRun`
|
|
83
|
+
handler. Unmarked steps still execute in a dry run. Read-only fetches may
|
|
84
|
+
still contact real systems; use fake I/O during verification.
|
|
85
|
+
- Follow skipped values through downstream steps and the finalizer. A skipped
|
|
86
|
+
write may prevent `requireOutputs` from producing a result. Either preserve
|
|
87
|
+
that contract or define an honest preview result; never fabricate a completed
|
|
88
|
+
write to make dry runs pass.
|
|
89
|
+
- Keep validation required for publication. Test this even with
|
|
90
|
+
`continueOnError` if the caller enables best-effort execution.
|
|
91
|
+
- Use `step.skippable` only for a successful policy decision. Preserve thrown
|
|
92
|
+
failures and their causes; branch on structured Tubeless error fields.
|
|
93
|
+
Map pipeline errors at the caller boundary if existing callers require a
|
|
94
|
+
specific error contract.
|
|
95
|
+
- Use `context.log` inside steps. Pass `context.signal` to I/O and retry helpers,
|
|
96
|
+
use `context.sleep` for waits, and report progress for long loops. Preserve
|
|
97
|
+
existing retry scope and idempotency; do not add retries to unsafe mutations.
|
|
98
|
+
- Keep pipeline imports free of execution and external effects. Leave invocation
|
|
99
|
+
in the existing caller or a thin command wrapper. Retain externally used
|
|
100
|
+
function signatures and return shapes unless the user requests changing them.
|
|
101
|
+
- Use `runOrThrow(options, controls?)` for value-or-error callers and `run` for
|
|
102
|
+
explicit report handling. `continueOnError` does not make `runOrThrow` succeed
|
|
103
|
+
after failures. Keep cleanup in a reliable `finally` or resource scope.
|
|
104
|
+
|
|
105
|
+
Respect the project's layout. If it has no convention, use
|
|
106
|
+
`pipelines/<name>.ts` and export `<Name>Pipeline`. Add a
|
|
107
|
+
`definePipelineCommand` wrapper only for a CLI workflow, following the CLI
|
|
108
|
+
recipe; register it in an existing project catalog when appropriate. Keep
|
|
109
|
+
storage, Studio, telemetry exporters, and remote engines optional.
|
|
110
|
+
|
|
111
|
+
## Prove the conversion
|
|
112
|
+
|
|
113
|
+
Typecheck and run the project's focused tests. Adapt existing tests to compare
|
|
114
|
+
observable behavior, not just graph shape. Cover relevant success, validation
|
|
115
|
+
failure, dependency failure, intentional skip, cancellation, and dry-run paths.
|
|
116
|
+
Use `createPipelineTestRuntime` from `tubeless/testing` with fake I/O, and assert
|
|
117
|
+
that forbidden writes never happen. Avoid executing production work as a test.
|
|
118
|
+
|
|
119
|
+
Inspect `pipeline.plan()` and, for declared goals, target selection. Planning
|
|
120
|
+
does not execute steps or validate domain inputs; it cannot prove runtime
|
|
121
|
+
correctness. Generate a graph with `pipeline.toMermaid()` when it helps explain
|
|
122
|
+
the design. Within the Tubeless source repository, also run `make check`.
|
|
123
|
+
|
|
124
|
+
Finish with the implemented step structure, preserved or intentionally changed
|
|
125
|
+
behavior, validation results, and any unresolved contract decisions. Remove
|
|
126
|
+
superseded orchestration once callers use the pipeline; keep reusable helpers.
|
package/dist/node/env.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function requireEnv(varName: string, usedBy: string): string;
|
package/dist/node/env.js
DELETED
package/dist/node/env.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"env.js","sourceRoot":"","sources":["../../src/node/env.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,MAAc;IACxD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,GAAG,OAAO,oBAAoB,MAAM,EAAE,CAAC,CAAC;IAC1D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC","sourcesContent":["export function requireEnv(varName: string, usedBy: string): string {\n const value = process.env[varName];\n if (!value) {\n throw new Error(`${varName} is required for ${usedBy}`);\n }\n return value;\n}\n"]}
|
package/dist/node/file-utils.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import { writeAtomicText } from "./atomic-text.js";
|
|
3
|
-
export function writeJson(filePath, value) {
|
|
4
|
-
writeAtomicText(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
5
|
-
}
|
|
6
|
-
export function readJson(filePath) {
|
|
7
|
-
// SAFETY: the caller's type parameter T is the contract for the file's
|
|
8
|
-
// contents; the file is expected to hold JSON matching T (files written by
|
|
9
|
-
// writeJson round-trip). JSON.parse returns `any`, so the assertion only
|
|
10
|
-
// documents the caller's guarantee — it performs no runtime narrowing.
|
|
11
|
-
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
|
|
12
|
-
}
|
|
13
|
-
export function resetDir(dirPath) {
|
|
14
|
-
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
15
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=file-utils.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"file-utils.js","sourceRoot":"","sources":["../../src/node/file-utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,KAAc;IACxD,eAAe,CAAC,QAAQ,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC;AACnE,CAAC;AAED,MAAM,UAAU,QAAQ,CAAI,QAAgB;IAC1C,uEAAuE;IACvE,2EAA2E;IAC3E,yEAAyE;IACzE,uEAAuE;IACvE,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAM,CAAC;AAC7D,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAC7C,CAAC","sourcesContent":["import * as fs from \"fs\";\nimport { writeAtomicText } from \"./atomic-text.js\";\n\nexport function writeJson(filePath: string, value: unknown): void {\n writeAtomicText(filePath, `${JSON.stringify(value, null, 2)}\\n`);\n}\n\nexport function readJson<T>(filePath: string): T {\n // SAFETY: the caller's type parameter T is the contract for the file's\n // contents; the file is expected to hold JSON matching T (files written by\n // writeJson round-trip). JSON.parse returns `any`, so the assertion only\n // documents the caller's guarantee — it performs no runtime narrowing.\n return JSON.parse(fs.readFileSync(filePath, \"utf-8\")) as T;\n}\n\nexport function resetDir(dirPath: string): void {\n fs.rmSync(dirPath, { recursive: true, force: true });\n fs.mkdirSync(dirPath, { recursive: true });\n}\n"]}
|
package/dist/node/paths.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Declares a set of workspace-relative default paths as one factory instead of one
|
|
3
|
-
* `function defaultXDir(cwd) { return path.join(cwd, "...") }` per path. The returned
|
|
4
|
-
* function re-resolves against `cwd` on every call — nothing is computed or cached at
|
|
5
|
-
* module-load time, so a frozen-`process.cwd()` bug cannot be reintroduced by
|
|
6
|
-
* construction.
|
|
7
|
-
*/
|
|
8
|
-
export declare function definePaths<T extends Record<string, string>>(relativePaths: T): (cwd: string) => T;
|
package/dist/node/paths.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import * as path from "path";
|
|
2
|
-
/**
|
|
3
|
-
* Declares a set of workspace-relative default paths as one factory instead of one
|
|
4
|
-
* `function defaultXDir(cwd) { return path.join(cwd, "...") }` per path. The returned
|
|
5
|
-
* function re-resolves against `cwd` on every call — nothing is computed or cached at
|
|
6
|
-
* module-load time, so a frozen-`process.cwd()` bug cannot be reintroduced by
|
|
7
|
-
* construction.
|
|
8
|
-
*/
|
|
9
|
-
export function definePaths(relativePaths) {
|
|
10
|
-
return (cwd) =>
|
|
11
|
-
// SAFETY: Object.entries(relativePaths) yields exactly T's keys, and every
|
|
12
|
-
// value is path.join(cwd, relativePath), a string — so the result satisfies
|
|
13
|
-
// T's Record<string, string> constraint.
|
|
14
|
-
Object.fromEntries(Object.entries(relativePaths).map(([key, relativePath]) => [
|
|
15
|
-
key,
|
|
16
|
-
path.join(cwd, relativePath),
|
|
17
|
-
]));
|
|
18
|
-
}
|
|
19
|
-
//# sourceMappingURL=paths.js.map
|
package/dist/node/paths.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/node/paths.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B;;;;;;GAMG;AACH,MAAM,UAAU,WAAW,CACzB,aAAgB;IAEhB,OAAO,CAAC,GAAW,EAAE,EAAE;IACrB,2EAA2E;IAC3E,4EAA4E;IAC5E,yCAAyC;IACzC,MAAM,CAAC,WAAW,CAChB,MAAM,CAAC,OAAO,CAAC,aAAa,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,YAAY,CAAC,EAAE,EAAE,CAAC;QACzD,GAAG;QACH,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,YAAY,CAAC;KAC7B,CAAC,CACE,CAAC;AACX,CAAC","sourcesContent":["import * as path from \"path\";\n\n/**\n * Declares a set of workspace-relative default paths as one factory instead of one\n * `function defaultXDir(cwd) { return path.join(cwd, \"...\") }` per path. The returned\n * function re-resolves against `cwd` on every call — nothing is computed or cached at\n * module-load time, so a frozen-`process.cwd()` bug cannot be reintroduced by\n * construction.\n */\nexport function definePaths<T extends Record<string, string>>(\n relativePaths: T\n): (cwd: string) => T {\n return (cwd: string) =>\n // SAFETY: Object.entries(relativePaths) yields exactly T's keys, and every\n // value is path.join(cwd, relativePath), a string — so the result satisfies\n // T's Record<string, string> constraint.\n Object.fromEntries(\n Object.entries(relativePaths).map(([key, relativePath]) => [\n key,\n path.join(cwd, relativePath),\n ])\n ) as T;\n}\n"]}
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* One option in an interactive multi-select list.
|
|
3
|
-
* Domain packages supply their own values and labels; this module stays generic.
|
|
4
|
-
*/
|
|
5
|
-
export interface MultiSelectChoice<T extends string = string> {
|
|
6
|
-
value: T;
|
|
7
|
-
/** Shown in fzf/readline. Defaults to `value`. */
|
|
8
|
-
label?: string;
|
|
9
|
-
}
|
|
10
|
-
/**
|
|
11
|
-
* Result of a multi-select prompt.
|
|
12
|
-
* - `all`: user chose the explicit all-option (meaning is domain-defined)
|
|
13
|
-
* - `values`: one or more concrete choice values, in choice-list order
|
|
14
|
-
*/
|
|
15
|
-
export type MultiSelectResult<T extends string = string> = {
|
|
16
|
-
kind: "all";
|
|
17
|
-
} | {
|
|
18
|
-
kind: "values";
|
|
19
|
-
values: readonly T[];
|
|
20
|
-
};
|
|
21
|
-
export interface PromptMultiSelectOptions<T extends string = string> {
|
|
22
|
-
/** Items the user may pick. */
|
|
23
|
-
choices: readonly (MultiSelectChoice<T> | T)[];
|
|
24
|
-
/**
|
|
25
|
-
* When true, show an "all" row that returns `{ kind: "all" }`.
|
|
26
|
-
* Domains decide what "all" expands to after the prompt returns.
|
|
27
|
-
*/
|
|
28
|
-
allowAll?: boolean;
|
|
29
|
-
/** Label for the all-option row. Defaults to `"All"`. */
|
|
30
|
-
allLabel?: string;
|
|
31
|
-
/** Value token recognized as all (input parse + fzf). Defaults to `"all"`. */
|
|
32
|
-
allValue?: string;
|
|
33
|
-
/** Heading printed above the numbered fallback menu. */
|
|
34
|
-
title?: string;
|
|
35
|
-
/** fzf `--header` text. */
|
|
36
|
-
header?: string;
|
|
37
|
-
/** fzf `--prompt` and readline question prefix. Defaults to `"> "`. */
|
|
38
|
-
prompt?: string;
|
|
39
|
-
/** Prefer fzf multi-select when the binary is available. Defaults to true. */
|
|
40
|
-
preferFzf?: boolean;
|
|
41
|
-
stdin?: NodeJS.ReadableStream;
|
|
42
|
-
stdout?: NodeJS.WritableStream;
|
|
43
|
-
}
|
|
44
|
-
export interface ParseMultiSelectInputOptions {
|
|
45
|
-
allowAll?: boolean;
|
|
46
|
-
allValue?: string;
|
|
47
|
-
}
|
|
48
|
-
/** Normalize string or `{ value, label }` choices into a stable list. */
|
|
49
|
-
export declare function normalizeMultiSelectChoices<T extends string>(choices: readonly (MultiSelectChoice<T> | T)[]): MultiSelectChoice<T>[];
|
|
50
|
-
/**
|
|
51
|
-
* Parse a space/comma-separated selection of 1-based indices, choice values,
|
|
52
|
-
* or the all-token. Returns null when the input is empty or unparseable.
|
|
53
|
-
*/
|
|
54
|
-
export declare function parseMultiSelectInput<T extends string>(input: string, choices: readonly (MultiSelectChoice<T> | T)[], options?: ParseMultiSelectInputOptions): MultiSelectResult<T> | null;
|
|
55
|
-
/**
|
|
56
|
-
* Interactively multi-select from a list of choices.
|
|
57
|
-
*
|
|
58
|
-
* Prefers fzf multi-select when available; falls back to a numbered prompt.
|
|
59
|
-
* Returns `null` when the user cancels or the session is non-interactive.
|
|
60
|
-
*
|
|
61
|
-
* @example
|
|
62
|
-
* ```ts
|
|
63
|
-
* import { promptMultiSelect } from "tubeless/cli";
|
|
64
|
-
*
|
|
65
|
-
* const picked = await promptMultiSelect({
|
|
66
|
-
* choices: [
|
|
67
|
-
* { value: "shard-a", label: "Shard A (east)" },
|
|
68
|
-
* { value: "shard-b", label: "Shard B (west)" },
|
|
69
|
-
* ],
|
|
70
|
-
* allowAll: true,
|
|
71
|
-
* allLabel: "All shards",
|
|
72
|
-
* title: "Select shards to process",
|
|
73
|
-
* prompt: "shards> ",
|
|
74
|
-
* });
|
|
75
|
-
* if (!picked) throw new Error("Selection cancelled");
|
|
76
|
-
* if (picked.kind === "all") runAll();
|
|
77
|
-
* else runSelected(picked.values);
|
|
78
|
-
* ```
|
|
79
|
-
*/
|
|
80
|
-
export declare function promptMultiSelect<T extends string>(options: PromptMultiSelectOptions<T>): Promise<MultiSelectResult<T> | null>;
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from "node:child_process";
|
|
2
|
-
import * as readline from "node:readline/promises";
|
|
3
|
-
function canUseInteractivePrompt(stdin, stdout) {
|
|
4
|
-
const inTty = "isTTY" in stdin && stdin.isTTY === true;
|
|
5
|
-
const outTty = "isTTY" in stdout && stdout.isTTY === true;
|
|
6
|
-
return Boolean(inTty && outTty);
|
|
7
|
-
}
|
|
8
|
-
function fzfAvailable() {
|
|
9
|
-
const result = spawnSync("fzf", ["--version"], {
|
|
10
|
-
encoding: "utf8",
|
|
11
|
-
stdio: ["ignore", "pipe", "ignore"],
|
|
12
|
-
});
|
|
13
|
-
return result.status === 0;
|
|
14
|
-
}
|
|
15
|
-
function isStringChoice(choice) {
|
|
16
|
-
return typeof choice === "string";
|
|
17
|
-
}
|
|
18
|
-
/** Normalize string or `{ value, label }` choices into a stable list. */
|
|
19
|
-
export function normalizeMultiSelectChoices(choices) {
|
|
20
|
-
return choices.map((choice) => isStringChoice(choice)
|
|
21
|
-
? { value: choice, label: choice }
|
|
22
|
-
: { ...choice, label: choice.label ?? choice.value });
|
|
23
|
-
}
|
|
24
|
-
function displayLabel(choice) {
|
|
25
|
-
return choice.label ?? choice.value;
|
|
26
|
-
}
|
|
27
|
-
/**
|
|
28
|
-
* Parse a space/comma-separated selection of 1-based indices, choice values,
|
|
29
|
-
* or the all-token. Returns null when the input is empty or unparseable.
|
|
30
|
-
*/
|
|
31
|
-
export function parseMultiSelectInput(input, choices, options = {}) {
|
|
32
|
-
const normalized = normalizeMultiSelectChoices(choices);
|
|
33
|
-
const allowAll = options.allowAll ?? false;
|
|
34
|
-
const allValue = (options.allValue ?? "all").toLowerCase();
|
|
35
|
-
const trimmed = input.trim().toLowerCase();
|
|
36
|
-
if (!trimmed)
|
|
37
|
-
return null;
|
|
38
|
-
if (allowAll && (trimmed === allValue || trimmed === "*")) {
|
|
39
|
-
return { kind: "all" };
|
|
40
|
-
}
|
|
41
|
-
const tokens = trimmed.split(/[\s,]+/).filter(Boolean);
|
|
42
|
-
if (tokens.length === 0)
|
|
43
|
-
return null;
|
|
44
|
-
const valueByLower = new Map(normalized.map((choice) => [choice.value.toLowerCase(), choice.value]));
|
|
45
|
-
const selected = new Set();
|
|
46
|
-
for (const token of tokens) {
|
|
47
|
-
if (allowAll && (token === allValue || token === "*")) {
|
|
48
|
-
return { kind: "all" };
|
|
49
|
-
}
|
|
50
|
-
const asIndex = Number(token);
|
|
51
|
-
if (Number.isInteger(asIndex) && asIndex >= 1 && asIndex <= normalized.length) {
|
|
52
|
-
selected.add(normalized[asIndex - 1].value);
|
|
53
|
-
continue;
|
|
54
|
-
}
|
|
55
|
-
const matched = valueByLower.get(token);
|
|
56
|
-
if (matched !== undefined) {
|
|
57
|
-
selected.add(matched);
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
if (selected.size === 0)
|
|
63
|
-
return null;
|
|
64
|
-
return {
|
|
65
|
-
kind: "values",
|
|
66
|
-
values: normalized.map((choice) => choice.value).filter((value) => selected.has(value)),
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
function promptWithFzf(choices, options) {
|
|
70
|
-
const lines = [
|
|
71
|
-
...(options.allowAll ? [`${options.allValue}\t${options.allLabel}`] : []),
|
|
72
|
-
...choices.map((choice) => `${choice.value}\t${displayLabel(choice)}`),
|
|
73
|
-
];
|
|
74
|
-
// Candidate list is piped on stdin; fzf opens /dev/tty for the interactive UI.
|
|
75
|
-
const result = spawnSync("fzf", [
|
|
76
|
-
"--multi",
|
|
77
|
-
"--delimiter=\t",
|
|
78
|
-
"--with-nth=2..",
|
|
79
|
-
`--header=${options.header}`,
|
|
80
|
-
`--prompt=${options.prompt}`,
|
|
81
|
-
"--height=40%",
|
|
82
|
-
"--reverse",
|
|
83
|
-
], {
|
|
84
|
-
input: `${lines.join("\n")}\n`,
|
|
85
|
-
encoding: "utf8",
|
|
86
|
-
stdio: ["pipe", "pipe", "inherit"],
|
|
87
|
-
});
|
|
88
|
-
if (result.status !== 0) {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
const selectedLines = (result.stdout ?? "")
|
|
92
|
-
.split("\n")
|
|
93
|
-
.map((line) => line.trim())
|
|
94
|
-
.filter(Boolean);
|
|
95
|
-
if (selectedLines.length === 0)
|
|
96
|
-
return null;
|
|
97
|
-
const allToken = options.allValue.toLowerCase();
|
|
98
|
-
if (options.allowAll &&
|
|
99
|
-
selectedLines.some((line) => {
|
|
100
|
-
const key = (line.split("\t")[0] ?? line).toLowerCase();
|
|
101
|
-
return key === allToken;
|
|
102
|
-
})) {
|
|
103
|
-
return { kind: "all" };
|
|
104
|
-
}
|
|
105
|
-
const selected = new Set();
|
|
106
|
-
for (const line of selectedLines) {
|
|
107
|
-
const key = line.split("\t")[0] ?? "";
|
|
108
|
-
const matched = choices.find((choice) => choice.value === key);
|
|
109
|
-
if (matched)
|
|
110
|
-
selected.add(matched.value);
|
|
111
|
-
}
|
|
112
|
-
if (selected.size === 0)
|
|
113
|
-
return null;
|
|
114
|
-
return {
|
|
115
|
-
kind: "values",
|
|
116
|
-
values: choices.map((choice) => choice.value).filter((value) => selected.has(value)),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
async function promptWithReadline(choices, options) {
|
|
120
|
-
const output = options.stdout;
|
|
121
|
-
output.write(`${options.title}:\n`);
|
|
122
|
-
if (options.allowAll) {
|
|
123
|
-
output.write(` ${options.allValue} ${options.allLabel}\n`);
|
|
124
|
-
}
|
|
125
|
-
choices.forEach((choice, index) => {
|
|
126
|
-
output.write(` ${String(index + 1).padStart(2, " ")}. ${displayLabel(choice)}\n`);
|
|
127
|
-
});
|
|
128
|
-
const allHint = options.allowAll ? `, or '${options.allValue}'` : "";
|
|
129
|
-
output.write(`\nEnter numbers and/or values (space/comma separated)${allHint}. Empty cancels.\n`);
|
|
130
|
-
const rl = readline.createInterface({ input: options.stdin, output: options.stdout });
|
|
131
|
-
try {
|
|
132
|
-
const answer = await rl.question(options.prompt.endsWith(" ") ? options.prompt : `${options.prompt} `);
|
|
133
|
-
return parseMultiSelectInput(answer, choices, {
|
|
134
|
-
allowAll: options.allowAll,
|
|
135
|
-
allValue: options.allValue,
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
finally {
|
|
139
|
-
rl.close();
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
/**
|
|
143
|
-
* Interactively multi-select from a list of choices.
|
|
144
|
-
*
|
|
145
|
-
* Prefers fzf multi-select when available; falls back to a numbered prompt.
|
|
146
|
-
* Returns `null` when the user cancels or the session is non-interactive.
|
|
147
|
-
*
|
|
148
|
-
* @example
|
|
149
|
-
* ```ts
|
|
150
|
-
* import { promptMultiSelect } from "tubeless/cli";
|
|
151
|
-
*
|
|
152
|
-
* const picked = await promptMultiSelect({
|
|
153
|
-
* choices: [
|
|
154
|
-
* { value: "shard-a", label: "Shard A (east)" },
|
|
155
|
-
* { value: "shard-b", label: "Shard B (west)" },
|
|
156
|
-
* ],
|
|
157
|
-
* allowAll: true,
|
|
158
|
-
* allLabel: "All shards",
|
|
159
|
-
* title: "Select shards to process",
|
|
160
|
-
* prompt: "shards> ",
|
|
161
|
-
* });
|
|
162
|
-
* if (!picked) throw new Error("Selection cancelled");
|
|
163
|
-
* if (picked.kind === "all") runAll();
|
|
164
|
-
* else runSelected(picked.values);
|
|
165
|
-
* ```
|
|
166
|
-
*/
|
|
167
|
-
export async function promptMultiSelect(options) {
|
|
168
|
-
const choices = normalizeMultiSelectChoices(options.choices);
|
|
169
|
-
if (choices.length === 0) {
|
|
170
|
-
throw new Error("promptMultiSelect requires at least one choice");
|
|
171
|
-
}
|
|
172
|
-
const stdin = options.stdin ?? process.stdin;
|
|
173
|
-
const stdout = options.stdout ?? process.stdout;
|
|
174
|
-
const preferFzf = options.preferFzf ?? true;
|
|
175
|
-
const allowAll = options.allowAll ?? false;
|
|
176
|
-
const allValue = options.allValue ?? "all";
|
|
177
|
-
const allLabel = options.allLabel ?? "All";
|
|
178
|
-
const title = options.title ?? "Select one or more options";
|
|
179
|
-
const header = options.header ?? "Select options (TAB multi-select, Enter confirm)";
|
|
180
|
-
const prompt = options.prompt ?? "> ";
|
|
181
|
-
if (!canUseInteractivePrompt(stdin, stdout)) {
|
|
182
|
-
return null;
|
|
183
|
-
}
|
|
184
|
-
if (preferFzf && fzfAvailable()) {
|
|
185
|
-
return promptWithFzf(choices, { allowAll, allLabel, allValue, header, prompt });
|
|
186
|
-
}
|
|
187
|
-
return promptWithReadline(choices, {
|
|
188
|
-
allowAll,
|
|
189
|
-
allLabel,
|
|
190
|
-
allValue,
|
|
191
|
-
title,
|
|
192
|
-
prompt,
|
|
193
|
-
stdin,
|
|
194
|
-
stdout,
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
//# sourceMappingURL=prompt-select.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-select.js","sourceRoot":"","sources":["../../src/reporter/prompt-select.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from \"node:child_process\";\nimport * as readline from \"node:readline/promises\";\n\n/**\n * One option in an interactive multi-select list.\n * Domain packages supply their own values and labels; this module stays generic.\n */\nexport interface MultiSelectChoice<T extends string = string> {\n value: T;\n /** Shown in fzf/readline. Defaults to `value`. */\n label?: string;\n}\n\n/**\n * Result of a multi-select prompt.\n * - `all`: user chose the explicit all-option (meaning is domain-defined)\n * - `values`: one or more concrete choice values, in choice-list order\n */\nexport type MultiSelectResult<T extends string = string> =\n | { kind: \"all\" }\n | { kind: \"values\"; values: readonly T[] };\n\nexport interface PromptMultiSelectOptions<T extends string = string> {\n /** Items the user may pick. */\n choices: readonly (MultiSelectChoice<T> | T)[];\n /**\n * When true, show an \"all\" row that returns `{ kind: \"all\" }`.\n * Domains decide what \"all\" expands to after the prompt returns.\n */\n allowAll?: boolean;\n /** Label for the all-option row. Defaults to `\"All\"`. */\n allLabel?: string;\n /** Value token recognized as all (input parse + fzf). Defaults to `\"all\"`. */\n allValue?: string;\n /** Heading printed above the numbered fallback menu. */\n title?: string;\n /** fzf `--header` text. */\n header?: string;\n /** fzf `--prompt` and readline question prefix. Defaults to `\"> \"`. */\n prompt?: string;\n /** Prefer fzf multi-select when the binary is available. Defaults to true. */\n preferFzf?: boolean;\n stdin?: NodeJS.ReadableStream;\n stdout?: NodeJS.WritableStream;\n}\n\nexport interface ParseMultiSelectInputOptions {\n allowAll?: boolean;\n allValue?: string;\n}\n\nfunction canUseInteractivePrompt(\n stdin: NodeJS.ReadableStream,\n stdout: NodeJS.WritableStream\n): boolean {\n const inTty = \"isTTY\" in stdin && stdin.isTTY === true;\n const outTty = \"isTTY\" in stdout && stdout.isTTY === true;\n return Boolean(inTty && outTty);\n}\n\nfunction fzfAvailable(): boolean {\n const result = spawnSync(\"fzf\", [\"--version\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n });\n return result.status === 0;\n}\n\nfunction isStringChoice<T extends string>(choice: MultiSelectChoice<T> | T): choice is T {\n return typeof choice === \"string\";\n}\n\n/** Normalize string or `{ value, label }` choices into a stable list. */\nexport function normalizeMultiSelectChoices<T extends string>(\n choices: readonly (MultiSelectChoice<T> | T)[]\n): MultiSelectChoice<T>[] {\n return choices.map((choice) =>\n isStringChoice(choice)\n ? { value: choice, label: choice }\n : { ...choice, label: choice.label ?? choice.value }\n );\n}\n\nfunction displayLabel(choice: MultiSelectChoice<string>): string {\n return choice.label ?? choice.value;\n}\n\n/**\n * Parse a space/comma-separated selection of 1-based indices, choice values,\n * or the all-token. Returns null when the input is empty or unparseable.\n */\nexport function parseMultiSelectInput<T extends string>(\n input: string,\n choices: readonly (MultiSelectChoice<T> | T)[],\n options: ParseMultiSelectInputOptions = {}\n): MultiSelectResult<T> | null {\n const normalized = normalizeMultiSelectChoices(choices);\n const allowAll = options.allowAll ?? false;\n const allValue = (options.allValue ?? \"all\").toLowerCase();\n const trimmed = input.trim().toLowerCase();\n if (!trimmed) return null;\n\n if (allowAll && (trimmed === allValue || trimmed === \"*\")) {\n return { kind: \"all\" };\n }\n\n const tokens = trimmed.split(/[\\s,]+/).filter(Boolean);\n if (tokens.length === 0) return null;\n\n const valueByLower = new Map(\n normalized.map((choice) => [choice.value.toLowerCase(), choice.value])\n );\n const selected = new Set<T>();\n\n for (const token of tokens) {\n if (allowAll && (token === allValue || token === \"*\")) {\n return { kind: \"all\" };\n }\n const asIndex = Number(token);\n if (Number.isInteger(asIndex) && asIndex >= 1 && asIndex <= normalized.length) {\n selected.add(normalized[asIndex - 1]!.value);\n continue;\n }\n const matched = valueByLower.get(token);\n if (matched !== undefined) {\n selected.add(matched);\n continue;\n }\n return null;\n }\n\n if (selected.size === 0) return null;\n return {\n kind: \"values\",\n values: normalized.map((choice) => choice.value).filter((value) => selected.has(value)),\n };\n}\n\nfunction promptWithFzf<T extends string>(\n choices: readonly MultiSelectChoice<T>[],\n options: {\n allowAll: boolean;\n allLabel: string;\n allValue: string;\n header: string;\n prompt: string;\n }\n): MultiSelectResult<T> | null {\n const lines = [\n ...(options.allowAll ? [`${options.allValue}\\t${options.allLabel}`] : []),\n ...choices.map((choice) => `${choice.value}\\t${displayLabel(choice)}`),\n ];\n // Candidate list is piped on stdin; fzf opens /dev/tty for the interactive UI.\n const result = spawnSync(\n \"fzf\",\n [\n \"--multi\",\n \"--delimiter=\\t\",\n \"--with-nth=2..\",\n `--header=${options.header}`,\n `--prompt=${options.prompt}`,\n \"--height=40%\",\n \"--reverse\",\n ],\n {\n input: `${lines.join(\"\\n\")}\\n`,\n encoding: \"utf8\",\n stdio: [\"pipe\", \"pipe\", \"inherit\"],\n }\n );\n if (result.status !== 0) {\n return null;\n }\n const selectedLines = (result.stdout ?? \"\")\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter(Boolean);\n if (selectedLines.length === 0) return null;\n\n const allToken = options.allValue.toLowerCase();\n if (\n options.allowAll &&\n selectedLines.some((line) => {\n const key = (line.split(\"\\t\")[0] ?? line).toLowerCase();\n return key === allToken;\n })\n ) {\n return { kind: \"all\" };\n }\n\n const selected = new Set<T>();\n for (const line of selectedLines) {\n const key = line.split(\"\\t\")[0] ?? \"\";\n const matched = choices.find((choice) => choice.value === key);\n if (matched) selected.add(matched.value);\n }\n if (selected.size === 0) return null;\n return {\n kind: \"values\",\n values: choices.map((choice) => choice.value).filter((value) => selected.has(value)),\n };\n}\n\nasync function promptWithReadline<T extends string>(\n choices: readonly MultiSelectChoice<T>[],\n options: {\n allowAll: boolean;\n allLabel: string;\n allValue: string;\n title: string;\n prompt: string;\n stdin: NodeJS.ReadableStream;\n stdout: NodeJS.WritableStream;\n }\n): Promise<MultiSelectResult<T> | null> {\n const output = options.stdout;\n output.write(`${options.title}:\\n`);\n if (options.allowAll) {\n output.write(` ${options.allValue} ${options.allLabel}\\n`);\n }\n choices.forEach((choice, index) => {\n output.write(` ${String(index + 1).padStart(2, \" \")}. ${displayLabel(choice)}\\n`);\n });\n const allHint = options.allowAll ? `, or '${options.allValue}'` : \"\";\n output.write(`\\nEnter numbers and/or values (space/comma separated)${allHint}. Empty cancels.\\n`);\n\n const rl = readline.createInterface({ input: options.stdin, output: options.stdout });\n try {\n const answer = await rl.question(\n options.prompt.endsWith(\" \") ? options.prompt : `${options.prompt} `\n );\n return parseMultiSelectInput(answer, choices, {\n allowAll: options.allowAll,\n allValue: options.allValue,\n });\n } finally {\n rl.close();\n }\n}\n\n/**\n * Interactively multi-select from a list of choices.\n *\n * Prefers fzf multi-select when available; falls back to a numbered prompt.\n * Returns `null` when the user cancels or the session is non-interactive.\n *\n * @example\n * ```ts\n * import { promptMultiSelect } from \"tubeless/cli\";\n *\n * const picked = await promptMultiSelect({\n * choices: [\n * { value: \"shard-a\", label: \"Shard A (east)\" },\n * { value: \"shard-b\", label: \"Shard B (west)\" },\n * ],\n * allowAll: true,\n * allLabel: \"All shards\",\n * title: \"Select shards to process\",\n * prompt: \"shards> \",\n * });\n * if (!picked) throw new Error(\"Selection cancelled\");\n * if (picked.kind === \"all\") runAll();\n * else runSelected(picked.values);\n * ```\n */\nexport async function promptMultiSelect<T extends string>(\n options: PromptMultiSelectOptions<T>\n): Promise<MultiSelectResult<T> | null> {\n const choices = normalizeMultiSelectChoices(options.choices);\n if (choices.length === 0) {\n throw new Error(\"promptMultiSelect requires at least one choice\");\n }\n\n const stdin = options.stdin ?? process.stdin;\n const stdout = options.stdout ?? process.stdout;\n const preferFzf = options.preferFzf ?? true;\n const allowAll = options.allowAll ?? false;\n const allValue = options.allValue ?? \"all\";\n const allLabel = options.allLabel ?? \"All\";\n const title = options.title ?? \"Select one or more options\";\n const header = options.header ?? \"Select options (TAB multi-select, Enter confirm)\";\n const prompt = options.prompt ?? \"> \";\n\n if (!canUseInteractivePrompt(stdin, stdout)) {\n return null;\n }\n\n if (preferFzf && fzfAvailable()) {\n return promptWithFzf(choices, { allowAll, allLabel, allValue, header, prompt });\n }\n\n return promptWithReadline(choices, {\n allowAll,\n allLabel,\n allValue,\n title,\n prompt,\n stdin,\n stdout,\n });\n}\n"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const PIPELINE_RUN_STUDIO_CLIENT_SOURCE: string;
|