@pipeline-builder/pipeline-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +69 -0
  3. package/lib/cdk.d.ts +38 -0
  4. package/lib/cdk.js +44 -0
  5. package/lib/config/app-config.d.ts +109 -0
  6. package/lib/config/app-config.js +197 -0
  7. package/lib/config/aws-config-cdk.d.ts +49 -0
  8. package/lib/config/aws-config-cdk.js +65 -0
  9. package/lib/config/billing-config.d.ts +24 -0
  10. package/lib/config/billing-config.js +497 -0
  11. package/lib/config/config-types.d.ts +346 -0
  12. package/lib/config/config-types.js +4 -0
  13. package/lib/config/entitlements.d.ts +23 -0
  14. package/lib/config/entitlements.js +46 -0
  15. package/lib/config/handler-constants.d.ts +10 -0
  16. package/lib/config/handler-constants.js +39 -0
  17. package/lib/config/infrastructure-config.d.ts +66 -0
  18. package/lib/config/infrastructure-config.js +197 -0
  19. package/lib/config/server-config.d.ts +53 -0
  20. package/lib/config/server-config.js +175 -0
  21. package/lib/config/service-client.d.ts +13 -0
  22. package/lib/config/service-client.js +20 -0
  23. package/lib/core/artifact-manager.d.ts +62 -0
  24. package/lib/core/artifact-manager.js +83 -0
  25. package/lib/core/id-generator.d.ts +50 -0
  26. package/lib/core/id-generator.js +50 -0
  27. package/lib/core/metadata-builder.d.ts +48 -0
  28. package/lib/core/metadata-builder.js +278 -0
  29. package/lib/core/metadata-helpers.d.ts +31 -0
  30. package/lib/core/metadata-helpers.js +55 -0
  31. package/lib/core/network-types.d.ts +200 -0
  32. package/lib/core/network-types.js +4 -0
  33. package/lib/core/network.d.ts +33 -0
  34. package/lib/core/network.js +112 -0
  35. package/lib/core/pipeline-helpers.d.ts +61 -0
  36. package/lib/core/pipeline-helpers.js +563 -0
  37. package/lib/core/pipeline-types.d.ts +118 -0
  38. package/lib/core/pipeline-types.js +131 -0
  39. package/lib/core/role-types.d.ts +254 -0
  40. package/lib/core/role-types.js +4 -0
  41. package/lib/core/role.d.ts +14 -0
  42. package/lib/core/role.js +117 -0
  43. package/lib/core/security-group-types.d.ts +84 -0
  44. package/lib/core/security-group-types.js +4 -0
  45. package/lib/core/security-group.d.ts +14 -0
  46. package/lib/core/security-group.js +33 -0
  47. package/lib/handlers/plugin-lookup-handler.d.ts +32 -0
  48. package/lib/handlers/plugin-lookup-handler.js +292 -0
  49. package/lib/handlers/pnpm-lock.yaml +12 -0
  50. package/lib/index.d.ts +45 -0
  51. package/lib/index.js +53 -0
  52. package/lib/pipeline/pipeline-builder.d.ts +123 -0
  53. package/lib/pipeline/pipeline-builder.js +476 -0
  54. package/lib/pipeline/pipeline-configuration.d.ts +81 -0
  55. package/lib/pipeline/pipeline-configuration.js +214 -0
  56. package/lib/pipeline/plugin-lookup.d.ts +131 -0
  57. package/lib/pipeline/plugin-lookup.js +316 -0
  58. package/lib/pipeline/plugin-spec.d.ts +138 -0
  59. package/lib/pipeline/plugin-spec.js +4 -0
  60. package/lib/pipeline/source-builder.d.ts +62 -0
  61. package/lib/pipeline/source-builder.js +164 -0
  62. package/lib/pipeline/source-types.d.ts +221 -0
  63. package/lib/pipeline/source-types.js +4 -0
  64. package/lib/pipeline/stage-builder.d.ts +78 -0
  65. package/lib/pipeline/stage-builder.js +129 -0
  66. package/lib/pipeline/step-types.d.ts +222 -0
  67. package/lib/pipeline/step-types.js +4 -0
  68. package/lib/template/evaluator.d.ts +42 -0
  69. package/lib/template/evaluator.js +164 -0
  70. package/lib/template/index.d.ts +34 -0
  71. package/lib/template/index.js +118 -0
  72. package/lib/template/metrics.d.ts +10 -0
  73. package/lib/template/metrics.js +43 -0
  74. package/lib/template/plugin-resolver.d.ts +8 -0
  75. package/lib/template/plugin-resolver.js +48 -0
  76. package/lib/template/tokenizer.d.ts +49 -0
  77. package/lib/template/tokenizer.js +233 -0
  78. package/lib/template/topo-sort.d.ts +17 -0
  79. package/lib/template/topo-sort.js +88 -0
  80. package/lib/template/validate.d.ts +45 -0
  81. package/lib/template/validate.js +96 -0
  82. package/lib/template/walker.d.ts +34 -0
  83. package/lib/template/walker.js +83 -0
  84. package/package.json +184 -0
@@ -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,88 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Topologically sort a set of nodes by their declared dependency keys.
5
+ * Returns `ordered` in resolution order, plus any detected cycles.
6
+ * Nodes whose deps reference keys not in the node set are treated as
7
+ * depending on "external" values and ordered first (no cycle risk).
8
+ */
9
+ export function topoSort(nodes) {
10
+ const keys = new Set(nodes.map(n => n.key));
11
+ const graph = new Map();
12
+ const inDegree = new Map();
13
+ // Init
14
+ for (const n of nodes) {
15
+ graph.set(n.key, new Set());
16
+ inDegree.set(n.key, 0);
17
+ }
18
+ // Self-loops short-circuit — detected before topo-sort so single-node
19
+ // cycles are reported even when Kahn's would otherwise accept them.
20
+ const selfLoops = [];
21
+ for (const n of nodes) {
22
+ if (n.deps.includes(n.key))
23
+ selfLoops.push([n.key, n.key]);
24
+ }
25
+ // Edges only between internal nodes (external deps resolve before pass starts)
26
+ for (const n of nodes) {
27
+ for (const dep of n.deps) {
28
+ if (!keys.has(dep))
29
+ continue;
30
+ if (dep === n.key)
31
+ continue; // self-dep → already captured above
32
+ // Edge: dep -> n.key (dep must come first)
33
+ if (!graph.get(dep).has(n.key)) {
34
+ graph.get(dep).add(n.key);
35
+ inDegree.set(n.key, (inDegree.get(n.key) ?? 0) + 1);
36
+ }
37
+ }
38
+ }
39
+ // Kahn's algorithm
40
+ const ordered = [];
41
+ const queue = [];
42
+ for (const [k, d] of inDegree)
43
+ if (d === 0)
44
+ queue.push(k);
45
+ while (queue.length) {
46
+ const k = queue.shift();
47
+ ordered.push(k);
48
+ for (const next of graph.get(k) ?? []) {
49
+ const d = (inDegree.get(next) ?? 0) - 1;
50
+ inDegree.set(next, d);
51
+ if (d === 0)
52
+ queue.push(next);
53
+ }
54
+ }
55
+ // Detect cycles: any node not in `ordered` is part of a cycle
56
+ const cycles = [...selfLoops];
57
+ if (ordered.length < nodes.length) {
58
+ const remaining = new Set(nodes.map(n => n.key).filter(k => !ordered.includes(k)));
59
+ const visited = new Set();
60
+ for (const start of remaining) {
61
+ if (visited.has(start))
62
+ continue;
63
+ const path = [];
64
+ const cycle = findCycle(start, graph, visited, path, remaining);
65
+ if (cycle)
66
+ cycles.push(cycle);
67
+ }
68
+ }
69
+ return { ordered, cycles };
70
+ }
71
+ function findCycle(start, graph, visited, path, remaining) {
72
+ if (path.includes(start)) {
73
+ const idx = path.indexOf(start);
74
+ return [...path.slice(idx), start];
75
+ }
76
+ if (visited.has(start) || !remaining.has(start))
77
+ return null;
78
+ visited.add(start);
79
+ path.push(start);
80
+ for (const next of graph.get(start) ?? []) {
81
+ const cycle = findCycle(next, graph, visited, path, remaining);
82
+ if (cycle)
83
+ return cycle;
84
+ }
85
+ path.pop();
86
+ return null;
87
+ }
88
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9wby1zb3J0LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3RvcG8tc29ydC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBY3RDOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLFFBQVEsQ0FBQyxLQUFpQjtJQUN4QyxNQUFNLElBQUksR0FBRyxJQUFJLEdBQUcsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDNUMsTUFBTSxLQUFLLEdBQUcsSUFBSSxHQUFHLEVBQXVCLENBQUM7SUFDN0MsTUFBTSxRQUFRLEdBQUcsSUFBSSxHQUFHLEVBQWtCLENBQUM7SUFFM0MsT0FBTztJQUNQLEtBQUssTUFBTSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7UUFDdEIsS0FBSyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsR0FBRyxFQUFFLElBQUksR0FBRyxFQUFFLENBQUMsQ0FBQztRQUM1QixRQUFRLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDekIsQ0FBQztJQUVELHNFQUFzRTtJQUN0RSxvRUFBb0U7SUFDcEUsTUFBTSxTQUFTLEdBQWUsRUFBRSxDQUFDO0lBQ2pDLEtBQUssTUFBTSxDQUFDLElBQUksS0FBSyxFQUFFLENBQUM7UUFDdEIsSUFBSSxDQUFDLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDO1lBQUUsU0FBUyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVELCtFQUErRTtJQUMvRSxLQUFLLE1BQU0sQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQ3RCLEtBQUssTUFBTSxHQUFHLElB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Q2IsS0FBK0IsRUFDL0IsT0FBb0IsRUFDcEIsSUFBYyxFQUNkLFNBQXNCO0lBRXRCLElBQUksSUFBSSxDQUFDLFFBQVEsQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ3pCLE1BQU0sR0FBRyxHQUFHLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDaEMsT0FBTyxDQUFDLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztJQUNyQyxDQUFDO0lBQ0QsSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxLQUFLLENBQUM7UUFBRSxPQUFPLElBQUksQ0FBQztJQUM3RCxPQUFPLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxDQUFDO0lBQ25CLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDakIsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLEtBQUssQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDO1FBQzFDLE1BQU0sS0FBSyxHQUFHLFNBQVMsQ0FBQyxJQUFJLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsU0FBUyxDQUFDLENBQUM7UUFDL0QsSUFBSSxLQUFLO1lBQUUsT0FBTyxLQUFLLENBQUM7SUFDMUIsQ0FBQztJQUNELElBQUksQ0FBQyxHQUFHLEVBQUUsQ0FBQztJQUNYLE9BQU8sSUFBSSxDQUFDO0FBQ2QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5leHBvcnQgaW50ZXJmYWNlIFRvcG9Ob2RlIHtcbiAgLyoqIFVuaXF1ZSBrZXkgaWRlbnRpZnlpbmcgdGhpcyBub2RlIChlLmcuIHNvdXJjZSBmaWVsZCBwYXRoKSAqL1xuICBrZXk6IHN0cmluZztcbiAgLyoqIERlcGVuZGVuY3kga2V5cyB0aGF0IG11c3QgcmVzb2x2ZSBiZWZvcmUgdGhpcyBub2RlICovXG4gIGRlcHM6IHN0cmluZ1tdO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIFRvcG9SZXN1bHQge1xuICBvcmRlcmVkOiBzdHJpbmdbXTtcbiAgY3ljbGVzOiBzdHJpbmdbXVtdO1xufVxuXG4vKipcbiAqIFRvcG9sb2dpY2FsbHkgc29ydCBhIHNldCBvZiBub2RlcyBieSB0aGVpciBkZWNsYXJlZCBkZXBlbmRlbmN5IGtleXMuXG4gKiBSZXR1cm5zIGBvcmRlcmVkYCBpbiByZXNvbHV0aW9uIG9yZGVyLCBwbHVzIGFueSBkZXRlY3RlZCBjeWNsZXMuXG4gKiBOb2RlcyB3aG9zZSBkZXBzIHJlZmVyZW5jZSBrZXlzIG5vdCBpbiB0aGUgbm9kZSBzZXQgYXJlIHRyZWF0ZWQgYXNcbiAqIGRlcGVuZGluZyBvbiBcImV4dGVybmFsXCIgdmFsdWVzIGFuZCBvcmRlcmVkIGZpcnN0IChubyBjeWNsZSByaXNrKS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHRvcG9Tb3J0KG5vZGVzOiBUb3BvTm9kZVtdKTogVG9wb1Jlc3VsdCB7XG4gIGNvbnN0IGtleXMgPSBuZXcgU2V0KG5vZGVzLm1hcChuID0+IG4ua2V5KSk7XG4gIGNvbnN0IGdyYXBoID0gbmV3IE1hcDxzdHJpbmcsIFNldDxzdHJpbmc+PigpO1xuICBjb25zdCBpbkRlZ3JlZSA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG5cbiAgLy8gSW5pdFxuICBmb3IgKGNvbnN0IG4gb2Ygbm9kZXMpIHtcbiAgICBncmFwaC5zZXQobi5rZXksIG5ldyBTZXQoKSk7XG4gICAgaW5EZWdyZWUuc2V0KG4ua2V5LCAwKTtcbiAgfVxuXG4gIC8vIFNlbGYtbG9vcHMgc2hvcnQtY2lyY3VpdCDigJQgZGV0ZWN0ZWQgYmVmb3JlIHRvcG8tc29ydCBzbyBzaW5nbGUtbm9kZVxuICAvLyBjeWNsZXMgYXJlIHJlcG9ydGVkIGV2ZW4gd2hlbiBLYWhuJ3Mgd291bGQgb3RoZXJ3aXNlIGFjY2VwdCB0aGVtLlxuICBjb25zdCBzZWxmTG9vcHM6IHN0cmluZ1tdW10gPSBbXTtcbiAgZm9yIChjb25zdCBuIG9mIG5vZGVzKSB7XG4gICAgaWYgKG4uZGVwcy5pbmNsdWRlcyhuLmtleSkpIHNlbGZMb29wcy5wdXNoKFtuLmtleSwgbi5rZXldKTtcbiAgfVxuXG4gIC8vIEVkZ2VzIG9ubHkgYmV0d2VlbiBpbnRlcm5hbCBub2RlcyAoZXh0ZXJuYWwgZGVwcyByZXNvbHZlIGJlZm9yZSBwYXNzIHN0YXJ0cylcbiAgZm9yIChjb25zdCBuIG9mIG5vZGVzKSB7XG4gICAgZm9yIChjb25zdCBkZXAgb2Ygbi5kZXBzKSB7XG4gICAgICBpZiAoIWtleXMuaGFzKGRlcCkpIGNvbnRpbnVlO1xuICAgICAgaWYgKGRlcCA9PT0gbi5rZXkpIGNvbnRpbnVlOyAvLyBzZWxmLWRlcCDihpIgYWxyZWFkeSBjYXB0dXJlZCBhYm92ZVxuICAgICAgLy8gRWRnZTogZGVwIC0+IG4ua2V5IChkZXAgbXVzdCBjb21lIGZpcnN0KVxuICAgICAgaWYgKCFncmFwaC5nZXQoZGVwKSEuaGFzKG4ua2V5KSkge1xuICAgICAgICBncmFwaC5nZXQoZGVwKSEuYWRkKG4ua2V5KTtcbiAgICAgICAgaW5EZWdyZWUuc2V0KG4ua2V5LCAoaW5EZWdyZWUuZ2V0KG4ua2V5KSA/PyAwKSArIDEpO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8vIEthaG4ncyBhbGdvcml0aG1cbiAgY29uc3Qgb3JkZXJlZDogc3RyaW5nW10gPSBbXTtcbiAgY29uc3QgcXVldWU6IHN0cmluZ1tdID0gW107XG4gIGZvciAoY29uc3QgW2ssIGRdIG9mIGluRGVncmVlKSBpZiAoZCA9PT0gMCkgcXVldWUucHVzaChrKTtcbiAgd2hpbGUgKHF1ZXVlLmxlbmd0aCkge1xuICAgIGNvbnN0IGsgPSBxdWV1ZS5zaGlmdCgpITtcbiAgICBvcmRlcmVkLnB1c2goayk7XG4gICAgZm9yIChjb25zdCBuZXh0IG9mIGdyYXBoLmdldChrKSA/PyBbXSkge1xuICAgICAgY29uc3QgZCA9IChpbkRlZ3JlZS5nZXQobmV4dCkgPz8gMCkgLSAxO1xuICAgICAgaW5EZWdyZWUuc2V0KG5leHQsIGQpO1xuICAgICAgaWYgKGQgPT09IDApIHF1ZXVlLnB1c2gobmV4dCk7XG4gICAgfVxuICB9XG5cbiAgLy8gRGV0ZWN0IGN5Y2xlczogYW55IG5vZGUgbm90IGluIGBvcmRlcmVkYCBpcyBwYXJ0IG9mIGEgY3ljbGVcbiAgY29uc3QgY3ljbGVzOiBzdHJpbmdbXVtdID0gWy4uLnNlbGZMb29wc107XG4gIGlmIChvcmRlcmVkLmxlbmd0aCA8IG5vZGVzLmxlbmd0aCkge1xuICAgIGNvbnN0IHJlbWFpbmluZyA9IG5ldyBTZXQobm9kZXMubWFwKG4gPT4gbi5rZXkpLmZpbHRlcihrID0+ICFvcmRlcmVkLmluY2x1ZGVzKGspKSk7XG4gICAgY29uc3QgdmlzaXRlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICAgIGZvciAoY29uc3Qgc3RhcnQgb2YgcmVtYWluaW5nKSB7XG4gICAgICBpZiAodmlzaXRlZC5oYXMoc3RhcnQpKSBjb250aW51ZTtcbiAgICAgIGNvbnN0IHBhdGg6IHN0cmluZ1tdID0gW107XG4gICAgICBjb25zdCBjeWNsZSA9IGZpbmRDeWNsZShzdGFydCwgZ3JhcGgsIHZpc2l0ZWQsIHBhdGgsIHJlbWFpbmluZyk7XG4gICAgICBpZiAoY3ljbGUpIGN5Y2xlcy5wdXNoKGN5Y2xlKTtcbiAgICB9XG4gIH1cblxuICByZXR1cm4geyBvcmRlcmVkLCBjeWNsZXMgfTtcbn1cblxuZnVuY3Rpb24gZmluZEN5Y2xlKFxuICBzdGFydDogc3RyaW5nLFxuICBncmFwaDogTWFwPHN0cmluZywgU2V0PHN0cmluZz4+LFxuICB2aXNpdGVkOiBTZXQ8c3RyaW5nPixcbiAgcGF0aDogc3RyaW5nW10sXG4gIHJlbWFpbmluZzogU2V0PHN0cmluZz4sXG4pOiBzdHJpbmdbXSB8IG51bGwge1xuICBpZiAocGF0aC5pbmNsdWRlcyhzdGFydCkpIHtcbiAgICBjb25zdCBpZHggPSBwYXRoLmluZGV4T2Yoc3RhcnQpO1xuICAgIHJldHVybiBbLi4ucGF0aC5zbGljZShpZHgpLCBzdGFydF07XG4gIH1cbiAgaWYgKHZpc2l0ZWQuaGFzKHN0YXJ0KSB8fCAhcmVtYWluaW5nLmhhcyhzdGFydCkpIHJldHVybiBudWxsO1xuICB2aXNpdGVkLmFkZChzdGFydCk7XG4gIHBhdGgucHVzaChzdGFydCk7XG4gIGZvciAoY29uc3QgbmV4dCBvZiBncmFwaC5nZXQoc3RhcnQpID8/IFtdKSB7XG4gICAgY29uc3QgY3ljbGUgPSBmaW5kQ3ljbGUobmV4dCwgZ3JhcGgsIHZpc2l0ZWQsIHBhdGgsIHJlbWFpbmluZyk7XG4gICAgaWYgKGN5Y2xlKSByZXR1cm4gY3ljbGU7XG4gIH1cbiAgcGF0aC5wb3AoKTtcbiAgcmV0dXJuIG51bGw7XG59XG4iXX0=
@@ -0,0 +1,45 @@
1
+ import { ErrorCode } from '@pipeline-builder/api-core';
2
+ import { type FieldPredicate } from './walker.js';
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 on each call.
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[];
@@ -0,0 +1,96 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { ErrorCode } from '@pipeline-builder/api-core';
4
+ import { dependencies } from './evaluator.js';
5
+ import { TokenizerError, tokenize } from './tokenizer.js';
6
+ import { topoSort } from './topo-sort.js';
7
+ import { visitStrings, walkAndBind } from './walker.js';
8
+ /**
9
+ * Build a predicate that accepts a template path when its root is in the
10
+ * given allow-list. Used by the validator to check that every `{{ path }}`
11
+ * resolves to a known scope root (`pipeline`, `plugin`, `env`, …).
12
+ *
13
+ * Only the root segment is checked — we don't enforce exact nested-key
14
+ * existence here because metadata / vars keys are user-provided per pipeline
15
+ * and not known at plugin upload time.
16
+ */
17
+ export function allowedScopeRoots(roots) {
18
+ const allowed = new Set(roots);
19
+ return (path) => {
20
+ const root = path[0];
21
+ return !!root && allowed.has(root);
22
+ };
23
+ }
24
+ /**
25
+ * Validate all template tokens in a document have well-formed paths and
26
+ * point at allowed scope roots. Returns all errors found — does NOT stop
27
+ * on first.
28
+ *
29
+ * Caller supplies `isTemplatable` (the schema allow-list) and
30
+ * `isKnownPath` (the scope-shape predicate). Template text is parsed
31
+ * fresh on each call.
32
+ */
33
+ export function validateTemplates(doc, isTemplatable, isKnownPath) {
34
+ const errors = [];
35
+ const entries = [];
36
+ // Pass 1: parse every templatable field, collecting parse errors instead
37
+ // of throwing on the first one.
38
+ visitStrings(doc, isTemplatable, (field, source) => {
39
+ try {
40
+ entries.push({ field, tokens: tokenize(source) });
41
+ }
42
+ catch (err) {
43
+ if (err instanceof TokenizerError) {
44
+ errors.push({ field, line: err.pos.line, col: err.pos.col, code: ErrorCode.TEMPLATE_PARSE_ERROR, message: err.message });
45
+ }
46
+ else {
47
+ errors.push({ field, code: ErrorCode.TEMPLATE_PARSE_ERROR, message: String(err) });
48
+ }
49
+ }
50
+ });
51
+ // Pass 2: validate each expr's path is shape-allowed and not reserved
52
+ for (const { field, tokens } of entries) {
53
+ for (const tok of tokens) {
54
+ if (tok.kind !== 'expr')
55
+ continue;
56
+ if (tok.path[0] === 'secrets') {
57
+ errors.push(mkErr(field, tok.pos, ErrorCode.TEMPLATE_SECRETS_RESERVED, '\'secrets\' is a reserved scope — use the plugin\'s \'secrets:\' field instead', tok.path.join('.')));
58
+ continue;
59
+ }
60
+ if (!isKnownPath(tok.path)) {
61
+ errors.push(mkErr(field, tok.pos, ErrorCode.TEMPLATE_UNKNOWN_PATH, `Template references unknown scope root '${tok.path[0]}'`, tok.path.join('.')));
62
+ }
63
+ }
64
+ }
65
+ return { valid: errors.length === 0, errors };
66
+ }
67
+ /**
68
+ * Detect cycles in a self-referencing document. Caller supplies the
69
+ * templatable predicate and a function to extract the scope-path that
70
+ * each template field writes to (same value used as a key in deps).
71
+ *
72
+ * Example for pipeline.json: the field `metadata.env` writes to scope
73
+ * path `metadata.env`; a template `{{ metadata.region }}` in that field
74
+ * declares a dependency on `metadata.region`.
75
+ */
76
+ export function detectCycles(doc, isTemplatable, fieldToScopePath) {
77
+ const nodes = [];
78
+ for (const entry of walkAndBind(doc, isTemplatable)) {
79
+ const key = fieldToScopePath(entry.field);
80
+ if (!key)
81
+ continue;
82
+ nodes.push({ key, deps: dependencies(entry.tokens) });
83
+ }
84
+ const { cycles } = topoSort(nodes);
85
+ return cycles.map(c => ({
86
+ field: c[0],
87
+ code: ErrorCode.TEMPLATE_CYCLE,
88
+ message: `Template cycle detected: ${c.join(' -> ')}`,
89
+ cycle: c,
90
+ }));
91
+ }
92
+ // -- helpers -----------------------------------------------------------------
93
+ function mkErr(field, pos, code, message, path) {
94
+ return { field, line: pos.line, col: pos.col, code, message, ...(path && { path }) };
95
+ }
96
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidmFsaWRhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVtcGxhdGUvdmFsaWRhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdkQsT0FBTyxFQUFFLFlBQVksRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzlDLE9BQU8sRUFBbUMsY0FBYyxFQUFFLFFBQVEsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQzNGLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxnQkFBZ0IsQ0FBQztBQUMxQyxPQUFPLEVBQXVCLFlBQVksRUFBRSxXQUFXLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFpQjdFOzs7Ozs7OztHQVFHO0FBQ0gsTUFBTSxVQUFVLGlCQUFpQixDQUFDLEtBQWU7SUFDL0MsTUFBTSxPQUFPLEdBQUcsSUFBSSxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDL0IsT0FBTyxDQUFDLElBQUksRUFBRSxFQUFFO1FBQ2QsTUFBTSxJQUFJLEdBQUcsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1FBQ3JCLE9BQU8sQ0FBQyxDQUFDLElBQUksSUFBSSxPQUFPLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ3JDLENBQUMsQ0FBQztBQUNKLENBQUM7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQU0sVUFBVSxpQkFBaUIsQ0FDL0IsR0FBTSxFQUNOLGFBQTZCLEVBQzdCLFdBQXdDO0lBRXhDLE1BQU0sTUFBTSxHQUFvQixFQUFFLENBQUM7SUFDbkMsTUFBTSxPQUFPLEdBQXlDLEVBQUUsQ0FBQztJQUV6RCx5RUFBeUU7SUFDekUsZ0NBQWdDO0lBQ2hDLFlBQVksQ0FBQyxHQUFHLEVBQUUsYUFBYSxFQUFFLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxFQUFFO1FBQ2pELElBQUksQ0FBQztZQUNILE9B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Q1osSUFBSSxFQUFFLFNBQVMsQ0FBQyxjQUFjO1FBQzlCLE9BQU8sRUFBRSw0QkFBNEIsQ0FBQyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRTtRQUNyRCxLQUFLLEVBQUUsQ0FBQztLQUNULENBQUMsQ0FBQyxDQUFDO0FBQ04sQ0FBQztBQUVELCtFQUErRTtBQUUvRSxTQUFTLEtBQUssQ0FDWixLQUFhLEVBQ2IsR0FBbUIsRUFDbkIsSUFBZSxFQUNmLE9BQWUsRUFDZixJQUFhO0lBRWIsT0FBTyxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLElBQUksRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLEdBQUcsRUFBRSxJQUFJLEVBQUUsT0FBTyxFQUFFLEdBQUcsQ0FBQyxJQUFJLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUM7QUFDdkYsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyBkZXBlbmRlbmNpZXMgfSBmcm9tICcuL2V2YWx1YXRvci5qcyc7XG5pbXBvcnQgeyB0eXBlIFNvdXJjZVBvc2l0aW9uLCB0eXBlIFRva2VuLCBUb2tlbml6ZXJFcnJvciwgdG9rZW5pemUgfSBmcm9tICcuL3Rva2VuaXplci5qcyc7XG5pbXBvcnQgeyB0b3BvU29ydCB9IGZyb20gJy4vdG9wby1zb3J0LmpzJztcbmltcG9ydCB7IHR5cGUgRmllbGRQcmVkaWNhdGUsIHZpc2l0U3RyaW5ncywgd2Fsa0FuZEJpbmQgfSBmcm9tICcuL3dhbGtlci5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgVGVtcGxhdGVFcnJvciB7XG4gIGZpZWxkOiBzdHJpbmc7XG4gIGxpbmU/OiBudW1iZXI7XG4gIGNvbD86IG51bWJlcjtcbiAgY29kZTogRXJyb3JDb2RlO1xuICBtZXNzYWdlOiBzdHJpbmc7XG4gIHBhdGg/OiBzdHJpbmc7XG4gIGN5Y2xlPzogc3RyaW5nW107XG59XG5cbmV4cG9ydCBpbnRlcmZhY2UgVmFsaWRhdGlvblJlc3VsdCB7XG4gIHZhbGlkOiBib29sZWFuO1xuICBlcnJvcnM6IFRlbXBsYXRlRXJyb3JbXTtcbn1cblxuLyoqXG4gKiBCdWlsZCBhIHByZWRpY2F0ZSB0aGF0IGFjY2VwdHMgYSB0ZW1wbGF0ZSBwYXRoIHdoZW4gaXRzIHJvb3QgaXMgaW4gdGhlXG4gKiBnaXZlbiBhbGxvdy1saXN0LiBVc2VkIGJ5IHRoZSB2YWxpZGF0b3IgdG8gY2hlY2sgdGhhdCBldmVyeSBge3sgcGF0aCB9fWBcbiAqIHJlc29sdmVzIHRvIGEga25vd24gc2NvcGUgcm9vdCAoYHBpcGVsaW5lYCwgYHBsdWdpbmAsIGBlbnZgLCDigKYpLlxuICpcbiAqIE9ubHkgdGhlIHJvb3Qgc2VnbWVudCBpcyBjaGVja2VkIOKAlCB3ZSBkb24ndCBlbmZvcmNlIGV4YWN0IG5lc3RlZC1rZXlcbiAqIGV4aXN0ZW5jZSBoZXJlIGJlY2F1c2UgbWV0YWRhdGEgLyB2YXJzIGtleXMgYXJlIHVzZXItcHJvdmlkZWQgcGVyIHBpcGVsaW5lXG4gKiBhbmQgbm90IGtub3duIGF0IHBsdWdpbiB1cGxvYWQgdGltZS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGFsbG93ZWRTY29wZVJvb3RzKHJvb3RzOiBzdHJpbmdbXSk6IChwYXRoOiBzdHJpbmdbXSkgPT4gYm9vbGVhbiB7XG4gIGNvbnN0IGFsbG93ZWQgPSBuZXcgU2V0KHJvb3RzKTtcbiAgcmV0dXJuIChwYXRoKSA9PiB7XG4gICAgY29uc3Qgcm9vdCA9IHBhdGhbMF07XG4gICAgcmV0dXJuICEhcm9vdCAmJiBhbGxvd2VkLmhhcyhyb290KTtcbiAgfTtcbn1cblxuLyoqXG4gKiBWYWxpZGF0ZSBhbGwgdGVtcGxhdGUgdG9rZW5zIGluIGEgZG9jdW1lbnQgaGF2ZSB3ZWxsLWZvcm1lZCBwYXRocyBhbmRcbiAqIHBvaW50IGF0IGFsbG93ZWQgc2NvcGUgcm9vdHMuIFJldHVybnMgYWxsIGVycm9ycyBmb3VuZCDigJQgZG9lcyBOT1Qgc3RvcFxuICogb24gZmlyc3QuXG4gKlxuICogQ2FsbGVyIHN1cHBsaWVzIGBpc1RlbXBsYXRhYmxlYCAodGhlIHNjaGVtYSBhbGxvdy1saXN0KSBhbmRcbiAqIGBpc0tub3duUGF0aGAgKHRoZSBzY29wZS1zaGFwZSBwcmVkaWNhdGUpLiBUZW1wbGF0ZSB0ZXh0IGlzIHBhcnNlZFxuICogZnJlc2ggb24gZWFjaCBjYWxsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gdmFsaWRhdGVUZW1wbGF0ZXM8VCBleHRlbmRzIG9iamVjdD4oXG4gIGRvYzogVCxcbiAgaXNUZW1wbGF0YWJsZTogRmllbGRQcmVkaWNhdGUsXG4gIGlzS25vd25QYXRoOiAocGF0aDogc3RyaW5nW10pID0+IGJvb2xlYW4sXG4pOiBWYWxpZGF0aW9uUmVzdWx0IHtcbiAgY29uc3QgZXJyb3JzOiBUZW1wbGF0ZUVycm9yW10gPSBbXTtcbiAgY29uc3QgZW50cmllczogeyBmaWVsZDogc3RyaW5nOyB0b2tlbnM6IFRva2VuW10gfVtdID0gW107XG5cbiAgLy8gUGFzcyAxOiBwYXJzZSBldmVyeSB0ZW1wbGF0YWJsZSBmaWVsZCwgY29sbGVjdGluZyBwYXJzZSBlcnJvcnMgaW5zdGVhZFxuICAvLyBvZiB0aHJvd2luZyBvbiB0aGUgZmlyc3Qgb25lLlxuICB2aXNpdFN0cmluZ3MoZG9jLCBpc1RlbXBsYXRhYmxlLCAoZmllbGQsIHNvdXJjZSkgPT4ge1xuICAgIHRyeSB7XG4gICAgICBlbnRyaWVzLnB1c2goeyBmaWVsZCwgdG9rZW5zOiB0b2tlbml6ZShzb3VyY2UpIH0pO1xuICAgIH0gY2F0Y2ggKGVycikge1xuICAgICAgaWYgKGVyciBpbnN0YW5jZW9mIFRva2VuaXplckVycm9yKSB7XG4gICAgICAgIGVycm9ycy5wdXNoKHsgZmllbGQsIGxpbmU6IGVyci5wb3MubGluZSwgY29sOiBlcnIucG9zLmNvbCwgY29kZTogRXJyb3JDb2RlLlRFTVBMQVRFX1BBUlNFX0VSUk9SLCBtZXNzYWdlOiBlcnIubWVzc2FnZSB9KTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGVycm9ycy5wdXNoKHsgZmllbGQsIGNvZGU6IEVycm9yQ29kZS5URU1QTEFURV9QQVJTRV9FUlJPUiwgbWVzc2FnZTogU3RyaW5nKGVycikgfSk7XG4gICAgICB9XG4gICAgfVxuICB9KTtcblxuICAvLyBQYXNzIDI6IHZhbGlkYXRlIGVhY2ggZXhwcidzIHBhdGggaXMgc2hhcGUtYWxsb3dlZCBhbmQgbm90IHJlc2VydmVkXG4gIGZvciAoY29uc3QgeyBmaWVsZCwgdG9rZW5zIH0gb2YgZW50cmllcykge1xuICAgIGZvciAoY29uc3QgdG9rIG9mIHRva2Vucykge1xuICAgICAgaWYgKHRvay5raW5kICE9PSAnZXhwcicpIGNvbnRpbnVlO1xuICAgICAgaWYgKHRvay5wYXRoWzBdID09PSAnc2VjcmV0cycpIHtcbiAgICAgICAgZXJyb3JzLnB1c2gobWtFcnIoZmllbGQsIHRvay5wb3MsIEVycm9yQ29kZS5URU1QTEFURV9TRUNSRVRTX1JFU0VSVkVELFxuICAgICAgICAgICdcXCdzZWNyZXRzXFwnIGlzIGEgcmVzZXJ2ZWQgc2NvcGUg4oCUIHVzZSB0aGUgcGx1Z2luXFwncyBcXCdzZWNyZXRzOlxcJyBmaWVsZCBpbnN0ZWFkJyxcbiAgICAgICAgICB0b2sucGF0aC5qb2luKCcuJykpKTtcbiAgICAgICAgY29udGludWU7XG4gICAgICB9XG4gICAgICBpZiAoIWlzS25vd25QYXRoKHRvay5wYXRoKSkge1xuICAgICAgICBlcnJvcnMucHVzaChta0VycihmaWVsZCwgdG9rLnBvcywgRXJyb3JDb2RlLlRFTVBMQVRFX1VOS05PV05fUEFUSCxcbiAgICAgICAgICBgVGVtcGxhdGUgcmVmZXJlbmNlcyB1bmtub3duIHNjb3BlIHJvb3QgJyR7dG9rLnBhdGhbMF19J2AsXG4gICAgICAgICAgdG9rLnBhdGguam9pbignLicpKSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHsgdmFsaWQ6IGVycm9ycy5sZW5ndGggPT09IDAsIGVycm9ycyB9O1xufVxuXG4vKipcbiAqIERldGVjdCBjeWNsZXMgaW4gYSBzZWxmLXJlZmVyZW5jaW5nIGRvY3VtZW50LiBDYWxsZXIgc3VwcGxpZXMgdGhlXG4gKiB0ZW1wbGF0YWJsZSBwcmVkaWNhdGUgYW5kIGEgZnVuY3Rpb24gdG8gZXh0cmFjdCB0aGUgc2NvcGUtcGF0aCB0aGF0XG4gKiBlYWNoIHRlbXBsYXRlIGZpZWxkIHdyaXRlcyB0byAoc2FtZSB2YWx1ZSB1c2VkIGFzIGEga2V5IGluIGRlcHMpLlxuICpcbiAqIEV4YW1wbGUgZm9yIHBpcGVsaW5lLmpzb246IHRoZSBmaWVsZCBgbWV0YWRhdGEuZW52YCB3cml0ZXMgdG8gc2NvcGVcbiAqIHBhdGggYG1ldGFkYXRhLmVudmA7IGEgdGVtcGxhdGUgYHt7IG1ldGFkYXRhLnJlZ2lvbiB9fWAgaW4gdGhhdCBmaWVsZFxuICogZGVjbGFyZXMgYSBkZXBlbmRlbmN5IG9uIGBtZXRhZGF0YS5yZWdpb25gLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZGV0ZWN0Q3ljbGVzPFQgZXh0ZW5kcyBvYmplY3Q+KFxuICBkb2M6IFQsXG4gIGlzVGVtcGxhdGFibGU6IEZpZWxkUHJlZGljYXRlLFxuICBmaWVsZFRvU2NvcGVQYXRoOiAoZmllbGQ6IHN0cmluZykgPT4gc3RyaW5nIHwgbnVsbCxcbik6IFRlbXBsYXRlRXJyb3JbXSB7XG4gIGNvbnN0IG5vZGVzID0gW107XG4gIGZvciAoY29uc3QgZW50cnkgb2Ygd2Fsa0FuZEJpbmQoZG9jLCBpc1RlbXBsYXRhYmxlKSkge1xuICAgIGNvbnN0IGtleSA9IGZpZWxkVG9TY29wZVBhdGgoZW50cnkuZmllbGQpO1xuICAgIGlmICgha2V5KSBjb250aW51ZTtcbiAgICBub2Rlcy5wdXNoKHsga2V5LCBkZXBzOiBkZXBlbmRlbmNpZXMoZW50cnkudG9rZW5zKSB9KTtcbiAgfVxuICBjb25zdCB7IGN5Y2xlcyB9ID0gdG9wb1NvcnQobm9kZXMpO1xuICByZXR1cm4gY3ljbGVzLm1hcChjID0+ICh7XG4gICAgZmllbGQ6IGNbMF0hLFxuICAgIGNvZGU6IEVycm9yQ29kZS5URU1QTEFURV9DWUNMRSxcbiAgICBtZXNzYWdlOiBgVGVtcGxhdGUgY3ljbGUgZGV0ZWN0ZWQ6ICR7Yy5qb2luKCcgLT4gJyl9YCxcbiAgICBjeWNsZTogYyxcbiAgfSkpO1xufVxuXG4vLyAtLSBoZWxwZXJzIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmZ1bmN0aW9uIG1rRXJyKFxuICBmaWVsZDogc3RyaW5nLFxuICBwb3M6IFNvdXJjZVBvc2l0aW9uLFxuICBjb2RlOiBFcnJvckNvZGUsXG4gIG1lc3NhZ2U6IHN0cmluZyxcbiAgcGF0aD86IHN0cmluZyxcbik6IFRlbXBsYXRlRXJyb3Ige1xuICByZXR1cm4geyBmaWVsZCwgbGluZTogcG9zLmxpbmUsIGNvbDogcG9zLmNvbCwgY29kZSwgbWVzc2FnZSwgLi4uKHBhdGggJiYgeyBwYXRoIH0pIH07XG59XG5cbiJdfQ==
@@ -0,0 +1,34 @@
1
+ import { type Token } from './tokenizer.js';
2
+ export interface WalkEntry {
3
+ /** Dotted path to the field inside the document, e.g. 'commands[3]' or 'env.STAGE' */
4
+ field: string;
5
+ /** Raw source string containing template tokens */
6
+ source: string;
7
+ /** Parsed token stream */
8
+ tokens: Token[];
9
+ /** Setter to write back a resolved value into the original `root` object */
10
+ set: (value: unknown) => void;
11
+ }
12
+ export type FieldPredicate = (field: string) => boolean;
13
+ /**
14
+ * Visit every templatable string leaf under `root`. Calls `handler(field, source)`
15
+ * for each string whose field path satisfies `isTemplatable` and contains `{{`.
16
+ * Does NOT tokenize — callers decide whether to throw or collect errors.
17
+ */
18
+ export declare function visitStrings(root: unknown, isTemplatable: FieldPredicate, handler: (field: string, source: string, keyPath: Array<string | number>) => void): void;
19
+ /**
20
+ * Walk all templatable string fields under `root`, returning one entry per
21
+ * templated field with a bound `set()` that mutates `root` in place.
22
+ * Tokenization happens eagerly here — parse errors throw.
23
+ *
24
+ * By default only fields containing at least one `{{ expr }}` are returned.
25
+ * Pass `includeLiteralOnly` to also return fields whose only template content
26
+ * is a `{{{{` escape sequence — these still need rewriting so the tokenizer's
27
+ * `{{{{` → `{{` unescape actually materializes into the document.
28
+ *
29
+ * The bound `set()` writes back via the exact key path captured during the
30
+ * walk (the real `Object.entries` keys), so document keys containing `.`,
31
+ * `[`, or `]` (e.g. `env["FOO.BAR"]`) are written correctly rather than being
32
+ * silently dropped by re-parsing a flattened dotted field string.
33
+ */
34
+ export declare function walkAndBind<T extends object>(root: T, isTemplatable: FieldPredicate, includeLiteralOnly?: boolean): WalkEntry[];
@@ -0,0 +1,83 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { hasTemplate, tokenize } from './tokenizer.js';
4
+ /**
5
+ * Visit every templatable string leaf under `root`. Calls `handler(field, source)`
6
+ * for each string whose field path satisfies `isTemplatable` and contains `{{`.
7
+ * Does NOT tokenize — callers decide whether to throw or collect errors.
8
+ */
9
+ export function visitStrings(root, isTemplatable, handler) {
10
+ step(root, '', [], isTemplatable, handler);
11
+ }
12
+ function step(node, field, keyPath, isTemplatable, handler) {
13
+ if (Array.isArray(node)) {
14
+ node.forEach((child, idx) => step(child, `${field}[${idx}]`, [...keyPath, idx], isTemplatable, handler));
15
+ return;
16
+ }
17
+ if (node !== null && typeof node === 'object') {
18
+ for (const [key, child] of Object.entries(node)) {
19
+ step(child, field ? `${field}.${key}` : key, [...keyPath, key], isTemplatable, handler);
20
+ }
21
+ return;
22
+ }
23
+ if (typeof node === 'string' && isTemplatable(field) && hasTemplate(node)) {
24
+ handler(field, node, keyPath);
25
+ }
26
+ }
27
+ /**
28
+ * Walk all templatable string fields under `root`, returning one entry per
29
+ * templated field with a bound `set()` that mutates `root` in place.
30
+ * Tokenization happens eagerly here — parse errors throw.
31
+ *
32
+ * By default only fields containing at least one `{{ expr }}` are returned.
33
+ * Pass `includeLiteralOnly` to also return fields whose only template content
34
+ * is a `{{{{` escape sequence — these still need rewriting so the tokenizer's
35
+ * `{{{{` → `{{` unescape actually materializes into the document.
36
+ *
37
+ * The bound `set()` writes back via the exact key path captured during the
38
+ * walk (the real `Object.entries` keys), so document keys containing `.`,
39
+ * `[`, or `]` (e.g. `env["FOO.BAR"]`) are written correctly rather than being
40
+ * silently dropped by re-parsing a flattened dotted field string.
41
+ */
42
+ export function walkAndBind(root, isTemplatable, includeLiteralOnly = false) {
43
+ const entries = [];
44
+ visitStrings(root, isTemplatable, (field, source, keyPath) => {
45
+ const tokens = tokenize(source);
46
+ if (!includeLiteralOnly && !tokens.some(t => t.kind === 'expr'))
47
+ return;
48
+ entries.push({
49
+ field,
50
+ source,
51
+ tokens,
52
+ set: (value) => writeField(root, keyPath, value),
53
+ });
54
+ });
55
+ return entries;
56
+ }
57
+ /**
58
+ * Keys that must never be walked into or written through: assigning to
59
+ * `__proto__` / `constructor` / `prototype` could retarget an object's
60
+ * prototype. `Object.entries` yields own enumerable keys only, but a JSON
61
+ * document parsed with `JSON.parse` can carry a genuine own enumerable
62
+ * `__proto__` (or `constructor`) key, so a captured keyPath could contain one.
63
+ * Refuse the write in that case rather than mutating the prototype.
64
+ */
65
+ const UNSAFE_WRITE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
66
+ function writeField(root, parts, value) {
67
+ if (parts.length === 0)
68
+ return;
69
+ for (const p of parts) {
70
+ if (typeof p === 'string' && UNSAFE_WRITE_KEYS.has(p))
71
+ return;
72
+ }
73
+ let cur = root;
74
+ for (let i = 0; i < parts.length - 1; i++) {
75
+ if (cur == null || typeof cur !== 'object')
76
+ return;
77
+ cur = cur[parts[i]];
78
+ }
79
+ if (cur == null || typeof cur !== 'object')
80
+ return;
81
+ cur[parts[parts.length - 1]] = value;
82
+ }
83
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid2Fsa2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3dhbGtlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDLE9BQU8sRUFBRSxXQUFXLEVBQUUsUUFBUSxFQUFjLE1BQU0sZ0JBQWdCLENBQUM7QUFlbkU7Ozs7R0FJRztBQUNILE1BQU0sVUFBVSxZQUFZLENBQzFCLElBQWEsRUFDYixhQUE2QixFQUM3QixPQUFpRjtJQUVqRixJQUFJLENBQUMsSUFBSSxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsYUFBYSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0FBQzdDLENBQUM7QUFFRCxTQUFTLElBQUksQ0FDWCxJQUFhLEVBQ2IsS0FBYSxFQUNiLE9BQStCLEVBQy9CLGFBQTZCLEVBQzdCLE9BQWlGO0lBRWpGLElBQUksS0FBSyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQ3hCLElBQUksQ0FBQyxPQUFPLENBQUMsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsS0FBSyxFQUFFLEdBQUcsS0FBSyxJQUFJLEdBQUcsR0FBRyxFQUFFLENBQUMsR0FBRyxPQUFPLEVBQUUsR0FBRyxDQUFDLEVBQUUsYUFBYSxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDekcsT0FBTztJQUNULENBQUM7SUFDRCxJQUFJLElBQUksS0FBSyxJQUFJLElBQUksT0FBTyxJQUFJLEtBQUssUUFBUSxFQUFFLENBQUM7UUFDOUMsS0FBSyxNQUFNLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsSUFBK0IsQ0FBQyxFQUFFLENBQUM7WUFDM0UsSUFBSSxDQUFDLEtBQUssRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDLEdBQUcsS0FBSyxJQUFJLEdBQUcsRUFBRSxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxHQUFHLE9BQU8sRUFBRSxHQUFHLENBQUMsRUFBRSxhQUFhLEVBQUUsT0FBTyxDQUFDLENBQUM7UUFDMUYsQ0FBQztRQUNELE9BQU87SUFDVCxDQUFDO0lBQ0QsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksYUFBYSxDQUFDLEtBQUssQ0FBQyxJQUFJLFdBQVcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO1FBQzFFLE9BQU8sQ0FBQyxLQUFLLEVBQUUsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ2hDLENBQUM7QUFDSCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7O0dBY0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUN6QixJQUFPLEVBQ1AsYUFBNkIsRUFDN0Isa0JBQWtCLEdBQUcsS0FBSztJQUUxQixNQUFNLE9BQU8sR0FBZ0IsRUFBRSxDQUFDO0lBQ2hDLFlBQVksQ0FBQyxJQUFJLEVBQUUsYUFBYSxFQUFFLENBQUMsS0FBSyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsRUFBRTtRQUMzRCxNQUFNLE1BQU0sR0FBRyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDaEMsSUFBSSxDQUFDLGtCQUFrQixJQUFJLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLEtBQUssTUFBTSxDQUFDO1lBQUUsT0FBTztRQUN4RSxPQUFPLENBQUMsSUFBSSxDQUFDO1lBQ1gsS0FBSztZQUNMLE1BQU07WUFDTixNQUFNO1lBQ04sR0FBRyxFQUFFLENBQUMsS0FBYyxFQUFFLEVBQUUsQ0FBQyxVQUFVLENBQUMsSUFBSSxFQUFFLE9BQU8sRUFBRSxLQUFLLENBQUM7U0FDMUQsQ0FBQyxDQUFDO0lBQ0wsQ0FBQyxDQUFDLENBQUM7SUFDSCxPQUFPLE9BQU8sQ0FBQztBQUNqQixDQUFDO0FBRUQ7Ozs7Ozs7R0FPRztBQUNILE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLENBQUMsQ0FBQyxXQUFXLEVBQUUsYUFBYSxFQUFFLFdBQVcsQ0FBQyxDQUFDLENBQUM7QUFFN0UsU0FBUyxVQUFVLENBQUMsSUFBYSxFQUFFLEtBQTZCLEVBQUUsS0FBYztJQUM5RSxJQUFJLEtBQUssQ0FBQyxNQUFNLEtBQUssQ0FBQztRQUFFLE9BQU87SUFDL0IsS0FBSyxNQUFNLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUN0QixJQUFJLE9BQU8sQ0FBQyxLQUFLLFFBQVEsSUFBSSxpQkFBaUIsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1lBQUUsT0FBTztJQUNoRSxDQUFDO0lBQ0QsSUFBSSxHQUFHLEdBQVksSUFBSSxDQUFDO0lBQ3hCLEtBQUssSUFBSSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDO1FBQzFDLElBQUksR0FBRyxJQUFJLElBQUksSUFBSSxPQUFPLEdBQUcsS0FBSyxRQUFRO1lBQUUsT0FBTztRQUNuRCxHQUFHLEdBQUksR0FBd0MsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFFLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBQ0QsSUFBSSxHQUFHLElBQUksSUFBSSxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVE7UUFBRSxPQUFPO0lBQ2xELEdBQXdDLENBQUMsS0FBSyxDQUFDLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFFLENBQUMsR0FBRyxLQUFLLENBQUM7QUFDOUUsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgeyBoYXNUZW1wbGF0ZSwgdG9rZW5pemUsIHR5cGUgVG9rZW4gfSBmcm9tICcuL3Rva2VuaXplci5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgV2Fsa0VudHJ5IHtcbiAgLyoqIERvdHRlZCBwYXRoIHRvIHRoZSBmaWVsZCBpbnNpZGUgdGhlIGRvY3VtZW50LCBlLmcuICdjb21tYW5kc1szXScgb3IgJ2Vudi5TVEFHRScgKi9cbiAgZmllbGQ6IHN0cmluZztcbiAgLyoqIFJhdyBzb3VyY2Ugc3RyaW5nIGNvbnRhaW5pbmcgdGVtcGxhdGUgdG9rZW5zICovXG4gIHNvdXJjZTogc3RyaW5nO1xuICAvKiogUGFyc2VkIHRva2VuIHN0cmVhbSAqL1xuICB0b2tlbnM6IFRva2VuW107XG4gIC8qKiBTZXR0ZXIgdG8gd3JpdGUgYmFjayBhIHJlc29sdmVkIHZhbHVlIGludG8gdGhlIG9yaWdpbmFsIGByb290YCBvYmplY3QgKi9cbiAgc2V0OiAodmFsdWU6IHVua25vd24pID0+IHZvaWQ7XG59XG5cbmV4cG9ydCB0eXBlIEZpZWxkUHJlZGljYXRlID0gKGZpZWxkOiBzdHJpbmcpID0+IGJvb2xlYW47XG5cbi8qKlxuICogVmlzaXQgZXZlcnkgdGVtcGxhdGFibGUgc3RyaW5nIGxlYWYgdW5kZXIgYHJvb3RgLiBDYWxscyBgaGFuZGxlcihmaWVsZCwgc291cmNlKWBcbiAqIGZvciBlYWNoIHN0cmluZyB3aG9zZSBmaWVsZCBwYXRoIHNhdGlzZmllcyBgaXNUZW1wbGF0YWJsZWAgYW5kIGNvbnRhaW5zIGB7e2AuXG4gKiBEb2VzIE5PVCB0b2tlbml6ZSDigJQgY2FsbGVycyBkZWNpZGUgd2hldGhlciB0byB0aHJvdyBvciBjb2xsZWN0IGVycm9ycy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHZpc2l0U3RyaW5ncyhcbiAgcm9vdDogdW5rbm93bixcbiAgaXNUZW1wbGF0YWJsZTogRmllbGRQcmVkaWNhdGUsXG4gIGhhbmRsZXI6IChmaWVsZDogc3RyaW5nLCBzb3VyY2U6IHN0cmluZywga2V5UGF0aDogQXJyYXk8c3RyaW5nIHwgbnVtYmVyPikgPT4gdm9pZCxcbik6IHZvaWQge1xuICBzdGVwKHJvb3QsICcnLCBbXSwgaXNUZW1wbGF0YWJsZSwgaGFuZGxlcik7XG59XG5cbmZ1bmN0aW9uIHN0ZXAoXG4gIG5vZGU6IHVua25vd24sXG4gIGZpZWxkOiBzdHJpbmcsXG4gIGtleVBhdGg6IEFycmF5PHN0cmluZyB8IG51bWJlcj4sXG4gIGlzVGVtcGxhdGFibGU6IEZpZWxkUHJlZGljYXRlLFxuICBoYW5kbGVyOiAoZmllbGQ6IHN0cmluZywgc291cmNlOiBzdHJpbmcsIGtleVBhdGg6IEFycmF5PHN0cmluZyB8IG51bWJlcj4pID0+IHZvaWQsXG4pOiB2b2lkIHtcbiAgaWYgKEFycmF5LmlzQXJyYXkobm9kZSkpIHtcbiAgICBub2RlLmZvckVhY2goKGNoaWxkLCBpZHgpID0+IHN0ZXAoY2hpbGQsIGAke2ZpZWxkfVske2lkeH1dYCwgWy4uLmtleVBhdGgsIGlkeF0sIGlzVGVtcGxhdGFibGUsIGhhbmRsZXIpKTtcbiAgICByZXR1cm47XG4gIH1cbiAgaWYgKG5vZGUgIT09IG51bGwgJiYgdHlwZW9mIG5vZGUgPT09ICdvYmplY3QnKSB7XG4gICAgZm9yIChjb25zdCBba2V5LCBjaGlsZF0gb2YgT2JqZWN0LmVudHJpZXMobm9kZSBhcyBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPikpIHtcbiAgICAgIHN0ZXAoY2hpbGQsIGZpZWxkID8gYCR7ZmllbGR9LiR7a2V5fWAgOiBrZXksIFsuLi5rZXlQYXRoLCBrZXldLCBpc1RlbXBsYXRhYmxlLCBoYW5kbGVyKTtcbiAgICB9XG4gICAgcmV0dXJuO1xuICB9XG4gIGlmICh0eXBlb2Ygbm9kZSA9PT0gJ3N0cmluZycgJiYgaXNUZW1wbGF0YWJsZShmaWVsZCkgJiYgaGFzVGVtcGxhdGUobm9kZSkpIHtcbiAgICBoYW5kbGVyKGZpZWxkLCBub2RlLCBrZXlQYXRoKTtcbiAgfVxufVxuXG4vKipcbiAqIFdhbGsgYWxsIHRlbXBsYXRhYmxlIHN0cmluZyBmaWVsZHMgdW5kZXIgYHJvb3RgLCByZXR1cm5pbmcgb25lIGVudHJ5IHBlclxuICogdGVtcGxhdGVkIGZpZWxkIHdpdGggYSBib3VuZCBgc2V0KClgIHRoYXQgbXV0YXRlcyBgcm9vdGAgaW4gcGxhY2UuXG4gKiBUb2tlbml6YXRpb24gaGFwcGVucyBlYWdlcmx5IGhlcmUg4oCUIHBhcnNlIGVycm9ycyB0aHJvdy5cbiAqXG4gKiBCeSBkZWZhdWx0IG9ubHkgZmllbGRzIGNvbnRhaW5pbmcgYXQgbGVhc3Qgb25lIGB7eyBleHByIH19YCBhcmUgcmV0dXJuZWQuXG4gKiBQYXNzIGBpbmNsdWRlTGl0ZXJhbE9ubHlgIHRvIGFsc28gcmV0dXJuIGZpZWxkcyB3aG9zZSBvbmx5IHRlbXBsYXRlIGNvbnRlbnRcbiAqIGlzIGEgYHt7e3tgIGVzY2FwZSBzZXF1ZW5jZSDigJQgdGhlc2Ugc3RpbGwgbmVlZCByZXdyaXRpbmcgc28gdGhlIHRva2VuaXplcidzXG4gKiBge3t7e2Ag4oaSIGB7e2AgdW5lc2NhcGUgYWN0dWFsbHkgbWF0ZXJpYWxpemVzIGludG8gdGhlIGRvY3VtZW50LlxuICpcbiAqIFRoZSBib3VuZCBgc2V0KClgIHdyaXRlcyBiYWNrIHZpYSB0aGUgZXhhY3Qga2V5IHBhdGggY2FwdHVyZWQgZHVyaW5nIHRoZVxuICogd2FsayAodGhlIHJlYWwgYE9iamVjdC5lbnRyaWVzYCBrZXlzKSwgc28gZG9jdW1lbnQga2V5cyBjb250YWluaW5nIGAuYCxcbiAqIGBbYCwgb3IgYF1gIChlLmcuIGBlbnZbXCJGT08uQkFSXCJdYCkgYXJlIHdyaXR0ZW4gY29ycmVjdGx5IHJhdGhlciB0aGFuIGJlaW5nXG4gKiBzaWxlbnRseSBkcm9wcGVkIGJ5IHJlLXBhcnNpbmcgYSBmbGF0dGVuZWQgZG90dGVkIGZpZWxkIHN0cmluZy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHdhbGtBbmRCaW5kPFQgZXh0ZW5kcyBvYmplY3Q+KFxuICByb290OiBULFxuICBpc1RlbXBsYXRhYmxlOiBGaWVsZFByZWRpY2F0ZSxcbiAgaW5jbHVkZUxpdGVyYWxPbmx5ID0gZmFsc2UsXG4pOiBXYWxrRW50cnlbXSB7XG4gIGNvbnN0IGVudHJpZXM6IFdhbGtFbnRyeVtdID0gW107XG4gIHZpc2l0U3RyaW5ncyhyb290LCBpc1RlbXBsYXRhYmxlLCAoZmllbGQsIHNvdXJjZSwga2V5UGF0aCkgPT4ge1xuICAgIGNvbnN0IHRva2VucyA9IHRva2VuaXplKHNvdXJjZSk7XG4gICAgaWYgKCFpbmNsdWRlTGl0ZXJhbE9ubHkgJiYgIXRva2Vucy5zb21lKHQgPT4gdC5raW5kID09PSAnZXhwcicpKSByZXR1cm47XG4gICAgZW50cmllcy5wdXNoKHtcbiAgICAgIGZpZWxkLFxuICAgICAgc291cmNlLFxuICAgICAgdG9rZW5zLFxuICAgICAgc2V0OiAodmFsdWU6IHVua25vd24pID0+IHdyaXRlRmllbGQocm9vdCwga2V5UGF0aCwgdmFsdWUpLFxuICAgIH0pO1xuICB9KTtcbiAgcmV0dXJuIGVudHJpZXM7XG59XG5cbi8qKlxuICogS2V5cyB0aGF0IG11c3QgbmV2ZXIgYmUgd2Fsa2VkIGludG8gb3Igd3JpdHRlbiB0aHJvdWdoOiBhc3NpZ25pbmcgdG9cbiAqIGBfX3Byb3RvX19gIC8gYGNvbnN0cnVjdG9yYCAvIGBwcm90b3R5cGVgIGNvdWxkIHJldGFyZ2V0IGFuIG9iamVjdCdzXG4gKiBwcm90b3R5cGUuIGBPYmplY3QuZW50cmllc2AgeWllbGRzIG93biBlbnVtZXJhYmxlIGtleXMgb25seSwgYnV0IGEgSlNPTlxuICogZG9jdW1lbnQgcGFyc2VkIHdpdGggYEpTT04ucGFyc2VgIGNhbiBjYXJyeSBhIGdlbnVpbmUgb3duIGVudW1lcmFibGVcbiAqIGBfX3Byb3RvX19gIChvciBgY29uc3RydWN0b3JgKSBrZXksIHNvIGEgY2FwdHVyZWQga2V5UGF0aCBjb3VsZCBjb250YWluIG9uZS5cbiAqIFJlZnVzZSB0aGUgd3JpdGUgaW4gdGhhdCBjYXNlIHJhdGhlciB0aGFuIG11dGF0aW5nIHRoZSBwcm90b3R5cGUuXG4gKi9cbmNvbnN0IFVOU0FGRV9XUklURV9LRVlTID0gbmV3IFNldChbJ19fcHJvdG9fXycsICdjb25zdHJ1Y3RvcicsICdwcm90b3R5cGUnXSk7XG5cbmZ1bmN0aW9uIHdyaXRlRmllbGQocm9vdDogdW5rbm93biwgcGFydHM6IEFycmF5PHN0cmluZyB8IG51bWJlcj4sIHZhbHVlOiB1bmtub3duKTogdm9pZCB7XG4gIGlmIChwYXJ0cy5sZW5ndGggPT09IDApIHJldHVybjtcbiAgZm9yIChjb25zdCBwIG9mIHBhcnRzKSB7XG4gICAgaWYgKHR5cGVvZiBwID09PSAnc3RyaW5nJyAmJiBVTlNBRkVfV1JJVEVfS0VZUy5oYXMocCkpIHJldHVybjtcbiAgfVxuICBsZXQgY3VyOiB1bmtub3duID0gcm9vdDtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBwYXJ0cy5sZW5ndGggLSAxOyBpKyspIHtcbiAgICBpZiAoY3VyID09IG51bGwgfHwgdHlwZW9mIGN1ciAhPT0gJ29iamVjdCcpIHJldHVybjtcbiAgICBjdXIgPSAoY3VyIGFzIFJlY29yZDxzdHJpbmcgfCBudW1iZXIsIHVua25vd24+KVtwYXJ0c1tpXSFdO1xuICB9XG4gIGlmIChjdXIgPT0gbnVsbCB8fCB0eXBlb2YgY3VyICE9PSAnb2JqZWN0JykgcmV0dXJuO1xuICAoY3VyIGFzIFJlY29yZDxzdHJpbmcgfCBudW1iZXIsIHVua25vd24+KVtwYXJ0c1twYXJ0cy5sZW5ndGggLSAxXSFdID0gdmFsdWU7XG59XG4iXX0=
package/package.json ADDED
@@ -0,0 +1,184 @@
1
+ {
2
+ "name": "@pipeline-builder/pipeline-core",
3
+ "description": "AWS CDK construct library for Pipeline Builder: the Builder construct that assembles plugin specs into a CodePipeline stack, PluginLookup custom resource, pipeline/plugin domain types, and shared configuration.",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/mwashburn160/pipeline-builder.git"
7
+ },
8
+ "devDependencies": {
9
+ "@aws-sdk/client-secrets-manager": "3.1101.0",
10
+ "@jest/globals": "30.4.1",
11
+ "@stylistic/eslint-plugin": "^2",
12
+ "@types/aws-lambda": "8.10.162",
13
+ "@types/jsonwebtoken": "9.0.10",
14
+ "@types/node": "26.1.2",
15
+ "@typescript-eslint/eslint-plugin": "^8",
16
+ "@typescript-eslint/parser": "^8",
17
+ "@typescript/native": "npm:typescript@^7.0.2",
18
+ "aws-cdk-lib": "2.263.0",
19
+ "constructs": "10.7.0",
20
+ "copyfiles": "2.4.1",
21
+ "eslint": "^9",
22
+ "eslint-import-resolver-typescript": "^4.4.4",
23
+ "eslint-plugin-import": "^2.32.0",
24
+ "jest": "30.4.2",
25
+ "jest-junit": "^17",
26
+ "ts-jest": "29.4.12",
27
+ "typescript": "npm:@typescript/typescript6@^6.0.2"
28
+ },
29
+ "peerDependencies": {
30
+ "aws-cdk-lib": "^2.263.0",
31
+ "constructs": "^10.5.0"
32
+ },
33
+ "dependencies": {
34
+ "axios": "1.19.0",
35
+ "jsonwebtoken": "9.0.3",
36
+ "uuid": "14.0.1",
37
+ "@pipeline-builder/api-core": "0.0.1",
38
+ "@pipeline-builder/pipeline-data": "0.0.1"
39
+ },
40
+ "keywords": [
41
+ "ci-cd",
42
+ "cicd",
43
+ "continuous-delivery",
44
+ "devops",
45
+ "self-service",
46
+ "platform-engineering",
47
+ "internal-developer-platform",
48
+ "developer-platform",
49
+ "aws",
50
+ "aws-cdk",
51
+ "cdk",
52
+ "codepipeline",
53
+ "codebuild",
54
+ "cloudformation",
55
+ "pipeline",
56
+ "pipeline-as-code",
57
+ "infrastructure-as-code",
58
+ "iac",
59
+ "compliance",
60
+ "policy-as-code",
61
+ "governance",
62
+ "golden-paths",
63
+ "multi-tenant",
64
+ "rbac",
65
+ "ai",
66
+ "ai-pipeline-generation",
67
+ "llm",
68
+ "bedrock",
69
+ "plugins",
70
+ "plugin-marketplace",
71
+ "containerized",
72
+ "docker",
73
+ "kubernetes",
74
+ "typescript",
75
+ "cli"
76
+ ],
77
+ "engines": {
78
+ "node": ">= 24.14.0"
79
+ },
80
+ "devEngines": {
81
+ "packageManager": {
82
+ "name": "pnpm",
83
+ "version": ">=10",
84
+ "onFail": "ignore"
85
+ }
86
+ },
87
+ "main": "lib/index.js",
88
+ "license": "Apache-2.0",
89
+ "homepage": "https://mwashburn160.github.io/pipeline-builder/",
90
+ "publishConfig": {
91
+ "access": "public",
92
+ "registry": "https://registry.npmjs.org/"
93
+ },
94
+ "version": "0.0.1",
95
+ "bugs": {
96
+ "url": "https://github.com/mwashburn160/pipeline-builder/issues"
97
+ },
98
+ "jest": {
99
+ "coverageProvider": "v8",
100
+ "testMatch": [
101
+ "<rootDir>/@(src|test)/**/*(*.)@(spec|test).ts?(x)",
102
+ "<rootDir>/@(src|test)/**/__tests__/**/*.ts?(x)"
103
+ ],
104
+ "clearMocks": true,
105
+ "collectCoverage": true,
106
+ "coverageReporters": [
107
+ "json",
108
+ "lcov",
109
+ "clover",
110
+ "cobertura",
111
+ "text"
112
+ ],
113
+ "coverageDirectory": "coverage",
114
+ "coveragePathIgnorePatterns": [
115
+ "/node_modules/"
116
+ ],
117
+ "testPathIgnorePatterns": [
118
+ "/node_modules/"
119
+ ],
120
+ "watchPathIgnorePatterns": [
121
+ "/node_modules/"
122
+ ],
123
+ "reporters": [
124
+ "default",
125
+ [
126
+ "jest-junit",
127
+ {
128
+ "outputDirectory": "test-reports"
129
+ }
130
+ ]
131
+ ],
132
+ "transform": {
133
+ "^.+\\.[t]sx?$": [
134
+ "ts-jest",
135
+ {
136
+ "useESM": true,
137
+ "tsconfig": "tsconfig.dev.json",
138
+ "diagnostics": {
139
+ "ignoreCodes": [
140
+ 151002
141
+ ]
142
+ }
143
+ }
144
+ ]
145
+ },
146
+ "extensionsToTreatAsEsm": [
147
+ ".ts",
148
+ ".tsx"
149
+ ],
150
+ "moduleNameMapper": {
151
+ "^uuid$": "<rootDir>/../../jest-uuid-stub.js",
152
+ "^(\\.{1,2}/.*)\\.js$": "$1"
153
+ },
154
+ "maxWorkers": 1
155
+ },
156
+ "types": "lib/index.d.ts",
157
+ "type": "module",
158
+ "exports": {
159
+ ".": {
160
+ "types": "./lib/index.d.ts",
161
+ "default": "./lib/index.js"
162
+ },
163
+ "./cdk": {
164
+ "types": "./lib/cdk.d.ts",
165
+ "default": "./lib/cdk.js"
166
+ },
167
+ "./lib/*": "./lib/*",
168
+ "./package.json": "./package.json"
169
+ },
170
+ "//": "~~ Generated by projen. To modify, edit .projenrc.js and run \"pnpm dlx projen\".",
171
+ "scripts": {
172
+ "build": "pnpm dlx projen build",
173
+ "compile": "pnpm dlx projen compile",
174
+ "default": "pnpm dlx projen default",
175
+ "eslint": "pnpm dlx projen eslint",
176
+ "package": "pnpm dlx projen package",
177
+ "post-compile": "pnpm dlx projen post-compile",
178
+ "pre-compile": "pnpm dlx projen pre-compile",
179
+ "test": "pnpm dlx projen test",
180
+ "test:watch": "pnpm dlx projen test:watch",
181
+ "watch": "pnpm dlx projen watch",
182
+ "projen": "pnpm dlx projen"
183
+ }
184
+ }