stageflow 0.2.0 → 0.3.0
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 +158 -14
- package/dist/agent/activity.d.ts +10 -1
- package/dist/agent/activity.js +37 -2
- package/dist/agent/activityObserver.d.ts +5 -1
- package/dist/agent/activityObserver.js +174 -11
- package/dist/agent/fakeAgent.js +4 -0
- package/dist/agent/piAdapter.d.ts +16 -0
- package/dist/agent/piAdapter.js +88 -21
- package/dist/agent/port.d.ts +2 -0
- package/dist/agent/providerAuth.js +12 -7
- package/dist/catalog/displayCatalogPath.d.ts +12 -0
- package/dist/catalog/displayCatalogPath.js +28 -0
- package/dist/cli/initCommand.d.ts +9 -0
- package/dist/cli/initCommand.js +71 -0
- package/dist/cli/initTemplates.d.ts +3 -0
- package/dist/cli/initTemplates.js +19 -0
- package/dist/cli/operatorCatalog.d.ts +10 -0
- package/dist/cli/operatorCatalog.js +16 -0
- package/dist/cli/runCommand.d.ts +3 -1
- package/dist/cli/runCommand.js +48 -7
- package/dist/cli/validateCommand.d.ts +1 -1
- package/dist/cli/validateCommand.js +20 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +52 -19
- package/dist/config/browseCatalog.d.ts +45 -0
- package/dist/config/browseCatalog.js +170 -0
- package/dist/config/createPipeline.d.ts +12 -3
- package/dist/config/createPipeline.js +292 -100
- package/dist/config/createStage.d.ts +10 -4
- package/dist/config/createStage.js +44 -14
- package/dist/config/listConfig.d.ts +14 -19
- package/dist/config/listConfig.js +26 -191
- package/dist/config/listModelsFromManifest.d.ts +2 -0
- package/dist/config/listModelsFromManifest.js +4 -0
- package/dist/config/loadOutcome.d.ts +4 -0
- package/dist/config/loadPipeline.d.ts +1 -15
- package/dist/config/loadPipeline.js +84 -142
- package/dist/config/loadStage.d.ts +4 -0
- package/dist/config/loadStage.js +60 -32
- package/dist/config/loadStageflowManifest.d.ts +5 -0
- package/dist/config/loadStageflowManifest.js +157 -0
- package/dist/config/mergePipelineIncludes.d.ts +9 -0
- package/dist/config/mergePipelineIncludes.js +141 -0
- package/dist/config/normalizePipelineStageEntry.d.ts +16 -0
- package/dist/config/normalizePipelineStageEntry.js +186 -0
- package/dist/config/pipelineStageKeys.d.ts +4 -0
- package/dist/config/pipelineStageKeys.js +14 -0
- package/dist/config/resolveCatalogContext.d.ts +13 -0
- package/dist/config/resolveCatalogContext.js +13 -0
- package/dist/config/resolveForkEmitContext.d.ts +3 -0
- package/dist/config/resolveForkEmitContext.js +12 -0
- package/dist/config/resolvePipelineDag.d.ts +5 -1
- package/dist/config/resolvePipelineDag.js +54 -36
- package/dist/config/scanCatalogPaths.d.ts +4 -0
- package/dist/config/scanCatalogPaths.js +83 -0
- package/dist/config/validateCatalog.d.ts +22 -4
- package/dist/config/validateCatalog.js +169 -132
- package/dist/envelope/check.js +7 -0
- package/dist/envelope/forkChoice.d.ts +3 -0
- package/dist/envelope/forkChoice.js +47 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/mcp/projectRun.d.ts +3 -0
- package/dist/mcp/projectRun.js +7 -0
- package/dist/mcp/tools.js +28 -11
- package/dist/project/findProjectRoot.d.ts +2 -0
- package/dist/project/findProjectRoot.js +51 -0
- package/dist/project/globalHome.d.ts +2 -0
- package/dist/project/globalHome.js +19 -0
- package/dist/project/resolveProjectContext.d.ts +9 -0
- package/dist/project/resolveProjectContext.js +38 -0
- package/dist/project/resolveStageflowContext.d.ts +11 -0
- package/dist/project/resolveStageflowContext.js +66 -0
- package/dist/runstore/normalizeCatalogPath.d.ts +1 -0
- package/dist/runstore/normalizeCatalogPath.js +4 -0
- package/dist/runstore/port.d.ts +9 -0
- package/dist/runstore/runProjection.js +11 -1
- package/dist/runstore/sqlite/SqliteRunStore.js +30 -4
- package/dist/runstore/sqlite/schema.d.ts +1 -1
- package/dist/runstore/sqlite/schema.js +4 -1
- package/dist/runtime/credentialBinding.d.ts +8 -6
- package/dist/runtime/credentialBinding.js +18 -24
- package/dist/runtime/pipelineRunner.d.ts +3 -0
- package/dist/runtime/pipelineRunner.js +15 -1
- package/dist/runtime/pipelineScheduler.d.ts +2 -0
- package/dist/runtime/pipelineScheduler.js +51 -11
- package/dist/runtime/reloadRunCatalog.d.ts +5 -0
- package/dist/runtime/reloadRunCatalog.js +44 -0
- package/dist/runtime/resumeReconstruct.d.ts +1 -0
- package/dist/runtime/resumeReconstruct.js +23 -7
- package/dist/runtime/runManager.d.ts +4 -0
- package/dist/runtime/runManager.js +36 -16
- package/dist/runtime/settingsFile.d.ts +12 -0
- package/dist/runtime/settingsFile.js +87 -6
- package/dist/runtime/stageAttemptBootstrap.js +3 -0
- package/dist/runtime/stageProcessLauncher.js +3 -0
- package/dist/runtime/stageRecovery.js +3 -1
- package/dist/runtime/stageWorker.js +2 -6
- package/dist/server/http.js +33 -9
- package/dist/tools/emitStageEnvelope.d.ts +3 -1
- package/dist/tools/emitStageEnvelope.js +11 -1
- package/dist/types/envelope.d.ts +1 -0
- package/dist/types/forkChoice.d.ts +8 -0
- package/dist/types/forkChoice.js +1 -0
- package/dist/types/pipeline.d.ts +51 -2
- package/dist/types/stageflowManifest.d.ts +23 -0
- package/dist/types/stageflowManifest.js +1 -0
- package/dist/ui/assets/index-CFSzDZje.js +118 -0
- package/dist/ui/assets/{index-DCsDopak.css → index-DefBlEvN.css} +1 -1
- package/dist/ui/index.html +3 -2
- package/dist/ui/stageflow-icon.svg +12 -0
- package/package.json +7 -5
- package/dist/ui/assets/index-Cry0Tpfx.js +0 -118
|
@@ -1,18 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
};
|
|
7
|
-
export type PipelineStageListing = {
|
|
8
|
-
id: string;
|
|
9
|
-
gate_kinds?: StageGateKind[];
|
|
10
|
-
};
|
|
11
|
-
export type PipelineListing = {
|
|
12
|
-
path: string;
|
|
13
|
-
id: string;
|
|
14
|
-
stages: PipelineStageListing[];
|
|
15
|
-
};
|
|
1
|
+
import type { StageGateKind } from "../types/stage.js";
|
|
2
|
+
import type { LoadedManifest } from "../types/stageflowManifest.js";
|
|
3
|
+
import { type PipelineListing, type TaskListing } from "./browseCatalog.js";
|
|
4
|
+
import { type StageflowContext } from "../project/resolveStageflowContext.js";
|
|
5
|
+
import { type CatalogContext } from "./resolveCatalogContext.js";
|
|
6
|
+
export type { PipelineListing, PipelineStageListing, TaskListing } from "./browseCatalog.js";
|
|
16
7
|
export type ValidStageListing = {
|
|
17
8
|
path: string;
|
|
18
9
|
id: string;
|
|
@@ -27,7 +18,11 @@ export type BrokenStageListing = {
|
|
|
27
18
|
gate_kinds?: StageGateKind[];
|
|
28
19
|
};
|
|
29
20
|
export type StageListing = ValidStageListing | BrokenStageListing;
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
21
|
+
export type CatalogListOptions = {
|
|
22
|
+
projectRoot: string;
|
|
23
|
+
manifest: LoadedManifest;
|
|
24
|
+
};
|
|
25
|
+
export declare function listTasks(options: CatalogListOptions): Promise<TaskListing[]>;
|
|
26
|
+
export declare function listPipelines(options: CatalogListOptions): Promise<PipelineListing[]>;
|
|
27
|
+
export declare function listStages(_options?: unknown): Promise<StageListing[]>;
|
|
28
|
+
export declare function listModels(cwdOrContext: string | CatalogContext | StageflowContext): Promise<string[]>;
|
|
@@ -1,195 +1,30 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"cursor/auto",
|
|
12
|
-
"cursor/composer-2-5",
|
|
13
|
-
];
|
|
14
|
-
function parseSafeGateKinds(raw) {
|
|
15
|
-
if (!Array.isArray(raw) || !raw.every((item) => typeof item === "string")) {
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
const allowed = new Set(STAGE_GATE_KINDS);
|
|
19
|
-
const gateKinds = raw.filter((item) => allowed.has(item));
|
|
20
|
-
return gateKinds.length > 0 ? gateKinds : undefined;
|
|
1
|
+
import { listModelsForContext, listPipelinesForContext, listTasksForContext, } from "./browseCatalog.js";
|
|
2
|
+
import { resolveStageflowContext } from "../project/resolveStageflowContext.js";
|
|
3
|
+
import { catalogContextFromStageflow } from "./resolveCatalogContext.js";
|
|
4
|
+
export async function listTasks(options) {
|
|
5
|
+
return listTasksForContext({
|
|
6
|
+
projectRoot: options.projectRoot,
|
|
7
|
+
manifest: options.manifest,
|
|
8
|
+
manifestStatus: "ok",
|
|
9
|
+
issues: [],
|
|
10
|
+
});
|
|
21
11
|
}
|
|
22
|
-
async function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
return new Map();
|
|
30
|
-
}
|
|
31
|
-
const usage = new Map();
|
|
32
|
-
for (const entry of entries) {
|
|
33
|
-
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
34
|
-
continue;
|
|
35
|
-
const filePath = path.join(dir, entry.name);
|
|
36
|
-
try {
|
|
37
|
-
const raw = await readYamlObject(filePath);
|
|
38
|
-
if (typeof raw?.id !== "string" || !Array.isArray(raw.stages))
|
|
39
|
-
continue;
|
|
40
|
-
const stageIds = extractPipelineStageIds(raw.stages);
|
|
41
|
-
if (!stageIds)
|
|
42
|
-
continue;
|
|
43
|
-
for (const stageId of stageIds) {
|
|
44
|
-
let pipelineIds = usage.get(stageId);
|
|
45
|
-
if (!pipelineIds) {
|
|
46
|
-
pipelineIds = new Set();
|
|
47
|
-
usage.set(stageId, pipelineIds);
|
|
48
|
-
}
|
|
49
|
-
pipelineIds.add(raw.id);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
// skip invalid pipeline files
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return usage;
|
|
12
|
+
export async function listPipelines(options) {
|
|
13
|
+
return listPipelinesForContext({
|
|
14
|
+
projectRoot: options.projectRoot,
|
|
15
|
+
manifest: options.manifest,
|
|
16
|
+
manifestStatus: "ok",
|
|
17
|
+
issues: [],
|
|
18
|
+
});
|
|
57
19
|
}
|
|
58
|
-
export async function
|
|
59
|
-
|
|
60
|
-
let entries;
|
|
61
|
-
try {
|
|
62
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
63
|
-
}
|
|
64
|
-
catch {
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
const listings = [];
|
|
68
|
-
for (const entry of entries) {
|
|
69
|
-
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
70
|
-
continue;
|
|
71
|
-
const filePath = path.join(dir, entry.name);
|
|
72
|
-
try {
|
|
73
|
-
const task = await loadTask(filePath);
|
|
74
|
-
listings.push({
|
|
75
|
-
path: path.relative(cwd, filePath),
|
|
76
|
-
id: task.id,
|
|
77
|
-
goal: task.goal,
|
|
78
|
-
});
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// skip invalid
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
listings.sort((a, b) => a.id.localeCompare(b.id));
|
|
85
|
-
return listings;
|
|
20
|
+
export async function listStages(_options) {
|
|
21
|
+
return [];
|
|
86
22
|
}
|
|
87
|
-
export async function
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
const listings = [];
|
|
97
|
-
for (const entry of entries) {
|
|
98
|
-
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
99
|
-
continue;
|
|
100
|
-
const filePath = path.join(dir, entry.name);
|
|
101
|
-
try {
|
|
102
|
-
const loaded = await loadPipeline(filePath, { cwd });
|
|
103
|
-
listings.push({
|
|
104
|
-
path: path.relative(cwd, filePath),
|
|
105
|
-
id: loaded.pipeline.id,
|
|
106
|
-
stages: loaded.stages.map((stage) => ({
|
|
107
|
-
id: stage.id,
|
|
108
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
109
|
-
})),
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
catch {
|
|
113
|
-
// skip invalid
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
listings.sort((a, b) => a.id.localeCompare(b.id));
|
|
117
|
-
return listings;
|
|
118
|
-
}
|
|
119
|
-
export async function listStages(cwd) {
|
|
120
|
-
const dir = path.join(cwd, "stages");
|
|
121
|
-
let entries;
|
|
122
|
-
try {
|
|
123
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
const usageByStage = await listPipelineUsageByStage(cwd);
|
|
129
|
-
const valid = [];
|
|
130
|
-
const broken = [];
|
|
131
|
-
for (const entry of entries) {
|
|
132
|
-
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
133
|
-
continue;
|
|
134
|
-
const filePath = path.join(dir, entry.name);
|
|
135
|
-
const relPath = path.relative(cwd, filePath);
|
|
136
|
-
try {
|
|
137
|
-
const stage = await loadStage(filePath);
|
|
138
|
-
valid.push({
|
|
139
|
-
path: relPath,
|
|
140
|
-
id: stage.id,
|
|
141
|
-
used_by_pipeline_ids: [...(usageByStage.get(stage.id) ?? new Set())].sort(),
|
|
142
|
-
...(stage.gate_kinds ? { gate_kinds: stage.gate_kinds } : {}),
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
catch (err) {
|
|
146
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
147
|
-
const brokenRow = {
|
|
148
|
-
path: relPath,
|
|
149
|
-
error: message,
|
|
150
|
-
};
|
|
151
|
-
try {
|
|
152
|
-
const raw = await readYamlObject(filePath);
|
|
153
|
-
if (typeof raw?.id === "string")
|
|
154
|
-
brokenRow.id = raw.id;
|
|
155
|
-
if (typeof raw?.model === "string")
|
|
156
|
-
brokenRow.model = raw.model;
|
|
157
|
-
const gateKinds = parseSafeGateKinds(raw?.gate_kinds);
|
|
158
|
-
if (gateKinds)
|
|
159
|
-
brokenRow.gate_kinds = gateKinds;
|
|
160
|
-
}
|
|
161
|
-
catch {
|
|
162
|
-
// keep path + error only when parsing itself fails
|
|
163
|
-
}
|
|
164
|
-
broken.push(brokenRow);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
valid.sort((a, b) => a.id.localeCompare(b.id));
|
|
168
|
-
broken.sort((a, b) => a.path.localeCompare(b.path));
|
|
169
|
-
return [...valid, ...broken];
|
|
170
|
-
}
|
|
171
|
-
export async function listModels(cwd) {
|
|
172
|
-
const dir = path.join(cwd, "stages");
|
|
173
|
-
const models = new Set(BAKED_MODEL_IDS);
|
|
174
|
-
let entries;
|
|
175
|
-
try {
|
|
176
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
return [...models].sort((a, b) => a.localeCompare(b));
|
|
180
|
-
}
|
|
181
|
-
for (const entry of entries) {
|
|
182
|
-
if (!entry.isFile() || !entry.name.endsWith(".yaml"))
|
|
183
|
-
continue;
|
|
184
|
-
try {
|
|
185
|
-
const raw = await readYamlObject(path.join(dir, entry.name));
|
|
186
|
-
if (typeof raw?.model === "string" && raw.model.trim()) {
|
|
187
|
-
models.add(raw.model);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch {
|
|
191
|
-
// ignore unreadable or invalid stage YAML
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return [...models].sort((a, b) => a.localeCompare(b));
|
|
23
|
+
export async function listModels(cwdOrContext) {
|
|
24
|
+
const ctx = typeof cwdOrContext === "string"
|
|
25
|
+
? catalogContextFromStageflow(await resolveStageflowContext(cwdOrContext))
|
|
26
|
+
: "manifestIssues" in cwdOrContext
|
|
27
|
+
? catalogContextFromStageflow(cwdOrContext)
|
|
28
|
+
: cwdOrContext;
|
|
29
|
+
return listModelsForContext(ctx);
|
|
195
30
|
}
|
|
@@ -1,25 +1,11 @@
|
|
|
1
1
|
import type { LoadedPipeline } from "../types/pipeline.js";
|
|
2
2
|
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
-
import type { ValidationFinding } from "./validateCatalog.js";
|
|
4
3
|
export type { LoadedPipeline } from "../types/pipeline.js";
|
|
5
4
|
export type { LoadIssue, LoadOutcome } from "./loadOutcome.js";
|
|
6
|
-
export
|
|
7
|
-
ok: true;
|
|
8
|
-
loaded: LoadedPipeline;
|
|
9
|
-
} | {
|
|
10
|
-
ok: false;
|
|
11
|
-
findings: ValidationFinding[];
|
|
12
|
-
};
|
|
5
|
+
export declare function resolvePipelinePath(pipelinePath: string, cwd: string): Promise<string>;
|
|
13
6
|
export declare function loadPipelineOutcome(nameOrPath: string, options?: {
|
|
14
7
|
cwd?: string;
|
|
15
|
-
stagesDir?: string;
|
|
16
8
|
}): Promise<LoadOutcome<LoadedPipeline>>;
|
|
17
|
-
export declare function loadPipelineValidated(nameOrPath: string, options?: {
|
|
18
|
-
cwd?: string;
|
|
19
|
-
stagesDir?: string;
|
|
20
|
-
validateStages?: boolean;
|
|
21
|
-
}): Promise<LoadPipelineValidatedResult>;
|
|
22
9
|
export declare function loadPipeline(nameOrPath: string, options?: {
|
|
23
10
|
cwd?: string;
|
|
24
|
-
stagesDir?: string;
|
|
25
11
|
}): Promise<LoadedPipeline>;
|
|
@@ -1,68 +1,38 @@
|
|
|
1
1
|
import { access } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { loadFailure, loadSuccess } from "./loadOutcome.js";
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
async function resolvePipelinePath(
|
|
9
|
-
const
|
|
4
|
+
import { mergePipelineStages } from "./mergePipelineIncludes.js";
|
|
5
|
+
import { normalizePipelineStageEntries, toWiringRefs, } from "./normalizePipelineStageEntry.js";
|
|
6
|
+
import { loadStageFromObjectOutcome, loadStageOutcome } from "./loadStage.js";
|
|
7
|
+
import { resolvePipelineDagFromRefs } from "./resolvePipelineDag.js";
|
|
8
|
+
export async function resolvePipelinePath(pipelinePath, cwd) {
|
|
9
|
+
const resolved = path.normalize(path.resolve(cwd, pipelinePath));
|
|
10
10
|
try {
|
|
11
|
-
await access(
|
|
12
|
-
return
|
|
11
|
+
await access(resolved);
|
|
12
|
+
return resolved;
|
|
13
13
|
}
|
|
14
14
|
catch {
|
|
15
|
-
|
|
16
|
-
await access(candidate);
|
|
17
|
-
return candidate;
|
|
15
|
+
throw new Error(`Pipeline file not found: ${resolved}. Pass a filesystem path to --pipeline (e.g. pipelines/hello.pipeline.yaml), not a bare pipeline id.`);
|
|
18
16
|
}
|
|
19
17
|
}
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
},
|
|
35
|
-
]);
|
|
36
|
-
}
|
|
37
|
-
let raw;
|
|
38
|
-
try {
|
|
39
|
-
raw = await readYamlObject(pipelinePath);
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
43
|
-
return loadFailure([
|
|
44
|
-
{
|
|
45
|
-
code: "pipeline.load_error",
|
|
46
|
-
message,
|
|
47
|
-
category: "pipeline",
|
|
48
|
-
},
|
|
49
|
-
]);
|
|
50
|
-
}
|
|
51
|
-
if (typeof raw?.id !== "string" || !Array.isArray(raw.stages)) {
|
|
52
|
-
return loadFailure([
|
|
53
|
-
{
|
|
54
|
-
code: "pipeline.invalid_shape",
|
|
55
|
-
message: `Invalid pipeline ${pipelinePath}: id and stages[] are required`,
|
|
56
|
-
category: "pipeline",
|
|
57
|
-
},
|
|
58
|
-
]);
|
|
59
|
-
}
|
|
60
|
-
const pipelineId = raw.id;
|
|
61
|
-
const ctx = { pipelineId: raw.id, path: pipelinePath };
|
|
18
|
+
async function loadPipelineFromPath(pipelinePath) {
|
|
19
|
+
const normalizedPipelinePath = path.normalize(path.resolve(pipelinePath));
|
|
20
|
+
const mergeOutcome = await mergePipelineStages(normalizedPipelinePath);
|
|
21
|
+
if (!mergeOutcome.ok) {
|
|
22
|
+
return loadFailure(mergeOutcome.issues);
|
|
23
|
+
}
|
|
24
|
+
const { entries: rawEntries, pipelineId } = mergeOutcome.value;
|
|
25
|
+
const ctx = { pipelineId, path: normalizedPipelinePath };
|
|
26
|
+
const normalizeOutcome = normalizePipelineStageEntries(rawEntries, ctx);
|
|
27
|
+
if (!normalizeOutcome.ok) {
|
|
28
|
+
return loadFailure(normalizeOutcome.issues);
|
|
29
|
+
}
|
|
30
|
+
const normalizedEntries = normalizeOutcome.value;
|
|
31
|
+
const wiringRefs = toWiringRefs(normalizedEntries);
|
|
62
32
|
let stageIds;
|
|
63
33
|
let dag;
|
|
64
34
|
try {
|
|
65
|
-
const resolved =
|
|
35
|
+
const resolved = resolvePipelineDagFromRefs(wiringRefs, ctx);
|
|
66
36
|
stageIds = resolved.stages;
|
|
67
37
|
dag = resolved.dag;
|
|
68
38
|
}
|
|
@@ -77,115 +47,87 @@ export async function loadPipelineOutcome(nameOrPath, options = {}) {
|
|
|
77
47
|
},
|
|
78
48
|
]);
|
|
79
49
|
}
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
stages: stageIds,
|
|
83
|
-
};
|
|
50
|
+
const entryById = new Map(normalizedEntries.map((entry) => [entry.id, entry]));
|
|
51
|
+
const stageSources = {};
|
|
84
52
|
const stages = [];
|
|
85
|
-
for (const stageId of
|
|
86
|
-
const
|
|
87
|
-
|
|
53
|
+
for (const stageId of stageIds) {
|
|
54
|
+
const entry = entryById.get(stageId);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return loadFailure([
|
|
57
|
+
{
|
|
58
|
+
code: "pipeline.invalid_shape",
|
|
59
|
+
message: `Pipeline ${pipelineId}: missing normalized entry for stage "${stageId}"`,
|
|
60
|
+
category: "pipeline",
|
|
61
|
+
pipelineId,
|
|
62
|
+
},
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
if (entry.body.kind === "inline") {
|
|
66
|
+
const inlineOutcome = loadStageFromObjectOutcome(entry.body.raw, {
|
|
67
|
+
entryId: entry.id,
|
|
68
|
+
declaringPath: entry.declaringPath,
|
|
69
|
+
});
|
|
70
|
+
if (!inlineOutcome.ok) {
|
|
71
|
+
return loadFailure(inlineOutcome.issues);
|
|
72
|
+
}
|
|
73
|
+
stages.push(inlineOutcome.value);
|
|
74
|
+
stageSources[stageId] = { kind: "inline" };
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const stageOutcome = await loadStageOutcome(entry.body.absolutePath);
|
|
88
78
|
if (!stageOutcome.ok) {
|
|
89
|
-
const message = `Pipeline ${pipeline.id} references missing stage "${stageId}" at ${stagePath}`;
|
|
90
79
|
return loadFailure([
|
|
91
80
|
{
|
|
92
81
|
code: "pipeline.missing_stage",
|
|
93
|
-
message
|
|
82
|
+
message: `Pipeline ${pipelineId} references missing stage "${stageId}" at ${entry.body.absolutePath}`,
|
|
94
83
|
category: "pipeline",
|
|
95
84
|
pipelineId,
|
|
96
85
|
},
|
|
86
|
+
...stageOutcome.issues,
|
|
97
87
|
]);
|
|
98
88
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return findingStageIdFilenameMismatch(cwd, stagePath, fileStem, raw.id);
|
|
89
|
+
if (stageOutcome.value.id !== entry.id) {
|
|
90
|
+
return loadFailure([
|
|
91
|
+
{
|
|
92
|
+
code: "pipeline.stage_id_mismatch",
|
|
93
|
+
message: `Pipeline ${pipelineId}: stage entry "${entry.id}" in ${entry.declaringPath} uses ${entry.body.path} which declares id "${stageOutcome.value.id}"`,
|
|
94
|
+
category: "pipeline",
|
|
95
|
+
pipelineId,
|
|
96
|
+
},
|
|
97
|
+
]);
|
|
109
98
|
}
|
|
99
|
+
stages.push(stageOutcome.value);
|
|
100
|
+
stageSources[stageId] = { kind: "file", path: entry.body.absolutePath };
|
|
110
101
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (!outcome.ok) {
|
|
123
|
-
findings.push(...findingsFromLoadIssues(cwd, stagePath, outcome.issues));
|
|
124
|
-
return findings;
|
|
125
|
-
}
|
|
126
|
-
if (!mismatch && path.basename(stagePath, ".yaml") !== outcome.value.id) {
|
|
127
|
-
findings.push(findingStageIdFilenameMismatch(cwd, stagePath, path.basename(stagePath, ".yaml"), outcome.value.id));
|
|
128
|
-
}
|
|
129
|
-
return findings;
|
|
102
|
+
const pipeline = {
|
|
103
|
+
id: pipelineId,
|
|
104
|
+
stages: stageIds,
|
|
105
|
+
};
|
|
106
|
+
return loadSuccess({
|
|
107
|
+
pipeline,
|
|
108
|
+
stages,
|
|
109
|
+
dag,
|
|
110
|
+
pipelinePath: normalizedPipelinePath,
|
|
111
|
+
stageSources,
|
|
112
|
+
});
|
|
130
113
|
}
|
|
131
|
-
export async function
|
|
114
|
+
export async function loadPipelineOutcome(nameOrPath, options = {}) {
|
|
132
115
|
const cwd = options.cwd ?? process.cwd();
|
|
133
|
-
const stagesDir = options.stagesDir ?? path.join(cwd, "stages");
|
|
134
|
-
const validateStages = options.validateStages ?? true;
|
|
135
116
|
let pipelinePath;
|
|
136
117
|
try {
|
|
137
118
|
pipelinePath = await resolvePipelinePath(nameOrPath, cwd);
|
|
138
119
|
}
|
|
139
120
|
catch (err) {
|
|
140
121
|
const message = err instanceof Error ? err.message : String(err);
|
|
141
|
-
return
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
},
|
|
149
|
-
]),
|
|
150
|
-
};
|
|
151
|
-
}
|
|
152
|
-
const referencedStageIds = new Set();
|
|
153
|
-
try {
|
|
154
|
-
const raw = await readYamlObject(pipelinePath);
|
|
155
|
-
if (typeof raw?.id === "string" && Array.isArray(raw.stages)) {
|
|
156
|
-
const stageIds = extractPipelineStageIds(raw.stages);
|
|
157
|
-
if (stageIds) {
|
|
158
|
-
for (const stageId of stageIds) {
|
|
159
|
-
referencedStageIds.add(stageId);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
catch {
|
|
165
|
-
// loadPipelineOutcome will report read errors
|
|
166
|
-
}
|
|
167
|
-
const outcome = await loadPipelineOutcome(nameOrPath, { cwd, stagesDir });
|
|
168
|
-
const findings = [];
|
|
169
|
-
if (!outcome.ok) {
|
|
170
|
-
findings.push(...findingsFromLoadIssues(cwd, pipelinePath, outcome.issues));
|
|
171
|
-
if (validateStages) {
|
|
172
|
-
for (const stageId of referencedStageIds) {
|
|
173
|
-
const stagePath = path.join(stagesDir, `${stageId}.yaml`);
|
|
174
|
-
findings.push(...(await validateStageFile(cwd, stagePath)));
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return { ok: false, findings };
|
|
178
|
-
}
|
|
179
|
-
if (validateStages) {
|
|
180
|
-
for (const stageId of outcome.value.pipeline.stages) {
|
|
181
|
-
const stagePath = path.join(stagesDir, `${stageId}.yaml`);
|
|
182
|
-
findings.push(...(await validateStageFile(cwd, stagePath)));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (findings.some((finding) => finding.severity === "error")) {
|
|
186
|
-
return { ok: false, findings };
|
|
122
|
+
return loadFailure([
|
|
123
|
+
{
|
|
124
|
+
code: "pipeline.load_error",
|
|
125
|
+
message,
|
|
126
|
+
category: "pipeline",
|
|
127
|
+
},
|
|
128
|
+
]);
|
|
187
129
|
}
|
|
188
|
-
return
|
|
130
|
+
return loadPipelineFromPath(pipelinePath);
|
|
189
131
|
}
|
|
190
132
|
export async function loadPipeline(nameOrPath, options = {}) {
|
|
191
133
|
const outcome = await loadPipelineOutcome(nameOrPath, options);
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { type StageConfig } from "../types/stage.js";
|
|
2
2
|
import { type LoadOutcome } from "./loadOutcome.js";
|
|
3
|
+
export declare function loadStageFromObjectOutcome(raw: Record<string, unknown>, ctx: {
|
|
4
|
+
entryId: string;
|
|
5
|
+
declaringPath: string;
|
|
6
|
+
}): LoadOutcome<StageConfig>;
|
|
3
7
|
export declare function loadStageOutcome(filePath: string): Promise<LoadOutcome<StageConfig>>;
|
|
4
8
|
export declare function loadStage(filePath: string): Promise<StageConfig>;
|