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
|
@@ -1,97 +1,8 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export declare const STEP_REMOTE: unique symbol;
|
|
7
|
-
export declare const EXECUTE_COMPILED_RUN: unique symbol;
|
|
8
|
-
/** Marks the exact object returned by `definePipeline` as a compiled pipeline. */
|
|
9
|
-
export declare function brandCompiledPipeline(pipeline: object): void;
|
|
10
|
-
/** True only for the object `definePipeline` returned, not `Object.create` wrappers. */
|
|
11
|
-
export declare function isCompiledPipeline(pipeline: object): boolean;
|
|
12
|
-
export interface CompiledStepGraph<TOptions extends object = object> {
|
|
13
|
-
readonly dependsOn: readonly AnyStep<TOptions>[];
|
|
14
|
-
readonly optionalDependsOn: readonly AnyStep<TOptions>[];
|
|
15
|
-
readonly skipAfterFailureOf: readonly AnyStep<TOptions>[];
|
|
16
|
-
}
|
|
17
|
-
export declare function compiledStepGraph<TOptions extends object>(compiled: Pick<CompiledPipeline, "stepGraph">, step: AnyStep<TOptions>): CompiledStepGraph<TOptions>;
|
|
18
|
-
/**
|
|
19
|
-
* Recovers the options type from the steps themselves rather than taking it as its own
|
|
20
|
-
* generic parameter. A `TOptions` parameter constrained by `TSteps extends
|
|
21
|
-
* readonly AnyStep<TOptions>[]` is self-referential — TypeScript can't solve that at a
|
|
22
|
-
* `definePipeline(...)` call site and silently widens `TOptions` to its bare
|
|
23
|
-
* `object` constraint (context.options would lose every pipeline-specific
|
|
24
|
-
* field). Structuring it as "derive TOptions from TSteps" instead of "constrain TSteps by
|
|
25
|
-
* TOptions" avoids the cycle, so callers never write out any of these type parameters.
|
|
26
|
-
*/
|
|
27
|
-
export type StepsOptions<TSteps extends readonly AnyStep[]> = TSteps[number] extends AnyStep<infer TOptions> ? TOptions : object;
|
|
28
|
-
export type StepsInputOptions<TSteps extends readonly AnyStep[]> = TSteps[number] extends Step<string, unknown, object, infer TInputOptions, infer _TRunOut> ? TInputOptions : StepsOptions<TSteps>;
|
|
29
|
-
export type StepIds<TSteps extends readonly AnyStep[]> = TSteps[number]["id"];
|
|
30
|
-
export type TargetIds<TTargets extends readonly AnyStep[]> = TTargets[number]["id"];
|
|
31
|
-
type StepDispositionInput<TOptions extends object> = {
|
|
32
|
-
dryRun: boolean;
|
|
33
|
-
graph: CompiledStepGraph<TOptions>;
|
|
34
|
-
planned: PipelinePlanStep;
|
|
35
|
-
reportsByStepId: ReadonlyMap<string, PipelineStepReport>;
|
|
36
|
-
step: AnyStep<TOptions>;
|
|
37
|
-
};
|
|
38
|
-
type StepDisposition = {
|
|
39
|
-
kind: "run";
|
|
40
|
-
} | {
|
|
41
|
-
kind: "skip";
|
|
42
|
-
reason: PipelineStepSkipReason;
|
|
43
|
-
dependencyId?: string;
|
|
44
|
-
message?: string;
|
|
45
|
-
};
|
|
46
|
-
/**
|
|
47
|
-
* Single skip ladder for plan and live execution.
|
|
48
|
-
* Policy skips stay outside: they run only after a step is scheduled.
|
|
49
|
-
* Remainder copies plan `skipReason` for structural skips and marks planned-run
|
|
50
|
-
* steps fail-fast or cancelled, so this ladder has no abort/fail-fast mode.
|
|
51
|
-
*/
|
|
52
|
-
export declare function decideStepDisposition<TOptions extends object>(input: StepDispositionInput<TOptions>): StepDisposition;
|
|
53
|
-
export declare function renderPipelineMermaid<TOptions extends object>(steps: readonly AnyStep<TOptions>[], options: PipelineMermaidOptions, stepGraph?: ReadonlyMap<AnyStep, CompiledStepGraph>): string;
|
|
1
|
+
import type { CompiledPipeline } from "./pipeline-compiler.js";
|
|
2
|
+
import type { StepIds, TargetIds } from "./pipeline-definition.js";
|
|
3
|
+
import { type CompiledStepGraph } from "./pipeline-graph.js";
|
|
4
|
+
import type { AnyStep } from "./pipeline-steps.js";
|
|
5
|
+
import type { PipelinePlan, PipelinePlanStep, PipelineRunControls, PipelineStepSelectionReason, PipelineStepSkipReason, StandardSchemaV1 } from "./pipeline-types.js";
|
|
54
6
|
export declare function stepToPlanStep<TOptions extends object>(step: AnyStep<TOptions>, selected: boolean, skipReason?: PipelineStepSkipReason, selectionReasons?: readonly PipelineStepSelectionReason[], graph?: CompiledStepGraph<TOptions>): PipelinePlanStep;
|
|
55
7
|
export declare function buildPipelinePlan<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][], TResultSchema extends StandardSchemaV1 | undefined>(compiled: CompiledPipeline<TSteps, TResult, TTargets, TResultSchema>, controls: PipelineRunControls<StepIds<TSteps>, TargetIds<TTargets>>): PipelinePlan;
|
|
56
8
|
export declare function planStepById(plan: PipelinePlan): Map<string, PipelinePlanStep>;
|
|
57
|
-
/** Programmer error raised immediately when a pipeline graph is invalid. */
|
|
58
|
-
export declare class PipelineDefinitionError extends Error {
|
|
59
|
-
readonly pipelineId: string;
|
|
60
|
-
readonly errors: readonly PipelineError[];
|
|
61
|
-
constructor(pipelineId: string, errors: readonly PipelineError[]);
|
|
62
|
-
}
|
|
63
|
-
export interface CompiledPipeline<TSteps extends readonly AnyStep[] = readonly AnyStep[], TResult = unknown, TTargets extends readonly TSteps[number][] = readonly [], TResultSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
64
|
-
readonly declaredTargets: readonly AnyStep<StepsOptions<TSteps>>[];
|
|
65
|
-
readonly finalize: PipelineDefinition<TSteps, TResult, TTargets, TResultSchema>["finalize"];
|
|
66
|
-
readonly id: string;
|
|
67
|
-
readonly optionsSchema: StandardSchemaV1 | undefined;
|
|
68
|
-
readonly orderedSteps: readonly AnyStep<StepsOptions<TSteps>>[];
|
|
69
|
-
readonly requiredFinalizerSteps: readonly AnyStep<StepsOptions<TSteps>>[] | undefined;
|
|
70
|
-
readonly stepGraph: ReadonlyMap<AnyStep, CompiledStepGraph>;
|
|
71
|
-
readonly resultSchema: TResultSchema | undefined;
|
|
72
|
-
readonly stepIds: readonly string[];
|
|
73
|
-
readonly targetIds: readonly string[];
|
|
74
|
-
}
|
|
75
|
-
export declare function compilePipeline<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][], TResultSchema extends StandardSchemaV1 | undefined>(definition: PipelineDefinition<TSteps, TResult, TTargets, TResultSchema>): CompiledPipeline<TSteps, TResult, TTargets, TResultSchema>;
|
|
76
|
-
type PipelineOutputs<TSteps extends readonly AnyStep[]> = {
|
|
77
|
-
[S in TSteps[number] as S["id"]]: StepOutput<S>;
|
|
78
|
-
};
|
|
79
|
-
type RequiredPipelineOutputs<TSteps extends readonly AnyStep[]> = {
|
|
80
|
-
[S in TSteps[number] as S["id"]]-?: StepOutput<S>;
|
|
81
|
-
};
|
|
82
|
-
/**
|
|
83
|
-
* Build a finalizer that only runs when every listed step published an output.
|
|
84
|
-
* Presence is checked by output slot, so a successfully published `undefined`
|
|
85
|
-
* remains distinct from a structural skip or failure.
|
|
86
|
-
*/
|
|
87
|
-
export declare function requireOutputs<const TRequiredSteps extends readonly AnyStep[], TResult>(requiredSteps: TRequiredSteps, finalize: (outputs: RequiredPipelineOutputs<TRequiredSteps>, context: PipelineExecutionContext<StepsOptions<TRequiredSteps>>) => TResult | Promise<TResult>): (outputs: Partial<RequiredPipelineOutputs<TRequiredSteps>>, context: PipelineExecutionContext<StepsOptions<TRequiredSteps>>) => TResult | Promise<TResult>;
|
|
88
|
-
export interface PipelineDefinition<TSteps extends readonly AnyStep[], TResult, TTargets extends readonly TSteps[number][] = readonly [], TResultSchema extends StandardSchemaV1 | undefined = undefined> {
|
|
89
|
-
id: string;
|
|
90
|
-
steps: TSteps;
|
|
91
|
-
/** Public downstream goals that callers may select with `targets`. */
|
|
92
|
-
targets?: TTargets;
|
|
93
|
-
/** Optional Standard Schema for the finalized result. */
|
|
94
|
-
resultSchema?: TResultSchema;
|
|
95
|
-
finalize(outputs: Partial<PipelineOutputs<TSteps>>, context: PipelineExecutionContext<StepsOptions<TSteps>>): (TResultSchema extends StandardSchemaV1 ? InferSchemaInput<TResultSchema> : TResult) | Promise<TResultSchema extends StandardSchemaV1 ? InferSchemaInput<TResultSchema> : TResult>;
|
|
96
|
-
}
|
|
97
|
-
export {};
|
|
@@ -1,221 +1,8 @@
|
|
|
1
1
|
import { duplicateValues } from "../utilities/collections.js";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
export const STEP_OPTIONS_SCHEMA = Symbol("tubeless.stepOptionsSchema");
|
|
7
|
-
export const STEP_NESTED_PIPELINE = Symbol("tubeless.stepNestedPipeline");
|
|
8
|
-
export const STEP_REMOTE = Symbol("tubeless.stepRemote");
|
|
9
|
-
const REQUIRED_FINALIZER_OUTPUTS = Symbol("tubeless.requiredFinalizerOutputs");
|
|
10
|
-
export const EXECUTE_COMPILED_RUN = Symbol("tubeless.executeCompiledRun");
|
|
11
|
-
const compiledPipelines = new WeakSet();
|
|
12
|
-
/** Marks the exact object returned by `definePipeline` as a compiled pipeline. */
|
|
13
|
-
export function brandCompiledPipeline(pipeline) {
|
|
14
|
-
compiledPipelines.add(pipeline);
|
|
15
|
-
}
|
|
16
|
-
/** True only for the object `definePipeline` returned, not `Object.create` wrappers. */
|
|
17
|
-
export function isCompiledPipeline(pipeline) {
|
|
18
|
-
return compiledPipelines.has(pipeline);
|
|
19
|
-
}
|
|
20
|
-
function liveStepGraph(step) {
|
|
21
|
-
return {
|
|
22
|
-
dependsOn: step.dependsOn ?? [],
|
|
23
|
-
optionalDependsOn: step.optionalDependsOn ?? [],
|
|
24
|
-
skipAfterFailureOf: step.skipAfterFailureOf ?? [],
|
|
25
|
-
};
|
|
26
|
-
}
|
|
27
|
-
function freezeStepList(steps) {
|
|
28
|
-
return Object.freeze([...(steps ?? [])]);
|
|
29
|
-
}
|
|
30
|
-
function snapshotCompiledStepGraph(step) {
|
|
31
|
-
return Object.freeze({
|
|
32
|
-
dependsOn: freezeStepList(step.dependsOn),
|
|
33
|
-
optionalDependsOn: freezeStepList(step.optionalDependsOn),
|
|
34
|
-
skipAfterFailureOf: freezeStepList(step.skipAfterFailureOf),
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
export function compiledStepGraph(compiled, step) {
|
|
38
|
-
const graph = compiled.stepGraph.get(step);
|
|
39
|
-
if (graph === undefined) {
|
|
40
|
-
throw new Error(`Compiled pipeline graph is missing step ${step.id}`);
|
|
41
|
-
}
|
|
42
|
-
// SAFETY: compile stores each original step object as the map key.
|
|
43
|
-
return graph;
|
|
44
|
-
}
|
|
45
|
-
function pipelineDiagnostic(code, phase, kind, message, fields = {}) {
|
|
46
|
-
return { code, kind, message, phase, ...fields };
|
|
47
|
-
}
|
|
48
|
-
/** Required dependencies are met when completed, or intentionally policy-skipped. */
|
|
49
|
-
function isRequiredDependencyMet(report) {
|
|
50
|
-
if (!report)
|
|
51
|
-
return false;
|
|
52
|
-
if (report.status === "completed")
|
|
53
|
-
return true;
|
|
54
|
-
return report.status === "skipped" && report.reason === "policy";
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* Single skip ladder for plan and live execution.
|
|
58
|
-
* Policy skips stay outside: they run only after a step is scheduled.
|
|
59
|
-
* Remainder copies plan `skipReason` for structural skips and marks planned-run
|
|
60
|
-
* steps fail-fast or cancelled, so this ladder has no abort/fail-fast mode.
|
|
61
|
-
*/
|
|
62
|
-
export function decideStepDisposition(input) {
|
|
63
|
-
const { dryRun, graph, planned, reportsByStepId, step } = input;
|
|
64
|
-
if (!planned.selected) {
|
|
65
|
-
return { kind: "skip", reason: "filtered" };
|
|
66
|
-
}
|
|
67
|
-
const unsuccessfulAncestor = graph.skipAfterFailureOf.find((dep) => {
|
|
68
|
-
const status = reportsByStepId.get(dep.id)?.status;
|
|
69
|
-
return status === "failed" || status === "cancelled";
|
|
70
|
-
});
|
|
71
|
-
if (unsuccessfulAncestor) {
|
|
72
|
-
return {
|
|
73
|
-
kind: "skip",
|
|
74
|
-
reason: "failed-dependency",
|
|
75
|
-
dependencyId: unsuccessfulAncestor.id,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
const unmetDependency = graph.dependsOn.find((dep) => !isRequiredDependencyMet(reportsByStepId.get(dep.id)));
|
|
79
|
-
if (unmetDependency) {
|
|
80
|
-
return {
|
|
81
|
-
kind: "skip",
|
|
82
|
-
reason: "unmet-dependency",
|
|
83
|
-
dependencyId: unmetDependency.id,
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
if (dryRun && skipsInDryRun(step)) {
|
|
87
|
-
return { kind: "skip", reason: "dry-run" };
|
|
88
|
-
}
|
|
89
|
-
return { kind: "run" };
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Deduplicated by object identity: the same dependency commonly appears in more than one
|
|
93
|
-
* of these arrays (e.g. a step that's both `optionalDependsOn` and `skipAfterFailureOf` the
|
|
94
|
-
* same upstream step). Without dedup, `topologicalSort`'s in-degree count would no longer
|
|
95
|
-
* equal the number of distinct prerequisites — harmless there since the inflated count and
|
|
96
|
-
* the duplicated `dependentsOf` entries cancel out, but confusing to read and a latent trap
|
|
97
|
-
* for future callers of this helper that assume it returns distinct edges.
|
|
98
|
-
*/
|
|
99
|
-
function stepEdges(step, graph = liveStepGraph(step)) {
|
|
100
|
-
return [
|
|
101
|
-
...new Set([...graph.dependsOn, ...graph.optionalDependsOn, ...graph.skipAfterFailureOf]),
|
|
102
|
-
];
|
|
103
|
-
}
|
|
104
|
-
/** Required data and failure-gate closure for dependency-aware targets. */
|
|
105
|
-
function targetClosure(targets) {
|
|
106
|
-
const selected = new Set();
|
|
107
|
-
const include = (step) => {
|
|
108
|
-
if (selected.has(step))
|
|
109
|
-
return;
|
|
110
|
-
selected.add(step);
|
|
111
|
-
for (const prerequisite of [...(step.dependsOn ?? []), ...(step.skipAfterFailureOf ?? [])]) {
|
|
112
|
-
include(prerequisite);
|
|
113
|
-
}
|
|
114
|
-
};
|
|
115
|
-
for (const target of targets)
|
|
116
|
-
include(target);
|
|
117
|
-
return selected;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Orders `steps` so every dependency runs before its dependents, using each step's
|
|
121
|
-
* original array position to break ties. For an array that's already in valid dependency
|
|
122
|
-
* order (true of every hand-ordered pipeline today) this provably reproduces that same
|
|
123
|
-
* order — each step becomes eligible (in-degree 0) at the same point it would've been
|
|
124
|
-
* reached by a plain declaration-order walk. What it adds is tolerance for a `steps` array
|
|
125
|
-
* that ISN'T already ordered, and a real error instead of silent misbehavior on a cycle.
|
|
126
|
-
* Returns null if the graph has a cycle (shouldn't happen given `const`-reference
|
|
127
|
-
* construction, but the executor should never spin or drop steps if it somehow does).
|
|
128
|
-
*/
|
|
129
|
-
function topologicalSort(steps) {
|
|
130
|
-
const indexById = new Map(steps.map((step, index) => [step.id, index]));
|
|
131
|
-
const remainingInDegree = new Map(steps.map((step) => [step.id, stepEdges(step).length]));
|
|
132
|
-
const dependentsOf = new Map();
|
|
133
|
-
for (const step of steps) {
|
|
134
|
-
for (const dep of stepEdges(step)) {
|
|
135
|
-
const dependents = dependentsOf.get(dep.id) ?? [];
|
|
136
|
-
dependents.push(step.id);
|
|
137
|
-
dependentsOf.set(dep.id, dependents);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
const ready = steps.filter((step) => remainingInDegree.get(step.id) === 0).map((step) => step.id);
|
|
141
|
-
const orderedIds = [];
|
|
142
|
-
while (ready.length > 0) {
|
|
143
|
-
ready.sort((left, right) => (indexById.get(left) ?? 0) - (indexById.get(right) ?? 0));
|
|
144
|
-
const nextId = ready.shift();
|
|
145
|
-
if (nextId === undefined) {
|
|
146
|
-
break;
|
|
147
|
-
}
|
|
148
|
-
orderedIds.push(nextId);
|
|
149
|
-
for (const dependentId of dependentsOf.get(nextId) ?? []) {
|
|
150
|
-
const remaining = (remainingInDegree.get(dependentId) ?? 0) - 1;
|
|
151
|
-
remainingInDegree.set(dependentId, remaining);
|
|
152
|
-
if (remaining === 0) {
|
|
153
|
-
ready.push(dependentId);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (orderedIds.length !== steps.length) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
const stepsById = new Map(steps.map((step) => [step.id, step]));
|
|
161
|
-
// SAFETY: every id in `orderedIds` was produced by walking the same `steps`
|
|
162
|
-
// array, so the map lookup always finds the matching `AnyStep<TOptions>`.
|
|
163
|
-
return orderedIds.map((id) => stepsById.get(id));
|
|
164
|
-
}
|
|
165
|
-
function escapeMermaidLabel(value) {
|
|
166
|
-
return value
|
|
167
|
-
.replace(/\r\n?|\n/g, " ")
|
|
168
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ")
|
|
169
|
-
.replace(/#/g, "#35;")
|
|
170
|
-
.replace(/&/g, "#38;")
|
|
171
|
-
.replace(/"/g, "#quot;")
|
|
172
|
-
.replace(/</g, "#60;")
|
|
173
|
-
.replace(/>/g, "#62;");
|
|
174
|
-
}
|
|
175
|
-
export function renderPipelineMermaid(steps, options, stepGraph) {
|
|
176
|
-
const direction = options.direction ?? "TD";
|
|
177
|
-
if (!PIPELINE_MERMAID_DIRECTIONS.includes(direction)) {
|
|
178
|
-
throw new Error(`Invalid Mermaid flowchart direction: ${direction}`);
|
|
179
|
-
}
|
|
180
|
-
const nodeIdByStep = new Map(steps.map((step, index) => [step, `step${index}`]));
|
|
181
|
-
const lines = [`flowchart ${direction}`];
|
|
182
|
-
for (const step of steps) {
|
|
183
|
-
const nodeId = nodeIdByStep.get(step);
|
|
184
|
-
const displayName = step.name ?? step.id;
|
|
185
|
-
const label = options.includeDescriptions && step.description
|
|
186
|
-
? `${displayName} — ${step.description}`
|
|
187
|
-
: displayName;
|
|
188
|
-
lines.push(` ${nodeId}["${escapeMermaidLabel(label)}"]`);
|
|
189
|
-
}
|
|
190
|
-
const edgeLines = [];
|
|
191
|
-
for (const step of steps) {
|
|
192
|
-
const graph = stepGraph?.get(step) ?? liveStepGraph(step);
|
|
193
|
-
const targetId = nodeIdByStep.get(step);
|
|
194
|
-
const required = new Set(graph.dependsOn);
|
|
195
|
-
const optional = new Set(graph.optionalDependsOn);
|
|
196
|
-
const failureGates = new Set(graph.skipAfterFailureOf);
|
|
197
|
-
for (const dependency of stepEdges(step, graph)) {
|
|
198
|
-
const sourceId = nodeIdByStep.get(dependency);
|
|
199
|
-
if (required.has(dependency)) {
|
|
200
|
-
edgeLines.push(` ${sourceId} --> ${targetId}`);
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
if (optional.has(dependency) && failureGates.has(dependency)) {
|
|
204
|
-
edgeLines.push(` ${sourceId} -. optional input + failure gate .-> ${targetId}`);
|
|
205
|
-
continue;
|
|
206
|
-
}
|
|
207
|
-
if (optional.has(dependency)) {
|
|
208
|
-
edgeLines.push(` ${sourceId} -. optional input .-> ${targetId}`);
|
|
209
|
-
continue;
|
|
210
|
-
}
|
|
211
|
-
edgeLines.push(` ${sourceId} -. failure gate .-> ${targetId}`);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
if (edgeLines.length > 0) {
|
|
215
|
-
lines.push("", ...edgeLines);
|
|
216
|
-
}
|
|
217
|
-
return `${lines.join("\n")}\n`;
|
|
218
|
-
}
|
|
2
|
+
import { decideStepDisposition } from "./pipeline-disposition.js";
|
|
3
|
+
import { pipelineDiagnostic } from "./pipeline-errors.js";
|
|
4
|
+
import { compiledStepGraph, liveStepGraph } from "./pipeline-graph.js";
|
|
5
|
+
import { STEP_NESTED_PIPELINE, STEP_REMOTE } from "./pipeline-step-metadata.js";
|
|
219
6
|
export function stepToPlanStep(step, selected, skipReason, selectionReasons, graph = liveStepGraph(step)) {
|
|
220
7
|
const reasons = selectionReasons ?? [{ kind: "all" }];
|
|
221
8
|
const planStep = {
|
|
@@ -291,107 +78,6 @@ function targetSelectionReasons(targets, stepGraph) {
|
|
|
291
78
|
function selectsStep(reason) {
|
|
292
79
|
return !["not-selected", "optional-only", "outside-target-closure"].includes(reason.kind);
|
|
293
80
|
}
|
|
294
|
-
function skipsInDryRun(step) {
|
|
295
|
-
return step.dryRun === "skip";
|
|
296
|
-
}
|
|
297
|
-
function validatePipelineDefinition(definition) {
|
|
298
|
-
const steps = definition.steps;
|
|
299
|
-
const errors = [];
|
|
300
|
-
if (definition.id.trim().length === 0) {
|
|
301
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_PIPELINE_ID_BLANK", "definition", "definition", "Pipeline id must not be blank"));
|
|
302
|
-
}
|
|
303
|
-
const stepIds = steps.map((step) => step.id);
|
|
304
|
-
const duplicateStepIds = duplicateValues(stepIds);
|
|
305
|
-
if (duplicateStepIds.length > 0) {
|
|
306
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_IDS_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} has duplicate step ids: ${duplicateStepIds.join(", ")}`));
|
|
307
|
-
}
|
|
308
|
-
const optionsSchemas = new Set(steps.map((step) => step[STEP_OPTIONS_SCHEMA]));
|
|
309
|
-
if (optionsSchemas.size > 1 && [...optionsSchemas].some(Boolean)) {
|
|
310
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_OPTIONS_SCHEMA_CONFLICT", "definition", "definition", `Pipeline ${definition.id} mixes steps from different options-schema scopes`));
|
|
311
|
-
}
|
|
312
|
-
for (const step of steps) {
|
|
313
|
-
if (step.id.trim().length === 0) {
|
|
314
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_ID_BLANK", "definition", "definition", `Pipeline ${definition.id} has a step with a blank id`, { stepId: step.id }));
|
|
315
|
-
}
|
|
316
|
-
if (step.id === PIPELINE_FINALIZE_STEP_ID) {
|
|
317
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_ID_RESERVED", "definition", "definition", `Pipeline ${definition.id} uses reserved step id ${PIPELINE_FINALIZE_STEP_ID}`, { stepId: step.id }));
|
|
318
|
-
}
|
|
319
|
-
if (step.name !== undefined && step.name.trim().length === 0) {
|
|
320
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_STEP_NAME_BLANK", "definition", "definition", `Pipeline ${definition.id} step ${step.id} has a blank display name`, { stepId: step.id }));
|
|
321
|
-
}
|
|
322
|
-
const dependencyGroups = [
|
|
323
|
-
["dependsOn", step.dependsOn ?? []],
|
|
324
|
-
["optionalDependsOn", step.optionalDependsOn ?? []],
|
|
325
|
-
["skipAfterFailureOf", step.skipAfterFailureOf ?? []],
|
|
326
|
-
];
|
|
327
|
-
for (const [field, dependencies] of dependencyGroups) {
|
|
328
|
-
const duplicates = duplicateValues(dependencies.map((dependency) => dependency.id));
|
|
329
|
-
if (duplicates.length > 0) {
|
|
330
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} step ${step.id} repeats ${field}: ${duplicates.join(", ")}`, { stepId: step.id }));
|
|
331
|
-
}
|
|
332
|
-
if (dependencies.includes(step)) {
|
|
333
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_SELF_REFERENCE", "definition", "definition", `Pipeline ${definition.id} step ${step.id} cannot reference itself in ${field}`, { stepId: step.id }));
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
const required = new Set(step.dependsOn ?? []);
|
|
337
|
-
const contradictory = (step.optionalDependsOn ?? []).filter((dependency) => required.has(dependency));
|
|
338
|
-
if (contradictory.length > 0) {
|
|
339
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_CONTRADICTORY", "definition", "definition", `Pipeline ${definition.id} step ${step.id} declares dependencies as both required and optional: ${contradictory.map(({ id }) => id).join(", ")}`, { stepId: step.id }));
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores
|
|
343
|
-
// the `TOptions` generic that the tuple erased, without changing the values.
|
|
344
|
-
const knownSteps = new Set(steps);
|
|
345
|
-
// SAFETY: targets are a subset of `TSteps[number]`, each an `AnyStep<TOptions>`.
|
|
346
|
-
const declaredTargets = (definition.targets ?? []);
|
|
347
|
-
const duplicateTargetIds = duplicateValues(declaredTargets.map((target) => target.id));
|
|
348
|
-
if (duplicateTargetIds.length > 0) {
|
|
349
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGETS_DUPLICATE", "definition", "definition", `Pipeline ${definition.id} declares duplicate targets: ${duplicateTargetIds.join(", ")}`));
|
|
350
|
-
}
|
|
351
|
-
const missingTargets = declaredTargets.filter((target) => !knownSteps.has(target));
|
|
352
|
-
if (missingTargets.length > 0) {
|
|
353
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGET_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} declares target step(s) not included in its steps list: ${missingTargets.map(({ id }) => id).join(", ")}`));
|
|
354
|
-
}
|
|
355
|
-
const missingReferences = [];
|
|
356
|
-
// SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores the
|
|
357
|
-
// `TOptions` generic that the tuple erased, without changing the values.
|
|
358
|
-
for (const step of steps) {
|
|
359
|
-
for (const dependency of stepEdges(step)) {
|
|
360
|
-
if (!knownSteps.has(dependency)) {
|
|
361
|
-
missingReferences.push(`${step.id} -> ${dependency.id}`);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
if (missingReferences.length > 0) {
|
|
366
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} references step(s) not included in its steps list: ${missingReferences.join(", ")}`));
|
|
367
|
-
}
|
|
368
|
-
// SAFETY: `requireOutputs` stamps the required step ids onto the finalizer
|
|
369
|
-
// function under `REQUIRED_FINALIZER_OUTPUTS`; the intersection only widens
|
|
370
|
-
// the function type to expose that optional property.
|
|
371
|
-
const requiredFinalizerSteps = definition.finalize[REQUIRED_FINALIZER_OUTPUTS];
|
|
372
|
-
const missingFinalizerSteps = (requiredFinalizerSteps ?? []).filter((step) => !knownSteps.has(step));
|
|
373
|
-
if (missingFinalizerSteps.length > 0) {
|
|
374
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_FINALIZER_STEP_NOT_IN_STEPS", "definition", "definition", `Pipeline ${definition.id} requires finalizer output from step(s) not included in its steps list: ${missingFinalizerSteps.map(({ id }) => id).join(", ")}`));
|
|
375
|
-
}
|
|
376
|
-
if (requiredFinalizerSteps &&
|
|
377
|
-
missingTargets.length === 0 &&
|
|
378
|
-
missingReferences.length === 0 &&
|
|
379
|
-
missingFinalizerSteps.length === 0) {
|
|
380
|
-
for (const target of declaredTargets) {
|
|
381
|
-
const selected = targetClosure([target]);
|
|
382
|
-
const missingRequiredSteps = requiredFinalizerSteps.filter((step) => !selected.has(step));
|
|
383
|
-
if (missingRequiredSteps.length > 0) {
|
|
384
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_TARGET_FINALIZER_MISMATCH", "definition", "definition", `Pipeline ${definition.id} target ${target.id} cannot satisfy required finalizer output(s): ${missingRequiredSteps.map(({ id }) => id).join(", ")}`, { stepId: target.id }));
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores the
|
|
389
|
-
// `TOptions` generic that the tuple erased, without changing the values.
|
|
390
|
-
if (errors.length === 0 && !topologicalSort(steps)) {
|
|
391
|
-
errors.push(pipelineDiagnostic("TUBELESS_DEFINITION_DEPENDENCY_CYCLE", "definition", "definition", `Pipeline ${definition.id} has a dependency cycle`));
|
|
392
|
-
}
|
|
393
|
-
return errors;
|
|
394
|
-
}
|
|
395
81
|
export function buildPipelinePlan(compiled, controls) {
|
|
396
82
|
const dryRun = controls.dryRun === true;
|
|
397
83
|
const steps = compiled.orderedSteps;
|
|
@@ -429,7 +115,6 @@ export function buildPipelinePlan(compiled, controls) {
|
|
|
429
115
|
if (undeclaredTargets.length > 0) {
|
|
430
116
|
errors.push(pipelineDiagnostic("TUBELESS_PLANNING_TARGET_UNDECLARED", "planning", "selection", `Pipeline ${compiled.id} requested undeclared targets: ${undeclaredTargets.join(", ")}`));
|
|
431
117
|
}
|
|
432
|
-
const orderedSteps = steps;
|
|
433
118
|
if (errors.length > 0) {
|
|
434
119
|
return {
|
|
435
120
|
dryRun,
|
|
@@ -470,7 +155,7 @@ export function buildPipelinePlan(compiled, controls) {
|
|
|
470
155
|
// Accumulated planned skip/complete reports so later steps see the same
|
|
471
156
|
// unmet-dependency chain as today's skipReason walk (not one empty map).
|
|
472
157
|
const plannedReportsByStepId = new Map();
|
|
473
|
-
for (const step of
|
|
158
|
+
for (const step of steps) {
|
|
474
159
|
const selectionReasons = selectionReasonsByStepId.get(step.id);
|
|
475
160
|
const selected = selectionReasons.some(selectsStep);
|
|
476
161
|
const graph = compiledStepGraph(compiled, step);
|
|
@@ -524,76 +209,4 @@ export function buildPipelinePlan(compiled, controls) {
|
|
|
524
209
|
export function planStepById(plan) {
|
|
525
210
|
return new Map(plan.steps.map((step) => [step.id, step]));
|
|
526
211
|
}
|
|
527
|
-
/** Programmer error raised immediately when a pipeline graph is invalid. */
|
|
528
|
-
export class PipelineDefinitionError extends Error {
|
|
529
|
-
pipelineId;
|
|
530
|
-
errors;
|
|
531
|
-
constructor(pipelineId, errors) {
|
|
532
|
-
super(errors.length > 0
|
|
533
|
-
? `Invalid pipeline ${pipelineId}: ${errors.map((error) => formatPipelineError(error)).join("; ")}`
|
|
534
|
-
: `Invalid pipeline ${pipelineId}`);
|
|
535
|
-
this.pipelineId = pipelineId;
|
|
536
|
-
this.errors = errors;
|
|
537
|
-
this.name = "PipelineDefinitionError";
|
|
538
|
-
brandTubelessError(this, "pipeline-definition");
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
export function compilePipeline(definition) {
|
|
542
|
-
const errors = validatePipelineDefinition(definition);
|
|
543
|
-
if (errors.length > 0) {
|
|
544
|
-
throw new PipelineDefinitionError(definition.id, errors);
|
|
545
|
-
}
|
|
546
|
-
// SAFETY: `steps` is `TSteps extends readonly AnyStep[]`; the cast restores
|
|
547
|
-
// the `TOptions` generic that the tuple erased, without changing the values.
|
|
548
|
-
const orderedSteps = topologicalSort(definition.steps);
|
|
549
|
-
// Keep the author's step objects so class private accessors and prototype
|
|
550
|
-
// `run`/`skip` keep working. Graph arrays live in compiled storage so frozen
|
|
551
|
-
// or later-mutated caller-owned steps cannot change planning.
|
|
552
|
-
const stepGraph = new Map(orderedSteps.map((step) => [step, snapshotCompiledStepGraph(step)]));
|
|
553
|
-
// SAFETY: `requireOutputs` stamps the required step ids onto the finalizer
|
|
554
|
-
// function under `REQUIRED_FINALIZER_OUTPUTS`; the intersection only widens
|
|
555
|
-
// the function type to expose that optional property.
|
|
556
|
-
const requiredFinalizerSteps = definition.finalize[REQUIRED_FINALIZER_OUTPUTS];
|
|
557
|
-
// SAFETY: targets are a subset of `TSteps[number]`, each an `AnyStep<TOptions>`.
|
|
558
|
-
const declaredTargets = (definition.targets ?? []);
|
|
559
|
-
return Object.freeze({
|
|
560
|
-
declaredTargets: Object.freeze([...declaredTargets]),
|
|
561
|
-
// Invoke on the author's definition so method-style finalizers keep `this`.
|
|
562
|
-
// SAFETY: the wrapper only forwards to `definition.finalize`.
|
|
563
|
-
finalize: ((outputs, context) => definition.finalize(outputs, context)),
|
|
564
|
-
id: definition.id,
|
|
565
|
-
optionsSchema: definition.steps[0]?.[STEP_OPTIONS_SCHEMA],
|
|
566
|
-
orderedSteps: Object.freeze([...orderedSteps]),
|
|
567
|
-
stepGraph,
|
|
568
|
-
requiredFinalizerSteps: requiredFinalizerSteps
|
|
569
|
-
? Object.freeze([...requiredFinalizerSteps])
|
|
570
|
-
: undefined,
|
|
571
|
-
resultSchema: definition.resultSchema,
|
|
572
|
-
stepIds: Object.freeze(definition.steps.map((step) => step.id)),
|
|
573
|
-
targetIds: Object.freeze((definition.targets ?? []).map((step) => step.id)),
|
|
574
|
-
});
|
|
575
|
-
}
|
|
576
|
-
/**
|
|
577
|
-
* Build a finalizer that only runs when every listed step published an output.
|
|
578
|
-
* Presence is checked by output slot, so a successfully published `undefined`
|
|
579
|
-
* remains distinct from a structural skip or failure.
|
|
580
|
-
*/
|
|
581
|
-
export function requireOutputs(requiredSteps, finalize) {
|
|
582
|
-
const uniqueRequiredSteps = [...new Set(requiredSteps)];
|
|
583
|
-
const requiredFinalizer = (outputs, context) => {
|
|
584
|
-
const missingStepIds = uniqueRequiredSteps
|
|
585
|
-
.filter((step) => !Object.prototype.hasOwnProperty.call(outputs, step.id))
|
|
586
|
-
.map((step) => step.id);
|
|
587
|
-
if (missingStepIds.length > 0) {
|
|
588
|
-
throw new Error(`Required pipeline outputs missing: ${missingStepIds.join(", ")}`);
|
|
589
|
-
}
|
|
590
|
-
// SAFETY: every required step id was verified present above, so the partial
|
|
591
|
-
// outputs contain all required keys and are a complete required-outputs map.
|
|
592
|
-
return finalize(outputs, context);
|
|
593
|
-
};
|
|
594
|
-
Object.defineProperty(requiredFinalizer, REQUIRED_FINALIZER_OUTPUTS, {
|
|
595
|
-
value: uniqueRequiredSteps,
|
|
596
|
-
});
|
|
597
|
-
return requiredFinalizer;
|
|
598
|
-
}
|
|
599
212
|
//# sourceMappingURL=pipeline-plan.js.map
|