@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,34 @@
1
+ export { tokenize, hasTemplate, TokenizerError, MAX_FIELD_SIZE_BYTES, MAX_PATH_DEPTH, MAX_IDENTIFIER_LENGTH, type Token, type LiteralToken, type ExprToken, type SourcePosition, } from './tokenizer.js';
2
+ export { resolve, lookupPath, dependencies, type Scope, type EvalError, } from './evaluator.js';
3
+ export { walkAndBind, type WalkEntry, type FieldPredicate, } from './walker.js';
4
+ export { topoSort, type TopoNode, type TopoResult, } from './topo-sort.js';
5
+ export { validateTemplates, detectCycles, allowedScopeRoots, type TemplateError, type ValidationResult, } from './validate.js';
6
+ export { recordResolution, templateResolutionsTotal, templateResolutionDurationMs, } from './metrics.js';
7
+ import { ErrorCode } from '@pipeline-builder/api-core';
8
+ import { type Scope } from './evaluator.js';
9
+ import { type FieldPredicate } from './walker.js';
10
+ export interface ResolveResult {
11
+ errors: Array<{
12
+ code: ErrorCode;
13
+ message: string;
14
+ field?: string;
15
+ path?: string;
16
+ }>;
17
+ }
18
+ /**
19
+ * Resolve all templates inside `doc` by mutating string fields in place.
20
+ * `doc` is treated as opaque — only string fields that match `isTemplatable`
21
+ * are rewritten. Returns collected errors (empty array on success).
22
+ *
23
+ * `docType` is used for metrics; pass 'pipeline' or 'plugin'.
24
+ */
25
+ export declare function resolveTemplates<T extends object>(doc: T, scope: Scope, isTemplatable: FieldPredicate, docType?: 'pipeline' | 'plugin'): ResolveResult;
26
+ /**
27
+ * Resolve a self-referencing document (e.g. pipeline.json where
28
+ * metadata fields reference each other). Topologically orders the
29
+ * resolution so dependencies come first. Mutates `doc` in place.
30
+ *
31
+ * `fieldToScopePath` maps each field (e.g. `metadata.env`) to the scope
32
+ * path it populates (typically the same, sans any array indices).
33
+ */
34
+ export declare function resolveSelfReferencing<T extends object>(doc: T, scope: Scope, isTemplatable: FieldPredicate, fieldToScopePath: (field: string) => string | null, docType?: 'pipeline' | 'plugin'): ResolveResult;
@@ -0,0 +1,118 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export { tokenize, hasTemplate, TokenizerError, MAX_FIELD_SIZE_BYTES, MAX_PATH_DEPTH, MAX_IDENTIFIER_LENGTH, } from './tokenizer.js';
4
+ export { resolve, lookupPath, dependencies, } from './evaluator.js';
5
+ export { walkAndBind, } from './walker.js';
6
+ export { topoSort, } from './topo-sort.js';
7
+ export { validateTemplates, detectCycles, allowedScopeRoots, } from './validate.js';
8
+ export { recordResolution, templateResolutionsTotal, templateResolutionDurationMs, } from './metrics.js';
9
+ // -- Convenience: high-level resolve() that walks + resolves + measures
10
+ import { ErrorCode } from '@pipeline-builder/api-core';
11
+ import { resolve as evaluatorResolve } from './evaluator.js';
12
+ import { recordResolution } from './metrics.js';
13
+ import { topoSort } from './topo-sort.js';
14
+ import { walkAndBind } from './walker.js';
15
+ /**
16
+ * Resolve all templates inside `doc` by mutating string fields in place.
17
+ * `doc` is treated as opaque — only string fields that match `isTemplatable`
18
+ * are rewritten. Returns collected errors (empty array on success).
19
+ *
20
+ * `docType` is used for metrics; pass 'pipeline' or 'plugin'.
21
+ */
22
+ export function resolveTemplates(doc, scope, isTemplatable, docType = 'plugin') {
23
+ const start = Date.now();
24
+ const errors = [];
25
+ // includeLiteralOnly: literal-only fields (a bare `{{{{` escape) still need
26
+ // rewriting so the tokenizer's `{{{{` → `{{` unescape lands in the document.
27
+ const entries = walkAndBind(doc, isTemplatable, true);
28
+ let success = true;
29
+ for (const entry of entries) {
30
+ try {
31
+ const value = evaluatorResolve(entry.tokens, scope, entry.field);
32
+ entry.set(value);
33
+ }
34
+ catch (err) {
35
+ success = false;
36
+ const e = err;
37
+ errors.push({
38
+ code: e.code ?? ErrorCode.TEMPLATE_PARSE_ERROR,
39
+ message: e.message,
40
+ field: entry.field,
41
+ path: e.path,
42
+ });
43
+ }
44
+ }
45
+ recordResolution(docType, Date.now() - start, success);
46
+ return { errors };
47
+ }
48
+ /**
49
+ * Resolve a self-referencing document (e.g. pipeline.json where
50
+ * metadata fields reference each other). Topologically orders the
51
+ * resolution so dependencies come first. Mutates `doc` in place.
52
+ *
53
+ * `fieldToScopePath` maps each field (e.g. `metadata.env`) to the scope
54
+ * path it populates (typically the same, sans any array indices).
55
+ */
56
+ export function resolveSelfReferencing(doc, scope, isTemplatable, fieldToScopePath, docType = 'pipeline') {
57
+ const start = Date.now();
58
+ // includeLiteralOnly: a self-referencing field that is only a `{{{{` escape
59
+ // still needs its unescape written back (same rationale as resolveTemplates).
60
+ const entries = walkAndBind(doc, isTemplatable, true);
61
+ // Build topo nodes keyed by target scope path
62
+ const nodes = entries
63
+ .map(e => {
64
+ const key = fieldToScopePath(e.field);
65
+ if (!key)
66
+ return null;
67
+ const deps = [];
68
+ for (const t of e.tokens)
69
+ if (t.kind === 'expr')
70
+ deps.push(t.path.join('.'));
71
+ return { key, deps, entry: e };
72
+ })
73
+ .filter((n) => n !== null);
74
+ const { ordered, cycles } = topoSort(nodes.map(n => ({ key: n.key, deps: n.deps })));
75
+ const errors = [];
76
+ if (cycles.length) {
77
+ for (const c of cycles) {
78
+ errors.push({
79
+ code: ErrorCode.TEMPLATE_CYCLE,
80
+ message: `Template cycle detected: ${c.join(' -> ')}`,
81
+ path: c.join(' -> '),
82
+ });
83
+ }
84
+ recordResolution(docType, Date.now() - start, false);
85
+ return { errors };
86
+ }
87
+ // Resolve in topo order; multiple entries may share a scope key (e.g. one
88
+ // pipeline field could map to the same scope path — unlikely here), so
89
+ // iterate all entries whose key == ordered[i].
90
+ const byKey = new Map();
91
+ for (const n of nodes) {
92
+ const list = byKey.get(n.key) ?? [];
93
+ list.push(n);
94
+ byKey.set(n.key, list);
95
+ }
96
+ let success = true;
97
+ for (const key of ordered) {
98
+ for (const n of byKey.get(key) ?? []) {
99
+ try {
100
+ const value = evaluatorResolve(n.entry.tokens, scope, n.entry.field);
101
+ n.entry.set(value);
102
+ }
103
+ catch (err) {
104
+ success = false;
105
+ const e = err;
106
+ errors.push({
107
+ code: e.code ?? ErrorCode.TEMPLATE_PARSE_ERROR,
108
+ message: e.message,
109
+ field: n.entry.field,
110
+ path: e.path,
111
+ });
112
+ }
113
+ }
114
+ }
115
+ recordResolution(docType, Date.now() - start, success);
116
+ return { errors };
117
+ }
118
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdGVtcGxhdGUvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsK0NBQStDO0FBQy9DLHNDQUFzQztBQUV0QyxPQUFPLEVBQ0wsUUFBUSxFQUNSLFdBQVcsRUFDWCxjQUFjLEVBQ2Qsb0JBQW9CLEVBQ3BCLGNBQWMsRUFDZCxxQkFBcUIsR0FLdEIsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4QixPQUFPLEVBQ0wsT0FBTyxFQUNQLFVBQVUsRUFDVixZQUFZLEdBR2IsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4QixPQUFPLEVBQ0wsV0FBVyxHQUdaLE1BQU0sYUFBYSxDQUFDO0FBRXJCLE9BQU8sRUFDTCxRQUFRLEdBR1QsTUFBTSxnQkFBZ0IsQ0FBQztBQUV4QixPQUFPLEVBQ0wsaUJBQWlCLEVBQ2pCLFlBQVksRUFDWixpQkFBaUIsR0FHbEIsTUFBTSxlQUFlLENBQUM7QUFFdkIsT0FBTyxFQUNMLGdCQUFnQixFQUNoQix3QkFBd0IsRUFDeEIsNEJBQTRCLEdBQzdCLE1BQU0sY0FBYyxDQUFDO0FBRXRCLHdFQUF3RTtBQUV4RSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDdkQsT0FBTyxFQUFFLE9BQU8sSUFBSSxnQkFBZ0IsRUFBYyxNQUFNLGdCQUFnQixDQUFDO0FBQ3pFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLGNBQWMsQ0FBQztBQUNoRCxPQUFPLEVBQUUsUUFBUSxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDMUMsT0FBTyxFQUFFLFdBQVcsRUFBdUIsTUFBTSxhQUFhLENBQUM7QUFNL0Q7Ozs7OztHQU1HO0FBQ0gsTUFBTSxVQUFVLGdCQUFnQixDQUM5QixHQUFNLEVBQ04sS0FBWSxFQUNaLGFBQTZCLEVBQzdCLE9BQU8sR0FBMEIsUUFBUTtJQUV6QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUM7SUFD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LEdBQUcsQ0FBQyxLQUFLLENBQUMsQ0FBQztZQUNyQixDQUFDO1lBQUMsT0FBTyxHQUFHLEVBQUUsQ0FBQztnQkFDYixPQUFPLEdBQUcsS0FBSyxDQUFDO2dCQUNoQixNQUFNLENBQUMsR0FBRyxHQUFrRCxDQUFDO2dCQUM3RCxNQUFNLENBQUMsSUFBSSxDQUFDO29CQUNWLElBQUksRUFBRSxDQUFDLENBQUMsSUFBSSxJQUFJLFNBQVMsQ0FBQyxvQkFBb0I7b0JBQzlDLE9BQU8sRUFBRSxDQUFDLENBQUMsT0FBTztvQkFDbEIsS0FBSyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsS0FBSztvQkFDcEIsSUFBSSxFQUFFLENBQUMsQ0FBQyxJQUFJO2lCQUNiLENBQUMsQ0FBQztZQUNMLENBQUM7UUFDSCxDQUFDO0lBQ0gsQ0FBQztJQUNELGdCQUFnQixDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxFQUFFLEdBQUcsS0FBSyxFQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZELE9BQU8sRUFBRSxNQUFNLEVBQUUsQ0FBQztBQUNwQixDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbmV4cG9ydCB7XG4gIHRva2VuaXplLFxuICBoYXNUZW1wbGF0ZSxcbiAgVG9rZW5pemVyRXJyb3IsXG4gIE1BWF9GSUVMRF9TSVpFX0JZVEVTLFxuICBNQVhfUEFUSF9ERVBUSCxcbiAgTUFYX0lERU5USUZJRVJfTEVOR1RILFxuICB0eXBlIFRva2VuLFxuICB0eXBlIExpdGVyYWxUb2tlbixcbiAgdHlwZSBFeHByVG9rZW4sXG4gIHR5cGUgU291cmNlUG9zaXRpb24sXG59IGZyb20gJy4vdG9rZW5pemVyLmpzJztcblxuZXhwb3J0IHtcbiAgcmVzb2x2ZSxcbiAgbG9va3VwUGF0aCxcbiAgZGVwZW5kZW5jaWVzLFxuICB0eXBlIFNjb3BlLFxuICB0eXBlIEV2YWxFcnJvcixcbn0gZnJvbSAnLi9ldmFsdWF0b3IuanMnO1xuXG5leHBvcnQge1xuICB3YWxrQW5kQmluZCxcbiAgdHlwZSBXYWxrRW50cnksXG4gIHR5cGUgRmllbGRQcmVkaWNhdGUsXG59IGZyb20gJy4vd2Fsa2VyLmpzJztcblxuZXhwb3J0IHtcbiAgdG9wb1NvcnQsXG4gIHR5cGUgVG9wb05vZGUsXG4gIHR5cGUgVG9wb1Jlc3VsdCxcbn0gZnJvbSAnLi90b3BvLXNvcnQuanMnO1xuXG5leHBvcnQge1xuICB2YWxpZGF0ZVRlbXBsYXRlcyxcbiAgZGV0ZWN0Q3ljbGVzLFxuICBhbGxvd2VkU2NvcGVSb290cyxcbiAgdHlwZSBUZW1wbGF0ZUVycm9yLFxuICB0eXBlIFZhbGlkYXRpb25SZXN1bHQsXG59IGZyb20gJy4vdmFsaWRhdGUuanMnO1xuXG5leHBvcnQge1xuICByZWNvcmRSZXNvbHV0aW9uLFxuICB0ZW1wbGF0ZVJlc29sdXRpb25zVG90YWwsXG4gIHRlbXBsYXRlUmVzb2x1dGlvbkR1cmF0aW9uTXMsXG59IGZyb20gJy4vbWV0cmljcy5qcyc7XG5cbi8vIC0tIENvbnZlbmllbmNlOiBoaWdoLWxldmVsIHJlc29sdmUoKSB0aGF0IHdhbGtzICsgcmVzb2x2ZXMgKyBtZWFzdXJlc1xuXG5pbXBvcnQgeyBFcnJvckNvZGUgfSBmcm9tICdAcGlwZWxpbmUtYnVpbGRlci9hcGktY29yZSc7XG5pbXBvcnQgeyByZXNvbHZlIGFzIGV2YWx1YXRvclJlc29sdmUsIHR5cGUgU2NvcGUgfSBmcm9tICcuL2V2YWx1YXRvci5qcyc7XG5pbXBvcnQgeyByZWNvcmRSZXNvbHV0aW9uIH0gZnJvbSAnLi9tZXRyaWNzLmpzJztcbmltcG9ydCB7IHRvcG9Tb3J0IH0gZnJvbSAnLi90b3BvLXNvcnQuanMnO1xuaW1wb3J0IHsgd2Fsa0FuZEJpbmQsIHR5cGUgRmllbGRQcmVkaWNhdGUgfSBmcm9tICcuL3dhbGtlci5qcyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgUmVzb2x2ZVJlc3VsdCB7XG4gIGVycm9yczogQXJyYXk8eyBjb2RlOiBFcnJvckNvZGU7IG1lc3NhZ2U6IHN0cmluZzsgZmllbGQ/OiBzdHJpbmc7IHBhdGg/OiBzdHJpbmcgfT47XG59XG5cbi8qKlxuICogUmVzb2x2ZSBhbGwgdGVtcGxhdGVzIGluc2lkZSBgZG9jYCBieSBtdXRhdGluZyBzdHJpbmcgZmllbGRzIGluIHBsYWNlLlxuICogYGRvY2AgaXMgdHJlYXRlZCBhcyBvcGFxdWUg4oCUIG9ubHkgc3RyaW5nIGZpZWxkcyB0aGF0IG1hdGNoIGBpc1RlbXBsYXRhYmxlYFxuICogYXJlIHJld3JpdHRlbi4gUmV0dXJucyBjb2xsZWN0ZWQgZXJyb3JzIChlbXB0eSBhcnJheSBvbiBzdWNjZXNzKS5cbiAqXG4gKiBgZG9jVHlwZWAgaXMgdXNlZCBmb3IgbWV0cmljczsgcGFzcyAncGlwZWxpbmUnIG9yICdwbHVnaW4nLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVRlbXBsYXRlczxUIGV4dGVuZHMgb2JqZWN0PihcbiAgZG9jOiBULFxuICBzY29wZTogU2NvcGUsXG4gIGlzVGVtcGxhdGFibGU6IEZpZWxkUHJlZGljYXRlLFxuICBkb2NUeXBlOiAncGlwZWxpbmUnIHwgJ3BsdWdpbicgPSAncGx1Z2luJyxcbik6IFJlc29sdmVSZXN1bHQge1xuICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7XG4gIGNvbnN0IGVycm9yczogUmVzb2x2ZVJlc3VsdFsnZXJyb3JzJ10gPSBbXTtcbiAgLy8gaW5jbHVkZUxpdGVyYWxPbmx5OiBsaXRlcmFsLW9ubHkgZmllbGRzIChhIGJhcmUgYHt7e3tgIGVzY2FwZSkgc3RpbGwgbmVlZFxuICAvLyByZXdyaXRpbmcgc28gdGhlIHRva2VuaXplcidzIGB7e3t7YCDihpIgYHt7YCB1bmVzY2FwZSBsYW5kcyBpbiB0aGUgZG9jdW1lbnQuXG4gIGNvbnN0IGVudHJpZXMgPSB3YWxrQW5kQmluZChkb2MsIGlzVGVtcGxhdGFibGUsIHRydWUpO1xuICBsZXQgc3VjY2VzcyA9IHRydWU7XG4gIGZvciAoY29uc3QgZW50cnkgb2YgZW50cmllcykge1xuICAgIHRyeSB7XG4gICAgICBjb25zdCB2YWx1ZSA9IGV2YWx1YXRvclJlc29sdmUoZW50cnkudG9rZW5zLCBzY29wZSwgZW50cnkuZmllbGQpO1xuICAgICAgZW50cnkuc2V0KHZhbHVlKTtcbiAgICB9IGNhdGNoIChlcnIpIHtcbiAgICAgIHN1Y2Nlc3MgPSBmYWxzZTtcbiAgICAgIGNvbnN0IGUgPSBlcnIgYXMgRXJyb3IgJiB7IGNvZGU/OiBFcnJvckNvZGU7IHBhdGg/OiBzdHJpbmcgfTtcbiAgICAgIGVycm9ycy5wdXNoKHtcbiAgICAgICAgY29kZTogZS5jb2RlID8/IEVycm9yQ29kZS5URU1QTEFURV9QQVJTRV9FUlJPUixcbiAgICAgICAgbWVzc2FnZTogZS5tZXNzYWdlLFxuICAgICAgICBmaWVsZDogZW50cnkuZmllbGQsXG4gICAgICAgIHBhdGg6IGUucGF0aCxcbiAgICAgIH0pO1xuICAgIH1cbiAgfVxuICByZWNvcmRSZXNvbHV0aW9uKGRvY1R5cGUsIERhdGUubm93KCkgLSBzdGFydCwgc3VjY2Vzcyk7XG4gIHJldHVybiB7IGVycm9ycyB9O1xufVxuXG4vKipcbiAqIFJlc29sdmUgYSBzZWxmLXJlZmVyZW5jaW5nIGRvY3VtZW50IChlLmcuIHBpcGVsaW5lLmpzb24gd2hlcmVcbiAqIG1ldGFkYXRhIGZpZWxkcyByZWZlcmVuY2UgZWFjaCBvdGhlcikuIFRvcG9sb2dpY2FsbHkgb3JkZXJzIHRoZVxuICogcmVzb2x1dGlvbiBzbyBkZXBlbmRlbmNpZXMgY29tZSBmaXJzdC4gTXV0YXRlcyBgZG9jYCBpbiBwbGFjZS5cbiAqXG4gKiBgZmllbGRUb1Njb3BlUGF0aGAgbWFwcyBlYWNoIGZpZWxkIChlLmcuIGBtZXRhZGF0YS5lbnZgKSB0byB0aGUgc2NvcGVcbiAqIHBhdGggaXQgcG9wdWxhdGVzICh0eXBpY2FsbHkgdGhlIHNhbWUsIHNhbnMgYW55IGFycmF5IGluZGljZXMpLlxuICovXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVNlbGZSZWZlcmVuY2luZzxUIGV4dGVuZHMgb2JqZWN0PihcbiAgZG9jOiBULFxuICBzY29wZTogU2NvcGUsXG4gIGlzVGVtcGxhdGFibGU6IEZpZWxkUHJlZGljYXRlLFxuICBmaWVsZFRvU2NvcGVQYXRoOiAoZmllbGQ6IHN0cmluZykgPT4gc3RyaW5nIHwgbnVsbCxcbiAgZG9jVHlwZTogJ3BpcGVsaW5lJyB8ICdwbHVnaW4nID0gJ3BpcGVsaW5lJyxcbik6IFJlc29sdmVSZXN1bHQge1xuICBjb25zdCBzdGFydCA9IERhdGUubm93KCk7XG4gIC8vIGluY2x1ZGVMaXRlcmFsT25seTogYSBzZWxmLXJlZmVyZW5jaW5nIGZpZWxkIHRoYXQgaXMgb25seSBhIGB7e3t7YCBlc2NhcGVcbiAgLy8gc3RpbGwgbmVlZHMgaXRzIHVuZXNjYXBlIHdyaXR0ZW4gYmFjayAoc2FtZSByYXRpb25hbGUgYXMgcmVzb2x2ZVRlbXBsYXRlcykuXG4gIGNvbnN0IGVudHJpZXMgPSB3YWxrQW5kQmluZChkb2MsIGlzVGVtcGxhdGFibGUsIHRydWUpO1xuICAvLyBCdWlsZCB0b3BvIG5vZGVzIGtleWVkIGJ5IHRhcmdldCBzY29wZSBwYXRoXG4gIGNvbnN0IG5vZGVzID0gZW50cmllc1xuICAgIC5tYXAoZSA9PiB7XG4gICAgICBjb25zdCBrZXkgPSBmaWVsZFRvU2NvcGVQYXRoKGUuZmllbGQpO1xuICAgICAgaWYgKCFrZXkpIHJldHVybiBudWxsO1xuICAgICAgY29uc3QgZGVwczogc3RyaW5nW10gPSBbXTtcbiAgICAgIGZvciAoY29uc3QgdCBvZiBlLnRva2VucykgaWYgKHQua2luZCA9PT0gJ2V4cHInKSBkZXBzLnB1c2godC5wYXRoLmpvaW4oJy4nKSk7XG4gICAgICByZXR1cm4geyBrZXksIGRlcHMsIGVudHJ5OiBlIH07XG4gICAgfSlcbiAgICAuZmlsdGVyKChuKTogbiBpcyBOb25OdWxsYWJsZTx0eXBlb2Ygbj4gPT4gbiAhPT0gbnVsbCk7XG5cbiAgY29uc3QgeyBvcmRlcmVkLCBjeWNsZXMgfSA9IHRvcG9Tb3J0KG5vZGVzLm1hcChuID0+ICh7IGtleTogbi5rZXksIGRlcHM6IG4uZGVwcyB9KSkpO1xuICBjb25zdCBlcnJvcnM6IFJlc29sdmVSZXN1bHRbJ2Vycm9ycyddID0gW107XG4gIGlmIChjeWNsZXMubGVuZ3RoKSB7XG4gICAgZm9yIChjb25zdCBjIG9mIGN5Y2xlcykge1xuICAgICAgZXJyb3JzLnB1c2goe1xuICAgICAgICBjb2RlOiBFcnJvckNvZGUuVEVNUExBVEVfQ1lDTEUsXG4gICAgICAgIG1lc3NhZ2U6IGBUZW1wbGF0ZSBjeWNsZSBkZXRlY3RlZDogJHtjLmpvaW4oJyAtPiAnKX1gLFxuICAgICAgICBwYXRoOiBjLmpvaW4oJyAtPiAnKSxcbiAgICAgIH0pO1xuICAgIH1cbiAgICByZWNvcmRSZXNvbHV0aW9uKGRvY1R5cGUsIERhdGUubm93KCkgLSBzdGFydCwgZmFsc2UpO1xuICAgIHJldHVybiB7IGVycm9ycyB9O1xuICB9XG5cbiAgLy8gUmVzb2x2ZSBpbiB0b3BvIG9yZGVyOyBtdWx0aXBsZSBlbnRyaWVzIG1heSBzaGFyZSBhIHNjb3BlIGtleSAoZS5nLiBvbmVcbiAgLy8gcGlwZWxpbmUgZmllbGQgY291bGQgbWFwIHRvIHRoZSBzYW1lIHNjb3BlIHBhdGgg4oCUIHVubGlrZWx5IGhlcmUpLCBzb1xuICAvLyBpdGVyYXRlIGFsbCBlbnRyaWVzIHdob3NlIGtleSA9PSBvcmRlcmVkW2ldLlxuICBjb25zdCBieUtleSA9IG5ldyBNYXA8c3RyaW5nLCB0eXBlb2Ygbm9kZXM+KCk7XG4gIGZvciAoY29uc3QgbiBvZiBub2Rlcykge1xuICAgIGNvbnN0IGxpc3QgPSBieUtleS5nZXQobi5rZXkpID8/IFtdO1xuICAgIGxpc3QucHVzaChuKTtcbiAgICBieUtleS5zZXQobi5rZXksIGxpc3QpO1xuICB9XG5cbiAgbGV0IHN1Y2Nlc3MgPSB0cnVlO1xuICBmb3IgKGNvbnN0IGtleSBvZiBvcmRlcmVkKSB7XG4gICAgZm9yIChjb25zdCBuIG9mIGJ5S2V5LmdldChrZXkpID8/IFtdKSB7XG4gICAgICB0cnkge1xuICAgICAgICBjb25zdCB2YWx1ZSA9IGV2YWx1YXRvclJlc29sdmUobi5lbnRyeS50b2tlbnMsIHNjb3BlLCBuLmVudHJ5LmZpZWxkKTtcbiAgICAgICAgbi5lbnRyeS5zZXQodmFsdWUpO1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIHN1Y2Nlc3MgPSBmYWxzZTtcbiAgICAgICAgY29uc3QgZSA9IGVyciBhcyBFcnJvciAmIHsgY29kZT86IEVycm9yQ29kZTsgcGF0aD86IHN0cmluZyB9O1xuICAgICAgICBlcnJvcnMucHVzaCh7XG4gICAgICAgICAgY29kZTogZS5jb2RlID8/IEVycm9yQ29kZS5URU1QTEFURV9QQVJTRV9FUlJPUixcbiAgICAgICAgICBtZXNzYWdlOiBlLm1lc3NhZ2UsXG4gICAgICAgICAgZmllbGQ6IG4uZW50cnkuZmllbGQsXG4gICAgICAgICAgcGF0aDogZS5wYXRoLFxuICAgICAgICB9KTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgcmVjb3JkUmVzb2x1dGlvbihkb2NUeXBlLCBEYXRlLm5vdygpIC0gc3RhcnQsIHN1Y2Nlc3MpO1xuICByZXR1cm4geyBlcnJvcnMgfTtcbn1cblxuIl19
@@ -0,0 +1,10 @@
1
+ interface Counter {
2
+ inc(labels?: Record<string, string>, value?: number): void;
3
+ }
4
+ interface Histogram {
5
+ observe(labels: Record<string, string>, value: number): void;
6
+ }
7
+ export declare const templateResolutionsTotal: Counter;
8
+ export declare const templateResolutionDurationMs: Histogram;
9
+ export declare function recordResolution(doc: 'pipeline' | 'plugin', durationMs: number, success: boolean): void;
10
+ export {};
@@ -0,0 +1,43 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Prometheus metrics for template resolution. Optional — the module is
5
+ * only wired up when `prom-client` is already present in the host
6
+ * process (as is the case for api-server based services). Falls back to
7
+ * no-op stubs when unavailable so pipeline-core stays usable outside
8
+ * server contexts.
9
+ */
10
+ import { safeCreateRequire } from '@pipeline-builder/api-core';
11
+ // ESM has no global `require`; safeCreateRequire enables the synchronous,
12
+ // OPTIONAL lazy-load of prom-client below (no-op stubs when it isn't installed).
13
+ // (CJS-bundle safe — see api-core's safe-require.ts.)
14
+ const require = safeCreateRequire(import.meta.url);
15
+ function resolvePromClient() {
16
+ try {
17
+ // eslint-disable-next-line @typescript-eslint/no-require-imports
18
+ return require('prom-client');
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ const promClient = resolvePromClient();
25
+ function makeCounter(name, help, labelNames) {
26
+ if (!promClient)
27
+ return { inc: () => { } };
28
+ const existing = promClient.register.getSingleMetric(name);
29
+ return existing ?? new promClient.Counter({ name, help, labelNames });
30
+ }
31
+ function makeHistogram(name, help, labelNames, buckets) {
32
+ if (!promClient)
33
+ return { observe: () => { } };
34
+ const existing = promClient.register.getSingleMetric(name);
35
+ return existing ?? new promClient.Histogram({ name, help, labelNames, buckets });
36
+ }
37
+ export const templateResolutionsTotal = makeCounter('pipeline_builder_template_resolutions_total', 'Total number of template resolution operations', ['outcome', 'doc']);
38
+ export const templateResolutionDurationMs = makeHistogram('pipeline_builder_template_resolution_duration_ms', 'Template resolution duration in milliseconds', ['doc'], [1, 5, 10, 50, 100, 500, 1000]);
39
+ export function recordResolution(doc, durationMs, success) {
40
+ templateResolutionsTotal.inc({ outcome: success ? 'success' : 'error', doc });
41
+ templateResolutionDurationMs.observe({ doc }, durationMs);
42
+ }
43
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWV0cmljcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy90ZW1wbGF0ZS9tZXRyaWNzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLCtDQUErQztBQUMvQyxzQ0FBc0M7QUFFdEM7Ozs7OztHQU1HO0FBRUgsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFFL0QsMEVBQTBFO0FBQzFFLGlGQUFpRjtBQUNqRixzREFBc0Q7QUFDdEQsTUFBTSxPQUFPLEdBQUcsaUJBQWlCLENBQUMsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7QUFLbkQsU0FBUyxpQkFBaUI7SUFDeEIsSUFBSSxDQUFDO1FBQ0gsaUVBQWlFO1FBQ2pFLE9BQU8sT0FBTyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ2hDLENBQUM7SUFBQyxNQUFNLENBQUM7UUFDUCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7QUFDSCxDQUFDO0FBRUQsTUFBTSxVQUFVLEdBQUcsaUJBQWlCLEVBQUUsQ0FBQztBQUV2QyxTQUFTLFdBQVcsQ0FBQyxJQUFZLEVBQUUsSUFBWSxFQUFFLFVBQW9CO0lBQ25FLElBQUksQ0FBQyxVQUFVO1FBQUUsT0FBTyxFQUFFLEdBQUcsRUFBRSxHQUFHLEVBQUUsR0FBYyxDQUFDLEVBQUUsQ0FBQztJQUN0RCxNQUFNLFFBQVEsR0FBRyxVQUFVLENBQUMsUUFBUSxDQUFDLGVBQWUsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMzRCxPQUFPLFFBQVEsSUFBSSxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLFVBQVUsRUFBRSxDQUFDLENBQUM7QUFDeEUsQ0FBQztBQUVELFNBQVMsYUFBYSxDQUFDLElBQVksRUFBRSxJQUFZLEVBQUUsVUFBb0IsRUFBRSxPQUFpQjtJQUN4RixJQUFJLENBQUMsVUFBVTtRQUFFLE9BQU8sRUFBRSxPQUFPLEVBQUUsR0FBRyxFQUFFLEdBQWMsQ0FBQyxFQUFFLENBQUM7SUFDMUQsTUFBTSxRQUFRLEdBQUcsVUFBVSxDQUFDLFFBQVEsQ0FBQyxlQUFlLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDM0QsT0FBTyxRQUFRLElBQUksSUFBSSxVQUFVLENBQUMsU0FBUyxDQUFDLEVBQUUsSUFBSSxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsT0FBTyxFQUFFLENBQUMsQ0FBQztBQUNuRixDQUFDO0FBRUQsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsV0FBVyxDQUNqRCw2Q0FBNkMsRUFDN0MsZ0RBQWdELEVBQ2hELENBQUMsU0FBUyxFQUFFLEtBQUssQ0FBQyxDQUNuQixDQUFDO0FBRUYsTUFBTSxDQUFDLE1BQU0sNEJBQTRCLEdBQUcsYUFBYSxDQUN2RCxrREFBa0QsRUFDbEQsOENBQThDLEVBQzlDLENBQUMsS0FBSyxDQUFDLEVBQ1AsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsRUFBRSxFQUFFLEVBQUUsR0FBRyxFQUFFLEdBQUcsRUFBRSxJQUFJLENBQUMsQ0FDL0IsQ0FBQztBQUVGLE1BQU0sVUFBVSxnQkFBZ0IsQ0FBQyxHQUEwQixFQUFFLFVBQWtCLEVBQUUsT0FBZ0I7SUFDL0Ysd0JBQXdCLENBQUMsR0FBRyxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQU8sQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxPQUFPLEVBQUUsR0FBRyxFQUFFLENBQUMsQ0FBQztJQUM5RSw0QkFBNEIsQ0FBQyxPQUFPLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFBRSxVQUFVLENBQUMsQ0FBQztBQUM1RCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogUHJvbWV0aGV1cyBtZXRyaWNzIGZvciB0ZW1wbGF0ZSByZXNvbHV0aW9uLiBPcHRpb25hbCDigJQgdGhlIG1vZHVsZSBpc1xuICogb25seSB3aXJlZCB1cCB3aGVuIGBwcm9tLWNsaWVudGAgaXMgYWxyZWFkeSBwcmVzZW50IGluIHRoZSBob3N0XG4gKiBwcm9jZXNzIChhcyBpcyB0aGUgY2FzZSBmb3IgYXBpLXNlcnZlciBiYXNlZCBzZXJ2aWNlcykuIEZhbGxzIGJhY2sgdG9cbiAqIG5vLW9wIHN0dWJzIHdoZW4gdW5hdmFpbGFibGUgc28gcGlwZWxpbmUtY29yZSBzdGF5cyB1c2FibGUgb3V0c2lkZVxuICogc2VydmVyIGNvbnRleHRzLlxuICovXG5cbmltcG9ydCB7IHNhZmVDcmVhdGVSZXF1aXJlIH0gZnJvbSAnQHBpcGVsaW5lLWJ1aWxkZXIvYXBpLWNvcmUnO1xuXG4vLyBFU00gaGFzIG5vIGdsb2JhbCBgcmVxdWlyZWA7IHNhZmVDcmVhdGVSZXF1aXJlIGVuYWJsZXMgdGhlIHN5bmNocm9ub3VzLFxuLy8gT1BUSU9OQUwgbGF6eS1sb2FkIG9mIHByb20tY2xpZW50IGJlbG93IChuby1vcCBzdHVicyB3aGVuIGl0IGlzbid0IGluc3RhbGxlZCkuXG4vLyAoQ0pTLWJ1bmRsZSBzYWZlIOKAlCBzZWUgYXBpLWNvcmUncyBzYWZlLXJlcXVpcmUudHMuKVxuY29uc3QgcmVxdWlyZSA9IHNhZmVDcmVhdGVSZXF1aXJlKGltcG9ydC5tZXRhLnVybCk7XG5cbmludGVyZmFjZSBDb3VudGVyIHsgaW5jKGxhYmVscz86IFJlY29yZDxzdHJpbmcsIHN0cmluZz4sIHZhbHVlPzogbnVtYmVyKTogdm9pZCB9XG5pbnRlcmZhY2UgSGlzdG9ncmFtIHsgb2JzZXJ2ZShsYWJlbHM6IFJlY29yZDxzdHJpbmcsIHN0cmluZz4sIHZhbHVlOiBudW1iZXIpOiB2b2lkIH1cblxuZnVuY3Rpb24gcmVzb2x2ZVByb21DbGllbnQoKTogYW55IHwgbnVsbCB7XG4gIHRyeSB7XG4gICAgLy8gZXNsaW50LWRpc2FibGUtbmV4dC1saW5lIEB0eXBlc2NyaXB0LWVzbGludC9uby1yZXF1aXJlLWltcG9ydHNcbiAgICByZXR1cm4gcmVxdWlyZSgncHJvbS1jbGllbnQnKTtcbiAgfSBjYXRjaCB7XG4gICAgcmV0dXJuIG51bGw7XG4gIH1cbn1cblxuY29uc3QgcHJvbUNsaWVudCA9IHJlc29sdmVQcm9tQ2xpZW50KCk7XG5cbmZ1bmN0aW9uIG1ha2VDb3VudGVyKG5hbWU6IHN0cmluZywgaGVscDogc3RyaW5nLCBsYWJlbE5hbWVzOiBzdHJpbmdbXSk6IENvdW50ZXIge1xuICBpZiAoIXByb21DbGllbnQpIHJldHVybiB7IGluYzogKCkgPT4geyAvKiBub29wICovIH0gfTtcbiAgY29uc3QgZXhpc3RpbmcgPSBwcm9tQ2xpZW50LnJlZ2lzdGVyLmdldFNpbmdsZU1ldHJpYyhuYW1lKTtcbiAgcmV0dXJuIGV4aXN0aW5nID8/IG5ldyBwcm9tQ2xpZW50LkNvdW50ZXIoeyBuYW1lLCBoZWxwLCBsYWJlbE5hbWVzIH0pO1xufVxuXG5mdW5jdGlvbiBtYWtlSGlzdG9ncmFtKG5hbWU6IHN0cmluZywgaGVscDogc3RyaW5nLCBsYWJlbE5hbWVzOiBzdHJpbmdbXSwgYnVja2V0czogbnVtYmVyW10pOiBIaXN0b2dyYW0ge1xuICBpZiAoIXByb21DbGllbnQpIHJldHVybiB7IG9ic2VydmU6ICgpID0+IHsgLyogbm9vcCAqLyB9IH07XG4gIGNvbnN0IGV4aXN0aW5nID0gcHJvbUNsaWVudC5yZWdpc3Rlci5nZXRTaW5nbGVNZXRyaWMobmFtZSk7XG4gIHJldHVybiBleGlzdGluZyA/PyBuZXcgcHJvbUNsaWVudC5IaXN0b2dyYW0oeyBuYW1lLCBoZWxwLCBsYWJlbE5hbWVzLCBidWNrZXRzIH0pO1xufVxuXG5leHBvcnQgY29uc3QgdGVtcGxhdGVSZXNvbHV0aW9uc1RvdGFsID0gbWFrZUNvdW50ZXIoXG4gICdwaXBlbGluZV9idWlsZGVyX3RlbXBsYXRlX3Jlc29sdXRpb25zX3RvdGFsJyxcbiAgJ1RvdGFsIG51bWJlciBvZiB0ZW1wbGF0ZSByZXNvbHV0aW9uIG9wZXJhdGlvbnMnLFxuICBbJ291dGNvbWUnLCAnZG9jJ10sXG4pO1xuXG5leHBvcnQgY29uc3QgdGVtcGxhdGVSZXNvbHV0aW9uRHVyYXRpb25NcyA9IG1ha2VIaXN0b2dyYW0oXG4gICdwaXBlbGluZV9idWlsZGVyX3RlbXBsYXRlX3Jlc29sdXRpb25fZHVyYXRpb25fbXMnLFxuICAnVGVtcGxhdGUgcmVzb2x1dGlvbiBkdXJhdGlvbiBpbiBtaWxsaXNlY29uZHMnLFxuICBbJ2RvYyddLFxuICBbMSwgNSwgMTAsIDUwLCAxMDAsIDUwMCwgMTAwMF0sXG4pO1xuXG5leHBvcnQgZnVuY3Rpb24gcmVjb3JkUmVzb2x1dGlvbihkb2M6ICdwaXBlbGluZScgfCAncGx1Z2luJywgZHVyYXRpb25NczogbnVtYmVyLCBzdWNjZXNzOiBib29sZWFuKTogdm9pZCB7XG4gIHRlbXBsYXRlUmVzb2x1dGlvbnNUb3RhbC5pbmMoeyBvdXRjb21lOiBzdWNjZXNzID8gJ3N1Y2Nlc3MnIDogJ2Vycm9yJywgZG9jIH0pO1xuICB0ZW1wbGF0ZVJlc29sdXRpb25EdXJhdGlvbk1zLm9ic2VydmUoeyBkb2MgfSwgZHVyYXRpb25Ncyk7XG59XG4iXX0=
@@ -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,48 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ import { resolveTemplates } from './index.js';
4
+ /**
5
+ * Fields inside a Plugin record that accept `{{ ... }}` templates.
6
+ *
7
+ * Pure-string leaves only. `name`, `version`, `pluginType`, `computeType`,
8
+ * `timeout`, `secrets`, `failureBehavior`, `requiredMetadata`, `requiredVars`
9
+ * stay literal. `metadata.*` is excluded because CDK-metadata values are
10
+ * structural, not user-interpolated.
11
+ */
12
+ const TEMPLATABLE_FIELDS = [
13
+ 'description',
14
+ 'commands', // string[]
15
+ 'installCommands', // string[]
16
+ 'env', // Record<string, string>
17
+ 'buildArgs', // Record<string, string>
18
+ ];
19
+ export function isPluginTemplatableField(field) {
20
+ // `commands` / `installCommands` are arrays of strings → entries like 'commands[0]'
21
+ // `env` / `buildArgs` are objects → entries like 'env.STAGE'
22
+ return TEMPLATABLE_FIELDS.some(f => field === f || field.startsWith(`${f}[`) || field.startsWith(`${f}.`));
23
+ }
24
+ /**
25
+ * Return a shallow clone of `plugin` with all `{{ ... }}` templates
26
+ * resolved against the given pipeline scope. Caller must pre-populate
27
+ * `pipelineScope` with `{ pipeline, plugin, env }` keys.
28
+ */
29
+ export function resolvePluginTemplates(plugin, pipelineScope) {
30
+ // Deep clone so mutations don't leak back to the caller's Plugin.
31
+ // Structural fields we care about are plain JSON; structuredClone is safe.
32
+ const clone = structuredClone(plugin);
33
+ const scope = {
34
+ ...pipelineScope,
35
+ plugin: { name: plugin.name, version: plugin.version },
36
+ env: plugin.env ?? {},
37
+ };
38
+ const { errors } = resolveTemplates(clone, scope, isPluginTemplatableField, 'plugin');
39
+ if (errors.length > 0) {
40
+ // First error wins — resolver errors should never be batched at synth time
41
+ // because a broken template is a programmer error, not a validation step.
42
+ const e = errors[0];
43
+ const msg = `Template resolution failed in plugin "${plugin.name}" at field '${e.field}': ${e.message}`;
44
+ throw new Error(msg);
45
+ }
46
+ return clone;
47
+ }
48
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLXJlc29sdmVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3BsdWdpbi1yZXNvbHZlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBR3RDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUU5Qzs7Ozs7OztHQU9HO0FBQ0gsTUFBTSxrQkFBa0IsR0FBRztJQUN6QixhQUFhO0lBQ2IsVUFBVSxFQUFFLFdBQVc7SUFDdkIsaUJBQWlCLEVBQUUsV0FBVztJQUM5QixLQUFLLEVBQUUseUJBQXlCO0lBQ2hDLFdBQVcsRUFBRSx5QkFBeUI7Q0FDOUIsQ0FBQztBQUVYLE1BQU0sVUFBVSx3QkFBd0IsQ0FBQyxLQUFhO0lBQ3BELG9GQUFvRjtJQUNwRiw2REFBNkQ7SUFDN0QsT0FBTyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEtBQUssQ0FBQyxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxJQUFJLEtBQUssQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7QUFDN0csQ0FBQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFNLFVBQVUsc0JBQXNCLENBQ3BDLE1BQWMsRUFDZCxhQUFzQztJQUV0QyxrRUFBa0U7SUFDbEUsMkVBQTJFO0lBQzNFLE1BQU0sS0FBSyxHQUFHLGVBQWUsQ0FBQyxNQUFNLENBQXFDLENBQUM7SUFFMUUsTUFBTSxLQUFLLEdBQUc7UUFDWixHQUFHLGFBQWE7UUFDaEIsTUFBTSxFQUFFLEVBQUUsSUFBSSxFQUFFLE1BQU0sQ0FBQyxJQUFJLEVBQUUsT0FBTyxFQUFFLE1BQU0sQ0FBQyxPQUFPLEVBQUU7UUFDdEQsR0FBRyxFQUFFLE1BQU0sQ0FBQyxHQUFHLElBQUksRUFBRTtLQUN0QixDQUFDO0lBRUYsTUFBTSxFQUFFLE1BQU0sRUFBRSxHQUFHLGdCQUFnQixDQUFDLEtBQUssRUFBRSxLQUFLLEVBQUUsd0JBQXdCLEVBQUUsUUFBUSxDQUFDLENBQUM7SUFDdEYsSUFBSSxNQUFNLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1FBQ3RCLDJFQUEyRTtRQUMzRSwwRUFBMEU7UUFDMUUsTUFBTSxDQUFDLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBRSxDQUFDO1FBQ3JCLE1BQU0sR0FBRyxHQUFHLHlDQUF5QyxNQUFNLENBQUMsSUFBSSxlQUFlLENBQUMsQ0FBQyxLQUFLLE1BQU0sQ0FBQyxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBQ3hHLE1BQU0sSUFBSSxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDdkIsQ0FBQztJQUNELE9BQU8sS0FBSyxDQUFDO0FBQ2YsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG5pbXBvcnQgdHlwZSB7IFBsdWdpbiB9IGZyb20gJ0BwaXBlbGluZS1idWlsZGVyL3BpcGVsaW5lLWRhdGEnO1xuaW1wb3J0IHsgcmVzb2x2ZVRlbXBsYXRlcyB9IGZyb20gJy4vaW5kZXguanMnO1xuXG4vKipcbiAqIEZpZWxkcyBpbnNpZGUgYSBQbHVnaW4gcmVjb3JkIHRoYXQgYWNjZXB0IGB7eyAuLi4gfX1gIHRlbXBsYXRlcy5cbiAqXG4gKiBQdXJlLXN0cmluZyBsZWF2ZXMgb25seS4gYG5hbWVgLCBgdmVyc2lvbmAsIGBwbHVnaW5UeXBlYCwgYGNvbXB1dGVUeXBlYCxcbiAqIGB0aW1lb3V0YCwgYHNlY3JldHNgLCBgZmFpbHVyZUJlaGF2aW9yYCwgYHJlcXVpcmVkTWV0YWRhdGFgLCBgcmVxdWlyZWRWYXJzYFxuICogc3RheSBsaXRlcmFsLiBgbWV0YWRhdGEuKmAgaXMgZXhjbHVkZWQgYmVjYXVzZSBDREstbWV0YWRhdGEgdmFsdWVzIGFyZVxuICogc3RydWN0dXJhbCwgbm90IHVzZXItaW50ZXJwb2xhdGVkLlxuICovXG5jb25zdCBURU1QTEFUQUJMRV9GSUVMRFMgPSBbXG4gICdkZXNjcmlwdGlvbicsXG4gICdjb21tYW5kcycsIC8vIHN0cmluZ1tdXG4gICdpbnN0YWxsQ29tbWFuZHMnLCAvLyBzdHJpbmdbXVxuICAnZW52JywgLy8gUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuICAnYnVpbGRBcmdzJywgLy8gUmVjb3JkPHN0cmluZywgc3RyaW5nPlxuXSBhcyBjb25zdDtcblxuZXhwb3J0IGZ1bmN0aW9uIGlzUGx1Z2luVGVtcGxhdGFibGVGaWVsZChmaWVsZDogc3RyaW5nKTogYm9vbGVhbiB7XG4gIC8vIGBjb21tYW5kc2AgLyBgaW5zdGFsbENvbW1hbmRzYCBhcmUgYXJyYXlzIG9mIHN0cmluZ3Mg4oaSIGVudHJpZXMgbGlrZSAnY29tbWFuZHNbMF0nXG4gIC8vIGBlbnZgIC8gYGJ1aWxkQXJnc2AgYXJlIG9iamVjdHMg4oaSIGVudHJpZXMgbGlrZSAnZW52LlNUQUdFJ1xuICByZXR1cm4gVEVNUExBVEFCTEVfRklFTERTLnNvbWUoZiA9PiBmaWVsZCA9PT0gZiB8fCBmaWVsZC5zdGFydHNXaXRoKGAke2Z9W2ApIHx8IGZpZWxkLnN0YXJ0c1dpdGgoYCR7Zn0uYCkpO1xufVxuXG4vKipcbiAqIFJldHVybiBhIHNoYWxsb3cgY2xvbmUgb2YgYHBsdWdpbmAgd2l0aCBhbGwgYHt7IC4uLiB9fWAgdGVtcGxhdGVzXG4gKiByZXNvbHZlZCBhZ2FpbnN0IHRoZSBnaXZlbiBwaXBlbGluZSBzY29wZS4gQ2FsbGVyIG11c3QgcHJlLXBvcHVsYXRlXG4gKiBgcGlwZWxpbmVTY29wZWAgd2l0aCBgeyBwaXBlbGluZSwgcGx1Z2luLCBlbnYgfWAga2V5cy5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHJlc29sdmVQbHVnaW5UZW1wbGF0ZXMoXG4gIHBsdWdpbjogUGx1Z2luLFxuICBwaXBlbGluZVNjb3BlOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPixcbik6IFBsdWdpbiB7XG4gIC8vIERlZXAgY2xvbmUgc28gbXV0YXRpb25zIGRvbid0IGxlYWsgYmFjayB0byB0aGUgY2FsbGVyJ3MgUGx1Z2luLlxuICAvLyBTdHJ1Y3R1cmFsIGZpZWxkcyB3ZSBjYXJlIGFib3V0IGFyZSBwbGFpbiBKU09OOyBzdHJ1Y3R1cmVkQ2xvbmUgaXMgc2FmZS5cbiAgY29uc3QgY2xvbmUgPSBzdHJ1Y3R1cmVkQ2xvbmUocGx1Z2luKSBhcyBQbHVnaW4gJiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPjtcblxuICBjb25zdCBzY29wZSA9IHtcbiAgICAuLi5waXBlbGluZVNjb3BlLFxuICAgIHBsdWdpbjogeyBuYW1lOiBwbHVnaW4ubmFtZSwgdmVyc2lvbjogcGx1Z2luLnZlcnNpb24gfSxcbiAgICBlbnY6IHBsdWdpbi5lbnYgPz8ge30sXG4gIH07XG5cbiAgY29uc3QgeyBlcnJvcnMgfSA9IHJlc29sdmVUZW1wbGF0ZXMoY2xvbmUsIHNjb3BlLCBpc1BsdWdpblRlbXBsYXRhYmxlRmllbGQsICdwbHVnaW4nKTtcbiAgaWYgKGVycm9ycy5sZW5ndGggPiAwKSB7XG4gICAgLy8gRmlyc3QgZXJyb3Igd2lucyDigJQgcmVzb2x2ZXIgZXJyb3JzIHNob3VsZCBuZXZlciBiZSBiYXRjaGVkIGF0IHN5bnRoIHRpbWVcbiAgICAvLyBiZWNhdXNlIGEgYnJva2VuIHRlbXBsYXRlIGlzIGEgcHJvZ3JhbW1lciBlcnJvciwgbm90IGEgdmFsaWRhdGlvbiBzdGVwLlxuICAgIGNvbnN0IGUgPSBlcnJvcnNbMF0hO1xuICAgIGNvbnN0IG1zZyA9IGBUZW1wbGF0ZSByZXNvbHV0aW9uIGZhaWxlZCBpbiBwbHVnaW4gXCIke3BsdWdpbi5uYW1lfVwiIGF0IGZpZWxkICcke2UuZmllbGR9JzogJHtlLm1lc3NhZ2V9YDtcbiAgICB0aHJvdyBuZXcgRXJyb3IobXNnKTtcbiAgfVxuICByZXR1cm4gY2xvbmU7XG59XG4iXX0=
@@ -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,233 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Tokenizer for the pipeline-builder template grammar.
5
+ *
6
+ * Grammar:
7
+ * Template := (Literal | Expr)*
8
+ * Expr := "{{" ws Path (ws Filter)? ws "}}"
9
+ * Path := Identifier ("." Identifier)*
10
+ * Identifier := [a-zA-Z_][a-zA-Z0-9_]{0,63}
11
+ * Filter := "|" ws "default" ws ":" ws Quoted
12
+ * Quoted := "'" ... "'" | "\"" ... "\""
13
+ *
14
+ * `{{{{` is the escape sequence for a literal `{{`.
15
+ */
16
+ export const MAX_FIELD_SIZE_BYTES = 4 * 1024;
17
+ export const MAX_PATH_DEPTH = 5;
18
+ export const MAX_IDENTIFIER_LENGTH = 64;
19
+ export class TokenizerError extends Error {
20
+ pos;
21
+ constructor(message, pos) {
22
+ super(`${message} at line ${pos.line}, col ${pos.col}`);
23
+ this.pos = pos;
24
+ this.name = 'TokenizerError';
25
+ }
26
+ }
27
+ /**
28
+ * Tokenize a template source string. Never throws on valid input;
29
+ * throws `TokenizerError` with source position on malformed templates.
30
+ */
31
+ export function tokenize(source) {
32
+ // Byte count, not `.length` (UTF-16 code units): the cap is stated in bytes,
33
+ // and multi-byte characters (emoji, non-ASCII) would otherwise be undercounted.
34
+ if (Buffer.byteLength(source, 'utf8') > MAX_FIELD_SIZE_BYTES) {
35
+ throw new TokenizerError(`Field exceeds max size of ${MAX_FIELD_SIZE_BYTES} bytes`, { line: 1, col: 1 });
36
+ }
37
+ const tokens = [];
38
+ let i = 0;
39
+ let line = 1;
40
+ let col = 1;
41
+ let literalStart = 0;
42
+ let literalStartPos = { line: 1, col: 1 };
43
+ const advance = (n) => {
44
+ for (let k = 0; k < n; k++) {
45
+ if (source[i + k] === '\n') {
46
+ line++;
47
+ col = 1;
48
+ }
49
+ else {
50
+ col++;
51
+ }
52
+ }
53
+ i += n;
54
+ };
55
+ const flushLiteral = (endIdx) => {
56
+ if (endIdx > literalStart) {
57
+ // Unescape `{{{{` → `{{`
58
+ const raw = source.slice(literalStart, endIdx);
59
+ const value = raw.replace(/\{\{\{\{/g, '{{');
60
+ tokens.push({ kind: 'literal', value, pos: literalStartPos });
61
+ }
62
+ };
63
+ const startLiteral = () => {
64
+ literalStart = i;
65
+ literalStartPos = { line, col };
66
+ };
67
+ startLiteral();
68
+ while (i < source.length) {
69
+ // Escape: `{{{{` → literal `{{`
70
+ if (source.startsWith('{{{{', i)) {
71
+ advance(4);
72
+ continue;
73
+ }
74
+ // Expression start
75
+ if (source.startsWith('{{', i)) {
76
+ flushLiteral(i);
77
+ const exprPos = { line, col };
78
+ advance(2);
79
+ const expr = readExpression(source, i, line, col);
80
+ tokens.push({
81
+ kind: 'expr',
82
+ path: expr.path,
83
+ defaultValue: expr.defaultValue,
84
+ coerce: expr.coerce,
85
+ source: source.slice(i - 2, expr.endIdx),
86
+ pos: exprPos,
87
+ });
88
+ advance(expr.endIdx - i);
89
+ startLiteral();
90
+ continue;
91
+ }
92
+ // Stray `}}` outside an expression
93
+ if (source.startsWith('}}', i)) {
94
+ throw new TokenizerError("Unexpected '}}' outside expression", { line, col });
95
+ }
96
+ advance(1);
97
+ }
98
+ flushLiteral(i);
99
+ return tokens;
100
+ }
101
+ function readExpression(source, startIdx, startLine, startCol) {
102
+ let i = startIdx;
103
+ let line = startLine;
104
+ let col = startCol;
105
+ const skipWs = () => {
106
+ while (i < source.length && (source[i] === ' ' || source[i] === '\t')) {
107
+ i++;
108
+ col++;
109
+ }
110
+ };
111
+ const readIdentifier = () => {
112
+ const startI = i;
113
+ if (i >= source.length || !/[a-zA-Z_]/.test(source[i])) {
114
+ throw new TokenizerError('Expected identifier inside template expression', { line, col });
115
+ }
116
+ while (i < source.length && /[a-zA-Z0-9_]/.test(source[i])) {
117
+ i++;
118
+ col++;
119
+ }
120
+ const ident = source.slice(startI, i);
121
+ if (ident.length > MAX_IDENTIFIER_LENGTH) {
122
+ throw new TokenizerError(`Identifier exceeds max length of ${MAX_IDENTIFIER_LENGTH}`, { line, col: col - ident.length });
123
+ }
124
+ return ident;
125
+ };
126
+ const readQuoted = () => {
127
+ const quote = source[i];
128
+ if (quote !== '"' && quote !== "'") {
129
+ throw new TokenizerError('Expected quoted string', { line, col });
130
+ }
131
+ i++;
132
+ col++;
133
+ const parts = [];
134
+ while (i < source.length && source[i] !== quote) {
135
+ if (source[i] === '\\') {
136
+ const next = source[i + 1];
137
+ if (next === '\\' || next === quote) {
138
+ parts.push(next);
139
+ i += 2;
140
+ col += 2;
141
+ continue;
142
+ }
143
+ throw new TokenizerError(`Invalid escape '\\${next ?? ''}' in quoted string`, { line, col });
144
+ }
145
+ if (source[i] === '\n') {
146
+ throw new TokenizerError('Unterminated quoted string', { line, col });
147
+ }
148
+ parts.push(source[i]);
149
+ i++;
150
+ col++;
151
+ }
152
+ if (i >= source.length) {
153
+ throw new TokenizerError('Unterminated quoted string', { line, col });
154
+ }
155
+ i++;
156
+ col++; // consume closing quote
157
+ return parts.join('');
158
+ };
159
+ skipWs();
160
+ const path = [readIdentifier()];
161
+ while (i < source.length && source[i] === '.') {
162
+ i++;
163
+ col++;
164
+ path.push(readIdentifier());
165
+ if (path.length > MAX_PATH_DEPTH) {
166
+ throw new TokenizerError(`Path depth exceeds max of ${MAX_PATH_DEPTH}`, { line, col });
167
+ }
168
+ }
169
+ skipWs();
170
+ let defaultValue;
171
+ let coerce;
172
+ // Filters chain: `| default: 'x' | number`, `| bool`, `| json`, etc.
173
+ // - `default` takes a quoted argument — applied first (before coercion)
174
+ // - `number`, `bool`, `json` are argument-less coercion filters — applied last
175
+ while (source[i] === '|') {
176
+ i++;
177
+ col++;
178
+ skipWs();
179
+ if (source.startsWith('default', i)) {
180
+ if (defaultValue !== undefined) {
181
+ throw new TokenizerError("'default' filter may only appear once", { line, col });
182
+ }
183
+ i += 'default'.length;
184
+ col += 'default'.length;
185
+ skipWs();
186
+ if (source[i] !== ':') {
187
+ throw new TokenizerError("Expected ':' after 'default'", { line, col });
188
+ }
189
+ i++;
190
+ col++;
191
+ skipWs();
192
+ defaultValue = readQuoted();
193
+ skipWs();
194
+ continue;
195
+ }
196
+ let matchedCoerce = null;
197
+ for (const kind of ['number', 'bool', 'json']) {
198
+ if (source.startsWith(kind, i)) {
199
+ // Ensure identifier boundary — `numberish` must not match `number`
200
+ const next = source[i + kind.length];
201
+ if (!next || !/[a-zA-Z0-9_]/.test(next)) {
202
+ matchedCoerce = kind;
203
+ i += kind.length;
204
+ col += kind.length;
205
+ break;
206
+ }
207
+ }
208
+ }
209
+ if (!matchedCoerce) {
210
+ throw new TokenizerError("Unknown filter — supported: 'default', 'number', 'bool', 'json'", { line, col });
211
+ }
212
+ if (coerce) {
213
+ throw new TokenizerError('Only one coercion filter is allowed per expression', { line, col });
214
+ }
215
+ coerce = matchedCoerce;
216
+ skipWs();
217
+ }
218
+ if (!source.startsWith('}}', i)) {
219
+ throw new TokenizerError("Expected '}}'", { line, col });
220
+ }
221
+ i += 2;
222
+ return { path, defaultValue, coerce, endIdx: i };
223
+ }
224
+ /**
225
+ * Convenience: returns true if the source contains any templates.
226
+ * Used as a fast-path to skip tokenization for strings that are obviously literal.
227
+ */
228
+ export function hasTemplate(source) {
229
+ // Not completely accurate (a lone `{{{{` would return true) but that's fine —
230
+ // tokenize() will produce a literal-only token list and round-trip correctly.
231
+ return source.includes('{{');
232
+ }
233
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidG9rZW5pemVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL3RlbXBsYXRlL3Rva2VuaXplci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7R0FZRztBQUVILE1BQU0sQ0FBQyxNQUFNLG9CQUFvQixHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFDN0MsTUFBTSxDQUFDLE1BQU0sY0FBYyxHQUFHLENBQUMsQ0FBQztBQUNoQyxNQUFNLENBQUMsTUFBTSxxQkFBcUIsR0FBRyxFQUFFLENBQUM7QUEwQnhDLE1BQU0sT0FBTyxjQUFlLFNBQVEsS0FBSztJQUdyQixHQUFHO0lBRnJCLFlBQ0UsT0FBZSxFQUNDLEdBQW1CO1FBRW5DLEtBQUssQ0FBQyxHQUFHLE9BQU8sWUFBWSxHQUFHLENBQUMsSUFBSSxTQUFTLEdBQUcsQ0FBQyxHQUFHLEVBQUUsQ0FBQyxDQUFDO21CQUZ4QyxHQUFHO1FBR25CLElBQUksQ0FBQyxJQUFJLEdBQUcsZ0JBQWdCLENBQUM7SUFDL0IsQ0FBQztDQUNGO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLFFBQVEsQ0FBQyxNQUFjO0lBQ3JDLDZFQUE2RTtJQUM3RSxnRkFBZ0Y7SUFDaEYsSUFBSSxNQUFNLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxNQUFNLENBQUMsR0FBRyxvQkFBb0IsRUFBRSxDQUFDO1FBQzdELE1BQU0sSUFBSSxjQUFjLENBQ3RCLDZCQUE2QixvQkFBb0IsUUFBUSxFQUN6RCxFQUFFLElBQUksRUFBRSxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsRUFBRSxDQUNwQixDQUFDO0lBQ0osQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFZLEVBQUUsQ0FBQztJQUMzQixJQUFJLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDVixJQUFJLElBQUksR0FBRyxDQUFDLENBQUM7SUFDYixJQUFJLEdBQUcsR0FBRyxDQUFDLENBQUM7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LFlBQVksRUFBRSxNQUFNLEVBQUUsTUFBTSxFQUFFLENBQUMsRUFBRSxDQUFDO0FBQ25ELENBQUM7QUFFRDs7O0dBR0c7QUFDSCxNQUFNLFVBQVUsV0FBVyxDQUFDLE1BQWM7SUFDeEMsOEVBQThFO0lBQzlFLDhFQUE4RTtJQUM5RSxPQUFPLE1BQU0sQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7QUFDL0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8vIENvcHlyaWdodCAyMDI2IFBpcGVsaW5lIEJ1aWxkZXIgQ29udHJpYnV0b3JzXG4vLyBTUERYLUxpY2Vuc2UtSWRlbnRpZmllcjogQXBhY2hlLTIuMFxuXG4vKipcbiAqIFRva2VuaXplciBmb3IgdGhlIHBpcGVsaW5lLWJ1aWxkZXIgdGVtcGxhdGUgZ3JhbW1hci5cbiAqXG4gKiBHcmFtbWFyOlxuICogICBUZW1wbGF0ZSAgIDo9IChMaXRlcmFsIHwgRXhwcikqXG4gKiAgIEV4cHIgICAgICAgOj0gXCJ7e1wiIHdzIFBhdGggKHdzIEZpbHRlcik/IHdzIFwifX1cIlxuICogICBQYXRoICAgICAgIDo9IElkZW50aWZpZXIgKFwiLlwiIElkZW50aWZpZXIpKlxuICogICBJZGVudGlmaWVyIDo9IFthLXpBLVpfXVthLXpBLVowLTlfXXswLDYzfVxuICogICBGaWx0ZXIgICAgIDo9IFwifFwiIHdzIFwiZGVmYXVsdFwiIHdzIFwiOlwiIHdzIFF1b3RlZFxuICogICBRdW90ZWQgICAgIDo9IFwiJ1wiIC4uLiBcIidcIiB8IFwiXFxcIlwiIC4uLiBcIlxcXCJcIlxuICpcbiAqIGB7e3t7YCBpcyB0aGUgZXNjYXBlIHNlcXVlbmNlIGZvciBhIGxpdGVyYWwgYHt7YC5cbiAqL1xuXG5leHBvcnQgY29uc3QgTUFYX0ZJRUxEX1NJWkVfQllURVMgPSA0ICogMTAyNDtcbmV4cG9ydCBjb25zdCBNQVhfUEFUSF9ERVBUSCA9IDU7XG5leHBvcnQgY29uc3QgTUFYX0lERU5USUZJRVJfTEVOR1RIID0gNjQ7XG5cbmV4cG9ydCBpbnRlcmZhY2UgU291cmNlUG9zaXRpb24ge1xuICBsaW5lOiBudW1iZXI7XG4gIGNvbDogbnVtYmVyO1xufVxuXG5leHBvcnQgaW50ZXJmYWNlIExpdGVyYWxUb2tlbiB7XG4gIGtpbmQ6ICdsaXRlcmFsJztcbiAgdmFsdWU6IHN0cmluZztcbiAgcG9zOiBTb3VyY2VQb3NpdGlvbjtcbn1cblxuZXhwb3J0IHR5cGUgQ29lcmNlS2luZCA9ICdudW1iZXInIHwgJ2Jvb2wnIHwgJ2pzb24nO1xuXG5leHBvcnQgaW50ZXJmYWNlIEV4cHJUb2tlbiB7XG4gIGtpbmQ6ICdleHByJztcbiAgcGF0aDogc3RyaW5nW107IC8vIGUuZy4gWydwaXBlbGluZScsICdtZXRhZGF0YScsICdlbnYnXVxuICBkZWZhdWx0VmFsdWU/OiBzdHJpbmc7IC8vIHZhbHVlIGZvciBgfCBkZWZhdWx0OiAnLi4uJ2AgZmlsdGVyXG4gIGNvZXJjZT86IENvZXJjZUtpbmQ7IC8vIGB8IG51bWJlcmAsIGB8IGJvb2xgLCBgfCBqc29uYFxuICBzb3VyY2U6IHN0cmluZzsgLy8gb3JpZ2luYWwgXCJ7eyAuLi4gfX1cIiB0ZXh0IChmb3IgZXJyb3IgbWVzc2FnZXMpXG4gIHBvczogU291cmNlUG9zaXRpb247XG59XG5cbmV4cG9ydCB0eXBlIFRva2VuID0gTGl0ZXJhbFRva2VuIHwgRXhwclRva2VuO1xuXG5leHBvcnQgY2xhc3MgVG9rZW5pemVyRXJyb3IgZXh0ZW5kcyBFcnJvciB7XG4gIGNvbnN0cnVjdG9yKFxuICAgIG1lc3NhZ2U6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgcG9zOiBTb3VyY2VQb3NpdGlvbixcbiAgKSB7XG4gICAgc3VwZXIoYCR7bWVzc2FnZX0gYXQgbGluZSAke3Bvcy5saW5lfSwgY29sICR7cG9zLmNvbH1gKTtcbiAgICB0aGlzLm5hbWUgPSAnVG9rZW5pemVyRXJyb3InO1xuICB9XG59XG5cbi8qKlxuICogVG9rZW5pemUgYSB0ZW1wbGF0ZSBzb3VyY2Ugc3RyaW5nLiBOZXZlciB0aHJvd3Mgb24gdmFsaWQgaW5wdXQ7XG4gKiB0aHJvd3MgYFRva2VuaXplckVycm9yYCB3aXRoIHNvdXJjZSBwb3NpdGlvbiBvbiBtYWxmb3JtZWQgdGVtcGxhdGVzLlxuICovXG5leHBvcnQgZnVuY3Rpb24gdG9rZW5pemUoc291cmNlOiBzdHJpbmcpOiBUb2tlbltdIHtcbiAgLy8gQnl0ZSBjb3VudCwgbm90IGAubGVuZ3RoYCAoVVRGLTE2IGNvZGUgdW5pdHMpOiB0aGUgY2FwIGlzIHN0YXRlZCBpbiBieXRlcyxcbiAgLy8gYW5kIG11bHRpLWJ5dGUgY2hhcmFjdGVycyAoZW1vamksIG5vbi1BU0NJSSkgd291bGQgb3RoZXJ3aXNlIGJlIHVuZGVyY291bnRlZC5cbiAgaWYgKEJ1ZmZlci5ieXRlTGVuZ3RoKHNvdXJjZSwgJ3V0ZjgnKSA+IE1BWF9GSUVMRF9TSVpFX0JZVEVTKSB7XG4gICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKFxuICAgICAgYEZpZWxkIGV4Y2VlZHMgbWF4IHNpemUgb2YgJHtNQVhfRklFTERfU0laRV9CWVRFU30gYnl0ZXNgLFxuICAgICAgeyBsaW5lOiAxLCBjb2w6IDEgfSxcbiAgICApO1xuICB9XG5cbiAgY29uc3QgdG9rZW5zOiBUb2tlbltdID0gW107XG4gIGxldCBpID0gMDtcbiAgbGV0IGxpbmUgPSAxO1xuICBsZXQgY29sID0gMTtcbiAgbGV0IGxpdGVyYWxTdGFydCA9IDA7XG4gIGxldCBsaXRlcmFsU3RhcnRQb3M6IFNvdXJjZVBvc2l0aW9uID0geyBsaW5lOiAxLCBjb2w6IDEgfTtcblxuICBjb25zdCBhZHZhbmNlID0gKG46IG51bWJlcik6IHZvaWQgPT4ge1xuICAgIGZvciAobGV0IGsgPSAwOyBrIDwgbjsgaysrKSB7XG4gICAgICBpZiAoc291cmNlW2kgKyBrXSA9PT0gJ1xcbicpIHtcbiAgICAgICAgbGluZSsrO1xuICAgICAgICBjb2wgPSAxO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgY29sKys7XG4gICAgICB9XG4gICAgfVxuICAgIGkgKz0gbjtcbiAgfTtcblxuICBjb25zdCBmbHVzaExpdGVyYWwgPSAoZW5kSWR4OiBudW1iZXIpOiB2b2lkID0+IHtcbiAgICBpZiAoZW5kSWR4ID4gbGl0ZXJhbFN0YXJ0KSB7XG4gICAgICAvLyBVbmVzY2FwZSBge3t7e2Ag4oaSIGB7e2BcbiAgICAgIGNvbnN0IHJhdyA9IHNvdXJjZS5zbGljZShsaXRlcmFsU3RhcnQsIGVuZElkeCk7XG4gICAgICBjb25zdCB2YWx1ZSA9IHJhdy5yZXBsYWNlKC9cXHtcXHtcXHtcXHsvZywgJ3t7Jyk7XG4gICAgICB0b2tlbnMucHVzaCh7IGtpbmQ6ICdsaXRlcmFsJywgdmFsdWUsIHBvczogbGl0ZXJhbFN0YXJ0UG9zIH0pO1xuICAgIH1cbiAgfTtcblxuICBjb25zdCBzdGFydExpdGVyYWwgPSAoKTogdm9pZCA9PiB7XG4gICAgbGl0ZXJhbFN0YXJ0ID0gaTtcbiAgICBsaXRlcmFsU3RhcnRQb3MgPSB7IGxpbmUsIGNvbCB9O1xuICB9O1xuXG4gIHN0YXJ0TGl0ZXJhbCgpO1xuXG4gIHdoaWxlIChpIDwgc291cmNlLmxlbmd0aCkge1xuICAgIC8vIEVzY2FwZTogYHt7e3tgIOKGkiBsaXRlcmFsIGB7e2BcbiAgICBpZiAoc291cmNlLnN0YXJ0c1dpdGgoJ3t7e3snLCBpKSkge1xuICAgICAgYWR2YW5jZSg0KTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cbiAgICAvLyBFeHByZXNzaW9uIHN0YXJ0XG4gICAgaWYgKHNvdXJjZS5zdGFydHNXaXRoKCd7eycsIGkpKSB7XG4gICAgICBmbHVzaExpdGVyYWwoaSk7XG4gICAgICBjb25zdCBleHByUG9zOiBTb3VyY2VQb3NpdGlvbiA9IHsgbGluZSwgY29sIH07XG4gICAgICBhZHZhbmNlKDIpO1xuICAgICAgY29uc3QgZXhwciA9IHJlYWRFeHByZXNzaW9uKHNvdXJjZSwgaSwgbGluZSwgY29sKTtcbiAgICAgIHRva2Vucy5wdXNoKHtcbiAgICAgICAga2luZDogJ2V4cHInLFxuICAgICAgICBwYXRoOiBleHByLnBhdGgsXG4gICAgICAgIGRlZmF1bHRWYWx1ZTogZXhwci5kZWZhdWx0VmFsdWUsXG4gICAgICAgIGNvZXJjZTogZXhwci5jb2VyY2UsXG4gICAgICAgIHNvdXJjZTogc291cmNlLnNsaWNlKGkgLSAyLCBleHByLmVuZElkeCksXG4gICAgICAgIHBvczogZXhwclBvcyxcbiAgICAgIH0pO1xuICAgICAgYWR2YW5jZShleHByLmVuZElkeCAtIGkpO1xuICAgICAgc3RhcnRMaXRlcmFsKCk7XG4gICAgICBjb250aW51ZTtcbiAgICB9XG4gICAgLy8gU3RyYXkgYH19YCBvdXRzaWRlIGFuIGV4cHJlc3Npb25cbiAgICBpZiAoc291cmNlLnN0YXJ0c1dpdGgoJ319JywgaSkpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcIlVuZXhwZWN0ZWQgJ319JyBvdXRzaWRlIGV4cHJlc3Npb25cIiwgeyBsaW5lLCBjb2wgfSk7XG4gICAgfVxuICAgIGFkdmFuY2UoMSk7XG4gIH1cblxuICBmbHVzaExpdGVyYWwoaSk7XG4gIHJldHVybiB0b2tlbnM7XG59XG5cbmludGVyZmFjZSBQYXJzZWRFeHByIHtcbiAgcGF0aDogc3RyaW5nW107XG4gIGRlZmF1bHRWYWx1ZT86IHN0cmluZztcbiAgY29lcmNlPzogQ29lcmNlS2luZDtcbiAgZW5kSWR4OiBudW1iZXI7XG59XG5cbmZ1bmN0aW9uIHJlYWRFeHByZXNzaW9uKFxuICBzb3VyY2U6IHN0cmluZyxcbiAgc3RhcnRJZHg6IG51bWJlcixcbiAgc3RhcnRMaW5lOiBudW1iZXIsXG4gIHN0YXJ0Q29sOiBudW1iZXIsXG4pOiBQYXJzZWRFeHByIHtcbiAgbGV0IGkgPSBzdGFydElkeDtcbiAgbGV0IGxpbmUgPSBzdGFydExpbmU7XG4gIGxldCBjb2wgPSBzdGFydENvbDtcblxuICBjb25zdCBza2lwV3MgPSAoKTogdm9pZCA9PiB7XG4gICAgd2hpbGUgKGkgPCBzb3VyY2UubGVuZ3RoICYmIChzb3VyY2VbaV0gPT09ICcgJyB8fCBzb3VyY2VbaV0gPT09ICdcXHQnKSkge1xuICAgICAgaSsrO1xuICAgICAgY29sKys7XG4gICAgfVxuICB9O1xuXG4gIGNvbnN0IHJlYWRJZGVudGlmaWVyID0gKCk6IHN0cmluZyA9PiB7XG4gICAgY29uc3Qgc3RhcnRJID0gaTtcbiAgICBpZiAoaSA+PSBzb3VyY2UubGVuZ3RoIHx8ICEvW2EtekEtWl9dLy50ZXN0KHNvdXJjZVtpXSEpKSB7XG4gICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoXG4gICAgICAgICdFeHBlY3RlZCBpZGVudGlmaWVyIGluc2lkZSB0ZW1wbGF0ZSBleHByZXNzaW9uJyxcbiAgICAgICAgeyBsaW5lLCBjb2wgfSxcbiAgICAgICk7XG4gICAgfVxuICAgIHdoaWxlIChpIDwgc291cmNlLmxlbmd0aCAmJiAvW2EtekEtWjAtOV9dLy50ZXN0KHNvdXJjZVtpXSEpKSB7XG4gICAgICBpKys7XG4gICAgICBjb2wrKztcbiAgICB9XG4gICAgY29uc3QgaWRlbnQgPSBzb3VyY2Uuc2xpY2Uoc3RhcnRJLCBpKTtcbiAgICBpZiAoaWRlbnQubGVuZ3RoID4gTUFYX0lERU5USUZJRVJfTEVOR1RIKSB7XG4gICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoXG4gICAgICAgIGBJZGVudGlmaWVyIGV4Y2VlZHMgbWF4IGxlbmd0aCBvZiAke01BWF9JREVOVElGSUVSX0xFTkdUSH1gLFxuICAgICAgICB7IGxpbmUsIGNvbDogY29sIC0gaWRlbnQubGVuZ3RoIH0sXG4gICAgICApO1xuICAgIH1cbiAgICByZXR1cm4gaWRlbnQ7XG4gIH07XG5cbiAgY29uc3QgcmVhZFF1b3RlZCA9ICgpOiBzdHJpbmcgPT4ge1xuICAgIGNvbnN0IHF1b3RlID0gc291cmNlW2ldO1xuICAgIGlmIChxdW90ZSAhPT0gJ1wiJyAmJiBxdW90ZSAhPT0gXCInXCIpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcignRXhwZWN0ZWQgcXVvdGVkIHN0cmluZycsIHsgbGluZSwgY29sIH0pO1xuICAgIH1cbiAgICBpKys7IGNvbCsrO1xuICAgIGNvbnN0IHBhcnRzOiBzdHJpbmdbXSA9IFtdO1xuICAgIHdoaWxlIChpIDwgc291cmNlLmxlbmd0aCAmJiBzb3VyY2VbaV0gIT09IHF1b3RlKSB7XG4gICAgICBpZiAoc291cmNlW2ldID09PSAnXFxcXCcpIHtcbiAgICAgICAgY29uc3QgbmV4dCA9IHNvdXJjZVtpICsgMV07XG4gICAgICAgIGlmIChuZXh0ID09PSAnXFxcXCcgfHwgbmV4dCA9PT0gcXVvdGUpIHtcbiAgICAgICAgICBwYXJ0cy5wdXNoKG5leHQpO1xuICAgICAgICAgIGkgKz0gMjtcbiAgICAgICAgICBjb2wgKz0gMjtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoXG4gICAgICAgICAgYEludmFsaWQgZXNjYXBlICdcXFxcJHtuZXh0ID8/ICcnfScgaW4gcXVvdGVkIHN0cmluZ2AsXG4gICAgICAgICAgeyBsaW5lLCBjb2wgfSxcbiAgICAgICAgKTtcbiAgICAgIH1cbiAgICAgIGlmIChzb3VyY2VbaV0gPT09ICdcXG4nKSB7XG4gICAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcignVW50ZXJtaW5hdGVkIHF1b3RlZCBzdHJpbmcnLCB7IGxpbmUsIGNvbCB9KTtcbiAgICAgIH1cbiAgICAgIHBhcnRzLnB1c2goc291cmNlW2ldISk7XG4gICAgICBpKys7IGNvbCsrO1xuICAgIH1cbiAgICBpZiAoaSA+PSBzb3VyY2UubGVuZ3RoKSB7XG4gICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoJ1VudGVybWluYXRlZCBxdW90ZWQgc3RyaW5nJywgeyBsaW5lLCBjb2wgfSk7XG4gICAgfVxuICAgIGkrKzsgY29sKys7IC8vIGNvbnN1bWUgY2xvc2luZyBxdW90ZVxuICAgIHJldHVybiBwYXJ0cy5qb2luKCcnKTtcbiAgfTtcblxuICBza2lwV3MoKTtcbiAgY29uc3QgcGF0aDogc3RyaW5nW10gPSBbcmVhZElkZW50aWZpZXIoKV07XG4gIHdoaWxlIChpIDwgc291cmNlLmxlbmd0aCAmJiBzb3VyY2VbaV0gPT09ICcuJykge1xuICAgIGkrKzsgY29sKys7XG4gICAgcGF0aC5wdXNoKHJlYWRJZGVudGlmaWVyKCkpO1xuICAgIGlmIChwYXRoLmxlbmd0aCA+IE1BWF9QQVRIX0RFUFRIKSB7XG4gICAgICB0aHJvdyBuZXcgVG9rZW5pemVyRXJyb3IoXG4gICAgICAgIGBQYXRoIGRlcHRoIGV4Y2VlZHMgbWF4IG9mICR7TUFYX1BBVEhfREVQVEh9YCxcbiAgICAgICAgeyBsaW5lLCBjb2wgfSxcbiAgICAgICk7XG4gICAgfVxuICB9XG4gIHNraXBXcygpO1xuXG4gIGxldCBkZWZhdWx0VmFsdWU6IHN0cmluZyB8IHVuZGVmaW5lZDtcbiAgbGV0IGNvZXJjZTogQ29lcmNlS2luZCB8IHVuZGVmaW5lZDtcblxuICAvLyBGaWx0ZXJzIGNoYWluOiBgfCBkZWZhdWx0OiAneCcgfCBudW1iZXJgLCBgfCBib29sYCwgYHwganNvbmAsIGV0Yy5cbiAgLy8gLSBgZGVmYXVsdGAgdGFrZXMgYSBxdW90ZWQgYXJndW1lbnQg4oCUIGFwcGxpZWQgZmlyc3QgKGJlZm9yZSBjb2VyY2lvbilcbiAgLy8gLSBgbnVtYmVyYCwgYGJvb2xgLCBganNvbmAgYXJlIGFyZ3VtZW50LWxlc3MgY29lcmNpb24gZmlsdGVycyDigJQgYXBwbGllZCBsYXN0XG4gIHdoaWxlIChzb3VyY2VbaV0gPT09ICd8Jykge1xuICAgIGkrKzsgY29sKys7XG4gICAgc2tpcFdzKCk7XG5cbiAgICBpZiAoc291cmNlLnN0YXJ0c1dpdGgoJ2RlZmF1bHQnLCBpKSkge1xuICAgICAgaWYgKGRlZmF1bHRWYWx1ZSAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcIidkZWZhdWx0JyBmaWx0ZXIgbWF5IG9ubHkgYXBwZWFyIG9uY2VcIiwgeyBsaW5lLCBjb2wgfSk7XG4gICAgICB9XG4gICAgICBpICs9ICdkZWZhdWx0Jy5sZW5ndGg7XG4gICAgICBjb2wgKz0gJ2RlZmF1bHQnLmxlbmd0aDtcbiAgICAgIHNraXBXcygpO1xuICAgICAgaWYgKHNvdXJjZVtpXSAhPT0gJzonKSB7XG4gICAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcIkV4cGVjdGVkICc6JyBhZnRlciAnZGVmYXVsdCdcIiwgeyBsaW5lLCBjb2wgfSk7XG4gICAgICB9XG4gICAgICBpKys7IGNvbCsrO1xuICAgICAgc2tpcFdzKCk7XG4gICAgICBkZWZhdWx0VmFsdWUgPSByZWFkUXVvdGVkKCk7XG4gICAgICBza2lwV3MoKTtcbiAgICAgIGNvbnRpbnVlO1xuICAgIH1cblxuICAgIGxldCBtYXRjaGVkQ29lcmNlOiBDb2VyY2VLaW5kIHwgbnVsbCA9IG51bGw7XG4gICAgZm9yIChjb25zdCBraW5kIG9mIFsnbnVtYmVyJywgJ2Jvb2wnLCAnanNvbiddIGFzIGNvbnN0KSB7XG4gICAgICBpZiAoc291cmNlLnN0YXJ0c1dpdGgoa2luZCwgaSkpIHtcbiAgICAgICAgLy8gRW5zdXJlIGlkZW50aWZpZXIgYm91bmRhcnkg4oCUIGBudW1iZXJpc2hgIG11c3Qgbm90IG1hdGNoIGBudW1iZXJgXG4gICAgICAgIGNvbnN0IG5leHQgPSBzb3VyY2VbaSArIGtpbmQubGVuZ3RoXTtcbiAgICAgICAgaWYgKCFuZXh0IHx8ICEvW2EtekEtWjAtOV9dLy50ZXN0KG5leHQpKSB7XG4gICAgICAgICAgbWF0Y2hlZENvZXJjZSA9IGtpbmQ7XG4gICAgICAgICAgaSArPSBraW5kLmxlbmd0aDtcbiAgICAgICAgICBjb2wgKz0ga2luZC5sZW5ndGg7XG4gICAgICAgICAgYnJlYWs7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG5cbiAgICBpZiAoIW1hdGNoZWRDb2VyY2UpIHtcbiAgICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcbiAgICAgICAgXCJVbmtub3duIGZpbHRlciDigJQgc3VwcG9ydGVkOiAnZGVmYXVsdCcsICdudW1iZXInLCAnYm9vbCcsICdqc29uJ1wiLFxuICAgICAgICB7IGxpbmUsIGNvbCB9LFxuICAgICAgKTtcbiAgICB9XG4gICAgaWYgKGNvZXJjZSkge1xuICAgICAgdGhyb3cgbmV3IFRva2VuaXplckVycm9yKCdPbmx5IG9uZSBjb2VyY2lvbiBmaWx0ZXIgaXMgYWxsb3dlZCBwZXIgZXhwcmVzc2lvbicsIHsgbGluZSwgY29sIH0pO1xuICAgIH1cbiAgICBjb2VyY2UgPSBtYXRjaGVkQ29lcmNlO1xuICAgIHNraXBXcygpO1xuICB9XG5cbiAgaWYgKCFzb3VyY2Uuc3RhcnRzV2l0aCgnfX0nLCBpKSkge1xuICAgIHRocm93IG5ldyBUb2tlbml6ZXJFcnJvcihcIkV4cGVjdGVkICd9fSdcIiwgeyBsaW5lLCBjb2wgfSk7XG4gIH1cbiAgaSArPSAyO1xuICByZXR1cm4geyBwYXRoLCBkZWZhdWx0VmFsdWUsIGNvZXJjZSwgZW5kSWR4OiBpIH07XG59XG5cbi8qKlxuICogQ29udmVuaWVuY2U6IHJldHVybnMgdHJ1ZSBpZiB0aGUgc291cmNlIGNvbnRhaW5zIGFueSB0ZW1wbGF0ZXMuXG4gKiBVc2VkIGFzIGEgZmFzdC1wYXRoIHRvIHNraXAgdG9rZW5pemF0aW9uIGZvciBzdHJpbmdzIHRoYXQgYXJlIG9idmlvdXNseSBsaXRlcmFsLlxuICovXG5leHBvcnQgZnVuY3Rpb24gaGFzVGVtcGxhdGUoc291cmNlOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgLy8gTm90IGNvbXBsZXRlbHkgYWNjdXJhdGUgKGEgbG9uZSBge3t7e2Agd291bGQgcmV0dXJuIHRydWUpIGJ1dCB0aGF0J3MgZmluZSDigJRcbiAgLy8gdG9rZW5pemUoKSB3aWxsIHByb2R1Y2UgYSBsaXRlcmFsLW9ubHkgdG9rZW4gbGlzdCBhbmQgcm91bmQtdHJpcCBjb3JyZWN0bHkuXG4gIHJldHVybiBzb3VyY2UuaW5jbHVkZXMoJ3t7Jyk7XG59XG4iXX0=