@pipeline-builder/pipeline-core 3.1.5 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.templateResolutionDurationMs = exports.templateResolutionsTotal = void 0;
6
+ exports.recordResolution = recordResolution;
7
+ function resolvePromClient() {
8
+ try {
9
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
10
+ return require('prom-client');
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ }
16
+ const promClient = resolvePromClient();
17
+ function makeCounter(name, help, labelNames) {
18
+ if (!promClient)
19
+ return { inc: () => { } };
20
+ const existing = promClient.register.getSingleMetric(name);
21
+ return existing ?? new promClient.Counter({ name, help, labelNames });
22
+ }
23
+ function makeHistogram(name, help, labelNames, buckets) {
24
+ if (!promClient)
25
+ return { observe: () => { } };
26
+ const existing = promClient.register.getSingleMetric(name);
27
+ return existing ?? new promClient.Histogram({ name, help, labelNames, buckets });
28
+ }
29
+ exports.templateResolutionsTotal = makeCounter('pipeline_builder_template_resolutions_total', 'Total number of template resolution operations', ['outcome', 'doc']);
30
+ exports.templateResolutionDurationMs = makeHistogram('pipeline_builder_template_resolution_duration_ms', 'Template resolution duration in milliseconds', ['doc'], [1, 5, 10, 50, 100, 500, 1000]);
31
+ function recordResolution(doc, durationMs, success) {
32
+ exports.templateResolutionsTotal.inc({ outcome: success ? 'success' : 'error', doc });
33
+ exports.templateResolutionDurationMs.observe({ doc }, durationMs);
34
+ }
35
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZW1wbGF0ZS9tZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDOzs7QUFpRHRDLDRDQUdDO0FBdkNELFNBQVMsaUJBQWlCO0lBQ3hCLElBQUksQ0FBQztRQUNILGlFQUFpRTtRQUNqRSxPQUFPLE9BQU8sQ0FBQyxhQUFhLENBQUMsQ0FBQztJQUNoQyxDQUFDO0lBQUMsTUFBTSxDQUFDO1FBQ1AsT0FBTyxJQUFJLENBQUM7SUFDZCxDQUFDO0FBQ0gsQ0FBQztBQUVELE1BQU0sVUFBVSxHQUFHLGlCQUFpQixFQUFFLENBQUM7QUFFdkMsU0FBUyxXQUFXLENBQUMsSUFBWSxFQUFFLElBQVksRUFBRSxVQUFvQjtJQUNuRSxJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLEdBQWMsQ0FBQyxFQUFFLENBQUM7SUFDdEQsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDM0QsT0FBTyxRQUFRLElBQUksSUFBSSxVQUFVLENBQUMsT0FBTyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsQ0FBQyxDQUFDO0FBQ3hFLENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxJQUFZLEVBQUUsSUFBWSxFQUFFLFVBQW9CLEVBQUUsT0FBaUI7SUFDeEYsSUFBSSxDQUFDLFVBQVU7UUFBRSxPQUFPLEVBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUFjLENBQUMsRUFBRSxDQUFDO0lBQzFELE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxRQUFRLENBQUMsZUFBZSxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzNELE9BQU8sUUFBUSxJQUFJLElBQUksVUFBVSxDQUFDLFNBQVMsQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsVUFBVSxFQUFFLE9BQU8sRUFBRSxDQUFDLENBQUM7QUFDbkYsQ0FBQztBQUVZLFFBQUEsd0JBQXdCLEdBQUcsV0FBVyxDQUNqRCw2Q0FBNkMsRUFDN0MsZ0RBQWdELEVBQ2hELENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUNuQixDQUFDO0FBRVcsUUFBQSw0QkFBNEIsR0FBRyxhQUFhLENBQ3ZELGtEQUFrRCxFQUNsRCw4Q0FBOEMsRUFDOUMsQ0FBQyxLQUFLLENBQUMsRUFDUCxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxFQUFFLEVBQUUsRUFBRSxHQUFHLEVBQUUsR0FBRyxFQUFFLElBQUksQ0FBQyxDQUMvQixDQUFDO0FBRUYsU0FBZ0IsZ0JBQWdCLENBQUMsR0FBMEIsRUFBRSxVQUFrQixFQUFFLE9BQWdCO0lBQy9GLGdDQUF3QixDQUFDLEdBQUcsQ0FBQyxFQUFFLE9BQU8sRUFBRSxPQUFPLENBQUMsQ0FBQyxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsT0FBTyxFQUFFLEdBQUcsRUFBRSxDQUFDLENBQUM7SUFDOUUsb0NBQTRCLENBQUMsT0FBTyxDQUFDLEVBQUUsR0FBRyxFQUFFLEVBQUUsVUFBVSxDQUFDLENBQUM7QUFDNUQsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFByb21ldGhldXMgbWV0cmljcyBmb3IgdGVtcGxhdGUgcmVzb2x1dGlvbi4gT3B0aW9uYWwg4oCUIHRoZSBtb2R1bGUgaXNcbiAqIG9ubHkgd2lyZWQgdXAgd2hlbiBgcHJvbS1jbGllbnRgIGlzIGFscmVhZHkgcHJlc2VudCBpbiB0aGUgaG9zdFxuICogcHJvY2VzcyAoYXMgaXMgdGhlIGNhc2UgZm9yIGFwaS1zZXJ2ZXIgYmFzZWQgc2VydmljZXMpLiBGYWxscyBiYWNrIHRvXG4gKiBuby1vcCBzdHVicyB3aGVuIHVuYXZhaWxhYmxlIHNvIHBpcGVsaW5lLWNvcmUgc3RheXMgdXNhYmxlIG91dHNpZGVcbiAqIHNlcnZlciBjb250ZXh0cy5cbiAqL1xuXG5pbnRlcmZhY2UgQ291bnRlciB7IGluYyhsYWJlbHM/OiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+LCB2YWx1ZT86IG51bWJlcik6IHZvaWQgfVxuaW50ZXJmYWNlIEhpc3RvZ3JhbSB7IG9ic2VydmUobGFiZWxzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+LCB2YWx1ZTogbnVtYmVyKTogdm9pZCB9XG5cbmZ1bmN0aW9uIHJlc29sdmVQcm9tQ2xpZW50KCk6IGFueSB8IG51bGwge1xuICB0cnkge1xuICAgIC8vIGVzbGludC1kaXNhYmxlLW5leHQtbGluZSBAdHlwZXNjcmlwdC1lc2xpbnQvbm8tcmVxdWlyZS1pbXBvcnRzXG4gICAgcmV0dXJuIHJlcXVpcmUoJ3Byb20tY2xpZW50Jyk7XG4gIH0gY2F0Y2gge1xuICAgIHJldHVybiBudWxsO1xuICB9XG59XG5cbmNvbnN0IHByb21DbGllbnQgPSByZXNvbHZlUHJvbUNsaWVudCgpO1xuXG5mdW5jdGlvbiBtYWtlQ291bnRlcihuYW1lOiBzdHJpbmcsIGhlbHA6IHN0cmluZywgbGFiZWxOYW1lczogc3RyaW5nW10pOiBDb3VudGVyIHtcbiAgaWYgKCFwcm9tQ2xpZW50KSByZXR1cm4geyBpbmM6ICgpID0+IHsgLyogbm9vcCAqLyB9IH07XG4gIGNvbnN0IGV4aXN0aW5nID0gcHJvbUNsaWVudC5yZWdpc3Rlci5nZXRTaW5nbGVNZXRyaWMobmFtZSk7XG4gIHJldHVybiBleGlzdGluZyA/PyBuZXcgcHJvbUNsaWVudC5Db3VudGVyKHsgbmFtZSwgaGVscCwgbGFiZWxOYW1lcyB9KTtcbn1cblxuZnVuY3Rpb24gbWFrZUhpc3RvZ3JhbShuYW1lOiBzdHJpbmcsIGhlbHA6IHN0cmluZywgbGFiZWxOYW1lczogc3RyaW5nW10sIGJ1Y2tldHM6IG51bWJlcltdKTogSGlzdG9ncmFtIHtcbiAgaWYgKCFwcm9tQ2xpZW50KSByZXR1cm4geyBvYnNlcnZlOiAoKSA9PiB7IC8qIG5vb3AgKi8gfSB9O1xuICBjb25zdCBleGlzdGluZyA9IHByb21DbGllbnQucmVnaXN0ZXIuZ2V0U2luZ2xlTWV0cmljKG5hbWUpO1xuICByZXR1cm4gZXhpc3RpbmcgPz8gbmV3IHByb21DbGllbnQuSGlzdG9ncmFtKHsgbmFtZSwgaGVscCwgbGFiZWxOYW1lcywgYnVja2V0cyB9KTtcbn1cblxuZXhwb3J0IGNvbnN0IHRlbXBsYXRlUmVzb2x1dGlvbnNUb3RhbCA9IG1ha2VDb3VudGVyKFxuICAncGlwZWxpbmVfYnVpbGRlcl90ZW1wbGF0ZV9yZXNvbHV0aW9uc190b3RhbCcsXG4gICdUb3RhbCBudW1iZXIgb2YgdGVtcGxhdGUgcmVzb2x1dGlvbiBvcGVyYXRpb25zJyxcbiAgWydvdXRjb21lJywgJ2RvYyddLFxuKTtcblxuZXhwb3J0IGNvbnN0IHRlbXBsYXRlUmVzb2x1dGlvbkR1cmF0aW9uTXMgPSBtYWtlSGlzdG9ncmFtKFxuICAncGlwZWxpbmVfYnVpbGRlcl90ZW1wbGF0ZV9yZXNvbHV0aW9uX2R1cmF0aW9uX21zJyxcbiAgJ1RlbXBsYXRlIHJlc29sdXRpb24gZHVyYXRpb24gaW4gbWlsbGlzZWNvbmRzJyxcbiAgWydkb2MnXSxcbiAgWzEsIDUsIDEwLCA1MCwgMTAwLCA1MDAsIDEwMDBdLFxuKTtcblxuZXhwb3J0IGZ1bmN0aW9uIHJlY29yZFJlc29sdXRpb24oZG9jOiAncGlwZWxpbmUnIHwgJ3BsdWdpbicsIGR1cmF0aW9uTXM6IG51bWJlciwgc3VjY2VzczogYm9vbGVhbik6IHZvaWQge1xuICB0ZW1wbGF0ZVJlc29sdXRpb25zVG90YWwuaW5jKHsgb3V0Y29tZTogc3VjY2VzcyA/ICdzdWNjZXNzJyA6ICdlcnJvcicsIGRvYyB9KTtcbiAgdGVtcGxhdGVSZXNvbHV0aW9uRHVyYXRpb25Ncy5vYnNlcnZlKHsgZG9jIH0sIGR1cmF0aW9uTXMpO1xufVxuIl19
@@ -0,0 +1,8 @@
1
+ import type { Plugin } from '@pipeline-builder/pipeline-data';
2
+ export declare function isPluginTemplatableField(field: string): boolean;
3
+ /**
4
+ * Return a shallow clone of `plugin` with all `{{ ... }}` templates
5
+ * resolved against the given pipeline scope. Caller must pre-populate
6
+ * `pipelineScope` with `{ pipeline, plugin, env }` keys.
7
+ */
8
+ export declare function resolvePluginTemplates(plugin: Plugin, pipelineScope: Record<string, unknown>): Plugin;
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.isPluginTemplatableField = isPluginTemplatableField;
6
+ exports.resolvePluginTemplates = resolvePluginTemplates;
7
+ const index_1 = require("./index");
8
+ /**
9
+ * Fields inside a Plugin record that accept `{{ ... }}` templates.
10
+ *
11
+ * Pure-string leaves only. `name`, `version`, `pluginType`, `computeType`,
12
+ * `timeout`, `secrets`, `failureBehavior`, `requiredMetadata`, `requiredVars`
13
+ * stay literal. `metadata.*` is excluded because CDK-metadata values are
14
+ * structural, not user-interpolated.
15
+ */
16
+ const TEMPLATABLE_FIELDS = [
17
+ 'description',
18
+ 'commands', // string[]
19
+ 'installCommands', // string[]
20
+ 'env', // Record<string, string>
21
+ 'buildArgs', // Record<string, string>
22
+ ];
23
+ function isPluginTemplatableField(field) {
24
+ // `commands` / `installCommands` are arrays of strings → entries like 'commands[0]'
25
+ // `env` / `buildArgs` are objects → entries like 'env.STAGE'
26
+ return TEMPLATABLE_FIELDS.some(f => field === f || field.startsWith(`${f}[`) || field.startsWith(`${f}.`));
27
+ }
28
+ /**
29
+ * Return a shallow clone of `plugin` with all `{{ ... }}` templates
30
+ * resolved against the given pipeline scope. Caller must pre-populate
31
+ * `pipelineScope` with `{ pipeline, plugin, env }` keys.
32
+ */
33
+ function resolvePluginTemplates(plugin, pipelineScope) {
34
+ // Deep clone so mutations don't leak back to the caller's Plugin.
35
+ // Structural fields we care about are plain JSON; structuredClone is safe.
36
+ const clone = structuredClone(plugin);
37
+ const scope = {
38
+ ...pipelineScope,
39
+ plugin: { name: plugin.name, version: plugin.version, imageTag: plugin.imageTag },
40
+ env: plugin.env ?? {},
41
+ };
42
+ const { errors } = (0, index_1.resolveTemplates)(clone, scope, isPluginTemplatableField, 'plugin');
43
+ if (errors.length > 0) {
44
+ // First error wins — resolver errors should never be batched at synth time
45
+ // because a broken template is a programmer error, not a validation step.
46
+ const e = errors[0];
47
+ const msg = `Template resolution failed in plugin "${plugin.name}" at field '${e.field}': ${e.message}`;
48
+ throw new Error(msg);
49
+ }
50
+ return clone;
51
+ }
52
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLXJlc29sdmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3BsdWdpbi1yZXNvbHZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFxQnRDLDREQUlDO0FBT0Qsd0RBdUJDO0FBcERELG1DQUEyQztBQUUzQzs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxrQkFBa0IsR0FBRztJQUN6QixhQUFhO0lBQ2IsVUFBVSxFQUFFLFdBQVc7SUFDdkIsaUJBQWlCLEVBQUUsV0FBVztJQUM5QixLQUFLLEVBQUUseUJBQXlCO0lBQ2hDLFdBQVcsRUFBRSx5QkFBeUI7Q0FDOUIsQ0FBQztBQUVYLFNBQWdCLHdCQUF3QixDQUFDLEtBQWE7SUFDcEQsb0ZBQW9GO0lBQ3BGLDZEQUE2RDtJQUM3RCxPQUFPLGtCQUFrQixDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssS0FBSyxDQUFDLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLElBQUksS0FBSyxDQUFDLFVBQVUsQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztBQUM3RyxDQUFDO0FBRUQ7Ozs7R0FJRztBQUNILFNBQWdCLHNCQUFzQixDQUNwQyxNQUFjLEVBQ2QsYUFBc0M7SUFFdEMsa0VBQWtFO0lBQ2xFLDJFQUEyRTtJQUMzRSxNQUFNLEtBQUssR0FBRyxlQUFlLENBQUMsTUFBTSxDQUFxQyxDQUFDO0lBRTFFLE1BQU0sS0FBSyxHQUFHO1FBQ1osR0FBRyxhQUFhO1FBQ2hCLE1BQU0sRUFBRSxFQUFFLElBQUksRUFBRSxNQUFNLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxNQUFNLENBQUMsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLENBQUMsUUFBUSxFQUFFO1FBQ2pGLEdBQUcsRUFBRSxNQUFNLENBQUMsR0FBRyxJQUFJLEVBQUU7S0FDdEIsQ0FBQztJQUVGLE1BQU0sRUFBRSxNQUFNLEVBQUUsR0FBRyxJQUFBLHdCQUFnQixFQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDdEYsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3RCLDJFQUEyRTtRQUMzRSwwRUFBMEU7UUFDMUUsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBRSxDQUFDO1FBQ3JCLE1BQU0sR0FBRyxHQUFHLHlDQUF5QyxNQUFNLENBQUMsSUFBSSxlQUFlLENBQUMsQ0FBQyxLQUFLLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3hHLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdkIsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFBsdWdpbiB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWRhdGEnO1xuaW1wb3J0IHsgcmVzb2x2ZVRlbXBsYXRlcyB9IGZyb20gJy4vaW5kZXgnO1xuXG4vKipcbiAqIEZpZWxkcyBpbnNpZGUgYSBQbHVnaW4gcmVjb3JkIHRoYXQgYWNjZXB0IGB7eyAuLi4gfX1gIHRlbXBsYXRlcy5cbiAqXG4gKiBQdXJlLXN0cmluZyBsZWF2ZXMgb25seS4gYG5hbWVgLCBgdmVyc2lvbmAsIGBwbHVnaW5UeXBlYCwgYGNvbXB1dGVUeXBlYCxcbiAqIGB0aW1lb3V0YCwgYHNlY3JldHNgLCBgZmFpbHVyZUJlaGF2aW9yYCwgYHJlcXVpcmVkTWV0YWRhdGFgLCBgcmVxdWlyZWRWYXJzYFxuICogc3RheSBsaXRlcmFsLiBgbWV0YWRhdGEuKmAgaXMgZXhjbHVkZWQgYmVjYXVzZSBDREstbWV0YWRhdGEgdmFsdWVzIGFyZVxuICogc3RydWN0dXJhbCwgbm90IHVzZXItaW50ZXJwb2xhdGVkLlxuICovXG5jb25zdCBURU1QTEFUQUJMRV9GSUVMRFMgPSBbXG4gICdkZXNjcmlwdGlvbicsXG4gICdjb21tYW5kcycsIC8vIHN0cmluZ1tdXG4gICdpbnN0YWxsQ29tbWFuZHMnLCAvLyBzdHJpbmdbXVxuICAnZW52JywgLy8gUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuICAnYnVpbGRBcmdzJywgLy8gUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuXSBhcyBjb25zdDtcblxuZXhwb3J0IGZ1bmN0aW9uIGlzUGx1Z2luVGVtcGxhdGFibGVGaWVsZChmaWVsZDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIC8vIGBjb21tYW5kc2AgLyBgaW5zdGFsbENvbW1hbmRzYCBhcmUgYXJyYXlzIG9mIHN0cmluZ3Mg4oaSIGVudHJpZXMgbGlrZSAnY29tbWFuZHNbMF0nXG4gIC8vIGBlbnZgIC8gYGJ1aWxkQXJnc2AgYXJlIG9iamVjdHMg4oaSIGVudHJpZXMgbGlrZSAnZW52LlNUQUdFJ1xuICByZXR1cm4gVEVNUExBVEFCTEVfRklFTERTLnNvbWUoZiA9PiBmaWVsZCA9PT0gZiB8fCBmaWVsZC5zdGFydHNXaXRoKGAke2Z9W2ApIHx8IGZpZWxkLnN0YXJ0c1dpdGgoYCR7Zn0uYCkpO1xufVxuXG4vKipcbiAqIFJldHVybiBhIHNoYWxsb3cgY2xvbmUgb2YgYHBsdWdpbmAgd2l0aCBhbGwgYHt7IC4uLiB9fWAgdGVtcGxhdGVzXG4gKiByZXNvbHZlZCBhZ2FpbnN0IHRoZSBnaXZlbiBwaXBlbGluZSBzY29wZS4gQ2FsbGVyIG11c3QgcHJlLXBvcHVsYXRlXG4gKiBgcGlwZWxpbmVTY29wZWAgd2l0aCBgeyBwaXBlbGluZSwgcGx1Z2luLCBlbnYgfWAga2V5cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVQbHVnaW5UZW1wbGF0ZXMoXG4gIHBsdWdpbjogUGx1Z2luLFxuICBwaXBlbGluZVNjb3BlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbik6IFBsdWdpbiB7XG4gIC8vIERlZXAgY2xvbmUgc28gbXV0YXRpb25zIGRvbid0IGxlYWsgYmFjayB0byB0aGUgY2FsbGVyJ3MgUGx1Z2luLlxuICAvLyBTdHJ1Y3R1cmFsIGZpZWxkcyB3ZSBjYXJlIGFib3V0IGFyZSBwbGFpbiBKU09OOyBzdHJ1Y3R1cmVkQ2xvbmUgaXMgc2FmZS5cbiAgY29uc3QgY2xvbmUgPSBzdHJ1Y3R1cmVkQ2xvbmUocGx1Z2luKSBhcyBQbHVnaW4gJiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcblxuICBjb25zdCBzY29wZSA9IHtcbiAgICAuLi5waXBlbGluZVNjb3BlLFxuICAgIHBsdWdpbjogeyBuYW1lOiBwbHVnaW4ubmFtZSwgdmVyc2lvbjogcGx1Z2luLnZlcnNpb24sIGltYWdlVGFnOiBwbHVnaW4uaW1hZ2VUYWcgfSxcbiAgICBlbnY6IHBsdWdpbi5lbnYgPz8ge30sXG4gIH07XG5cbiAgY29uc3QgeyBlcnJvcnMgfSA9IHJlc29sdmVUZW1wbGF0ZXMoY2xvbmUsIHNjb3BlLCBpc1BsdWdpblRlbXBsYXRhYmxlRmllbGQsICdwbHVnaW4nKTtcbiAgaWYgKGVycm9ycy5sZW5ndGggPiAwKSB7XG4gICAgLy8gRmlyc3QgZXJyb3Igd2lucyDigJQgcmVzb2x2ZXIgZXJyb3JzIHNob3VsZCBuZXZlciBiZSBiYXRjaGVkIGF0IHN5bnRoIHRpbWVcbiAgICAvLyBiZWNhdXNlIGEgYnJva2VuIHRlbXBsYXRlIGlzIGEgcHJvZ3JhbW1lciBlcnJvciwgbm90IGEgdmFsaWRhdGlvbiBzdGVwLlxuICAgIGNvbnN0IGUgPSBlcnJvcnNbMF0hO1xuICAgIGNvbnN0IG1zZyA9IGBUZW1wbGF0ZSByZXNvbHV0aW9uIGZhaWxlZCBpbiBwbHVnaW4gXCIke3BsdWdpbi5uYW1lfVwiIGF0IGZpZWxkICcke2UuZmllbGR9JzogJHtlLm1lc3NhZ2V9YDtcbiAgICB0aHJvdyBuZXcgRXJyb3IobXNnKTtcbiAgfVxuICByZXR1cm4gY2xvbmU7XG59XG4iXX0=
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Tokenizer for the pipeline-builder template grammar.
3
+ *
4
+ * Grammar:
5
+ * Template := (Literal | Expr)*
6
+ * Expr := "{{" ws Path (ws Filter)? ws "}}"
7
+ * Path := Identifier ("." Identifier)*
8
+ * Identifier := [a-zA-Z_][a-zA-Z0-9_]{0,63}
9
+ * Filter := "|" ws "default" ws ":" ws Quoted
10
+ * Quoted := "'" ... "'" | "\"" ... "\""
11
+ *
12
+ * `{{{{` is the escape sequence for a literal `{{`.
13
+ */
14
+ export declare const MAX_FIELD_SIZE_BYTES: number;
15
+ export declare const MAX_PATH_DEPTH = 5;
16
+ export declare const MAX_IDENTIFIER_LENGTH = 64;
17
+ export interface SourcePosition {
18
+ line: number;
19
+ col: number;
20
+ }
21
+ export interface LiteralToken {
22
+ kind: 'literal';
23
+ value: string;
24
+ pos: SourcePosition;
25
+ }
26
+ export type CoerceKind = 'number' | 'bool' | 'json';
27
+ export interface ExprToken {
28
+ kind: 'expr';
29
+ path: string[];
30
+ defaultValue?: string;
31
+ coerce?: CoerceKind;
32
+ source: string;
33
+ pos: SourcePosition;
34
+ }
35
+ export type Token = LiteralToken | ExprToken;
36
+ export declare class TokenizerError extends Error {
37
+ readonly pos: SourcePosition;
38
+ constructor(message: string, pos: SourcePosition);
39
+ }
40
+ /**
41
+ * Tokenize a template source string. Never throws on valid input;
42
+ * throws `TokenizerError` with source position on malformed templates.
43
+ */
44
+ export declare function tokenize(source: string): Token[];
45
+ /**
46
+ * Convenience: returns true if the source contains any templates.
47
+ * Used as a fast-path to skip tokenization for strings that are obviously literal.
48
+ */
49
+ export declare function hasTemplate(source: string): boolean;
@@ -0,0 +1,237 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.TokenizerError = exports.MAX_IDENTIFIER_LENGTH = exports.MAX_PATH_DEPTH = exports.MAX_FIELD_SIZE_BYTES = void 0;
6
+ exports.tokenize = tokenize;
7
+ exports.hasTemplate = hasTemplate;
8
+ /**
9
+ * Tokenizer for the pipeline-builder template grammar.
10
+ *
11
+ * Grammar:
12
+ * Template := (Literal | Expr)*
13
+ * Expr := "{{" ws Path (ws Filter)? ws "}}"
14
+ * Path := Identifier ("." Identifier)*
15
+ * Identifier := [a-zA-Z_][a-zA-Z0-9_]{0,63}
16
+ * Filter := "|" ws "default" ws ":" ws Quoted
17
+ * Quoted := "'" ... "'" | "\"" ... "\""
18
+ *
19
+ * `{{{{` is the escape sequence for a literal `{{`.
20
+ */
21
+ exports.MAX_FIELD_SIZE_BYTES = 4 * 1024;
22
+ exports.MAX_PATH_DEPTH = 5;
23
+ exports.MAX_IDENTIFIER_LENGTH = 64;
24
+ class TokenizerError extends Error {
25
+ pos;
26
+ constructor(message, pos) {
27
+ super(`${message} at line ${pos.line}, col ${pos.col}`);
28
+ this.pos = pos;
29
+ this.name = 'TokenizerError';
30
+ }
31
+ }
32
+ exports.TokenizerError = TokenizerError;
33
+ /**
34
+ * Tokenize a template source string. Never throws on valid input;
35
+ * throws `TokenizerError` with source position on malformed templates.
36
+ */
37
+ function tokenize(source) {
38
+ if (source.length > exports.MAX_FIELD_SIZE_BYTES) {
39
+ throw new TokenizerError(`Field exceeds max size of ${exports.MAX_FIELD_SIZE_BYTES} bytes`, { line: 1, col: 1 });
40
+ }
41
+ const tokens = [];
42
+ let i = 0;
43
+ let line = 1;
44
+ let col = 1;
45
+ let literalStart = 0;
46
+ let literalStartPos = { line: 1, col: 1 };
47
+ const advance = (n) => {
48
+ for (let k = 0; k < n; k++) {
49
+ if (source[i + k] === '\n') {
50
+ line++;
51
+ col = 1;
52
+ }
53
+ else {
54
+ col++;
55
+ }
56
+ }
57
+ i += n;
58
+ };
59
+ const flushLiteral = (endIdx) => {
60
+ if (endIdx > literalStart) {
61
+ // Unescape `{{{{` → `{{`
62
+ const raw = source.slice(literalStart, endIdx);
63
+ const value = raw.replace(/\{\{\{\{/g, '{{');
64
+ tokens.push({ kind: 'literal', value, pos: literalStartPos });
65
+ }
66
+ };
67
+ const startLiteral = () => {
68
+ literalStart = i;
69
+ literalStartPos = { line, col };
70
+ };
71
+ startLiteral();
72
+ while (i < source.length) {
73
+ // Escape: `{{{{` → literal `{{`
74
+ if (source.startsWith('{{{{', i)) {
75
+ advance(4);
76
+ continue;
77
+ }
78
+ // Expression start
79
+ if (source.startsWith('{{', i)) {
80
+ flushLiteral(i);
81
+ const exprPos = { line, col };
82
+ advance(2);
83
+ const expr = readExpression(source, i, line, col);
84
+ tokens.push({
85
+ kind: 'expr',
86
+ path: expr.path,
87
+ defaultValue: expr.defaultValue,
88
+ coerce: expr.coerce,
89
+ source: source.slice(exprPos.col - 1 === 0 ? i - 2 : (() => { return i - 2; })(), expr.endIdx),
90
+ pos: exprPos,
91
+ });
92
+ advance(expr.endIdx - i);
93
+ startLiteral();
94
+ continue;
95
+ }
96
+ // Stray `}}` outside an expression
97
+ if (source.startsWith('}}', i)) {
98
+ throw new TokenizerError("Unexpected '}}' outside expression", { line, col });
99
+ }
100
+ advance(1);
101
+ }
102
+ flushLiteral(i);
103
+ return tokens;
104
+ }
105
+ function readExpression(source, startIdx, startLine, startCol) {
106
+ let i = startIdx;
107
+ let line = startLine;
108
+ let col = startCol;
109
+ const skipWs = () => {
110
+ while (i < source.length && (source[i] === ' ' || source[i] === '\t')) {
111
+ i++;
112
+ col++;
113
+ }
114
+ };
115
+ const readIdentifier = () => {
116
+ const startI = i;
117
+ if (i >= source.length || !/[a-zA-Z_]/.test(source[i])) {
118
+ throw new TokenizerError('Expected identifier inside template expression', { line, col });
119
+ }
120
+ while (i < source.length && /[a-zA-Z0-9_]/.test(source[i])) {
121
+ i++;
122
+ col++;
123
+ }
124
+ const ident = source.slice(startI, i);
125
+ if (ident.length > exports.MAX_IDENTIFIER_LENGTH) {
126
+ throw new TokenizerError(`Identifier exceeds max length of ${exports.MAX_IDENTIFIER_LENGTH}`, { line, col: col - ident.length });
127
+ }
128
+ return ident;
129
+ };
130
+ const readQuoted = () => {
131
+ const quote = source[i];
132
+ if (quote !== '"' && quote !== "'") {
133
+ throw new TokenizerError('Expected quoted string', { line, col });
134
+ }
135
+ i++;
136
+ col++;
137
+ const parts = [];
138
+ while (i < source.length && source[i] !== quote) {
139
+ if (source[i] === '\\') {
140
+ const next = source[i + 1];
141
+ if (next === '\\' || next === quote) {
142
+ parts.push(next);
143
+ i += 2;
144
+ col += 2;
145
+ continue;
146
+ }
147
+ throw new TokenizerError(`Invalid escape '\\${next ?? ''}' in quoted string`, { line, col });
148
+ }
149
+ if (source[i] === '\n') {
150
+ throw new TokenizerError('Unterminated quoted string', { line, col });
151
+ }
152
+ parts.push(source[i]);
153
+ i++;
154
+ col++;
155
+ }
156
+ if (i >= source.length) {
157
+ throw new TokenizerError('Unterminated quoted string', { line, col });
158
+ }
159
+ i++;
160
+ col++; // consume closing quote
161
+ return parts.join('');
162
+ };
163
+ skipWs();
164
+ const path = [readIdentifier()];
165
+ while (i < source.length && source[i] === '.') {
166
+ i++;
167
+ col++;
168
+ path.push(readIdentifier());
169
+ if (path.length > exports.MAX_PATH_DEPTH) {
170
+ throw new TokenizerError(`Path depth exceeds max of ${exports.MAX_PATH_DEPTH}`, { line, col });
171
+ }
172
+ }
173
+ skipWs();
174
+ let defaultValue;
175
+ let coerce;
176
+ // Filters chain: `| default: 'x' | number`, `| bool`, `| json`, etc.
177
+ // - `default` takes a quoted argument — applied first (before coercion)
178
+ // - `number`, `bool`, `json` are argument-less coercion filters — applied last
179
+ while (source[i] === '|') {
180
+ i++;
181
+ col++;
182
+ skipWs();
183
+ if (source.startsWith('default', i)) {
184
+ if (defaultValue !== undefined) {
185
+ throw new TokenizerError("'default' filter may only appear once", { line, col });
186
+ }
187
+ i += 'default'.length;
188
+ col += 'default'.length;
189
+ skipWs();
190
+ if (source[i] !== ':') {
191
+ throw new TokenizerError("Expected ':' after 'default'", { line, col });
192
+ }
193
+ i++;
194
+ col++;
195
+ skipWs();
196
+ defaultValue = readQuoted();
197
+ skipWs();
198
+ continue;
199
+ }
200
+ let matchedCoerce = null;
201
+ for (const kind of ['number', 'bool', 'json']) {
202
+ if (source.startsWith(kind, i)) {
203
+ // Ensure identifier boundary — `numberish` must not match `number`
204
+ const next = source[i + kind.length];
205
+ if (!next || !/[a-zA-Z0-9_]/.test(next)) {
206
+ matchedCoerce = kind;
207
+ i += kind.length;
208
+ col += kind.length;
209
+ break;
210
+ }
211
+ }
212
+ }
213
+ if (!matchedCoerce) {
214
+ throw new TokenizerError("Unknown filter — supported: 'default', 'number', 'bool', 'json'", { line, col });
215
+ }
216
+ if (coerce) {
217
+ throw new TokenizerError('Only one coercion filter is allowed per expression', { line, col });
218
+ }
219
+ coerce = matchedCoerce;
220
+ skipWs();
221
+ }
222
+ if (!source.startsWith('}}', i)) {
223
+ throw new TokenizerError("Expected '}}'", { line, col });
224
+ }
225
+ i += 2;
226
+ return { path, defaultValue, coerce, endIdx: i };
227
+ }
228
+ /**
229
+ * Convenience: returns true if the source contains any templates.
230
+ * Used as a fast-path to skip tokenization for strings that are obviously literal.
231
+ */
232
+ function hasTemplate(source) {
233
+ // Not completely accurate (a lone `{{{{` would return true) but that's fine —
234
+ // tokenize() will produce a literal-only token list and round-trip correctly.
235
+ return source.includes('{{');
236
+ }
237
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5pemVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3Rva2VuaXplci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7O0FBMER0Qyw0QkE0RUM7QUFpS0Qsa0NBSUM7QUF6U0Q7Ozs7Ozs7Ozs7OztHQVlHO0FBRVUsUUFBQSxvQkFBb0IsR0FBRyxDQUFDLEdBQUcsSUFBSSxDQUFDO0FBQ2hDLFFBQUEsY0FBYyxHQUFHLENBQUMsQ0FBQztBQUNuQixRQUFBLHFCQUFxQixHQUFHLEVBQUUsQ0FBQztBQTBCeEMsTUFBYSxjQUFlLFNBQVEsS0FBSztJQUdyQjtJQUZsQixZQUNFLE9BQWUsRUFDQyxHQUFtQjtRQUVuQyxLQUFLLENBQUMsR0FBRyxPQUFPLFlBQVksR0FBRyxDQUFDLElBQUksU0FBUyxHQUFHLENBQUMsR0FBRyxFQUFFLENBQUMsQ0FBQztRQUZ4QyxRQUFHLEdBQUgsR0FBRyxDQUFnQjtRQUduQyxJQUFJLENBQUMsSUFBSSxHQUFHLGdCQUFnQixDQUFDO0lBQy9CLENBQUM7Q0FDRjtBQVJELHdDQVFDO0FBRUQ7OztHQUdHO0FBQ0gsU0FBZ0IsUUFBUSxDQUFDLE1BQWM7SUFDckMsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLDRCQUFvQixFQUFFLENBQUM7UUFDekMsTUFBTSxJQUFJLGNBQWMsQ0FDdEIsNkJBQTZCLDRCQUFvQixRQUFRLEVBQ3pELEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxFQUFFLENBQ3BCLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxNQUFNLEdBQVksRUFBRSxDQUFDO0lBQzNCLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUNWLElBQUksSUFBSSxHQUFHLENBQUMsQ0FBQztJQUNiLElBQUksR0FBRyxHQUFHLENBQUMsQ0FBQztJQUNaLElBQUksWUFBWSxHQUFHLENBQUMsQ0FBQztJQUNyQixJQUFJ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RSxJQUFJLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUMzRCxDQUFDO0lBQ0QsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNQLE9BQU8sRUFBRSxJQUFJLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLENBQUM7QUFDbkQsQ0FBQztBQUVEOzs7R0FHRztBQUNILFNBQWdCLFdBQVcsQ0FBQyxNQUFjO0lBQ3hDLDhFQUE4RTtJQUM5RSw4RUFBOEU7SUFDOUUsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO0FBQy9CLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvLyBDb3B5cmlnaHQgMjAyNiBQaXBlbGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBUb2tlbml6ZXIgZm9yIHRoZSBwaXBlbGluZS1idWlsZGVyIHRlbXBsYXRlIGdyYW1tYXIuXG4gKlxuICogR3JhbW1hcjpcbiAqICAgVGVtcGxhdGUgICA6PSAoTGl0ZXJhbCB8IEV4cHIpKlxuICogICBFeHByICAgICAgIDo9IFwie3tcIiB3cyBQYXRoICh3cyBGaWx0ZXIpPyB3cyBcIn19XCJcbiAqICAgUGF0aCAgICAgICA6PSBJZGVudGlmaWVyIChcIi5cIiBJZGVudGlmaWVyKSpcbiAqICAgSWRlbnRpZmllciA6PSBbYS16QS1aX11bYS16QS1aMC05X117MCw2M31cbiAqICAgRmlsdGVyICAgICA6PSBcInxcIiB3cyBcImRlZmF1bHRcIiB3cyBcIjpcIiB3cyBRdW90ZWRcbiAqICAgUXVvdGVkICAgICA6PSBcIidcIiAuLi4gXCInXCIgfCBcIlxcXCJcIiAuLi4gXCJcXFwiXCJcbiAqXG4gKiBge3t7e2AgaXMgdGhlIGVzY2FwZSBzZXF1ZW5jZSBmb3IgYSBsaXRlcmFsIGB7e2AuXG4gKi9cblxuZXhwb3J0IGNvbnN0IE1BWF9GSUVMRF9TSVpFX0JZVEVTID0gNCAqIDEwMjQ7XG5leHBvcnQgY29uc3QgTUFYX1BBVEhfREVQVEggPSA1O1xuZXhwb3J0IGNvbnN0IE1BWF9JREVOVElGSUVSX0xFTkdUSCA9IDY0O1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvdXJjZVBvc2l0aW9uIHtcbiAgbGluZTogbnVtYmVyO1xuICBjb2w6IG51bWJlcjtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBMaXRlcmFsVG9rZW4ge1xuICBraW5kOiAnbGl0ZXJhbCc7XG4gIHZhbHVlOiBzdHJpbmc7XG4gIHBvczogU291cmNlUG9zaXRpb247XG59XG5cbmV4cG9ydCB0eXBlIENvZXJjZUtpbmQgPSAnbnVtYmVyJyB8ICdib29sJyB8ICdqc29uJztcblxuZXhwb3J0IGludGVyZmFjZSBFeHByVG9rZW4ge1xuICBraW5kOiAnZXhwcic7XG4gIHBhdGg6IHN0cmluZ1tdOyAvLyBlLmcuIFsncGlwZWxpbmUnLCAnbWV0YWRhdGEnLCAnZW52J11cbiAgZGVmYXVsdFZhbHVlPzogc3RyaW5nOyAvLyB2YWx1ZSBmb3IgYHwgZGVmYXVsdDogJy4uLidgIGZpbHRlclxuICBjb2VyY2U/OiBDb2VyY2VLaW5kOyAvLyBgfCBudW1iZXJgLCBgfCBib29sYCwgYHwganNvbmBcbiAgc291cmNlOiBzdHJpbmc7IC8vIG9yaWdpbmFsIFwie3sgLi4uIH19XCIgdGV4dCAoZm9yIGVycm9yIG1lc3NhZ2VzKVxuICBwb3M6IFNvdXJjZVBvc2l0aW9uO1xufVxuXG5leHBvcnQgdHlwZSBUb2tlbiA9IExpdGVyYWxUb2tlbiB8IEV4cHJUb2tlbjtcblxuZXhwb3J0IGNsYXNzIFRva2VuaXplckVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICBjb25zdHJ1Y3RvcihcbiAgICBtZXNzYWdlOiBzdHJpbmcsXG4gICAgcHVibGljIHJlYWRvbmx5IHBvczogU291cmNlUG9zaXRpb24sXG4gICkge1xuICAgIHN1cGVyKGAke21lc3NhZ2V9IGF0IGxpbmUgJHtwb3MubGluZX0sIGNvbCAke3Bvcy5jb2x9YCk7XG4gICAgdGhpcy5uYW1lID0gJ1Rva2VuaXplckVycm9yJztcbiAgfVxufVxuXG4vKipcbiAqIFRva2VuaXplIGEgdGVtcGxhdGUgc291cmNlIHN0cmluZy4gTmV2ZXIgdGhyb3dzIG9uIHZhbGlkIGlucHV0O1xuICogdGhyb3dzIGBUb2tlbml6ZXJFcnJvcmAgd2l0aCBzb3VyY2UgcG9zaXRpb24gb24gbWFsZm9ybWVkIHRlbXBsYXRlcy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRva2VuaXplKHNvdXJjZTogc3RyaW5nKTogVG9rZW5bXSB7XG4gIGlmIChzb3VyY2UubGVuZ3RoID4gTUFYX0ZJRUxEX1NJWkVfQllURVMpIHtcbiAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoXG4gICAgICBgRmllbGQgZXhjZWVkcyBtYXggc2l6ZSBvZiAke01BWF9GSUVMRF9TSVpFX0JZVEVTfSBieXRlc2AsXG4gICAgICB7IGxpbmU6IDEsIGNvbDogMSB9LFxuICAgICk7XG4gIH1cblxuICBjb25zdCB0b2tlbnM6IFRva2VuW10gPSBbXTtcbiAgbGV0IGkgPSAwO1xuICBsZXQgbGluZSA9IDE7XG4gIGxldCBjb2wgPSAxO1xuICBsZXQgbGl0ZXJhbFN0YXJ0ID0gMDtcbiAgbGV0IGxpdGVyYWxTdGFydFBvczogU291cmNlUG9zaXRpb24gPSB7IGxpbmU6IDEsIGNvbDogMSB9O1xuXG4gIGNvbnN0IGFkdmFuY2UgPSAobjogbnVtYmVyKTogdm9pZCA9PiB7XG4gICAgZm9yIChsZXQgayA9IDA7IGsgPCBuOyBrKyspIHtcbiAgICAgIGlmIChzb3VyY2VbaSArIGtdID09PSAnXFxuJykge1xuICAgICAgICBsaW5lKys7XG4gICAgICAgIGNvbCA9IDE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBjb2wrKztcbiAgICAgIH1cbiAgICB9XG4gICAgaSArPSBuO1xuICB9O1xuXG4gIGNvbnN0IGZsdXNoTGl0ZXJhbCA9IChlbmRJZHg6IG51bWJlcik6IHZvaWQgPT4ge1xuICAgIGlmIChlbmRJZHggPiBsaXRlcmFsU3RhcnQpIHtcbiAgICAgIC8vIFVuZXNjYXBlIGB7e3t7YCDihpIgYHt7YFxuICAgICAgY29uc3QgcmF3ID0gc291cmNlLnNsaWNlKGxpdGVyYWxTdGFydCwgZW5kSWR4KTtcbiAgICAgIGNvbnN0IHZhbHVlID0gcmF3LnJlcGxhY2UoL1xce1xce1xce1xcey9nLCAne3snKTtcbiAgICAgIHRva2Vucy5wdXNoKHsga2luZDogJ2xpdGVyYWwnLCB2YWx1ZSwgcG9zOiBsaXRlcmFsU3RhcnRQb3MgfSk7XG4gICAgfVxuICB9O1xuXG4gIGNvbnN0IHN0YXJ0TGl0ZXJhbCA9ICgpOiB2b2lkID0+IHtcbiAgICBsaXRlcmFsU3RhcnQgPSBpO1xuICAgIGxpdGVyYWxTdGFydFBvcyA9IHsgbGluZSwgY29sIH07XG4gIH07XG5cbiAgc3RhcnRMaXRlcmFsKCk7XG5cbiAgd2hpbGUgKGkgPCBzb3VyY2UubGVuZ3RoKSB7XG4gICAgLy8gRXNjYXBlOiBge3t7e2Ag4oaSIGxpdGVyYWwgYHt7YFxuICAgIGlmIChzb3VyY2Uuc3RhcnRzV2l0aCgne3t7eycsIGkpKSB7XG4gICAgICBhZHZhbmNlKDQpO1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIC8vIEV4cHJlc3Npb24gc3RhcnRcbiAgICBpZiAoc291cmNlLnN0YXJ0c1dpdGgoJ3t7JywgaSkpIHtcbiAgICAgIGZsdXNoTGl0ZXJhbChpKTtcbiAgICAgIGNvbnN0IGV4cHJQb3M6IFNvdXJjZVBvc2l0aW9uID0geyBsaW5lLCBjb2wgfTtcbiAgICAgIGFkdmFuY2UoMik7XG4gICAgICBjb25zdCBleHByID0gcmVhZEV4cHJlc3Npb24oc291cmNlLCBpLCBsaW5lLCBjb2wpO1xuICAgICAgdG9rZW5zLnB1c2goe1xuICAgICAgICBraW5kOiAnZXhwcicsXG4gICAgICAgIHBhdGg6IGV4cHIucGF0aCxcbiAgICAgICAgZGVmYXVsdFZhbHVlOiBleHByLmRlZmF1bHRWYWx1ZSxcbiAgICAgICAgY29lcmNlOiBleHByLmNvZXJjZSxcbiAgICAgICAgc291cmNlOiBzb3VyY2Uuc2xpY2UoZXhwclBvcy5jb2wgLSAxID09PSAwID8gaSAtIDIgOiAoKCkgPT4geyByZXR1cm4gaSAtIDI7IH0pKCksIGV4cHIuZW5kSWR4KSxcbiAgICAgICAgcG9zOiBleHByUG9zLFxuICAgICAgfSk7XG4gICAgICBhZHZhbmNlKGV4cHIuZW5kSWR4IC0gaSk7XG4gICAgICBzdGFydExpdGVyYWwoKTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICAvLyBTdHJheSBgfX1gIG91dHNpZGUgYW4gZXhwcmVzc2lvblxuICAgIGlmIChzb3VyY2Uuc3RhcnRzV2l0aCgnfX0nLCBpKSkge1xuICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFwiVW5leHBlY3RlZCAnfX0nIG91dHNpZGUgZXhwcmVzc2lvblwiLCB7IGxpbmUsIGNvbCB9KTtcbiAgICB9XG4gICAgYWR2YW5jZSgxKTtcbiAgfVxuXG4gIGZsdXNoTGl0ZXJhbChpKTtcbiAgcmV0dXJuIHRva2Vucztcbn1cblxuaW50ZXJmYWNlIFBhcnNlZEV4cHIge1xuICBwYXRoOiBzdHJpbmdbXTtcbiAgZGVmYXVsdFZhbHVlPzogc3RyaW5nO1xuICBjb2VyY2U/OiBDb2VyY2VLaW5kO1xuICBlbmRJZHg6IG51bWJlcjtcbn1cblxuZnVuY3Rpb24gcmVhZEV4cHJlc3Npb24oXG4gIHNvdXJjZTogc3RyaW5nLFxuICBzdGFydElkeDogbnVtYmVyLFxuICBzdGFydExpbmU6IG51bWJlcixcbiAgc3RhcnRDb2w6IG51bWJlcixcbik6IFBhcnNlZEV4cHIge1xuICBsZXQgaSA9IHN0YXJ0SWR4O1xuICBsZXQgbGluZSA9IHN0YXJ0TGluZTtcbiAgbGV0IGNvbCA9IHN0YXJ0Q29sO1xuXG4gIGNvbnN0IHNraXBXcyA9ICgpOiB2b2lkID0+IHtcbiAgICB3aGlsZSAoaSA8IHNvdXJjZS5sZW5ndGggJiYgKHNvdXJjZVtpXSA9PT0gJyAnIHx8IHNvdXJjZVtpXSA9PT0gJ1xcdCcpKSB7XG4gICAgICBpKys7XG4gICAgICBjb2wrKztcbiAgICB9XG4gIH07XG5cbiAgY29uc3QgcmVhZElkZW50aWZpZXIgPSAoKTogc3RyaW5nID0+IHtcbiAgICBjb25zdCBzdGFydEkgPSBpO1xuICAgIGlmIChpID49IHNvdXJjZS5sZW5ndGggfHwgIS9bYS16QS1aX10vLnRlc3Qoc291cmNlW2ldISkpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcbiAgICAgICAgJ0V4cGVjdGVkIGlkZW50aWZpZXIgaW5zaWRlIHRlbXBsYXRlIGV4cHJlc3Npb24nLFxuICAgICAgICB7IGxpbmUsIGNvbCB9LFxuICAgICAgKTtcbiAgICB9XG4gICAgd2hpbGUgKGkgPCBzb3VyY2UubGVuZ3RoICYmIC9bYS16QS1aMC05X10vLnRlc3Qoc291cmNlW2ldISkpIHtcbiAgICAgIGkrKztcbiAgICAgIGNvbCsrO1xuICAgIH1cbiAgICBjb25zdCBpZGVudCA9IHNvdXJjZS5zbGljZShzdGFydEksIGkpO1xuICAgIGlmIChpZGVudC5sZW5ndGggPiBNQVhfSURFTlRJRklFUl9MRU5HVEgpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcbiAgICAgICAgYElkZW50aWZpZXIgZXhjZWVkcyBtYXggbGVuZ3RoIG9mICR7TUFYX0lERU5USUZJRVJfTEVOR1RIfWAsXG4gICAgICAgIHsgbGluZSwgY29sOiBjb2wgLSBpZGVudC5sZW5ndGggfSxcbiAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiBpZGVudDtcbiAgfTtcblxuICBjb25zdCByZWFkUXVvdGVkID0gKCk6IHN0cmluZyA9PiB7XG4gICAgY29uc3QgcXVvdGUgPSBzb3VyY2VbaV07XG4gICAgaWYgKHF1b3RlICE9PSAnXCInICYmIHF1b3RlICE9PSBcIidcIikge1xuICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKCdFeHBlY3RlZCBxdW90ZWQgc3RyaW5nJywgeyBsaW5lLCBjb2wgfSk7XG4gICAgfVxuICAgIGkrKzsgY29sKys7XG4gICAgY29uc3QgcGFydHM6IHN0cmluZ1tdID0gW107XG4gICAgd2hpbGUgKGkgPCBzb3VyY2UubGVuZ3RoICYmIHNvdXJjZVtpXSAhPT0gcXVvdGUpIHtcbiAgICAgIGlmIChzb3VyY2VbaV0gPT09ICdcXFxcJykge1xuICAgICAgICBjb25zdCBuZXh0ID0gc291cmNlW2kgKyAxXTtcbiAgICAgICAgaWYgKG5leHQgPT09ICdcXFxcJyB8fCBuZXh0ID09PSBxdW90ZSkge1xuICAgICAgICAgIHBhcnRzLnB1c2gobmV4dCk7XG4gICAgICAgICAgaSArPSAyO1xuICAgICAgICAgIGNvbCArPSAyO1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcbiAgICAgICAgICBgSW52YWxpZCBlc2NhcGUgJ1xcXFwke25leHQgPz8gJyd9JyBpbiBxdW90ZWQgc3RyaW5nYCxcbiAgICAgICAgICB7IGxpbmUsIGNvbCB9LFxuICAgICAgICApO1xuICAgICAgfVxuICAgICAgaWYgKHNvdXJjZVtpXSA9PT0gJ1xcbicpIHtcbiAgICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKCdVbnRlcm1pbmF0ZWQgcXVvdGVkIHN0cmluZycsIHsgbGluZSwgY29sIH0pO1xuICAgICAgfVxuICAgICAgcGFydHMucHVzaChzb3VyY2VbaV0hKTtcbiAgICAgIGkrKzsgY29sKys7XG4gICAgfVxuICAgIGlmIChpID49IHNvdXJjZS5sZW5ndGgpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcignVW50ZXJtaW5hdGVkIHF1b3RlZCBzdHJpbmcnLCB7IGxpbmUsIGNvbCB9KTtcbiAgICB9XG4gICAgaSsrOyBjb2wrKzsgLy8gY29uc3VtZSBjbG9zaW5nIHF1b3RlXG4gICAgcmV0dXJuIHBhcnRzLmpvaW4oJycpO1xuICB9O1xuXG4gIHNraXBXcygpO1xuICBjb25zdCBwYXRoOiBzdHJpbmdbXSA9IFtyZWFkSWRlbnRpZmllcigpXTtcbiAgd2hpbGUgKGkgPCBzb3VyY2UubGVuZ3RoICYmIHNvdXJjZVtpXSA9PT0gJy4nKSB7XG4gICAgaSsrOyBjb2wrKztcbiAgICBwYXRoLnB1c2gocmVhZElkZW50aWZpZXIoKSk7XG4gICAgaWYgKHBhdGgubGVuZ3RoID4gTUFYX1BBVEhfREVQVEgpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcbiAgICAgICAgYFBhdGggZGVwdGggZXhjZWVkcyBtYXggb2YgJHtNQVhfUEFUSF9ERVBUSH1gLFxuICAgICAgICB7IGxpbmUsIGNvbCB9LFxuICAgICAgKTtcbiAgICB9XG4gIH1cbiAgc2tpcFdzKCk7XG5cbiAgbGV0IGRlZmF1bHRWYWx1ZTogc3RyaW5nIHwgdW5kZWZpbmVkO1xuICBsZXQgY29lcmNlOiBDb2VyY2VLaW5kIHwgdW5kZWZpbmVkO1xuXG4gIC8vIEZpbHRlcnMgY2hhaW46IGB8IGRlZmF1bHQ6ICd4JyB8IG51bWJlcmAsIGB8IGJvb2xgLCBgfCBqc29uYCwgZXRjLlxuICAvLyAtIGBkZWZhdWx0YCB0YWtlcyBhIHF1b3RlZCBhcmd1bWVudCDigJQgYXBwbGllZCBmaXJzdCAoYmVmb3JlIGNvZXJjaW9uKVxuICAvLyAtIGBudW1iZXJgLCBgYm9vbGAsIGBqc29uYCBhcmUgYXJndW1lbnQtbGVzcyBjb2VyY2lvbiBmaWx0ZXJzIOKAlCBhcHBsaWVkIGxhc3RcbiAgd2hpbGUgKHNvdXJjZVtpXSA9PT0gJ3wnKSB7XG4gICAgaSsrOyBjb2wrKztcbiAgICBza2lwV3MoKTtcblxuICAgIGlmIChzb3VyY2Uuc3RhcnRzV2l0aCgnZGVmYXVsdCcsIGkpKSB7XG4gICAgICBpZiAoZGVmYXVsdFZhbHVlICE9PSB1bmRlZmluZWQpIHtcbiAgICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFwiJ2RlZmF1bHQnIGZpbHRlciBtYXkgb25seSBhcHBlYXIgb25jZVwiLCB7IGxpbmUsIGNvbCB9KTtcbiAgICAgIH1cbiAgICAgIGkgKz0gJ2RlZmF1bHQnLmxlbmd0aDtcbiAgICAgIGNvbCArPSAnZGVmYXVsdCcubGVuZ3RoO1xuICAgICAgc2tpcFdzKCk7XG4gICAgICBpZiAoc291cmNlW2ldICE9PSAnOicpIHtcbiAgICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFwiRXhwZWN0ZWQgJzonIGFmdGVyICdkZWZhdWx0J1wiLCB7IGxpbmUsIGNvbCB9KTtcbiAgICAgIH1cbiAgICAgIGkrKzsgY29sKys7XG4gICAgICBza2lwV3MoKTtcbiAgICAgIGRlZmF1bHRWYWx1ZSA9IHJlYWRRdW90ZWQoKTtcbiAgICAgIHNraXBXcygpO1xuICAgICAgY29udGludWU7XG4gICAgfVxuXG4gICAgbGV0IG1hdGNoZWRDb2VyY2U6IENvZXJjZUtpbmQgfCBudWxsID0gbnVsbDtcbiAgICBmb3IgKGNvbnN0IGtpbmQgb2YgWydudW1iZXInLCAnYm9vbCcsICdqc29uJ10gYXMgY29uc3QpIHtcbiAgICAgIGlmIChzb3VyY2Uuc3RhcnRzV2l0aChraW5kLCBpKSkge1xuICAgICAgICAvLyBFbnN1cmUgaWRlbnRpZmllciBib3VuZGFyeSDigJQgYG51bWJlcmlzaGAgbXVzdCBub3QgbWF0Y2ggYG51bWJlcmBcbiAgICAgICAgY29uc3QgbmV4dCA9IHNvdXJjZVtpICsga2luZC5sZW5ndGhdO1xuICAgICAgICBpZiAoIW5leHQgfHwgIS9bYS16QS1aMC05X10vLnRlc3QobmV4dCkpIHtcbiAgICAgICAgICBtYXRjaGVkQ29lcmNlID0ga2luZDtcbiAgICAgICAgICBpICs9IGtpbmQubGVuZ3RoO1xuICAgICAgICAgIGNvbCArPSBraW5kLmxlbmd0aDtcbiAgICAgICAgICBicmVhaztcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cblxuICAgIGlmICghbWF0Y2hlZENvZXJjZSkge1xuICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFxuICAgICAgICBcIlVua25vd24gZmlsdGVyIOKAlCBzdXBwb3J0ZWQ6ICdkZWZhdWx0JywgJ251bWJlcicsICdib29sJywgJ2pzb24nXCIsXG4gICAgICAgIHsgbGluZSwgY29sIH0sXG4gICAgICApO1xuICAgIH1cbiAgICBpZiAoY29lcmNlKSB7XG4gICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoJ09ubHkgb25lIGNvZXJjaW9uIGZpbHRlciBpcyBhbGxvd2VkIHBlciBleHByZXNzaW9uJywgeyBsaW5lLCBjb2wgfSk7XG4gICAgfVxuICAgIGNvZXJjZSA9IG1hdGNoZWRDb2VyY2U7XG4gICAgc2tpcFdzKCk7XG4gIH1cblxuICBpZiAoIXNvdXJjZS5zdGFydHNXaXRoKCd9fScsIGkpKSB7XG4gICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFwiRXhwZWN0ZWQgJ319J1wiLCB7IGxpbmUsIGNvbCB9KTtcbiAgfVxuICBpICs9IDI7XG4gIHJldHVybiB7IHBhdGgsIGRlZmF1bHRWYWx1ZSwgY29lcmNlLCBlbmRJZHg6IGkgfTtcbn1cblxuLyoqXG4gKiBDb252ZW5pZW5jZTogcmV0dXJucyB0cnVlIGlmIHRoZSBzb3VyY2UgY29udGFpbnMgYW55IHRlbXBsYXRlcy5cbiAqIFVzZWQgYXMgYSBmYXN0LXBhdGggdG8gc2tpcCB0b2tlbml6YXRpb24gZm9yIHN0cmluZ3MgdGhhdCBhcmUgb2J2aW91c2x5IGxpdGVyYWwuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBoYXNUZW1wbGF0ZShzb3VyY2U6IHN0cmluZyk6IGJvb2xlYW4ge1xuICAvLyBOb3QgY29tcGxldGVseSBhY2N1cmF0ZSAoYSBsb25lIGB7e3t7YCB3b3VsZCByZXR1cm4gdHJ1ZSkgYnV0IHRoYXQncyBmaW5lIOKAlFxuICAvLyB0b2tlbml6ZSgpIHdpbGwgcHJvZHVjZSBhIGxpdGVyYWwtb25seSB0b2tlbiBsaXN0IGFuZCByb3VuZC10cmlwIGNvcnJlY3RseS5cbiAgcmV0dXJuIHNvdXJjZS5pbmNsdWRlcygne3snKTtcbn1cbiJdfQ==
@@ -0,0 +1,17 @@
1
+ export interface TopoNode {
2
+ /** Unique key identifying this node (e.g. source field path) */
3
+ key: string;
4
+ /** Dependency keys that must resolve before this node */
5
+ deps: string[];
6
+ }
7
+ export interface TopoResult {
8
+ ordered: string[];
9
+ cycles: string[][];
10
+ }
11
+ /**
12
+ * Topologically sort a set of nodes by their declared dependency keys.
13
+ * Returns `ordered` in resolution order, plus any detected cycles.
14
+ * Nodes whose deps reference keys not in the node set are treated as
15
+ * depending on "external" values and ordered first (no cycle risk).
16
+ */
17
+ export declare function topoSort(nodes: TopoNode[]): TopoResult;
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ // Copyright 2026 Pipeline Builder Contributors
3
+ // SPDX-License-Identifier: Apache-2.0
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.topoSort = topoSort;
6
+ /**
7
+ * Topologically sort a set of nodes by their declared dependency keys.
8
+ * Returns `ordered` in resolution order, plus any detected cycles.
9
+ * Nodes whose deps reference keys not in the node set are treated as
10
+ * depending on "external" values and ordered first (no cycle risk).
11
+ */
12
+ function topoSort(nodes) {
13
+ const keys = new Set(nodes.map(n => n.key));
14
+ const graph = new Map();
15
+ const inDegree = new Map();
16
+ // Init
17
+ for (const n of nodes) {
18
+ graph.set(n.key, new Set());
19
+ inDegree.set(n.key, 0);
20
+ }
21
+ // Self-loops short-circuit — detected before topo-sort so single-node
22
+ // cycles are reported even when Kahn's would otherwise accept them.
23
+ const selfLoops = [];
24
+ for (const n of nodes) {
25
+ if (n.deps.includes(n.key))
26
+ selfLoops.push([n.key, n.key]);
27
+ }
28
+ // Edges only between internal nodes (external deps resolve before pass starts)
29
+ for (const n of nodes) {
30
+ for (const dep of n.deps) {
31
+ if (!keys.has(dep))
32
+ continue;
33
+ if (dep === n.key)
34
+ continue; // self-dep → already captured above
35
+ // Edge: dep -> n.key (dep must come first)
36
+ if (!graph.get(dep).has(n.key)) {
37
+ graph.get(dep).add(n.key);
38
+ inDegree.set(n.key, (inDegree.get(n.key) ?? 0) + 1);
39
+ }
40
+ }
41
+ }
42
+ // Kahn's algorithm
43
+ const ordered = [];
44
+ const queue = [];
45
+ for (const [k, d] of inDegree)
46
+ if (d === 0)
47
+ queue.push(k);
48
+ while (queue.length) {
49
+ const k = queue.shift();
50
+ ordered.push(k);
51
+ for (const next of graph.get(k) ?? []) {
52
+ const d = (inDegree.get(next) ?? 0) - 1;
53
+ inDegree.set(next, d);
54
+ if (d === 0)
55
+ queue.push(next);
56
+ }
57
+ }
58
+ // Detect cycles: any node not in `ordered` is part of a cycle
59
+ const cycles = [...selfLoops];
60
+ if (ordered.length < nodes.length) {
61
+ const remaining = new Set(nodes.map(n => n.key).filter(k => !ordered.includes(k)));
62
+ const visited = new Set();
63
+ for (const start of remaining) {
64
+ if (visited.has(start))
65
+ continue;
66
+ const path = [];
67
+ const cycle = findCycle(start, graph, visited, path, remaining);
68
+ if (cycle)
69
+ cycles.push(cycle);
70
+ }
71
+ }
72
+ return { ordered, cycles };
73
+ }
74
+ function findCycle(start, graph, visited, path, remaining) {
75
+ if (path.includes(start)) {
76
+ const idx = path.indexOf(start);
77
+ return [...path.slice(idx), start];
78
+ }
79
+ if (visited.has(start) || !remaining.has(start))
80
+ return null;
81
+ visited.add(start);
82
+ path.push(start);
83
+ for (const next of graph.get(start) ?? []) {
84
+ const cycle = findCycle(next, graph, visited, path, remaining);
85
+ if (cycle)
86
+ return cycle;
87
+ }
88
+ path.pop();
89
+ return null;
90
+ }
91
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9wby1zb3J0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3RvcG8tc29ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsK0NBQStDO0FBQy9DLHNDQUFzQzs7QUFvQnRDLDRCQTJEQztBQWpFRDs7Ozs7R0FLRztBQUNILFNBQWdCLFFBQVEsQ0FBQyxLQUFpQjtJQUN4QyxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDNUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7SUFDN0MsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFFM0MsT0FBTztJQUNQLEtBQUssTUFBTSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7UUFDdEIsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQztRQUM1QixRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDekIsQ0FBQztJQUVELHNFQUFzRTtJQUN0RSxvRUFBb0U7SUFDcEUsTUFBTSxTQUFTLEdBQWUsRUFBRSxDQUFDO0lBQ2pDLEtBQUssTUFBTSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7UUFDdEIsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVELCtFQUErRTtJQUMvRSxLQUFLLE1BQU0sQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3RCLEtBQUss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QUNoQixLQUFhLEVBQ2IsS0FBK0IsRUFDL0IsT0FBb0IsRUFDcEIsSUFBYyxFQUNkLFNBQXNCO0lBRXRCLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDaEMsT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNyQyxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUM3RCxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ25CLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDakIsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO1FBQzFDLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDL0QsSUFBSSxLQUFLO1lBQUUsT0FBTyxLQUFLLENBQUM7SUFDMUIsQ0FBQztJQUNELElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUNYLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQgaW50ZXJmYWNlIFRvcG9Ob2RlIHtcbiAgLyoqIFVuaXF1ZSBrZXkgaWRlbnRpZnlpbmcgdGhpcyBub2RlIChlLmcuIHNvdXJjZSBmaWVsZCBwYXRoKSAqL1xuICBrZXk6IHN0cmluZztcbiAgLyoqIERlcGVuZGVuY3kga2V5cyB0aGF0IG11c3QgcmVzb2x2ZSBiZWZvcmUgdGhpcyBub2RlICovXG4gIGRlcHM6IHN0cmluZ1tdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFRvcG9SZXN1bHQge1xuICBvcmRlcmVkOiBzdHJpbmdbXTtcbiAgY3ljbGVzOiBzdHJpbmdbXVtdO1xufVxuXG4vKipcbiAqIFRvcG9sb2dpY2FsbHkgc29ydCBhIHNldCBvZiBub2RlcyBieSB0aGVpciBkZWNsYXJlZCBkZXBlbmRlbmN5IGtleXMuXG4gKiBSZXR1cm5zIGBvcmRlcmVkYCBpbiByZXNvbHV0aW9uIG9yZGVyLCBwbHVzIGFueSBkZXRlY3RlZCBjeWNsZXMuXG4gKiBOb2RlcyB3aG9zZSBkZXBzIHJlZmVyZW5jZSBrZXlzIG5vdCBpbiB0aGUgbm9kZSBzZXQgYXJlIHRyZWF0ZWQgYXNcbiAqIGRlcGVuZGluZyBvbiBcImV4dGVybmFsXCIgdmFsdWVzIGFuZCBvcmRlcmVkIGZpcnN0IChubyBjeWNsZSByaXNrKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvcG9Tb3J0KG5vZGVzOiBUb3BvTm9kZVtdKTogVG9wb1Jlc3VsdCB7XG4gIGNvbnN0IGtleXMgPSBuZXcgU2V0KG5vZGVzLm1hcChuID0+IG4ua2V5KSk7XG4gIGNvbnN0IGdyYXBoID0gbmV3IE1hcDxzdHJpbmcsIFNldDxzdHJpbmc+PigpO1xuICBjb25zdCBpbkRlZ3JlZSA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG5cbiAgLy8gSW5pdFxuICBmb3IgKGNvbnN0IG4gb2Ygbm9kZXMpIHtcbiAgICBncmFwaC5zZXQobi5rZXksIG5ldyBTZXQoKSk7XG4gICAgaW5EZWdyZWUuc2V0KG4ua2V5LCAwKTtcbiAgfVxuXG4gIC8vIFNlbGYtbG9vcHMgc2hvcnQtY2lyY3VpdCDigJQgZGV0ZWN0ZWQgYmVmb3JlIHRvcG8tc29ydCBzbyBzaW5nbGUtbm9kZVxuICAvLyBjeWNsZXMgYXJlIHJlcG9ydGVkIGV2ZW4gd2hlbiBLYWhuJ3Mgd291bGQgb3RoZXJ3aXNlIGFjY2VwdCB0aGVtLlxuICBjb25zdCBzZWxmTG9vcHM6IHN0cmluZ1tdW10gPSBbXTtcbiAgZm9yIChjb25zdCBuIG9mIG5vZGVzKSB7XG4gICAgaWYgKG4uZGVwcy5pbmNsdWRlcyhuLmtleSkpIHNlbGZMb29wcy5wdXNoKFtuLmtleSwgbi5rZXldKTtcbiAgfVxuXG4gIC8vIEVkZ2VzIG9ubHkgYmV0d2VlbiBpbnRlcm5hbCBub2RlcyAoZXh0ZXJuYWwgZGVwcyByZXNvbHZlIGJlZm9yZSBwYXNzIHN0YXJ0cylcbiAgZm9yIChjb25zdCBuIG9mIG5vZGVzKSB7XG4gICAgZm9yIChjb25zdCBkZXAgb2Ygbi5kZXBzKSB7XG4gICAgICBpZiAoIWtleXMuaGFzKGRlcCkpIGNvbnRpbnVlO1xuICAgICAgaWYgKGRlcCA9PT0gbi5rZXkpIGNvbnRpbnVlOyAvLyBzZWxmLWRlcCDihpIgYWxyZWFkeSBjYXB0dXJlZCBhYm92ZVxuICAgICAgLy8gRWRnZTogZGVwIC0+IG4ua2V5IChkZXAgbXVzdCBjb21lIGZpcnN0KVxuICAgICAgaWYgKCFncmFwaC5nZXQoZGVwKSEuaGFzKG4ua2V5KSkge1xuICAgICAgICBncmFwaC5nZXQoZGVwKSEuYWRkKG4ua2V5KTtcbiAgICAgICAgaW5EZWdyZWUuc2V0KG4ua2V5LCAoaW5EZWdyZWUuZ2V0KG4ua2V5KSA/PyAwKSArIDEpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEthaG4ncyBhbGdvcml0aG1cbiAgY29uc3Qgb3JkZXJlZDogc3RyaW5nW10gPSBbXTtcbiAgY29uc3QgcXVldWU6IHN0cmluZ1tdID0gW107XG4gIGZvciAoY29uc3QgW2ssIGRdIG9mIGluRGVncmVlKSBpZiAoZCA9PT0gMCkgcXVldWUucHVzaChrKTtcbiAgd2hpbGUgKHF1ZXVlLmxlbmd0aCkge1xuICAgIGNvbnN0IGsgPSBxdWV1ZS5zaGlmdCgpITtcbiAgICBvcmRlcmVkLnB1c2goayk7XG4gICAgZm9yIChjb25zdCBuZXh0IG9mIGdyYXBoLmdldChrKSA/PyBbXSkge1xuICAgICAgY29uc3QgZCA9IChpbkRlZ3JlZS5nZXQobmV4dCkgPz8gMCkgLSAxO1xuICAgICAgaW5EZWdyZWUuc2V0KG5leHQsIGQpO1xuICAgICAgaWYgKGQgPT09IDApIHF1ZXVlLnB1c2gobmV4dCk7XG4gICAgfVxuICB9XG5cbiAgLy8gRGV0ZWN0IGN5Y2xlczogYW55IG5vZGUgbm90IGluIGBvcmRlcmVkYCBpcyBwYXJ0IG9mIGEgY3ljbGVcbiAgY29uc3QgY3ljbGVzOiBzdHJpbmdbXVtdID0gWy4uLnNlbGZMb29wc107XG4gIGlmIChvcmRlcmVkLmxlbmd0aCA8IG5vZGVzLmxlbmd0aCkge1xuICAgIGNvbnN0IHJlbWFpbmluZyA9IG5ldyBTZXQobm9kZXMubWFwKG4gPT4gbi5rZXkpLmZpbHRlcihrID0+ICFvcmRlcmVkLmluY2x1ZGVzKGspKSk7XG4gICAgY29uc3QgdmlzaXRlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICAgIGZvciAoY29uc3Qgc3RhcnQgb2YgcmVtYWluaW5nKSB7XG4gICAgICBpZiAodmlzaXRlZC5oYXMoc3RhcnQpKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IHBhdGg6IHN0cmluZ1tdID0gW107XG4gICAgICBjb25zdCBjeWNsZSA9IGZpbmRDeWNsZShzdGFydCwgZ3JhcGgsIHZpc2l0ZWQsIHBhdGgsIHJlbWFpbmluZyk7XG4gICAgICBpZiAoY3ljbGUpIGN5Y2xlcy5wdXNoKGN5Y2xlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4geyBvcmRlcmVkLCBjeWNsZXMgfTtcbn1cblxuZnVuY3Rpb24gZmluZEN5Y2xlKFxuICBzdGFydDogc3RyaW5nLFxuICBncmFwaDogTWFwPHN0cmluZywgU2V0PHN0cmluZz4+LFxuICB2aXNpdGVkOiBTZXQ8c3RyaW5nPixcbiAgcGF0aDogc3RyaW5nW10sXG4gIHJlbWFpbmluZzogU2V0PHN0cmluZz4sXG4pOiBzdHJpbmdbXSB8IG51bGwge1xuICBpZiAocGF0aC5pbmNsdWRlcyhzdGFydCkpIHtcbiAgICBjb25zdCBpZHggPSBwYXRoLmluZGV4T2Yoc3RhcnQpO1xuICAgIHJldHVybiBbLi4ucGF0aC5zbGljZShpZHgpLCBzdGFydF07XG4gIH1cbiAgaWYgKHZpc2l0ZWQuaGFzKHN0YXJ0KSB8fCAhcmVtYWluaW5nLmhhcyhzdGFydCkpIHJldHVybiBudWxsO1xuICB2aXNpdGVkLmFkZChzdGFydCk7XG4gIHBhdGgucHVzaChzdGFydCk7XG4gIGZvciAoY29uc3QgbmV4dCBvZiBncmFwaC5nZXQoc3RhcnQpID8/IFtdKSB7XG4gICAgY29uc3QgY3ljbGUgPSBmaW5kQ3ljbGUobmV4dCwgZ3JhcGgsIHZpc2l0ZWQsIHBhdGgsIHJlbWFpbmluZyk7XG4gICAgaWYgKGN5Y2xlKSByZXR1cm4gY3ljbGU7XG4gIH1cbiAgcGF0aC5wb3AoKTtcbiAgcmV0dXJuIG51bGw7XG59XG4iXX0=
@@ -0,0 +1,45 @@
1
+ import { ErrorCode } from '@pipeline-builder/api-core';
2
+ import { FieldPredicate } from './walker';
3
+ export interface TemplateError {
4
+ field: string;
5
+ line?: number;
6
+ col?: number;
7
+ code: ErrorCode;
8
+ message: string;
9
+ path?: string;
10
+ cycle?: string[];
11
+ }
12
+ export interface ValidationResult {
13
+ valid: boolean;
14
+ errors: TemplateError[];
15
+ }
16
+ /**
17
+ * Build a predicate that accepts a template path when its root is in the
18
+ * given allow-list. Used by the validator to check that every `{{ path }}`
19
+ * resolves to a known scope root (`pipeline`, `plugin`, `env`, …).
20
+ *
21
+ * Only the root segment is checked — we don't enforce exact nested-key
22
+ * existence here because metadata / vars keys are user-provided per pipeline
23
+ * and not known at plugin upload time.
24
+ */
25
+ export declare function allowedScopeRoots(roots: string[]): (path: string[]) => boolean;
26
+ /**
27
+ * Validate all template tokens in a document have well-formed paths and
28
+ * point at allowed scope roots. Returns all errors found — does NOT stop
29
+ * on first.
30
+ *
31
+ * Caller supplies `isTemplatable` (the schema allow-list) and
32
+ * `isKnownPath` (the scope-shape predicate). Template text is parsed
33
+ * fresh; use a TokenCache externally if repeated parsing is a concern.
34
+ */
35
+ export declare function validateTemplates<T extends object>(doc: T, isTemplatable: FieldPredicate, isKnownPath: (path: string[]) => boolean): ValidationResult;
36
+ /**
37
+ * Detect cycles in a self-referencing document. Caller supplies the
38
+ * templatable predicate and a function to extract the scope-path that
39
+ * each template field writes to (same value used as a key in deps).
40
+ *
41
+ * Example for pipeline.json: the field `metadata.env` writes to scope
42
+ * path `metadata.env`; a template `{{ metadata.region }}` in that field
43
+ * declares a dependency on `metadata.region`.
44
+ */
45
+ export declare function detectCycles<T extends object>(doc: T, isTemplatable: FieldPredicate, fieldToScopePath: (field: string) => string | null): TemplateError[];