tubeless 0.2.4 → 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.
- package/README.md +1 -1
- package/dist/cli/cli-types.d.ts +1 -1
- package/dist/cli/cli-types.js.map +1 -1
- package/dist/cli/cli.d.ts +4 -2
- package/dist/cli/cli.js +1 -1
- package/dist/cli/cli.js.map +1 -1
- package/dist/node/atomic-text.js +3 -2
- package/dist/node/atomic-text.js.map +1 -1
- package/dist/node/env.d.ts +1 -0
- package/dist/node/env.js +8 -0
- package/dist/node/env.js.map +1 -0
- package/dist/node/file-utils.d.ts +3 -0
- package/dist/node/file-utils.js +21 -0
- package/dist/node/file-utils.js.map +1 -0
- package/dist/node/node.d.ts +3 -0
- package/dist/node/node.js +3 -0
- package/dist/node/node.js.map +1 -1
- package/dist/node/paths.d.ts +10 -0
- package/dist/node/paths.js +19 -0
- package/dist/node/paths.js.map +1 -0
- package/dist/project/project-compiler.d.ts +19 -0
- package/dist/project/project-compiler.js +94 -0
- package/dist/project/project-compiler.js.map +1 -0
- package/dist/project/project-document.d.ts +47 -0
- package/dist/project/project-document.js +136 -0
- package/dist/project/project-document.js.map +1 -0
- package/dist/{workbench/workbench-project.js → project/project-manifest.js} +1 -1
- package/dist/project/project-manifest.js.map +1 -0
- package/dist/project/project.d.ts +3 -0
- package/dist/project/project.js +4 -0
- package/dist/project/project.js.map +1 -0
- package/dist/workbench/workbench-project-loader.d.ts +1 -1
- package/dist/workbench/workbench-project-loader.js +1 -1
- package/dist/workbench/workbench-project-loader.js.map +1 -1
- package/dist/workbench/workbench-validate.d.ts +2 -0
- package/dist/workbench/workbench-validate.js +76 -0
- package/dist/workbench/workbench-validate.js.map +1 -0
- package/dist/workbench/workbench.js +4 -0
- package/dist/workbench/workbench.js.map +1 -1
- package/docs/README.md +7 -4
- package/docs/agent-guide.md +27 -1
- package/docs/api-reference.md +66 -12
- package/docs/api-report.json +70 -6
- package/docs/cli.md +61 -9
- package/docs/declarative-pipelines.md +247 -0
- package/docs/getting-started.md +4 -3
- package/docs/llms.txt +5 -1
- package/docs/pipeline-document.schema.json +93 -0
- package/docs/recipes.md +58 -23
- package/docs/studio.md +1 -1
- 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 +25 -2
- package/examples/cli-job.ts +1 -1
- package/examples/declarative/handlers.ts +44 -0
- package/examples/declarative/peloton-handlers.ts +231 -0
- package/examples/declarative/peloton.yaml +65 -0
- package/examples/declarative/pipelines.yaml +33 -0
- package/examples/declarative/yaml.d.ts +5 -0
- package/examples/live-tui.ts +1 -1
- package/examples/node-artifacts.ts +28 -0
- package/examples/peloton.ts +1 -1
- package/examples/yaml-peloton.ts +49 -0
- package/examples/yaml-pipelines.ts +26 -0
- package/package.json +12 -5
- package/skills/tubeless/SKILL.md +20 -1
- package/dist/workbench/workbench-entry.d.ts +0 -2
- package/dist/workbench/workbench-entry.js +0 -3
- package/dist/workbench/workbench-entry.js.map +0 -1
- package/dist/workbench/workbench-project.js.map +0 -1
- /package/dist/{workbench/workbench-project.d.ts → project/project-manifest.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -74,7 +74,7 @@ report with step statuses, errors, and timings; `plan` to preview execution; or
|
|
|
74
74
|
|
|
75
75
|
## Use the CLI
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
Define commands with `tubeless/cli` and catalogs with `tubeless/project`.
|
|
78
78
|
The [CLI guide](./docs/cli.md) explains how to set up the manifest and commands.
|
|
79
79
|
With an `import-rows` command registered, you can run:
|
|
80
80
|
|
package/dist/cli/cli-types.d.ts
CHANGED
|
@@ -135,7 +135,7 @@ export type CliParams<TSchema extends CliParamsSchema> = {
|
|
|
135
135
|
export interface CliContext {
|
|
136
136
|
cwd: string;
|
|
137
137
|
/** Environment used for `param.env` fallbacks. Defaults to `process.env`. */
|
|
138
|
-
env?:
|
|
138
|
+
env?: Record<string, string | undefined>;
|
|
139
139
|
log: PipelineLogger;
|
|
140
140
|
/** Optional executor-only context used when this command runs a pipeline. */
|
|
141
141
|
pipelineContext?: Omit<PipelineContext, "cwd" | "log" | "signal">;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli-types.js","sourceRoot":"","sources":["../../src/cli/cli-types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAgLpE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC,MAAM;IACN,QAAQ;IAFnB,YACW,MAAyB,EACzB,QAAgB;QAEzB,KAAK,CAAC,oCAAoC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;sBAHrF,MAAM;wBACN,QAAQ;QAGjB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACpB,QAAQ;IAA7B,YAAqB,QAAgB;QACnC,KAAK,CAAC,QAAQ,CAAC,CAAC;wBADG,QAAQ;QAE3B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["import type { CheckpointStore } from \"../node/checkpoint.js\";\nimport type { PipelineContext, PipelineLogger } from \"../core/pipeline.js\";\nimport { brandTubelessError } from \"../utilities/tubeless-error.js\";\n\n/**\n * Typed command-line entry points for scripts, mirroring how `definePipeline` turns a\n * declarative step list into a runnable pipeline: declare a flag schema once and get a\n * fully-typed, validated `values` object handed to `run`, instead of each script hand-\n * rolling its own `process.argv` scanning and `as` casts.\n */\n\nexport type CliParamType = \"string\" | \"number\" | \"boolean\" | \"path\";\n\ninterface CliParamBase {\n /** Shown next to the flag in generated `--help` output. */\n description?: string;\n /** UI adapters place execution controls in one section. Built-ins set this. */\n group?: \"execution\";\n /** UI adapters treat exclusive repeatable parameters as mutually exclusive. Built-ins set this on step and target selection. */\n exclusive?: true;\n /** Overrides the derived `--flag-name`; defaults to the kebab-case of the schema key. */\n flag?: string;\n /** Optional one-letter alias, for example `short: \"v\"` enables `-v`. */\n short?: string;\n /**\n * Optional environment-variable fallback. Explicit argv values always win;\n * the environment value is validated like argv but never echoed in errors.\n */\n env?: string;\n}\n\nexport interface CliStringParam extends CliParamBase {\n type: \"string\";\n choices?: readonly string[];\n default?: string;\n optional?: boolean;\n /** Repeatable: `--flag a --flag b` accumulates into `readonly string[]`, `[]` when absent. Cannot combine with `default`. */\n multiple?: boolean;\n}\n\nexport interface CliNumberParam extends CliParamBase {\n type: \"number\";\n default?: number;\n optional?: boolean;\n integer?: boolean;\n min?: number;\n max?: number;\n /** Repeatable: `--flag 1 --flag 2` accumulates into `readonly number[]`, `[]` when absent. Cannot combine with `default`. */\n multiple?: boolean;\n}\n\nexport interface CliBooleanParam extends CliParamBase {\n type: \"boolean\";\n /** Defaults to `false`. A bare `--flag` sets `true`; `--no-flag` sets `false`. */\n default?: boolean;\n}\n\nexport interface CliPathParam extends CliParamBase {\n type: \"path\";\n /** Relative defaults and values are resolved against `context.cwd`. */\n default?: string;\n optional?: boolean;\n /** Validates the resolved path exists before `run` is called. */\n mustExist?: boolean;\n /** Only checked when `mustExist` is true; asserts the resolved path's kind. */\n kind?: \"file\" | \"directory\";\n}\n\nexport type CliParam = CliStringParam | CliNumberParam | CliBooleanParam | CliPathParam;\n\nexport type CliParamsSchema = Record<string, CliParam>;\n\n/** JSON-safe description of one validated command parameter for non-terminal clients. */\nexport interface CliParameterDescriptor {\n readonly choices?: readonly string[];\n readonly default?: string | number | boolean;\n readonly description?: string;\n readonly environment?: string;\n readonly exclusive?: true;\n readonly flag: string;\n readonly group?: \"execution\";\n readonly integer?: boolean;\n readonly key: string;\n readonly max?: number;\n readonly min?: number;\n readonly multiple: boolean;\n readonly mustExist?: boolean;\n readonly pathKind?: \"directory\" | \"file\";\n readonly positional: boolean;\n readonly required: boolean;\n readonly short?: string;\n readonly type: CliParamType;\n}\n\n/** Immutable, presentation-neutral command contract shared by CLI and UI adapters. */\nexport interface CliCommandDescriptor {\n readonly description?: string;\n readonly name: string;\n readonly parameters: readonly CliParameterDescriptor[];\n}\n\ntype CliScalarStringValue<P extends CliParam> = P extends {\n choices: readonly (infer TChoice extends string)[];\n}\n ? TChoice\n : string;\n\ntype CliParamValue<P extends CliParam> = P extends { type: \"string\" }\n ? P extends { multiple: true }\n ? readonly CliScalarStringValue<P>[]\n : CliScalarStringValue<P>\n : P extends { type: \"number\" }\n ? P extends { multiple: true }\n ? readonly number[]\n : number\n : P extends { type: \"boolean\" }\n ? boolean\n : P extends { type: \"path\" }\n ? string\n : never;\n\n/**\n * A field is optional in the parsed output only when it's explicitly marked `optional:\n * true` (booleans never are — an absent boolean flag just means `false`). A field with a\n * `default` is also always present at runtime, but that's already covered: schemas\n * shouldn't combine `default` with `optional: true`, and if one does, the parsed type is\n * merely wider than necessary (`T | undefined` instead of `T`), never wrong.\n */\ntype CliParamIsOptional<P extends CliParam> = P extends { type: \"boolean\" }\n ? false\n : P extends { multiple: true }\n ? false\n : P extends { optional: true }\n ? true\n : false;\n\ntype RequiredCliParams<TSchema extends CliParamsSchema> = {\n [K in keyof TSchema as CliParamIsOptional<TSchema[K]> extends true ? never : K]: CliParamValue<\n TSchema[K]\n >;\n};\n\ntype OptionalCliParams<TSchema extends CliParamsSchema> = {\n [K in keyof TSchema as CliParamIsOptional<TSchema[K]> extends true ? K : never]?: CliParamValue<\n TSchema[K]\n >;\n};\n\n/**\n * Every command's parsed values include `dryRun` and `resume`, whether or not `TSchema`\n * declares them. `resume` is `false` unless `--resume` is passed or `checkpoint.defaultResume`\n * is `true`; it's meaningful even without `checkpoint` configured — a command can implement\n * its own \"is this done\" check and just read `values.resume` directly.\n */\nexport type CliParams<TSchema extends CliParamsSchema> = {\n dryRun: boolean;\n resume: boolean;\n} & RequiredCliParams<TSchema> &\n OptionalCliParams<TSchema>;\n\nexport interface CliContext {\n cwd: string;\n /** Environment used for `param.env` fallbacks. Defaults to `process.env`. */\n env?: NodeJS.ProcessEnv;\n log: PipelineLogger;\n /** Optional executor-only context used when this command runs a pipeline. */\n pipelineContext?: Omit<PipelineContext, \"cwd\" | \"log\" | \"signal\">;\n /** Optional caller-owned cancellation signal forwarded to command work. */\n signal?: AbortSignal;\n /** Present whenever the command's `checkpoint` config is set; see `CliCheckpointConfig`. */\n checkpoint?: CheckpointStore;\n}\n\nexport type CliParseResult<TSchema extends CliParamsSchema> =\n | { kind: \"values\"; values: CliParams<TSchema> }\n | { kind: \"help\"; helpText: string }\n | { kind: \"error\"; errors: readonly string[]; helpText: string };\n\nexport class CliValidationError extends Error {\n constructor(\n readonly errors: readonly string[],\n readonly helpText: string\n ) {\n super(`Invalid command-line arguments:\\n${errors.map((error) => ` - ${error}`).join(\"\\n\")}`);\n this.name = \"CliValidationError\";\n brandTubelessError(this, \"cli-validation\");\n }\n}\n\nexport class CliHelpRequested extends Error {\n constructor(readonly helpText: string) {\n super(helpText);\n this.name = \"CliHelpRequested\";\n brandTubelessError(this, \"cli-help\");\n }\n}\n\nexport interface CliCheckpointConfig {\n /** Relative paths resolve against `context.cwd`, like `CliPathParam`. */\n path: string;\n /**\n * Defaults to `true`: a fully successful, non-dry-run `run` clears the checkpoint —\n * the one-shot resumable-job pattern (start fresh next time). Set `false` for an\n * incrementally-growing \"done\" set (e.g. entity enrichment) that must never auto-clear,\n * since clearing it would cause already-processed items to be reprocessed.\n */\n clearOnSuccess?: boolean;\n /**\n * Defaults to `false`: start fresh unless `--resume` is passed. Set `true` to always\n * resume by default (skip already-recorded items every run); `--no-resume` is the free\n * opt-out, since boolean flags already support `--no-<flag>` negation.\n */\n defaultResume?: boolean;\n}\n\nexport interface CliCommandConfig<TSchema extends CliParamsSchema, TResult> {\n /** Shown in generated usage text; defaults to the running script's file name. */\n name?: string;\n description?: string;\n /** `dryRun`/`--dry-run` and `-h`/`--help` are provided automatically; don't redeclare them. */\n params: TSchema;\n /**\n * Schema keys accepted in positional order. Flags for an earlier positional\n * let the next positional fill the next unset field; a repeatable positional\n * must be last.\n */\n positionals?: readonly (keyof TSchema & string)[];\n /**\n * Opts into a managed checkpoint file. Adds a reserved `--resume`/`--no-resume` flag\n * (also don't redeclare a `resume` param) and populates `context.checkpoint` before\n * `run` is called: resolved fresh (existing file cleared) unless resuming, and cleared\n * again after a successful non-dry-run unless `clearOnSuccess` is `false`.\n */\n checkpoint?: CliCheckpointConfig;\n /** Runs after every field passes its own validation; return error messages to reject. */\n validate?(values: CliParams<TSchema>, context: CliContext): string[] | void;\n run(values: CliParams<TSchema>, context: CliContext): TResult | Promise<TResult>;\n}\n\nexport interface CliCommand<TSchema extends CliParamsSchema, TResult> {\n /** Structured schema metadata for adapters that should not parse generated help text. */\n readonly descriptor: CliCommandDescriptor;\n /** Pure: parses and validates without calling `run` or touching the process. */\n parse(argv?: readonly string[], context?: Partial<CliContext>): CliParseResult<TSchema>;\n /** Validates structured parameter values without tokenizing command-line arguments. */\n parseValues(\n values: Record<string, unknown>,\n context?: Partial<CliContext>\n ): CliParseResult<TSchema>;\n /** Calls `run` from already validated parameter values. */\n execute(values: CliParams<TSchema>, context?: Partial<CliContext>): Promise<TResult>;\n /** Parses and calls `run`; throws `CliValidationError`/`CliHelpRequested` instead of running it. */\n run(argv?: readonly string[], context?: Partial<CliContext>): Promise<TResult>;\n /**\n * Entry point for `if (import.meta.main) void command.main();`. Prints help/errors and\n * sets `process.exitCode` — never calls `process.exit`, so buffered output still flushes\n * — instead of throwing.\n */\n main(argv?: readonly string[], context?: Partial<CliContext>): Promise<void>;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"cli-types.js","sourceRoot":"","sources":["../../src/cli/cli-types.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAgLpE,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAEhC,MAAM;IACN,QAAQ;IAFnB,YACW,MAAyB,EACzB,QAAgB;QAEzB,KAAK,CAAC,oCAAoC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;sBAHrF,MAAM;wBACN,QAAQ;QAGjB,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,kBAAkB,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;IAC7C,CAAC;CACF;AAED,MAAM,OAAO,gBAAiB,SAAQ,KAAK;IACpB,QAAQ;IAA7B,YAAqB,QAAgB;QACnC,KAAK,CAAC,QAAQ,CAAC,CAAC;wBADG,QAAQ;QAE3B,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAC/B,kBAAkB,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC;IACvC,CAAC;CACF","sourcesContent":["import type { CheckpointStore } from \"../node/checkpoint.js\";\nimport type { PipelineContext, PipelineLogger } from \"../core/pipeline.js\";\nimport { brandTubelessError } from \"../utilities/tubeless-error.js\";\n\n/**\n * Typed command-line entry points for scripts, mirroring how `definePipeline` turns a\n * declarative step list into a runnable pipeline: declare a flag schema once and get a\n * fully-typed, validated `values` object handed to `run`, instead of each script hand-\n * rolling its own `process.argv` scanning and `as` casts.\n */\n\nexport type CliParamType = \"string\" | \"number\" | \"boolean\" | \"path\";\n\ninterface CliParamBase {\n /** Shown next to the flag in generated `--help` output. */\n description?: string;\n /** UI adapters place execution controls in one section. Built-ins set this. */\n group?: \"execution\";\n /** UI adapters treat exclusive repeatable parameters as mutually exclusive. Built-ins set this on step and target selection. */\n exclusive?: true;\n /** Overrides the derived `--flag-name`; defaults to the kebab-case of the schema key. */\n flag?: string;\n /** Optional one-letter alias, for example `short: \"v\"` enables `-v`. */\n short?: string;\n /**\n * Optional environment-variable fallback. Explicit argv values always win;\n * the environment value is validated like argv but never echoed in errors.\n */\n env?: string;\n}\n\nexport interface CliStringParam extends CliParamBase {\n type: \"string\";\n choices?: readonly string[];\n default?: string;\n optional?: boolean;\n /** Repeatable: `--flag a --flag b` accumulates into `readonly string[]`, `[]` when absent. Cannot combine with `default`. */\n multiple?: boolean;\n}\n\nexport interface CliNumberParam extends CliParamBase {\n type: \"number\";\n default?: number;\n optional?: boolean;\n integer?: boolean;\n min?: number;\n max?: number;\n /** Repeatable: `--flag 1 --flag 2` accumulates into `readonly number[]`, `[]` when absent. Cannot combine with `default`. */\n multiple?: boolean;\n}\n\nexport interface CliBooleanParam extends CliParamBase {\n type: \"boolean\";\n /** Defaults to `false`. A bare `--flag` sets `true`; `--no-flag` sets `false`. */\n default?: boolean;\n}\n\nexport interface CliPathParam extends CliParamBase {\n type: \"path\";\n /** Relative defaults and values are resolved against `context.cwd`. */\n default?: string;\n optional?: boolean;\n /** Validates the resolved path exists before `run` is called. */\n mustExist?: boolean;\n /** Only checked when `mustExist` is true; asserts the resolved path's kind. */\n kind?: \"file\" | \"directory\";\n}\n\nexport type CliParam = CliStringParam | CliNumberParam | CliBooleanParam | CliPathParam;\n\nexport type CliParamsSchema = Record<string, CliParam>;\n\n/** JSON-safe description of one validated command parameter for non-terminal clients. */\nexport interface CliParameterDescriptor {\n readonly choices?: readonly string[];\n readonly default?: string | number | boolean;\n readonly description?: string;\n readonly environment?: string;\n readonly exclusive?: true;\n readonly flag: string;\n readonly group?: \"execution\";\n readonly integer?: boolean;\n readonly key: string;\n readonly max?: number;\n readonly min?: number;\n readonly multiple: boolean;\n readonly mustExist?: boolean;\n readonly pathKind?: \"directory\" | \"file\";\n readonly positional: boolean;\n readonly required: boolean;\n readonly short?: string;\n readonly type: CliParamType;\n}\n\n/** Immutable, presentation-neutral command contract shared by CLI and UI adapters. */\nexport interface CliCommandDescriptor {\n readonly description?: string;\n readonly name: string;\n readonly parameters: readonly CliParameterDescriptor[];\n}\n\ntype CliScalarStringValue<P extends CliParam> = P extends {\n choices: readonly (infer TChoice extends string)[];\n}\n ? TChoice\n : string;\n\ntype CliParamValue<P extends CliParam> = P extends { type: \"string\" }\n ? P extends { multiple: true }\n ? readonly CliScalarStringValue<P>[]\n : CliScalarStringValue<P>\n : P extends { type: \"number\" }\n ? P extends { multiple: true }\n ? readonly number[]\n : number\n : P extends { type: \"boolean\" }\n ? boolean\n : P extends { type: \"path\" }\n ? string\n : never;\n\n/**\n * A field is optional in the parsed output only when it's explicitly marked `optional:\n * true` (booleans never are — an absent boolean flag just means `false`). A field with a\n * `default` is also always present at runtime, but that's already covered: schemas\n * shouldn't combine `default` with `optional: true`, and if one does, the parsed type is\n * merely wider than necessary (`T | undefined` instead of `T`), never wrong.\n */\ntype CliParamIsOptional<P extends CliParam> = P extends { type: \"boolean\" }\n ? false\n : P extends { multiple: true }\n ? false\n : P extends { optional: true }\n ? true\n : false;\n\ntype RequiredCliParams<TSchema extends CliParamsSchema> = {\n [K in keyof TSchema as CliParamIsOptional<TSchema[K]> extends true ? never : K]: CliParamValue<\n TSchema[K]\n >;\n};\n\ntype OptionalCliParams<TSchema extends CliParamsSchema> = {\n [K in keyof TSchema as CliParamIsOptional<TSchema[K]> extends true ? K : never]?: CliParamValue<\n TSchema[K]\n >;\n};\n\n/**\n * Every command's parsed values include `dryRun` and `resume`, whether or not `TSchema`\n * declares them. `resume` is `false` unless `--resume` is passed or `checkpoint.defaultResume`\n * is `true`; it's meaningful even without `checkpoint` configured — a command can implement\n * its own \"is this done\" check and just read `values.resume` directly.\n */\nexport type CliParams<TSchema extends CliParamsSchema> = {\n dryRun: boolean;\n resume: boolean;\n} & RequiredCliParams<TSchema> &\n OptionalCliParams<TSchema>;\n\nexport interface CliContext {\n cwd: string;\n /** Environment used for `param.env` fallbacks. Defaults to `process.env`. */\n env?: Record<string, string | undefined>;\n log: PipelineLogger;\n /** Optional executor-only context used when this command runs a pipeline. */\n pipelineContext?: Omit<PipelineContext, \"cwd\" | \"log\" | \"signal\">;\n /** Optional caller-owned cancellation signal forwarded to command work. */\n signal?: AbortSignal;\n /** Present whenever the command's `checkpoint` config is set; see `CliCheckpointConfig`. */\n checkpoint?: CheckpointStore;\n}\n\nexport type CliParseResult<TSchema extends CliParamsSchema> =\n | { kind: \"values\"; values: CliParams<TSchema> }\n | { kind: \"help\"; helpText: string }\n | { kind: \"error\"; errors: readonly string[]; helpText: string };\n\nexport class CliValidationError extends Error {\n constructor(\n readonly errors: readonly string[],\n readonly helpText: string\n ) {\n super(`Invalid command-line arguments:\\n${errors.map((error) => ` - ${error}`).join(\"\\n\")}`);\n this.name = \"CliValidationError\";\n brandTubelessError(this, \"cli-validation\");\n }\n}\n\nexport class CliHelpRequested extends Error {\n constructor(readonly helpText: string) {\n super(helpText);\n this.name = \"CliHelpRequested\";\n brandTubelessError(this, \"cli-help\");\n }\n}\n\nexport interface CliCheckpointConfig {\n /** Relative paths resolve against `context.cwd`, like `CliPathParam`. */\n path: string;\n /**\n * Defaults to `true`: a fully successful, non-dry-run `run` clears the checkpoint —\n * the one-shot resumable-job pattern (start fresh next time). Set `false` for an\n * incrementally-growing \"done\" set (e.g. entity enrichment) that must never auto-clear,\n * since clearing it would cause already-processed items to be reprocessed.\n */\n clearOnSuccess?: boolean;\n /**\n * Defaults to `false`: start fresh unless `--resume` is passed. Set `true` to always\n * resume by default (skip already-recorded items every run); `--no-resume` is the free\n * opt-out, since boolean flags already support `--no-<flag>` negation.\n */\n defaultResume?: boolean;\n}\n\nexport interface CliCommandConfig<TSchema extends CliParamsSchema, TResult> {\n /** Shown in generated usage text; defaults to the running script's file name. */\n name?: string;\n description?: string;\n /** `dryRun`/`--dry-run` and `-h`/`--help` are provided automatically; don't redeclare them. */\n params: TSchema;\n /**\n * Schema keys accepted in positional order. Flags for an earlier positional\n * let the next positional fill the next unset field; a repeatable positional\n * must be last.\n */\n positionals?: readonly (keyof TSchema & string)[];\n /**\n * Opts into a managed checkpoint file. Adds a reserved `--resume`/`--no-resume` flag\n * (also don't redeclare a `resume` param) and populates `context.checkpoint` before\n * `run` is called: resolved fresh (existing file cleared) unless resuming, and cleared\n * again after a successful non-dry-run unless `clearOnSuccess` is `false`.\n */\n checkpoint?: CliCheckpointConfig;\n /** Runs after every field passes its own validation; return error messages to reject. */\n validate?(values: CliParams<TSchema>, context: CliContext): string[] | void;\n run(values: CliParams<TSchema>, context: CliContext): TResult | Promise<TResult>;\n}\n\nexport interface CliCommand<TSchema extends CliParamsSchema, TResult> {\n /** Structured schema metadata for adapters that should not parse generated help text. */\n readonly descriptor: CliCommandDescriptor;\n /** Pure: parses and validates without calling `run` or touching the process. */\n parse(argv?: readonly string[], context?: Partial<CliContext>): CliParseResult<TSchema>;\n /** Validates structured parameter values without tokenizing command-line arguments. */\n parseValues(\n values: Record<string, unknown>,\n context?: Partial<CliContext>\n ): CliParseResult<TSchema>;\n /** Calls `run` from already validated parameter values. */\n execute(values: CliParams<TSchema>, context?: Partial<CliContext>): Promise<TResult>;\n /** Parses and calls `run`; throws `CliValidationError`/`CliHelpRequested` instead of running it. */\n run(argv?: readonly string[], context?: Partial<CliContext>): Promise<TResult>;\n /**\n * Entry point for `if (import.meta.main) void command.main();`. Prints help/errors and\n * sets `process.exitCode` — never calls `process.exit`, so buffered output still flushes\n * — instead of throwing.\n */\n main(argv?: readonly string[], context?: Partial<CliContext>): Promise<void>;\n}\n"]}
|
package/dist/cli/cli.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
-
export { CliHelpRequested, CliValidationError, type CliCommandDescriptor, type CliContext, type CliParameterDescriptor, } from "./cli-types.js";
|
|
1
|
+
export { CliHelpRequested, CliValidationError, type CliBooleanParam, type CliCheckpointConfig, type CliCommand, type CliCommandConfig, type CliCommandDescriptor, type CliContext, type CliNumberParam, type CliParam, type CliParameterDescriptor, type CliParams, type CliParamsSchema, type CliParamType, type CliParseResult, type CliPathParam, type CliStringParam, } from "./cli-types.js";
|
|
2
2
|
export { defineCommand } from "./cli-command.js";
|
|
3
|
-
export { definePipelineCommand } from "./cli-pipeline-command.js";
|
|
3
|
+
export { definePipelineCommand, type DefinePipelineCommandConfig, type PipelineCliParseResult, type PipelineCliValues, type PipelineCommand, type PipelineCommandHookConfig, type PipelineCommandHookContext, type PipelineCommandHookSets, } from "./cli-pipeline-command.js";
|
|
4
|
+
export type { PipelineReporterConfig, PipelineReporterMode, ReporterOutput, } from "../reporter/interactive-reporter.js";
|
|
5
|
+
export type { ReporterColorMode, ReporterSymbolMode, ReporterTerminalCapabilities, RunReporterConfig, } from "../reporter/reporter.js";
|
package/dist/cli/cli.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export { CliHelpRequested, CliValidationError, } from "./cli-types.js";
|
|
2
2
|
export { defineCommand } from "./cli-command.js";
|
|
3
|
-
export { definePipelineCommand } from "./cli-pipeline-command.js";
|
|
3
|
+
export { definePipelineCommand, } from "./cli-pipeline-command.js";
|
|
4
4
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/cli/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,gBAAgB,EAChB,kBAAkB,GAgBnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EACL,qBAAqB,GAQtB,MAAM,2BAA2B,CAAC","sourcesContent":["export {\n CliHelpRequested,\n CliValidationError,\n type CliBooleanParam,\n type CliCheckpointConfig,\n type CliCommand,\n type CliCommandConfig,\n type CliCommandDescriptor,\n type CliContext,\n type CliNumberParam,\n type CliParam,\n type CliParameterDescriptor,\n type CliParams,\n type CliParamsSchema,\n type CliParamType,\n type CliParseResult,\n type CliPathParam,\n type CliStringParam,\n} from \"./cli-types.js\";\nexport { defineCommand } from \"./cli-command.js\";\nexport {\n definePipelineCommand,\n type DefinePipelineCommandConfig,\n type PipelineCliParseResult,\n type PipelineCliValues,\n type PipelineCommand,\n type PipelineCommandHookConfig,\n type PipelineCommandHookContext,\n type PipelineCommandHookSets,\n} from \"./cli-pipeline-command.js\";\nexport type {\n PipelineReporterConfig,\n PipelineReporterMode,\n ReporterOutput,\n} from \"../reporter/interactive-reporter.js\";\nexport type {\n ReporterColorMode,\n ReporterSymbolMode,\n ReporterTerminalCapabilities,\n RunReporterConfig,\n} from \"../reporter/reporter.js\";\n"]}
|
package/dist/node/atomic-text.js
CHANGED
|
@@ -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
|
-
//
|
|
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;
|
|
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;
|
package/dist/node/env.js
ADDED
|
@@ -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,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"]}
|
package/dist/node/node.d.ts
CHANGED
package/dist/node/node.js
CHANGED
package/dist/node/node.js.map
CHANGED
|
@@ -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"]}
|
|
@@ -63,4 +63,4 @@ export function isPipelineProjectManifest(value) {
|
|
|
63
63
|
const candidate = value;
|
|
64
64
|
return (candidate.version === PIPELINE_PROJECT_MANIFEST_VERSION && Array.isArray(candidate.commands));
|
|
65
65
|
}
|
|
66
|
-
//# sourceMappingURL=
|
|
66
|
+
//# sourceMappingURL=project-manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-manifest.js","sourceRoot":"","sources":["../../src/project/project-manifest.ts"],"names":[],"mappings":"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","sourcesContent":["/** Current version of the declarative Tubeless project manifest contract. */\nexport const PIPELINE_PROJECT_MANIFEST_VERSION = 1 as const;\n\nconst PIPELINE_PROJECT_MANIFEST_MARKER = Symbol.for(\"tubeless/pipeline-project-manifest/v1\");\n\nexport interface PipelineProjectCommandModule {\n /** Stable project-wide identity used by workbench commands and the studio. */\n id: string;\n /** Command module path, resolved relative to the project manifest file. */\n file: string;\n /** Select a named definePipelineCommand export when the module has more than one. */\n export?: string;\n /** Optional display-name override. Defaults to the command's generated name. */\n name?: string;\n}\n\nexport interface PipelineProjectManifestInput {\n /** Explicitly registered definePipelineCommand modules. */\n commands: readonly PipelineProjectCommandModule[];\n /** Command execution directory, relative to this file. Defaults to this file's directory. */\n cwd?: string;\n}\n\nexport interface PipelineProjectManifest {\n readonly commands: readonly PipelineProjectCommandModule[];\n readonly cwd?: string;\n readonly version: typeof PIPELINE_PROJECT_MANIFEST_VERSION;\n}\n\ntype MutableProjectManifest = {\n commands: readonly PipelineProjectCommandModule[];\n cwd?: string;\n version: typeof PIPELINE_PROJECT_MANIFEST_VERSION;\n};\n\nfunction requiredText(value: unknown, label: string): asserts value is string {\n if (typeof value !== \"string\" || value.trim().length === 0) {\n throw new Error(`${label} must be a non-empty string.`);\n }\n}\n\n/** Declare a versioned, dependency-free project manifest of workbench commands. */\nexport function definePipelineProject(\n manifest: PipelineProjectManifestInput\n): PipelineProjectManifest {\n if (!Array.isArray(manifest.commands) || manifest.commands.length === 0) {\n throw new Error(\"Pipeline project manifest must declare at least one command.\");\n }\n if (manifest.cwd !== undefined) requiredText(manifest.cwd, \"Pipeline project cwd\");\n\n const ids = new Set<string>();\n const modules = new Set<string>();\n const commands = manifest.commands.map((command, index): PipelineProjectCommandModule => {\n requiredText(command.id, `Pipeline project command ${index + 1} id`);\n requiredText(command.file, `Pipeline project command ${index + 1} file`);\n if (command.export !== undefined) {\n requiredText(command.export, `Pipeline project command ${index + 1} export`);\n }\n if (command.name !== undefined) {\n requiredText(command.name, `Pipeline project command ${index + 1} name`);\n }\n if (ids.has(command.id)) {\n throw new Error(\n `Pipeline project command id ${JSON.stringify(command.id)} is declared more than once.`\n );\n }\n ids.add(command.id);\n const moduleIdentity = `${command.file}\\0${command.export ?? \"\"}`;\n if (modules.has(moduleIdentity)) {\n throw new Error(\n `Pipeline project command ${JSON.stringify(command.file)}${command.export ? ` export ${JSON.stringify(command.export)}` : \"\"} is declared more than once.`\n );\n }\n modules.add(moduleIdentity);\n\n const frozenCommand: PipelineProjectCommandModule = {\n file: command.file,\n id: command.id,\n };\n if (command.export !== undefined) frozenCommand.export = command.export;\n if (command.name !== undefined) frozenCommand.name = command.name;\n return Object.freeze(frozenCommand);\n });\n\n const defined: MutableProjectManifest = {\n commands: Object.freeze(commands),\n version: PIPELINE_PROJECT_MANIFEST_VERSION,\n };\n if (manifest.cwd !== undefined) defined.cwd = manifest.cwd;\n Object.defineProperty(defined, PIPELINE_PROJECT_MANIFEST_MARKER, { value: true });\n return Object.freeze(defined);\n}\n\n/** Runtime guard used by the workbench when selecting an exported project manifest. */\nexport function isPipelineProjectManifest(value: unknown): value is PipelineProjectManifest {\n if (typeof value !== \"object\" || value === null) return false;\n if (!(PIPELINE_PROJECT_MANIFEST_MARKER in value)) return false;\n // SAFETY: The marker is only installed by definePipelineProject, which sets version and\n // commands to their declared types, so reading them via a structural view is safe.\n const candidate = value as { version?: unknown; commands?: unknown };\n return (\n candidate.version === PIPELINE_PROJECT_MANIFEST_VERSION && Array.isArray(candidate.commands)\n );\n}\n"]}
|