tubeless 0.2.5 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/node/atomic-text.js +3 -2
  2. package/dist/node/atomic-text.js.map +1 -1
  3. package/dist/node/env.d.ts +1 -0
  4. package/dist/node/env.js +8 -0
  5. package/dist/node/env.js.map +1 -0
  6. package/dist/node/file-utils.d.ts +3 -0
  7. package/dist/node/file-utils.js +21 -0
  8. package/dist/node/file-utils.js.map +1 -0
  9. package/dist/node/node.d.ts +3 -0
  10. package/dist/node/node.js +3 -0
  11. package/dist/node/node.js.map +1 -1
  12. package/dist/node/paths.d.ts +10 -0
  13. package/dist/node/paths.js +19 -0
  14. package/dist/node/paths.js.map +1 -0
  15. package/dist/project/project-compiler.d.ts +19 -0
  16. package/dist/project/project-compiler.js +94 -0
  17. package/dist/project/project-compiler.js.map +1 -0
  18. package/dist/project/project-document.d.ts +47 -0
  19. package/dist/project/project-document.js +136 -0
  20. package/dist/project/project-document.js.map +1 -0
  21. package/dist/project/project.d.ts +2 -0
  22. package/dist/project/project.js +2 -0
  23. package/dist/project/project.js.map +1 -1
  24. package/dist/workbench/workbench-validate.d.ts +2 -0
  25. package/dist/workbench/workbench-validate.js +76 -0
  26. package/dist/workbench/workbench-validate.js.map +1 -0
  27. package/dist/workbench/workbench.js +4 -0
  28. package/dist/workbench/workbench.js.map +1 -1
  29. package/docs/README.md +7 -4
  30. package/docs/agent-guide.md +24 -2
  31. package/docs/api-reference.md +17 -2
  32. package/docs/api-report.json +23 -4
  33. package/docs/cli.md +18 -11
  34. package/docs/declarative-pipelines.md +247 -0
  35. package/docs/llms.txt +4 -0
  36. package/docs/pipeline-document.schema.json +93 -0
  37. package/docs/recipes.md +54 -22
  38. package/examples/catalog/tubeless.project.ts +21 -0
  39. package/examples/declarative/handlers.ts +44 -0
  40. package/examples/declarative/peloton-handlers.ts +231 -0
  41. package/examples/declarative/peloton.yaml +65 -0
  42. package/examples/declarative/pipelines.yaml +33 -0
  43. package/examples/declarative/yaml.d.ts +5 -0
  44. package/examples/node-artifacts.ts +28 -0
  45. package/examples/yaml-peloton.ts +49 -0
  46. package/examples/yaml-pipelines.ts +26 -0
  47. package/package.json +3 -1
  48. package/skills/tubeless/SKILL.md +16 -0
@@ -1,9 +1,10 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
+ import { randomUUID } from "node:crypto";
3
4
  export function writeAtomicText(filePath, text) {
4
5
  fs.mkdirSync(path.dirname(filePath), { recursive: true });
5
- // Rename a sibling temp file so readers see only complete writes.
6
- const tmpPath = `${filePath}.tmp-${process.pid}`;
6
+ // Each call owns its sibling temp file, including writers on same-PID workers.
7
+ const tmpPath = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
7
8
  try {
8
9
  fs.writeFileSync(tmpPath, text);
9
10
  fs.renameSync(tmpPath, filePath);
@@ -1 +1 @@
1
- {"version":3,"file":"atomic-text.js","sourceRoot":"","sources":["../../src/node/atomic-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,IAAY;IAC5D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,kEAAkE;IAClE,MAAM,OAAO,GAAG,GAAG,QAAQ,QAAQ,OAAO,CAAC,GAAG,EAAE,CAAC;IACjD,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAChC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\n\nexport function writeAtomicText(filePath: string, text: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n // Rename a sibling temp file so readers see only complete writes.\n const tmpPath = `${filePath}.tmp-${process.pid}`;\n try {\n fs.writeFileSync(tmpPath, text);\n fs.renameSync(tmpPath, filePath);\n } catch (error) {\n try {\n fs.rmSync(tmpPath, { force: true });\n } catch {\n // Best-effort cleanup; surface the original write/rename error.\n }\n throw error;\n }\n}\n"]}
1
+ {"version":3,"file":"atomic-text.js","sourceRoot":"","sources":["../../src/node/atomic-text.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,UAAU,eAAe,CAAC,QAAgB,EAAE,IAAY;IAC5D,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1D,+EAA+E;IAC/E,MAAM,OAAO,GAAG,GAAG,QAAQ,QAAQ,OAAO,CAAC,GAAG,IAAI,UAAU,EAAE,EAAE,CAAC;IACjE,IAAI,CAAC;QACH,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAChC,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QAAC,MAAM,CAAC;YACP,gEAAgE;QAClE,CAAC;QACD,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["import * as fs from \"fs\";\nimport * as path from \"path\";\nimport { randomUUID } from \"node:crypto\";\n\nexport function writeAtomicText(filePath: string, text: string): void {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n // Each call owns its sibling temp file, including writers on same-PID workers.\n const tmpPath = `${filePath}.tmp-${process.pid}-${randomUUID()}`;\n try {\n fs.writeFileSync(tmpPath, text);\n fs.renameSync(tmpPath, filePath);\n } catch (error) {\n try {\n fs.rmSync(tmpPath, { force: true });\n } catch {\n // Best-effort cleanup; surface the original write/rename error.\n }\n throw error;\n }\n}\n"]}
@@ -0,0 +1 @@
1
+ export declare function requireEnv(varName: string, usedBy: string): string;
@@ -0,0 +1,8 @@
1
+ export function requireEnv(varName, usedBy) {
2
+ const value = process.env[varName];
3
+ if (!value) {
4
+ throw new Error(`${varName} is required for ${usedBy}`);
5
+ }
6
+ return value;
7
+ }
8
+ //# sourceMappingURL=env.js.map
@@ -0,0 +1 @@
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"]}
@@ -0,0 +1,3 @@
1
+ export declare function writeJson(filePath: string, value: unknown): void;
2
+ export declare function readJson<T>(filePath: string): T;
3
+ export declare function resetDir(dirPath: string): void;
@@ -0,0 +1,21 @@
1
+ import * as fs from "fs";
2
+ import { writeAtomicText } from "./atomic-text.js";
3
+ export function writeJson(filePath, value) {
4
+ const serialized = JSON.stringify(value, null, 2);
5
+ if (serialized === undefined) {
6
+ throw new TypeError("Value cannot be serialized as JSON");
7
+ }
8
+ writeAtomicText(filePath, `${serialized}\n`);
9
+ }
10
+ export function readJson(filePath) {
11
+ // SAFETY: the caller's type parameter T is the contract for the file's
12
+ // contents; the file is expected to hold JSON matching T (files written by
13
+ // writeJson round-trip). JSON.parse returns `any`, so the assertion only
14
+ // documents the caller's guarantee — it performs no runtime narrowing.
15
+ return JSON.parse(fs.readFileSync(filePath, "utf-8"));
16
+ }
17
+ export function resetDir(dirPath) {
18
+ fs.rmSync(dirPath, { recursive: true, force: true });
19
+ fs.mkdirSync(dirPath, { recursive: true });
20
+ }
21
+ //# sourceMappingURL=file-utils.js.map
@@ -0,0 +1 @@
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,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAClD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,SAAS,CAAC,oCAAoC,CAAC,CAAC;IAC5D,CAAC;IACD,eAAe,CAAC,QAAQ,EAAE,GAAG,UAAU,IAAI,CAAC,CAAC;AAC/C,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 const serialized = JSON.stringify(value, null, 2);\n if (serialized === undefined) {\n throw new TypeError(\"Value cannot be serialized as JSON\");\n }\n writeAtomicText(filePath, `${serialized}\\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"]}
@@ -1 +1,4 @@
1
1
  export { openCheckpoint, withCheckpointedBatch, type CheckpointStore } from "./checkpoint.js";
2
+ export { requireEnv } from "./env.js";
3
+ export { readJson, resetDir, writeJson } from "./file-utils.js";
4
+ export { definePaths } from "./paths.js";
package/dist/node/node.js CHANGED
@@ -1,2 +1,5 @@
1
1
  export { openCheckpoint, withCheckpointedBatch } from "./checkpoint.js";
2
+ export { requireEnv } from "./env.js";
3
+ export { readJson, resetDir, writeJson } from "./file-utils.js";
4
+ export { definePaths } from "./paths.js";
2
5
  //# sourceMappingURL=node.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAwB,MAAM,iBAAiB,CAAC","sourcesContent":["export { openCheckpoint, withCheckpointedBatch, type CheckpointStore } from \"./checkpoint.js\";\n"]}
1
+ {"version":3,"file":"node.js","sourceRoot":"","sources":["../../src/node/node.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,qBAAqB,EAAwB,MAAM,iBAAiB,CAAC;AAC9F,OAAO,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC","sourcesContent":["export { openCheckpoint, withCheckpointedBatch, type CheckpointStore } from \"./checkpoint.js\";\nexport { requireEnv } from \"./env.js\";\nexport { readJson, resetDir, writeJson } from \"./file-utils.js\";\nexport { definePaths } from \"./paths.js\";\n"]}
@@ -0,0 +1,10 @@
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) => {
9
+ [K in keyof T]: string;
10
+ };
@@ -0,0 +1,19 @@
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 a resolved string, so the result preserves T's keys while widening
13
+ // relative path literals to string.
14
+ Object.fromEntries(Object.entries(relativePaths).map(([key, relativePath]) => [
15
+ key,
16
+ path.join(cwd, relativePath),
17
+ ]));
18
+ }
19
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
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,8EAA8E;IAC9E,oCAAoC;IACpC,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,CAC2B,CAAC;AACpC,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) => { [K in keyof T]: string } {\n return (cwd: string) =>\n // SAFETY: Object.entries(relativePaths) yields exactly T's keys, and every\n // value is a resolved string, so the result preserves T's keys while widening\n // relative path literals to string.\n Object.fromEntries(\n Object.entries(relativePaths).map(([key, relativePath]) => [\n key,\n path.join(cwd, relativePath),\n ])\n ) as { [K in keyof T]: string };\n}\n"]}
@@ -0,0 +1,19 @@
1
+ import { type Pipeline, type PipelineExecutionContext, type PipelineStepContext, type StandardSchemaV1 } from "../core/pipeline.js";
2
+ import { PipelineDocumentError } from "./project-document.js";
3
+ export { PipelineDocumentError };
4
+ export type { PipelineDocument, PipelineDocumentDefinition, PipelineDocumentStep, } from "./project-document.js";
5
+ /** Dependency values are checked at runtime rather than inferred from a project document. */
6
+ export type PipelineDocumentHandler = (inputs: Record<string, unknown>, context: PipelineStepContext<object>) => unknown;
7
+ export type PipelineDocumentFinalizer = (outputs: Record<string, unknown>, context: PipelineExecutionContext<object>) => unknown;
8
+ /** Only explicitly registered functions and schemas can be referenced by a document. */
9
+ export interface PipelineDocumentRegistry {
10
+ steps: Readonly<Record<string, PipelineDocumentHandler>>;
11
+ finalizers: Readonly<Record<string, PipelineDocumentFinalizer>>;
12
+ optionsSchemas?: Readonly<Record<string, StandardSchemaV1<object, object>>>;
13
+ schemas?: Readonly<Record<string, StandardSchemaV1>>;
14
+ }
15
+ /**
16
+ * Compile parsed YAML or JSON into ordinary pipelines. Does not load modules,
17
+ * invoke handlers or schemas, or perform I/O. Graph validation belongs to core.
18
+ */
19
+ export declare function compilePipelineDocument(document: unknown, registry: PipelineDocumentRegistry): ReadonlyMap<string, Pipeline<object, unknown>>;
@@ -0,0 +1,94 @@
1
+ import { createSteps, definePipeline, requireOutputs, } from "../core/pipeline.js";
2
+ import { validatePipelineDocument, PipelineDocumentError } from "./project-document.js";
3
+ export { PipelineDocumentError };
4
+ function resolve(registry, name, path) {
5
+ if (!registry || !Object.hasOwn(registry, name)) {
6
+ throw new PipelineDocumentError(path, `Unknown registered name ${JSON.stringify(name)}`);
7
+ }
8
+ return registry[name];
9
+ }
10
+ function handler(registry, name, path) {
11
+ const value = resolve(registry, name, path);
12
+ if (typeof value !== "function")
13
+ throw new PipelineDocumentError(path, "Expected a registered function");
14
+ return value;
15
+ }
16
+ function schema(registry, name, path) {
17
+ if (name === undefined)
18
+ return undefined;
19
+ const value = resolve(registry, name, path);
20
+ if (!value ||
21
+ value["~standard"]?.version !== 1 ||
22
+ typeof value["~standard"].validate !== "function") {
23
+ throw new PipelineDocumentError(path, "Expected a Standard Schema v1 schema");
24
+ }
25
+ return value;
26
+ }
27
+ /**
28
+ * Compile parsed YAML or JSON into ordinary pipelines. Does not load modules,
29
+ * invoke handlers or schemas, or perform I/O. Graph validation belongs to core.
30
+ */
31
+ export function compilePipelineDocument(document, registry) {
32
+ const parsed = validatePipelineDocument(document);
33
+ const pipelines = new Map();
34
+ for (const [id, definition] of Object.entries(parsed.pipelines)) {
35
+ const path = `$.pipelines[${JSON.stringify(id)}]`;
36
+ const optionsSchema = schema(registry.optionsSchemas, definition.optionsSchema, `${path}.optionsSchema`);
37
+ const step = optionsSchema ? createSteps(optionsSchema) : createSteps();
38
+ const entries = definition.steps.map((definition, index) => {
39
+ const stepPath = `${path}.steps[${index}]`;
40
+ // Populate these arrays after all steps exist, allowing forward references
41
+ // and leaving cycle detection to the existing definition validator.
42
+ const dependsOn = [];
43
+ const optionalDependsOn = [];
44
+ const skipAfterFailureOf = [];
45
+ const dryRun = definition.dryRun === undefined || definition.dryRun === "skip"
46
+ ? definition.dryRun
47
+ : handler(registry.steps, definition.dryRun.run, `${stepPath}.dryRun.run`);
48
+ const outputSchema = schema(registry.schemas, definition.outputSchema, `${stepPath}.outputSchema`);
49
+ const config = {
50
+ name: definition.name,
51
+ description: definition.description,
52
+ run: handler(registry.steps, definition.run, `${stepPath}.run`),
53
+ dryRun,
54
+ dependsOn,
55
+ optionalDependsOn,
56
+ skipAfterFailureOf,
57
+ };
58
+ return {
59
+ definition,
60
+ path: stepPath,
61
+ dependsOn,
62
+ optionalDependsOn,
63
+ skipAfterFailureOf,
64
+ step: outputSchema
65
+ ? step(definition.id, { ...config, outputSchema })
66
+ : step(definition.id, config),
67
+ };
68
+ });
69
+ const byId = new Map(entries.map((entry) => [entry.step.id, entry.step]));
70
+ const resolveSteps = (ids = [], fieldPath) => ids.map((stepId, index) => {
71
+ const found = byId.get(stepId);
72
+ if (!found)
73
+ throw new PipelineDocumentError(`${fieldPath}[${index}]`, `Unknown step ${JSON.stringify(stepId)}`);
74
+ return found;
75
+ });
76
+ for (const entry of entries) {
77
+ for (const field of ["dependsOn", "optionalDependsOn", "skipAfterFailureOf"]) {
78
+ entry[field].push(...resolveSteps(entry.definition[field], `${entry.path}.${field}`));
79
+ }
80
+ }
81
+ const finalize = handler(registry.finalizers, definition.finalize.run, `${path}.finalize.run`);
82
+ pipelines.set(id, definePipeline({
83
+ id,
84
+ steps: entries.map((entry) => entry.step),
85
+ targets: resolveSteps(definition.targets, `${path}.targets`),
86
+ resultSchema: schema(registry.schemas, definition.resultSchema, `${path}.resultSchema`),
87
+ finalize: definition.finalize.requireOutputs === undefined
88
+ ? finalize
89
+ : requireOutputs(resolveSteps(definition.finalize.requireOutputs, `${path}.finalize.requireOutputs`), finalize),
90
+ }));
91
+ }
92
+ return pipelines;
93
+ }
94
+ //# sourceMappingURL=project-compiler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-compiler.js","sourceRoot":"","sources":["../../src/project/project-compiler.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n createSteps,\n definePipeline,\n requireOutputs,\n type AnyStep,\n type Pipeline,\n type PipelineExecutionContext,\n type PipelineStepContext,\n type StandardSchemaV1,\n} from \"../core/pipeline.js\";\nimport { validatePipelineDocument, PipelineDocumentError } from \"./project-document.js\";\n\nexport { PipelineDocumentError };\nexport type {\n PipelineDocument,\n PipelineDocumentDefinition,\n PipelineDocumentStep,\n} from \"./project-document.js\";\n\n/** Dependency values are checked at runtime rather than inferred from a project document. */\nexport type PipelineDocumentHandler = (\n inputs: Record<string, unknown>,\n context: PipelineStepContext<object>\n) => unknown;\n\nexport type PipelineDocumentFinalizer = (\n outputs: Record<string, unknown>,\n context: PipelineExecutionContext<object>\n) => unknown;\n\n/** Only explicitly registered functions and schemas can be referenced by a document. */\nexport interface PipelineDocumentRegistry {\n steps: Readonly<Record<string, PipelineDocumentHandler>>;\n finalizers: Readonly<Record<string, PipelineDocumentFinalizer>>;\n optionsSchemas?: Readonly<Record<string, StandardSchemaV1<object, object>>>;\n schemas?: Readonly<Record<string, StandardSchemaV1>>;\n}\n\nfunction resolve<T>(\n registry: Readonly<Record<string, T>> | undefined,\n name: string,\n path: string\n): T {\n if (!registry || !Object.hasOwn(registry, name)) {\n throw new PipelineDocumentError(path, `Unknown registered name ${JSON.stringify(name)}`);\n }\n return registry[name];\n}\n\nfunction handler<T extends PipelineDocumentHandler | PipelineDocumentFinalizer>(\n registry: Readonly<Record<string, T>>,\n name: string,\n path: string\n): T {\n const value = resolve(registry, name, path);\n if (typeof value !== \"function\")\n throw new PipelineDocumentError(path, \"Expected a registered function\");\n return value;\n}\n\nfunction schema<T extends StandardSchemaV1>(\n registry: Readonly<Record<string, T>> | undefined,\n name: string | undefined,\n path: string\n): T | undefined {\n if (name === undefined) return undefined;\n const value = resolve(registry, name, path);\n if (\n !value ||\n value[\"~standard\"]?.version !== 1 ||\n typeof value[\"~standard\"].validate !== \"function\"\n ) {\n throw new PipelineDocumentError(path, \"Expected a Standard Schema v1 schema\");\n }\n return value;\n}\n\n/**\n * Compile parsed YAML or JSON into ordinary pipelines. Does not load modules,\n * invoke handlers or schemas, or perform I/O. Graph validation belongs to core.\n */\nexport function compilePipelineDocument(\n document: unknown,\n registry: PipelineDocumentRegistry\n): ReadonlyMap<string, Pipeline<object, unknown>> {\n const parsed = validatePipelineDocument(document);\n const pipelines = new Map<string, Pipeline<object, unknown>>();\n for (const [id, definition] of Object.entries(parsed.pipelines)) {\n const path = `$.pipelines[${JSON.stringify(id)}]`;\n const optionsSchema = schema(\n registry.optionsSchemas,\n definition.optionsSchema,\n `${path}.optionsSchema`\n );\n const step = optionsSchema ? createSteps(optionsSchema) : createSteps<object>();\n const entries = definition.steps.map((definition, index) => {\n const stepPath = `${path}.steps[${index}]`;\n // Populate these arrays after all steps exist, allowing forward references\n // and leaving cycle detection to the existing definition validator.\n const dependsOn: AnyStep[] = [];\n const optionalDependsOn: AnyStep[] = [];\n const skipAfterFailureOf: AnyStep[] = [];\n const dryRun =\n definition.dryRun === undefined || definition.dryRun === \"skip\"\n ? definition.dryRun\n : handler(registry.steps, definition.dryRun.run, `${stepPath}.dryRun.run`);\n const outputSchema = schema(\n registry.schemas,\n definition.outputSchema,\n `${stepPath}.outputSchema`\n );\n const config = {\n name: definition.name,\n description: definition.description,\n run: handler(registry.steps, definition.run, `${stepPath}.run`),\n dryRun,\n dependsOn,\n optionalDependsOn,\n skipAfterFailureOf,\n };\n return {\n definition,\n path: stepPath,\n dependsOn,\n optionalDependsOn,\n skipAfterFailureOf,\n step: outputSchema\n ? step(definition.id, { ...config, outputSchema })\n : step(definition.id, config),\n };\n });\n const byId = new Map(entries.map((entry) => [entry.step.id, entry.step]));\n const resolveSteps = (ids: readonly string[] = [], fieldPath: string): AnyStep[] =>\n ids.map((stepId, index) => {\n const found = byId.get(stepId);\n if (!found)\n throw new PipelineDocumentError(\n `${fieldPath}[${index}]`,\n `Unknown step ${JSON.stringify(stepId)}`\n );\n return found;\n });\n for (const entry of entries) {\n for (const field of [\"dependsOn\", \"optionalDependsOn\", \"skipAfterFailureOf\"] as const) {\n entry[field].push(...resolveSteps(entry.definition[field], `${entry.path}.${field}`));\n }\n }\n const finalize = handler(registry.finalizers, definition.finalize.run, `${path}.finalize.run`);\n pipelines.set(\n id,\n definePipeline({\n id,\n steps: entries.map((entry) => entry.step),\n targets: resolveSteps(definition.targets, `${path}.targets`),\n resultSchema: schema(registry.schemas, definition.resultSchema, `${path}.resultSchema`),\n finalize:\n definition.finalize.requireOutputs === undefined\n ? finalize\n : requireOutputs(\n resolveSteps(definition.finalize.requireOutputs, `${path}.finalize.requireOutputs`),\n finalize\n ),\n })\n );\n }\n return pipelines;\n}\n"]}
@@ -0,0 +1,47 @@
1
+ /** The serializable version 1 pipeline authoring format for project documents. */
2
+ export interface PipelineDocument {
3
+ /** Optional editor schema association; never fetched during validation. */
4
+ $schema?: string;
5
+ version: 1;
6
+ metadata?: PipelineDocumentMetadata;
7
+ pipelines: Record<string, PipelineDocumentDefinition>;
8
+ }
9
+ /** Optional human-facing document information, never execution policy. */
10
+ export interface PipelineDocumentMetadata {
11
+ name?: string;
12
+ description?: string;
13
+ authors?: readonly string[];
14
+ /** Author-maintained date in YYYY-MM-DD format. */
15
+ date?: string;
16
+ }
17
+ export interface PipelineDocumentDefinition {
18
+ optionsSchema?: string;
19
+ resultSchema?: string;
20
+ targets?: readonly string[];
21
+ steps: readonly PipelineDocumentStep[];
22
+ finalize: {
23
+ run: string;
24
+ requireOutputs?: readonly string[];
25
+ };
26
+ }
27
+ export interface PipelineDocumentStep {
28
+ id: string;
29
+ run: string;
30
+ name?: string;
31
+ description?: string;
32
+ dependsOn?: readonly string[];
33
+ optionalDependsOn?: readonly string[];
34
+ skipAfterFailureOf?: readonly string[];
35
+ dryRun?: "skip" | {
36
+ run: string;
37
+ };
38
+ outputSchema?: string;
39
+ }
40
+ /** A document shape or reference error; graph errors remain PipelineDefinitionError. */
41
+ export declare class PipelineDocumentError extends Error {
42
+ readonly path: string;
43
+ readonly code = "TUBELESS_DOCUMENT_INVALID";
44
+ constructor(path: string, message: string);
45
+ }
46
+ /** Validate and copy a parsed document without resolving handlers or executing code. */
47
+ export declare function validatePipelineDocument(value: unknown): PipelineDocument;
@@ -0,0 +1,136 @@
1
+ /** A document shape or reference error; graph errors remain PipelineDefinitionError. */
2
+ export class PipelineDocumentError extends Error {
3
+ path;
4
+ code = "TUBELESS_DOCUMENT_INVALID";
5
+ constructor(path, message) {
6
+ super(`${path}: ${message}`);
7
+ this.path = path;
8
+ this.name = "PipelineDocumentError";
9
+ }
10
+ }
11
+ function record(value, path) {
12
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
13
+ throw new PipelineDocumentError(path, "Expected an object");
14
+ }
15
+ // SAFETY: the check above establishes a non-null, non-array object; values stay unknown.
16
+ return value;
17
+ }
18
+ function fields(value, path, allowed) {
19
+ const object = record(value, path);
20
+ for (const key of Object.keys(object)) {
21
+ if (!allowed.includes(key)) {
22
+ throw new PipelineDocumentError(`${path}.${key}`, "Unknown field");
23
+ }
24
+ }
25
+ return object;
26
+ }
27
+ function text(value, path) {
28
+ if (typeof value !== "string" || value.trim().length === 0) {
29
+ throw new PipelineDocumentError(path, "Expected a non-empty string");
30
+ }
31
+ return value;
32
+ }
33
+ function optionalText(value, path) {
34
+ return value === undefined ? undefined : text(value, path);
35
+ }
36
+ function references(value, path) {
37
+ if (value === undefined)
38
+ return undefined;
39
+ if (!Array.isArray(value))
40
+ throw new PipelineDocumentError(path, "Expected an array of names");
41
+ return value.map((item, index) => text(item, `${path}[${index}]`));
42
+ }
43
+ function parseStep(value, path) {
44
+ const step = fields(value, path, [
45
+ "id",
46
+ "run",
47
+ "name",
48
+ "description",
49
+ "dependsOn",
50
+ "optionalDependsOn",
51
+ "skipAfterFailureOf",
52
+ "dryRun",
53
+ "outputSchema",
54
+ ]);
55
+ let dryRun;
56
+ if (step.dryRun === "skip")
57
+ dryRun = "skip";
58
+ else if (step.dryRun !== undefined) {
59
+ const handler = fields(step.dryRun, `${path}.dryRun`, ["run"]);
60
+ dryRun = { run: text(handler.run, `${path}.dryRun.run`) };
61
+ }
62
+ return {
63
+ id: text(step.id, `${path}.id`),
64
+ run: text(step.run, `${path}.run`),
65
+ name: optionalText(step.name, `${path}.name`),
66
+ description: optionalText(step.description, `${path}.description`),
67
+ dependsOn: references(step.dependsOn, `${path}.dependsOn`),
68
+ optionalDependsOn: references(step.optionalDependsOn, `${path}.optionalDependsOn`),
69
+ skipAfterFailureOf: references(step.skipAfterFailureOf, `${path}.skipAfterFailureOf`),
70
+ outputSchema: optionalText(step.outputSchema, `${path}.outputSchema`),
71
+ dryRun,
72
+ };
73
+ }
74
+ function metadata(value) {
75
+ if (value === undefined)
76
+ return undefined;
77
+ const entry = fields(value, "$.metadata", ["name", "description", "authors", "date"]);
78
+ const date = optionalText(entry.date, "$.metadata.date");
79
+ if (date !== undefined &&
80
+ (!/^\d{4}-\d{2}-\d{2}$/.test(date) ||
81
+ !Number.isFinite(Date.parse(date)) ||
82
+ new Date(date).toISOString().slice(0, 10) !== date)) {
83
+ throw new PipelineDocumentError("$.metadata.date", "Expected a valid YYYY-MM-DD date");
84
+ }
85
+ return {
86
+ name: optionalText(entry.name, "$.metadata.name"),
87
+ description: optionalText(entry.description, "$.metadata.description"),
88
+ authors: references(entry.authors, "$.metadata.authors"),
89
+ date,
90
+ };
91
+ }
92
+ /** Validate and copy a parsed document without resolving handlers or executing code. */
93
+ export function validatePipelineDocument(value) {
94
+ const document = fields(value, "$", ["$schema", "version", "metadata", "pipelines"]);
95
+ if (document.version !== 1) {
96
+ throw new PipelineDocumentError("$.version", "Expected document version 1");
97
+ }
98
+ const pipelines = record(document.pipelines, "$.pipelines");
99
+ if (Object.keys(pipelines).length === 0) {
100
+ throw new PipelineDocumentError("$.pipelines", "Expected at least one pipeline");
101
+ }
102
+ return {
103
+ $schema: optionalText(document.$schema, "$.$schema"),
104
+ version: 1,
105
+ metadata: metadata(document.metadata),
106
+ pipelines: Object.fromEntries(Object.entries(pipelines).map(([id, value]) => {
107
+ const path = `$.pipelines[${JSON.stringify(id)}]`;
108
+ text(id, path);
109
+ const pipeline = fields(value, path, [
110
+ "optionsSchema",
111
+ "resultSchema",
112
+ "targets",
113
+ "steps",
114
+ "finalize",
115
+ ]);
116
+ if (!Array.isArray(pipeline.steps) || pipeline.steps.length === 0) {
117
+ throw new PipelineDocumentError(`${path}.steps`, "Expected at least one step");
118
+ }
119
+ const finalize = fields(pipeline.finalize, `${path}.finalize`, ["run", "requireOutputs"]);
120
+ return [
121
+ id,
122
+ {
123
+ optionsSchema: optionalText(pipeline.optionsSchema, `${path}.optionsSchema`),
124
+ resultSchema: optionalText(pipeline.resultSchema, `${path}.resultSchema`),
125
+ targets: references(pipeline.targets, `${path}.targets`),
126
+ steps: pipeline.steps.map((step, index) => parseStep(step, `${path}.steps[${index}]`)),
127
+ finalize: {
128
+ run: text(finalize.run, `${path}.finalize.run`),
129
+ requireOutputs: references(finalize.requireOutputs, `${path}.finalize.requireOutputs`),
130
+ },
131
+ },
132
+ ];
133
+ })),
134
+ };
135
+ }
136
+ //# sourceMappingURL=project-document.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project-document.js","sourceRoot":"","sources":["../../src/project/project-document.ts"],"names":[],"mappings":"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","sourcesContent":["/** The serializable version 1 pipeline authoring format for project documents. */\nexport interface PipelineDocument {\n /** Optional editor schema association; never fetched during validation. */\n $schema?: string;\n version: 1;\n metadata?: PipelineDocumentMetadata;\n pipelines: Record<string, PipelineDocumentDefinition>;\n}\n\n/** Optional human-facing document information, never execution policy. */\nexport interface PipelineDocumentMetadata {\n name?: string;\n description?: string;\n authors?: readonly string[];\n /** Author-maintained date in YYYY-MM-DD format. */\n date?: string;\n}\n\nexport interface PipelineDocumentDefinition {\n optionsSchema?: string;\n resultSchema?: string;\n targets?: readonly string[];\n steps: readonly PipelineDocumentStep[];\n finalize: { run: string; requireOutputs?: readonly string[] };\n}\n\nexport interface PipelineDocumentStep {\n id: string;\n run: string;\n name?: string;\n description?: string;\n dependsOn?: readonly string[];\n optionalDependsOn?: readonly string[];\n skipAfterFailureOf?: readonly string[];\n dryRun?: \"skip\" | { run: string };\n outputSchema?: string;\n}\n\n/** A document shape or reference error; graph errors remain PipelineDefinitionError. */\nexport class PipelineDocumentError extends Error {\n readonly code = \"TUBELESS_DOCUMENT_INVALID\";\n\n constructor(\n readonly path: string,\n message: string\n ) {\n super(`${path}: ${message}`);\n this.name = \"PipelineDocumentError\";\n }\n}\n\nfunction record(value: unknown, path: string): Record<string, unknown> {\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n throw new PipelineDocumentError(path, \"Expected an object\");\n }\n // SAFETY: the check above establishes a non-null, non-array object; values stay unknown.\n return value as Record<string, unknown>;\n}\n\nfunction fields(value: unknown, path: string, allowed: readonly string[]) {\n const object = record(value, path);\n for (const key of Object.keys(object)) {\n if (!allowed.includes(key)) {\n throw new PipelineDocumentError(`${path}.${key}`, \"Unknown field\");\n }\n }\n return object;\n}\n\nfunction text(value: unknown, path: string): string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new PipelineDocumentError(path, \"Expected a non-empty string\");\n }\n return value;\n}\n\nfunction optionalText(value: unknown, path: string): string | undefined {\n return value === undefined ? undefined : text(value, path);\n}\n\nfunction references(value: unknown, path: string): readonly string[] | undefined {\n if (value === undefined) return undefined;\n if (!Array.isArray(value)) throw new PipelineDocumentError(path, \"Expected an array of names\");\n return value.map((item: unknown, index) => text(item, `${path}[${index}]`));\n}\n\nfunction parseStep(value: unknown, path: string): PipelineDocumentStep {\n const step = fields(value, path, [\n \"id\",\n \"run\",\n \"name\",\n \"description\",\n \"dependsOn\",\n \"optionalDependsOn\",\n \"skipAfterFailureOf\",\n \"dryRun\",\n \"outputSchema\",\n ]);\n let dryRun: PipelineDocumentStep[\"dryRun\"];\n if (step.dryRun === \"skip\") dryRun = \"skip\";\n else if (step.dryRun !== undefined) {\n const handler = fields(step.dryRun, `${path}.dryRun`, [\"run\"]);\n dryRun = { run: text(handler.run, `${path}.dryRun.run`) };\n }\n return {\n id: text(step.id, `${path}.id`),\n run: text(step.run, `${path}.run`),\n name: optionalText(step.name, `${path}.name`),\n description: optionalText(step.description, `${path}.description`),\n dependsOn: references(step.dependsOn, `${path}.dependsOn`),\n optionalDependsOn: references(step.optionalDependsOn, `${path}.optionalDependsOn`),\n skipAfterFailureOf: references(step.skipAfterFailureOf, `${path}.skipAfterFailureOf`),\n outputSchema: optionalText(step.outputSchema, `${path}.outputSchema`),\n dryRun,\n };\n}\n\nfunction metadata(value: unknown): PipelineDocumentMetadata | undefined {\n if (value === undefined) return undefined;\n const entry = fields(value, \"$.metadata\", [\"name\", \"description\", \"authors\", \"date\"]);\n const date = optionalText(entry.date, \"$.metadata.date\");\n if (\n date !== undefined &&\n (!/^\\d{4}-\\d{2}-\\d{2}$/.test(date) ||\n !Number.isFinite(Date.parse(date)) ||\n new Date(date).toISOString().slice(0, 10) !== date)\n ) {\n throw new PipelineDocumentError(\"$.metadata.date\", \"Expected a valid YYYY-MM-DD date\");\n }\n return {\n name: optionalText(entry.name, \"$.metadata.name\"),\n description: optionalText(entry.description, \"$.metadata.description\"),\n authors: references(entry.authors, \"$.metadata.authors\"),\n date,\n };\n}\n\n/** Validate and copy a parsed document without resolving handlers or executing code. */\nexport function validatePipelineDocument(value: unknown): PipelineDocument {\n const document = fields(value, \"$\", [\"$schema\", \"version\", \"metadata\", \"pipelines\"]);\n if (document.version !== 1) {\n throw new PipelineDocumentError(\"$.version\", \"Expected document version 1\");\n }\n const pipelines = record(document.pipelines, \"$.pipelines\");\n if (Object.keys(pipelines).length === 0) {\n throw new PipelineDocumentError(\"$.pipelines\", \"Expected at least one pipeline\");\n }\n return {\n $schema: optionalText(document.$schema, \"$.$schema\"),\n version: 1,\n metadata: metadata(document.metadata),\n pipelines: Object.fromEntries(\n Object.entries(pipelines).map(([id, value]) => {\n const path = `$.pipelines[${JSON.stringify(id)}]`;\n text(id, path);\n const pipeline = fields(value, path, [\n \"optionsSchema\",\n \"resultSchema\",\n \"targets\",\n \"steps\",\n \"finalize\",\n ]);\n if (!Array.isArray(pipeline.steps) || pipeline.steps.length === 0) {\n throw new PipelineDocumentError(`${path}.steps`, \"Expected at least one step\");\n }\n const finalize = fields(pipeline.finalize, `${path}.finalize`, [\"run\", \"requireOutputs\"]);\n return [\n id,\n {\n optionsSchema: optionalText(pipeline.optionsSchema, `${path}.optionsSchema`),\n resultSchema: optionalText(pipeline.resultSchema, `${path}.resultSchema`),\n targets: references(pipeline.targets, `${path}.targets`),\n steps: pipeline.steps.map((step: unknown, index) =>\n parseStep(step, `${path}.steps[${index}]`)\n ),\n finalize: {\n run: text(finalize.run, `${path}.finalize.run`),\n requireOutputs: references(\n finalize.requireOutputs,\n `${path}.finalize.requireOutputs`\n ),\n },\n },\n ];\n })\n ),\n };\n}\n"]}
@@ -1 +1,3 @@
1
1
  export { definePipelineProject, type PipelineProjectCommandModule, type PipelineProjectManifest, type PipelineProjectManifestInput, } from "./project-manifest.js";
2
+ export { validatePipelineDocument, type PipelineDocumentMetadata } from "./project-document.js";
3
+ export { compilePipelineDocument, PipelineDocumentError, type PipelineDocument, type PipelineDocumentDefinition, type PipelineDocumentFinalizer, type PipelineDocumentHandler, type PipelineDocumentRegistry, type PipelineDocumentStep, } from "./project-compiler.js";
@@ -1,2 +1,4 @@
1
1
  export { definePipelineProject, } from "./project-manifest.js";
2
+ export { validatePipelineDocument } from "./project-document.js";
3
+ export { compilePipelineDocument, PipelineDocumentError, } from "./project-compiler.js";
2
4
  //# sourceMappingURL=project.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/project/project.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAItB,MAAM,uBAAuB,CAAC","sourcesContent":["export {\n definePipelineProject,\n type PipelineProjectCommandModule,\n type PipelineProjectManifest,\n type PipelineProjectManifestInput,\n} from \"./project-manifest.js\";\n"]}
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/project/project.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,GAItB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,wBAAwB,EAAiC,MAAM,uBAAuB,CAAC;AAEhG,OAAO,EACL,uBAAuB,EACvB,qBAAqB,GAOtB,MAAM,uBAAuB,CAAC","sourcesContent":["export {\n definePipelineProject,\n type PipelineProjectCommandModule,\n type PipelineProjectManifest,\n type PipelineProjectManifestInput,\n} from \"./project-manifest.js\";\n\nexport { validatePipelineDocument, type PipelineDocumentMetadata } from \"./project-document.js\";\n\nexport {\n compilePipelineDocument,\n PipelineDocumentError,\n type PipelineDocument,\n type PipelineDocumentDefinition,\n type PipelineDocumentFinalizer,\n type PipelineDocumentHandler,\n type PipelineDocumentRegistry,\n type PipelineDocumentStep,\n} from \"./project-compiler.js\";\n"]}
@@ -0,0 +1,2 @@
1
+ import { type WorkbenchCliIo } from "./workbench-shared.js";
2
+ export declare function runValidate(argv: readonly string[], io: WorkbenchCliIo): Promise<number>;
@@ -0,0 +1,76 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { extname, resolve } from "node:path";
3
+ import { parseArgs } from "node:util";
4
+ import { PipelineDocumentError, validatePipelineDocument } from "../project/project.js";
5
+ import { errorMessage, TUBELESS_WORKBENCH_EXIT_CODE, } from "./workbench-shared.js";
6
+ import { runWorkbenchSubcommand } from "./workbench-subcommand.js";
7
+ const VALIDATE_USAGE = `Usage: tubeless validate [--json] <document.yaml|document.yml|document.json>
8
+
9
+ Check pipeline document structure and metadata without importing handlers.
10
+ Does not check graph semantics, registered names, or domain values. Use inspect
11
+ or plan on the compiled command module for engine validation.
12
+ YAML uses Bun's parser; duplicate mapping keys may be overwritten during parsing.
13
+
14
+ Options:
15
+ --json Emit a structured validation result
16
+ -h, --help Show this help
17
+ `;
18
+ export async function runValidate(argv, io) {
19
+ return runWorkbenchSubcommand({
20
+ usage: VALIDATE_USAGE,
21
+ parse: (args) => parseArgs({
22
+ args: [...args],
23
+ allowPositionals: true,
24
+ strict: true,
25
+ options: {
26
+ help: { type: "boolean", short: "h" },
27
+ json: { type: "boolean" },
28
+ },
29
+ }),
30
+ positionalCountError: { count: 1, message: "Pass exactly one YAML or JSON document." },
31
+ async run(parsed, commandIo) {
32
+ const file = resolve(commandIo.cwd, parsed.positionals[0]);
33
+ try {
34
+ const extension = extname(file).toLowerCase();
35
+ if (![".yaml", ".yml", ".json"].includes(extension)) {
36
+ throw new Error("Expected a .yaml, .yml, or .json document");
37
+ }
38
+ const source = await readFile(file, "utf8");
39
+ let value;
40
+ if (extension === ".json")
41
+ value = JSON.parse(source);
42
+ else {
43
+ // The executable runs under Bun; keep its parser out of library entrypoints.
44
+ const specifier = "bun";
45
+ // SAFETY: Bun's documented YAML.parse API returns parsed, untrusted data.
46
+ const { YAML } = (await import(specifier));
47
+ value = YAML.parse(source);
48
+ }
49
+ const document = validatePipelineDocument(value);
50
+ const pipelines = Object.keys(document.pipelines);
51
+ commandIo.stdout.write(parsed.values.json
52
+ ? `${JSON.stringify({ ok: true, file, version: document.version, metadata: document.metadata, pipelines })}\n`
53
+ : `Valid pipeline document: ${file} (${pipelines.length} pipeline(s))\n`);
54
+ return TUBELESS_WORKBENCH_EXIT_CODE.success;
55
+ }
56
+ catch (error) {
57
+ if (parsed.values.json) {
58
+ commandIo.stdout.write(`${JSON.stringify({
59
+ ok: false,
60
+ file,
61
+ error: {
62
+ message: errorMessage(error),
63
+ ...(error instanceof PipelineDocumentError
64
+ ? { code: error.code, path: error.path }
65
+ : {}),
66
+ },
67
+ })}\n`);
68
+ }
69
+ else
70
+ commandIo.stderr.write(`${errorMessage(error)}\n`);
71
+ return TUBELESS_WORKBENCH_EXIT_CODE.validation;
72
+ }
73
+ },
74
+ }, argv, io);
75
+ }
76
+ //# sourceMappingURL=workbench-validate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workbench-validate.js","sourceRoot":"","sources":["../../src/workbench/workbench-validate.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { extname, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\nimport { PipelineDocumentError, validatePipelineDocument } from \"../project/project.js\";\nimport {\n errorMessage,\n TUBELESS_WORKBENCH_EXIT_CODE,\n type WorkbenchCliIo,\n} from \"./workbench-shared.js\";\nimport { runWorkbenchSubcommand } from \"./workbench-subcommand.js\";\n\nconst VALIDATE_USAGE = `Usage: tubeless validate [--json] <document.yaml|document.yml|document.json>\n\nCheck pipeline document structure and metadata without importing handlers.\nDoes not check graph semantics, registered names, or domain values. Use inspect\nor plan on the compiled command module for engine validation.\nYAML uses Bun's parser; duplicate mapping keys may be overwritten during parsing.\n\nOptions:\n --json Emit a structured validation result\n -h, --help Show this help\n`;\n\nexport async function runValidate(argv: readonly string[], io: WorkbenchCliIo): Promise<number> {\n return runWorkbenchSubcommand(\n {\n usage: VALIDATE_USAGE,\n parse: (args) =>\n parseArgs({\n args: [...args],\n allowPositionals: true,\n strict: true,\n options: {\n help: { type: \"boolean\", short: \"h\" },\n json: { type: \"boolean\" },\n },\n }),\n positionalCountError: { count: 1, message: \"Pass exactly one YAML or JSON document.\" },\n async run(parsed, commandIo) {\n const file = resolve(commandIo.cwd, parsed.positionals[0]!);\n try {\n const extension = extname(file).toLowerCase();\n if (![\".yaml\", \".yml\", \".json\"].includes(extension)) {\n throw new Error(\"Expected a .yaml, .yml, or .json document\");\n }\n const source = await readFile(file, \"utf8\");\n let value: unknown;\n if (extension === \".json\") value = JSON.parse(source);\n else {\n // The executable runs under Bun; keep its parser out of library entrypoints.\n const specifier = \"bun\";\n // SAFETY: Bun's documented YAML.parse API returns parsed, untrusted data.\n const { YAML } = (await import(specifier)) as {\n YAML: { parse(text: string): unknown };\n };\n value = YAML.parse(source);\n }\n const document = validatePipelineDocument(value);\n const pipelines = Object.keys(document.pipelines);\n commandIo.stdout.write(\n parsed.values.json\n ? `${JSON.stringify({ ok: true, file, version: document.version, metadata: document.metadata, pipelines })}\\n`\n : `Valid pipeline document: ${file} (${pipelines.length} pipeline(s))\\n`\n );\n return TUBELESS_WORKBENCH_EXIT_CODE.success;\n } catch (error) {\n if (parsed.values.json) {\n commandIo.stdout.write(\n `${JSON.stringify({\n ok: false,\n file,\n error: {\n message: errorMessage(error),\n ...(error instanceof PipelineDocumentError\n ? { code: error.code, path: error.path }\n : {}),\n },\n })}\\n`\n );\n } else commandIo.stderr.write(`${errorMessage(error)}\\n`);\n return TUBELESS_WORKBENCH_EXIT_CODE.validation;\n }\n },\n },\n argv,\n io\n );\n}\n"]}
@@ -6,6 +6,7 @@ import { runPlan } from "./workbench-plan.js";
6
6
  import { runCommand } from "./workbench-run.js";
7
7
  import { TUBELESS_WORKBENCH_EXIT_CODE, writeUsageError, } from "./workbench-shared.js";
8
8
  import { runUi } from "./workbench-ui.js";
9
+ import { runValidate } from "./workbench-validate.js";
9
10
  export { TUBELESS_WORKBENCH_EXIT_CODE } from "./workbench-shared.js";
10
11
  const WORKBENCH_USAGE = `Usage: tubeless <command> [options] <registered-id|pipeline-or-command-file>
11
12
 
@@ -13,6 +14,7 @@ Inspect, plan, visualize, or safely run exported tubeless workflows.
13
14
 
14
15
  Commands:
15
16
  tubeless list List commands explicitly registered in the project manifest
17
+ tubeless validate Check a YAML or JSON pipeline document without loading handlers
16
18
  tubeless inspect Show pipeline identity and the default structural plan
17
19
  tubeless plan Preview step selection without executing the pipeline
18
20
  tubeless graph Generate Mermaid flowchart source
@@ -34,6 +36,8 @@ export async function runWorkbenchCli(argv, io) {
34
36
  }
35
37
  if (command === "list")
36
38
  return runList(commandArgs, io);
39
+ if (command === "validate")
40
+ return runValidate(commandArgs, io);
37
41
  if (command === "inspect")
38
42
  return runInspect(commandArgs, io);
39
43
  if (command === "plan")