timonel 2.13.0 → 3.0.0-beta.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.
@@ -1,114 +1,660 @@
1
- import * as jsYaml from 'js-yaml';
2
- const _HELM_EXPRESSION_PATTERNS = [
3
- /\{\{-?\s*define\s+[^}]+\s*-?\}\}[\s\S]*?\{\{-?\s*end\s*-?\}\}/g,
4
- /\{\{[^}]*\{\{[^}]*\}\}[^}]*\}\}/g,
5
- /\{\{\/\*[\s\S]*?\*\/\}\}/g,
6
- /\{\{-?[\s\S]*?-?\}\}/g,
7
- ];
8
- const COMPILED_PATTERNS = [
9
- { regex: /\{\{-?\s*define\s+[^}]+\s*-?\}\}[\s\S]*?\{\{-?\s*end\s*-?\}\}/g, type: 'block' },
10
- { regex: /\{\{[^}]*\{\{[^}]*\}\}[^}]*\}\}/g, type: 'nested' },
11
- { regex: /\{\{\/\*[\s\S]*?\*\/\}\}/g, type: 'comment' },
12
- { regex: /\{\{-?[\s\S]*?-?\}\}/g, type: 'action-trimmed' },
13
- { regex: /\{\{`[\s\S]*?`\}\}/g, type: 'raw' },
14
- { regex: /\{\{\s*include\s+"[^"]+"\s+[^}]+\s*\}\}/g, type: 'include-context' },
15
- ];
16
- function createHelmExpression(value) {
17
- return {
18
- __helmExpression: true,
19
- value,
20
- };
1
+ import { Document, Scalar, isMap, isScalar, visit, Pair } from 'yaml';
2
+ import { isHelmConstruct, isHelmExpression, createHelmExpression, } from './helmControlStructures.js';
3
+ import { isHelmValue, isHelmFieldConditional, isHelmRange, isHelmWith, } from './valuesRef.js';
4
+ const FIELD_CONDITIONAL = '__FIELD_CONDITIONAL__:';
5
+ const FIELD_WITH = '__FIELD_WITH__:';
6
+ const FIELD_WITH_MARKER = '__FIELD_WITH_MARKER__:';
7
+ function simpleHelmYaml(obj) {
8
+ function convertToPlain(value) {
9
+ if (isHelmValue(value)) {
10
+ return `{{ ${value.__path} }}`;
11
+ }
12
+ if (isHelmExpression(value)) {
13
+ return value.value;
14
+ }
15
+ if (Array.isArray(value)) {
16
+ return value.map(convertToPlain);
17
+ }
18
+ if (value !== null && typeof value === 'object') {
19
+ const result = {};
20
+ for (const [k, v] of Object.entries(value)) {
21
+ result[k] = convertToPlain(v);
22
+ }
23
+ return result;
24
+ }
25
+ return value;
26
+ }
27
+ const plain = convertToPlain(obj);
28
+ const doc = new Document(plain);
29
+ return doc.toString({ lineWidth: 0 });
21
30
  }
22
- function isHelmExpression(value) {
23
- return (typeof value === 'object' &&
24
- value !== null &&
25
- '__helmExpression' in value &&
26
- value.__helmExpression === true);
31
+ function serializeElseIfChain(elseContent, openTag, closeTag) {
32
+ let result = '';
33
+ let currentElse = elseContent;
34
+ if (isHelmConstruct(currentElse) && currentElse.type === 'if') {
35
+ while (isHelmConstruct(currentElse) && currentElse.type === 'if') {
36
+ const elseIfData = currentElse.data;
37
+ result += `\n${openTag}else if ${elseIfData.condition}${closeTag}\n`;
38
+ result += serializeHelmContent(elseIfData.then);
39
+ currentElse = elseIfData.else;
40
+ }
41
+ }
42
+ return { result, remainingElse: currentElse };
27
43
  }
28
- function detectHelmExpressions(str) {
29
- const matches = [];
30
- for (const { regex, type } of COMPILED_PATTERNS) {
31
- regex.lastIndex = 0;
32
- let result;
33
- while ((result = regex.exec(str)) !== null) {
34
- matches.push({
35
- type,
36
- expression: result[0],
37
- start: result.index,
38
- end: result.index + result[0].length,
39
- });
40
- if (result.index === regex.lastIndex)
41
- regex.lastIndex++;
44
+ function serializeHelmConstruct(construct) {
45
+ if (construct.type === 'fieldConditional') {
46
+ throw new Error('fieldConditional should not be serialized directly. It must be handled in preprocessHelmConstructs.');
47
+ }
48
+ const trimLeft = construct.options?.trimLeft ?? true;
49
+ const trimRight = construct.options?.trimRight ?? true;
50
+ const openTag = `{{${trimLeft ? '-' : ''} `;
51
+ const closeTag = ` ${trimRight ? '-' : ''}}}`;
52
+ switch (construct.type) {
53
+ case 'if': {
54
+ const data = construct.data;
55
+ const isInline = construct.options?.inline ?? false;
56
+ const newline = isInline ? '' : '\n';
57
+ const space = isInline ? ' ' : '';
58
+ let result = `${openTag}if ${data.condition}${closeTag}${newline}`;
59
+ result += serializeHelmContent(data.then);
60
+ if (data.else !== undefined) {
61
+ if (isInline) {
62
+ result += `${space}${openTag}else${closeTag}${space}`;
63
+ result += serializeHelmContent(data.else);
64
+ }
65
+ else {
66
+ const { result: elseIfResult, remainingElse } = serializeElseIfChain(data.else, openTag, closeTag);
67
+ result += elseIfResult;
68
+ if (remainingElse !== undefined) {
69
+ result += `\n${openTag}else${closeTag}\n`;
70
+ result += serializeHelmContent(remainingElse);
71
+ }
72
+ }
73
+ }
74
+ result += `${newline}${openTag}end${closeTag}`;
75
+ return result;
76
+ }
77
+ case 'fragment': {
78
+ const data = construct.data;
79
+ const strings = [];
80
+ const objects = [];
81
+ for (const item of data) {
82
+ if (typeof item === 'object' &&
83
+ item !== null &&
84
+ !Array.isArray(item) &&
85
+ !isHelmConstruct(item) &&
86
+ !isHelmExpression(item)) {
87
+ objects.push(item);
88
+ }
89
+ else {
90
+ const serialized = serializeHelmContent(item);
91
+ if (serialized.trim().length > 0) {
92
+ strings.push(serialized);
93
+ }
94
+ }
95
+ }
96
+ if (strings.length > 0 && objects.length > 0) {
97
+ const combinedObject = objects.reduce((acc, obj) => {
98
+ if (typeof obj === 'object' && obj !== null) {
99
+ return { ...acc, ...obj };
100
+ }
101
+ return acc;
102
+ }, {});
103
+ const preprocessed = preprocessHelmConstructs(combinedObject);
104
+ const doc = new Document(preprocessed);
105
+ visit(doc, (_key, node) => {
106
+ if (isMap(node)) {
107
+ const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
108
+ pair.key.value === '__helmExpression' &&
109
+ isScalar(pair.value) &&
110
+ pair.value.value === true);
111
+ if (isHelmExpr) {
112
+ const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
113
+ if (valuePair && isScalar(valuePair.value)) {
114
+ const value = String(valuePair.value.value);
115
+ const scalar = new Scalar(value);
116
+ if (value.trim().startsWith('{{') && value.includes('\n')) {
117
+ scalar.type = 'BLOCK_LITERAL';
118
+ }
119
+ else {
120
+ scalar.type = 'QUOTE_DOUBLE';
121
+ }
122
+ return scalar;
123
+ }
124
+ }
125
+ }
126
+ return undefined;
127
+ });
128
+ let objectYaml = doc.toString({ lineWidth: 0 }).trim();
129
+ return [...strings, objectYaml].filter((s) => s.trim().length > 0).join('\n');
130
+ }
131
+ if (objects.length > 0) {
132
+ const combinedObject = objects.reduce((acc, obj) => {
133
+ if (typeof obj === 'object' && obj !== null) {
134
+ return { ...acc, ...obj };
135
+ }
136
+ return acc;
137
+ }, {});
138
+ const preprocessed = preprocessHelmConstructs(combinedObject);
139
+ const doc = new Document(preprocessed);
140
+ visit(doc, (_key, node) => {
141
+ if (isMap(node)) {
142
+ const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
143
+ pair.key.value === '__helmExpression' &&
144
+ isScalar(pair.value) &&
145
+ pair.value.value === true);
146
+ if (isHelmExpr) {
147
+ const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
148
+ if (valuePair && isScalar(valuePair.value)) {
149
+ const value = String(valuePair.value.value);
150
+ const scalar = new Scalar(value);
151
+ if (value.trim().startsWith('{{') && value.includes('\n')) {
152
+ scalar.type = 'BLOCK_LITERAL';
153
+ }
154
+ else {
155
+ scalar.type = 'QUOTE_DOUBLE';
156
+ }
157
+ return scalar;
158
+ }
159
+ }
160
+ }
161
+ return undefined;
162
+ });
163
+ return doc.toString({ lineWidth: 0 }).trim();
164
+ }
165
+ return strings.filter((s) => s.trim().length > 0).join('\n');
166
+ }
167
+ case 'range': {
168
+ const data = construct.data;
169
+ let result = `${openTag}range ${data.vars} := ${data.collection}${closeTag}\n`;
170
+ result += serializeHelmContent(data.content);
171
+ result += `\n${openTag}end${closeTag}`;
172
+ return result;
173
+ }
174
+ case 'with': {
175
+ const data = construct.data;
176
+ let result = `${openTag}with ${data.scope}${closeTag}\n`;
177
+ result += serializeHelmContent(data.content);
178
+ result += `\n${openTag}end${closeTag}`;
179
+ return result;
180
+ }
181
+ case 'include': {
182
+ const data = construct.data;
183
+ let result = `${openTag}include "${data.templateName}" ${data.scope}`;
184
+ if (data.pipe) {
185
+ result += ` | ${data.pipe}`;
186
+ }
187
+ result += closeTag;
188
+ return result;
189
+ }
190
+ case 'define': {
191
+ const data = construct.data;
192
+ let result = `${openTag}define "${data.name}"${closeTag}\n`;
193
+ result += serializeHelmContent(data.content);
194
+ result += `\n${openTag}end${closeTag}`;
195
+ return result;
196
+ }
197
+ case 'var': {
198
+ const data = construct.data;
199
+ return `${openTag}${data.name} := ${data.value}${closeTag}`;
200
+ }
201
+ case 'block': {
202
+ const data = construct.data;
203
+ let result = `${openTag}block "${data.name}" .${closeTag}\n`;
204
+ result += serializeHelmContent(data.content);
205
+ result += `\n${openTag}end${closeTag}`;
206
+ return result;
42
207
  }
208
+ case 'comment': {
209
+ const data = construct.data;
210
+ return `{{/* ${data.text} */}}`;
211
+ }
212
+ default:
213
+ throw new Error(`Unknown Helm construct type: ${construct.type}`);
43
214
  }
44
- return matches.sort((a, b) => a.start - b.start);
45
215
  }
46
- function preprocessHelmExpressions(obj, depth = 0) {
47
- if (depth > 100) {
48
- throw new Error('Maximum recursion depth exceeded during Helm preprocessing');
216
+ function serializeHelmContent(content) {
217
+ if (isHelmConstruct(content)) {
218
+ return serializeHelmConstruct(content);
219
+ }
220
+ if (isHelmExpression(content)) {
221
+ return content.value;
222
+ }
223
+ if (content === null || content === undefined) {
224
+ return '';
225
+ }
226
+ if (typeof content === 'string') {
227
+ return content;
49
228
  }
50
- if (isHelmExpression(obj)) {
229
+ if (typeof content === 'number' || typeof content === 'boolean') {
230
+ return String(content);
231
+ }
232
+ if (typeof content === 'object') {
233
+ const preprocessed = preprocessHelmConstructs(content);
234
+ const doc = new Document(preprocessed);
235
+ let yaml = doc.toString({ lineWidth: 0 });
236
+ yaml = yaml.trim();
237
+ return yaml;
238
+ }
239
+ return '';
240
+ }
241
+ export function preprocessHelmConstructs(obj) {
242
+ if (isHelmValue(obj)) {
243
+ const value = obj;
244
+ return createHelmExpression(`{{ ${value.__path} }}`);
245
+ }
246
+ if (isHelmFieldConditional(obj)) {
51
247
  return obj;
52
248
  }
53
- if (typeof obj === 'string') {
54
- if (detectHelmExpressions(obj).length > 0) {
55
- return createHelmExpression(obj);
56
- }
249
+ if (isHelmRange(obj)) {
250
+ const range = obj;
251
+ const sourcePath = range.source.__path;
252
+ const itemProxy = { __path: '$item' };
253
+ const indexProxy = { __path: '$index' };
254
+ const content = range.callback(itemProxy, indexProxy);
255
+ const processedContent = preprocessHelmConstructs(content);
256
+ const contentDoc = new Document(processedContent);
257
+ const contentStr = contentDoc.toString({ lineWidth: 0 }).trim();
258
+ return createHelmExpression(`{{- range ${sourcePath} }}\n${contentStr}\n{{- end }}`);
259
+ }
260
+ if (isHelmWith(obj)) {
57
261
  return obj;
58
262
  }
263
+ if (isHelmConstruct(obj)) {
264
+ if (obj.type === 'fieldConditional') {
265
+ return obj;
266
+ }
267
+ if (obj.type === 'if') {
268
+ const ifData = obj.data;
269
+ const isInline = obj.options?.inline ?? false;
270
+ if (!isInline && (ifData.else === undefined || !('else' in ifData))) {
271
+ return obj;
272
+ }
273
+ }
274
+ const helmTemplate = serializeHelmConstruct(obj);
275
+ return createHelmExpression(helmTemplate);
276
+ }
59
277
  if (Array.isArray(obj)) {
60
- return obj.map((item) => preprocessHelmExpressions(item, depth + 1));
278
+ return obj.map((item) => preprocessHelmConstructs(item));
61
279
  }
62
- if (typeof obj === 'object' && obj !== null) {
280
+ if (obj !== null && typeof obj === 'object') {
281
+ if (isHelmExpression(obj)) {
282
+ return obj;
283
+ }
63
284
  const result = {};
64
285
  for (const [key, value] of Object.entries(obj)) {
65
- Object.defineProperty(result, key, {
66
- value: preprocessHelmExpressions(value, depth + 1),
67
- writable: true,
68
- enumerable: true,
69
- configurable: true,
70
- });
286
+ if (isHelmWith(value)) {
287
+ const withBlock = value;
288
+ const source = withBlock.source;
289
+ const sourcePath = source?.__path;
290
+ if (!sourcePath) {
291
+ throw new Error('HelmWith source must have a valid __path');
292
+ }
293
+ const ctxProxy = { __path: '.' };
294
+ const content = withBlock.callback(ctxProxy);
295
+ let contentStr;
296
+ if (isHelmExpression(content)) {
297
+ contentStr = content.value;
298
+ }
299
+ else {
300
+ const processedContent = preprocessHelmConstructs(content);
301
+ const contentDoc = new Document(processedContent);
302
+ contentStr = contentDoc.toString({ lineWidth: 0 }).trim();
303
+ }
304
+ const template = `{{- with ${sourcePath} }}\n${key}:\n ${contentStr}\n{{- end }}`;
305
+ const marker = `__FIELD_WITH__:${key}:${template}`;
306
+ result[`__fieldWithTemplate_${key}`] = createHelmExpression(marker);
307
+ continue;
308
+ }
309
+ if (isHelmConstruct(value) && value.type === 'if') {
310
+ const ifData = value.data;
311
+ const isInline = value.options?.inline ?? false;
312
+ if (!isInline && (ifData.else === undefined || !('else' in ifData))) {
313
+ const preprocessedThen = preprocessHelmConstructs(ifData.then);
314
+ let thenValueStr = '';
315
+ let isMultilineObject = false;
316
+ if (isHelmExpression(preprocessedThen)) {
317
+ thenValueStr = preprocessedThen.value.trim();
318
+ isMultilineObject = thenValueStr.includes('\n');
319
+ }
320
+ else if (typeof preprocessedThen === 'string') {
321
+ thenValueStr = preprocessedThen.trim();
322
+ isMultilineObject = thenValueStr.includes('\n');
323
+ }
324
+ else if (Array.isArray(preprocessedThen)) {
325
+ const tempDoc = new Document(preprocessedThen);
326
+ thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
327
+ isMultilineObject = true;
328
+ }
329
+ else {
330
+ const tempDoc = new Document(preprocessedThen);
331
+ thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
332
+ isMultilineObject = thenValueStr.includes('\n');
333
+ }
334
+ const constructOptions = isHelmConstruct(value) ? value.options : undefined;
335
+ const trimLeft = constructOptions?.trimLeft ?? true;
336
+ const openTag = `{{${trimLeft ? '-' : ''} `;
337
+ const closeTag = ` ${(constructOptions?.trimRight ?? true) ? '-' : ''}}}`;
338
+ let formattedContent;
339
+ if (isMultilineObject) {
340
+ const indentedValue = thenValueStr
341
+ .split('\n')
342
+ .map((line) => ' ' + line)
343
+ .join('\n');
344
+ formattedContent = `${key}:\n${indentedValue}`;
345
+ }
346
+ else {
347
+ formattedContent = `${key}: ${thenValueStr}`;
348
+ }
349
+ const conditionalTemplate = `${openTag}if ${ifData.condition}${closeTag}\n ${formattedContent}\n${openTag}end${closeTag}`;
350
+ result[key] = createHelmExpression(`__FIELD_CONDITIONAL__:${key}:${conditionalTemplate}`);
351
+ continue;
352
+ }
353
+ }
354
+ if (isHelmFieldConditional(value)) {
355
+ const fieldCond = value;
356
+ const condition = fieldCond.condition.__condition;
357
+ const thenValueStr = simpleHelmYaml(fieldCond.thenValue).trim();
358
+ const isMultilineObject = thenValueStr.includes('\n');
359
+ const startsWithDash = thenValueStr.trimStart().startsWith('-');
360
+ let formattedContent;
361
+ if (isMultilineObject || startsWithDash) {
362
+ const lines = thenValueStr.split('\n');
363
+ const indentedValue = lines.map((line) => ' ' + line).join('\n');
364
+ formattedContent = `${key}:\n${indentedValue}`;
365
+ }
366
+ else {
367
+ formattedContent = `${key}: ${thenValueStr}`;
368
+ }
369
+ const conditionalTemplate = `{{- if ${condition} }}\n${formattedContent}\n{{- end }}`;
370
+ result[key] = createHelmExpression(`__FIELD_CONDITIONAL__:${key}:${conditionalTemplate}`);
371
+ continue;
372
+ }
373
+ if (isHelmConstruct(value) && value.type === 'fieldConditional') {
374
+ const fieldData = value.data;
375
+ const preprocessedThen = preprocessHelmConstructs(fieldData.then);
376
+ let thenValueStr = '';
377
+ if (isHelmExpression(preprocessedThen)) {
378
+ thenValueStr = preprocessedThen.value;
379
+ }
380
+ else if (typeof preprocessedThen === 'string') {
381
+ thenValueStr = preprocessedThen;
382
+ }
383
+ else {
384
+ const tempDoc = new Document(preprocessedThen);
385
+ thenValueStr = tempDoc.toString({ lineWidth: 0 }).trim();
386
+ }
387
+ const conditionalTemplate = `{{- if ${fieldData.condition} }}\n ${fieldData.fieldKey}: ${thenValueStr}\n{{- end }}`;
388
+ result[`__fieldConditionalTemplate_${fieldData.fieldKey}`] = conditionalTemplate;
389
+ continue;
390
+ }
391
+ result[key] = preprocessHelmConstructs(value);
71
392
  }
72
393
  return result;
73
394
  }
74
395
  return obj;
75
396
  }
76
- function helmAwareReplacer(key, value) {
77
- if (isHelmExpression(value)) {
78
- return value.value;
397
+ export function dumpHelmAwareYaml(obj, options = {}) {
398
+ const preprocessed = preprocessHelmConstructs(obj);
399
+ const doc = new Document(preprocessed);
400
+ visit(doc, (_key, node) => {
401
+ if (isMap(node)) {
402
+ const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
403
+ pair.key.value === '__helmExpression' &&
404
+ isScalar(pair.value) &&
405
+ pair.value.value === true);
406
+ if (isHelmExpr) {
407
+ const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
408
+ if (valuePair && isScalar(valuePair.value)) {
409
+ const value = String(valuePair.value.value);
410
+ if (value.startsWith(FIELD_CONDITIONAL) ||
411
+ value.startsWith(FIELD_WITH) ||
412
+ value.startsWith(FIELD_WITH_MARKER)) {
413
+ const scalar = new Scalar(value);
414
+ scalar.type = 'BLOCK_LITERAL';
415
+ return scalar;
416
+ }
417
+ const scalar = new Scalar(value);
418
+ const isMultilineTemplate = value.includes('\n') && value.trim().startsWith('{{');
419
+ const isWithOrRange = value.trim().startsWith('{{- with ') || value.trim().startsWith('{{- range ');
420
+ if (isMultilineTemplate && !isWithOrRange) {
421
+ scalar.type = 'BLOCK_LITERAL';
422
+ }
423
+ else if (isWithOrRange) {
424
+ scalar.type = 'PLAIN';
425
+ }
426
+ else {
427
+ scalar.type = 'QUOTE_DOUBLE';
428
+ }
429
+ return scalar;
430
+ }
431
+ }
432
+ }
433
+ return undefined;
434
+ });
435
+ const toStringOptions = { lineWidth: 0 };
436
+ if (options.lineWidth !== undefined) {
437
+ toStringOptions.lineWidth = options.lineWidth;
79
438
  }
80
- return value;
81
- }
82
- function postProcessHelmExpressions(yaml) {
83
- let processed = yaml;
84
- processed = processed.replace(/'(\{\{[^}]*\}\})'/g, '$1');
85
- processed = processed.replace(/"(\{\{[^}]*\}\})"/g, '$1');
86
- processed = processed.replace(/'(\{\{[^}]*\\"[^}]*\}\})'/g, '$1');
87
- processed = processed.replace(/"(\{\{[^}]*\\"[^}]*\}\})"/g, '$1');
88
- processed = processed.replace(/\\(\{\{[^}]+\}\})/g, '$1');
89
- return processed;
439
+ const templatePairs = [];
440
+ visit(doc, (_key, node) => {
441
+ if (isMap(node)) {
442
+ for (let i = 0; i < node.items.length; i++) {
443
+ const pair = node.items[i];
444
+ if (pair && isScalar(pair.key)) {
445
+ const keyStr = String(pair.key.value);
446
+ if (keyStr.startsWith('__fieldConditionalTemplate_') && isScalar(pair.value)) {
447
+ const template = String(pair.value.value);
448
+ const fieldKey = keyStr.replace('__fieldConditionalTemplate_', '');
449
+ templatePairs.push({ parent: node, index: i, template, fieldKey });
450
+ }
451
+ }
452
+ }
453
+ }
454
+ return undefined;
455
+ });
456
+ for (let i = templatePairs.length - 1; i >= 0; i--) {
457
+ const templatePair = templatePairs[i];
458
+ if (!templatePair)
459
+ continue;
460
+ const { parent, index, template } = templatePair;
461
+ parent.items.splice(index, 1);
462
+ const tempDoc = new Document({ __temp: template });
463
+ const _tempYaml = tempDoc.toString({ lineWidth: 0 });
464
+ const templateScalar = new Scalar(template);
465
+ templateScalar.type = 'BLOCK_LITERAL';
466
+ const specialKeyScalar = new Scalar('__fieldConditionalContent');
467
+ const templatePairObj = new Pair(specialKeyScalar, templateScalar);
468
+ parent.items.splice(index, 0, templatePairObj);
469
+ }
470
+ let result = doc.toString(toStringOptions);
471
+ while (result.includes('__fieldConditionalContent:')) {
472
+ const markerIndex = result.indexOf('__fieldConditionalContent:');
473
+ if (markerIndex === -1)
474
+ break;
475
+ const lineStart = result.lastIndexOf('\n', markerIndex);
476
+ const lineBeforeMarker = result.substring(lineStart + 1, markerIndex);
477
+ const indentMatch = lineBeforeMarker.match(/^(\s*)/);
478
+ const baseIndent = indentMatch && indentMatch[1] ? indentMatch[1] : '';
479
+ const afterMarker = result.substring(markerIndex);
480
+ let pipeLineMatch = afterMarker.match(/^__fieldConditionalContent:\s*(\|-?)\s*\n(\s*)/);
481
+ let pipeOnSameLine = true;
482
+ if (!pipeLineMatch) {
483
+ pipeLineMatch = afterMarker.match(/^__fieldConditionalContent:\s*\n(\s*)(\|-?)\s*\n/);
484
+ pipeOnSameLine = false;
485
+ }
486
+ if (!pipeLineMatch)
487
+ break;
488
+ const pipeChar = pipeOnSameLine ? pipeLineMatch[1] : pipeLineMatch[2];
489
+ const pipeIndent = pipeOnSameLine ? pipeLineMatch[2] || '' : pipeLineMatch[1] || '';
490
+ if (!pipeChar)
491
+ break;
492
+ const pipeIndex = markerIndex + pipeLineMatch[0].length;
493
+ const afterPipe = result.substring(pipeIndex);
494
+ const templateStartMatch = afterPipe.match(/^\n(\s*)(\{\{-)/);
495
+ if (!templateStartMatch) {
496
+ const templateStartMatchNoNewline = afterPipe.match(/^(\s*)(\{\{-)/);
497
+ if (!templateStartMatchNoNewline)
498
+ break;
499
+ const _templateIndent = templateStartMatchNoNewline[1] || '';
500
+ const templateStart = templateStartMatchNoNewline[2];
501
+ if (!templateStart)
502
+ break;
503
+ const templateStartIndex = pipeIndex + templateStartMatchNoNewline[0].length;
504
+ const templateSection = result.substring(templateStartIndex);
505
+ const endMatch = templateSection.match(/(\{\{-?\s*end\s*-?\}\})/);
506
+ if (!endMatch)
507
+ break;
508
+ const endIndex = endMatch.index;
509
+ if (endIndex === undefined)
510
+ break;
511
+ const templateEndIndex = templateStartIndex + endIndex + endMatch[0].length;
512
+ const templateContent = result.substring(templateStartIndex, templateEndIndex);
513
+ const beforeMarker = result.substring(0, lineStart + 1);
514
+ const afterTemplate = result.substring(templateEndIndex);
515
+ const pipeIndentEscaped = pipeIndent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
516
+ const adjustedTemplate = templateContent.replace(new RegExp(`^${pipeIndentEscaped}`, 'gm'), baseIndent);
517
+ result = beforeMarker + adjustedTemplate + afterTemplate;
518
+ continue;
519
+ }
520
+ const _templateIndent = templateStartMatch[1] || '';
521
+ const templateStart = templateStartMatch[2];
522
+ if (!templateStart)
523
+ break;
524
+ const templateStartIndex = pipeIndex + templateStartMatch[0].length;
525
+ const templateSection = result.substring(templateStartIndex);
526
+ const endMatch = templateSection.match(/(\{\{-?\s*end\s*-?\}\})/);
527
+ if (!endMatch)
528
+ break;
529
+ const endIndex = endMatch.index;
530
+ if (endIndex === undefined)
531
+ break;
532
+ const templateEndIndex = templateStartIndex + endIndex + endMatch[0].length;
533
+ const templateContent = result.substring(templateStartIndex, templateEndIndex);
534
+ const beforeMarker = result.substring(0, lineStart + 1);
535
+ const afterTemplate = result.substring(templateEndIndex);
536
+ const pipeIndentEscaped = pipeIndent.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
537
+ const adjustedTemplate = templateContent.replace(new RegExp(`^${pipeIndentEscaped}`, 'gm'), baseIndent);
538
+ result = beforeMarker + adjustedTemplate + afterTemplate;
539
+ }
540
+ result = result.replace(/\\"/g, '"');
541
+ result = result.replace(/^(\s+)(\w+):\s+(\{\{-?\s*if\s+[^}]+\}\})\s*\n(\s+)(.+?)\n(\s+)(\{\{-?\s*end\s*-?\}\})/gm, (match, baseIndent, fieldKey, ifTag, contentIndent, content, endIndent, endTag) => {
542
+ const contentLines = content.split('\n');
543
+ const firstContentLine = contentLines[0]?.trim() || '';
544
+ if (firstContentLine.startsWith(`${fieldKey}:`)) {
545
+ contentLines[0] = contentLines[0].replace(new RegExp(`^\\s*${fieldKey}:\\s*`), '');
546
+ content = contentLines.join('\n');
547
+ }
548
+ const contentFormatted = content
549
+ ? '\n' +
550
+ content
551
+ .split('\n')
552
+ .map((line) => baseIndent + ' ' + line.trimStart())
553
+ .join('\n')
554
+ : '';
555
+ return `${baseIndent}${ifTag}\n${baseIndent} ${fieldKey}:${contentFormatted}\n${baseIndent}${endTag}`;
556
+ });
557
+ result = postProcessFieldConditionals(result);
558
+ return result;
90
559
  }
91
- export function dumpHelmAwareYaml(obj, options = {}) {
92
- const preprocessed = preprocessHelmExpressions(obj);
93
- const dumpOptions = {
94
- forceQuotes: false,
95
- lineWidth: options.lineWidth ?? 0,
96
- flowLevel: options.flowLevel ?? -1,
97
- replacer: helmAwareReplacer,
98
- ...options,
99
- };
100
- let yamlOutput = jsYaml.dump(preprocessed, dumpOptions);
101
- yamlOutput = postProcessHelmExpressions(yamlOutput);
102
- return yamlOutput;
560
+ export function postProcessFieldConditionals(yaml) {
561
+ let result = yaml;
562
+ while (result.includes('__FIELD_WITH_MARKER__:')) {
563
+ const markerIndex = result.indexOf('__FIELD_WITH_MARKER__:');
564
+ if (markerIndex === -1)
565
+ break;
566
+ const beforeMarker = result.substring(0, markerIndex);
567
+ const lastNewline = beforeMarker.lastIndexOf('\n');
568
+ const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
569
+ const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
570
+ const fieldKeyMatch = fieldKeyLine.match(/^(\s*)(\w+):\s*|[-]?\s*$/);
571
+ if (!fieldKeyMatch || !fieldKeyMatch[2])
572
+ break;
573
+ const baseIndent = fieldKeyMatch[1] || '';
574
+ const fieldKey = fieldKeyMatch[2];
575
+ const afterMarker = result.substring(markerIndex);
576
+ const markerMatch = afterMarker.match(/^__FIELD_WITH_MARKER__:([^:]+):(.+?)$/m);
577
+ if (!markerMatch || !markerMatch[1] || !markerMatch[2])
578
+ break;
579
+ const path = markerMatch[1];
580
+ const content = markerMatch[2].trim();
581
+ const markerEnd = markerIndex + markerMatch[0].length;
582
+ const template = `${baseIndent}{{- with ${path} }}\n${baseIndent}${fieldKey}:\n${baseIndent} ${content}\n${baseIndent}{{- end }}`;
583
+ result = result.substring(0, secondLastNewline + 1) + template + result.substring(markerEnd);
584
+ }
585
+ while (result.includes('__FIELD_WITH__:')) {
586
+ const markerIndex = result.indexOf('__FIELD_WITH__:');
587
+ if (markerIndex === -1)
588
+ break;
589
+ const beforeMarker = result.substring(0, markerIndex);
590
+ const lastNewline = beforeMarker.lastIndexOf('\n');
591
+ const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
592
+ const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
593
+ const fieldKeyMatch = fieldKeyLine.match(/^(\s*)__fieldWithTemplate_\w+:\s*|[-]?\s*$/);
594
+ if (!fieldKeyMatch)
595
+ break;
596
+ const baseIndent = fieldKeyMatch[1];
597
+ const afterMarker = result.substring(markerIndex);
598
+ const markerMatch = afterMarker.match(/^__FIELD_WITH__:\w+:(.+?\{\{-?\s*end\s*-?\}\})/s);
599
+ if (!markerMatch || !markerMatch[1])
600
+ break;
601
+ const template = markerMatch[1];
602
+ const markerEnd = markerIndex + markerMatch[0].length;
603
+ const lines = template.split('\n');
604
+ const processed = lines
605
+ .map((line) => {
606
+ if (!line.trim())
607
+ return '';
608
+ const trimmed = line.trimStart();
609
+ return baseIndent + trimmed;
610
+ })
611
+ .join('\n');
612
+ result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
613
+ }
614
+ while (result.includes('__FIELD_CONDITIONAL__:')) {
615
+ const markerIndex = result.indexOf('__FIELD_CONDITIONAL__:');
616
+ if (markerIndex === -1)
617
+ break;
618
+ const beforeMarker = result.substring(0, markerIndex);
619
+ const lastNewline = beforeMarker.lastIndexOf('\n');
620
+ const secondLastNewline = beforeMarker.lastIndexOf('\n', lastNewline - 1);
621
+ const fieldKeyLine = result.substring(secondLastNewline + 1, lastNewline);
622
+ const fieldKeyMatch = fieldKeyLine.match(/^(\s*)(\w+):\s*|[-]?\s*$/);
623
+ if (!fieldKeyMatch)
624
+ break;
625
+ const baseIndent = fieldKeyMatch[1];
626
+ const afterMarker = result.substring(markerIndex);
627
+ const markerMatch = afterMarker.match(/^__FIELD_CONDITIONAL__:\w+:(.+?\{\{-?\s*end\s*-?\}\})/s);
628
+ if (!markerMatch || !markerMatch[1])
629
+ break;
630
+ const template = markerMatch[1];
631
+ const markerEnd = markerIndex + markerMatch[0].length;
632
+ const lines = template.split('\n');
633
+ const processed = lines
634
+ .map((line) => {
635
+ if (!line.trim())
636
+ return '';
637
+ const trimmed = line.trimStart();
638
+ return baseIndent + trimmed;
639
+ })
640
+ .join('\n');
641
+ result = result.substring(0, secondLastNewline + 1) + processed + result.substring(markerEnd);
642
+ }
643
+ result = result.replace(/:\s*\|[-+]?\s*\n(\s*\{\{)/g, ':\n$1');
644
+ result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
645
+ return result;
103
646
  }
104
647
  export function stringify(obj, options = {}) {
105
- const jsYamlOptions = {
106
- lineWidth: options.lineWidth ?? 0,
107
- quotingType: options.doubleQuotedAsJSON ? '"' : "'",
108
- forceQuotes: options.doubleQuotedAsJSON,
109
- };
110
- return dumpHelmAwareYaml(obj, jsYamlOptions);
648
+ return dumpHelmAwareYaml(obj, options.lineWidth !== undefined ? { lineWidth: options.lineWidth } : {});
111
649
  }
650
+ const COMPILED_PATTERNS = [
651
+ { regex: /\{\{-?\s*define\s+[^}]+\s*-?\}\}[\s\S]*?\{\{-?\s*end\s*-?\}\}/g, type: 'block' },
652
+ { regex: /\{\{[^}]*\{\{[^}]*\}\}[^}]*\}\}/g, type: 'nested' },
653
+ { regex: /\{\{\/\*[\s\S]*?\*\/\}\}/g, type: 'comment' },
654
+ { regex: /\{\{-?[\s\S]*?-?\}\}/g, type: 'action-trimmed' },
655
+ { regex: /\{\{`[\s\S]*?`\}\}/g, type: 'raw' },
656
+ { regex: /\{\{\s*include\s+"[^"]+"\s+[^}]+\s*\}\}/g, type: 'include-context' },
657
+ ];
112
658
  export function validateHelmYaml(yaml) {
113
659
  const errors = [];
114
660
  const warnings = [];
@@ -280,120 +826,12 @@ function calculateComplexity(expressions) {
280
826
  }
281
827
  return score;
282
828
  }
283
- export function prettifyHelmTemplate(yaml, options = {}) {
284
- const { indentSize = 2, alignExpressions = true, preserveComments: _preserveComments = true, maxLineWidth = 120, sortKeys = false, groupHelpers = true, } = options;
285
- let formatted = yaml;
286
- if (groupHelpers) {
287
- formatted = groupHelmHelpers(formatted);
288
- }
289
- if (alignExpressions) {
290
- formatted = alignTemplateExpressions(formatted, indentSize);
291
- }
292
- formatted = formatConditionalBlocks(formatted, indentSize);
293
- formatted = formatLoopBlocks(formatted, indentSize);
294
- formatted = wrapLongLines(formatted, maxLineWidth);
295
- if (sortKeys) {
296
- formatted = sortYamlKeys(formatted);
297
- }
298
- return formatted;
299
- }
300
- function alignTemplateExpressions(yaml, _indentSize) {
301
- const lines = yaml.split('\n');
302
- const aligned = [];
303
- for (const line of lines) {
304
- const expressions = line.match(/\{\{[^}]*\}\}/g);
305
- if (expressions && expressions.length > 1) {
306
- const baseIndent = (line.match(/^\s*/) || [''])[0];
307
- const content = line.trim();
308
- const parts = content.split(/(\{\{[^}]*\}\})/);
309
- let alignedLine = baseIndent;
310
- for (let i = 0; i < parts.length; i++) {
311
- alignedLine += parts[i] || '';
312
- const nextPart = parts[i + 1];
313
- if (i < parts.length - 1 && nextPart && nextPart.startsWith('{{')) {
314
- alignedLine += ' ';
315
- }
316
- }
317
- aligned.push(alignedLine);
318
- }
319
- else {
320
- aligned.push(line);
321
- }
322
- }
323
- return aligned.join('\n');
829
+ export function detectHelmExpressions(str) {
830
+ return parseHelmExpressions(str);
324
831
  }
325
- function groupHelmHelpers(yaml) {
326
- const lines = yaml.split('\n');
327
- const groups = {
328
- comments: [],
329
- definitions: [],
330
- conditionals: [],
331
- loops: [],
332
- other: [],
333
- };
334
- for (const line of lines) {
335
- if (line.includes('{{/*')) {
336
- groups.comments.push(line);
337
- }
338
- else if (line.includes('{{- define')) {
339
- groups.definitions.push(line);
340
- }
341
- else if (/\{\{\s*(if|else|end)\s/.test(line)) {
342
- groups.conditionals.push(line);
343
- }
344
- else if (/\{\{\s*(range|with)\s/.test(line)) {
345
- groups.loops.push(line);
346
- }
347
- else {
348
- groups.other.push(line);
349
- }
350
- }
351
- return [
352
- ...groups.comments,
353
- ...groups.definitions,
354
- ...groups.conditionals,
355
- ...groups.loops,
356
- ...groups.other,
357
- ].join('\n');
358
- }
359
- function formatConditionalBlocks(yaml, indentSize) {
360
- const indent = ' '.repeat(indentSize);
361
- return yaml
362
- .replace(/(\{\{\s*if\s+[^}]+\}\})/g, '$1\n' + indent)
363
- .replace(/(\{\{\s*else\s*\}\})/g, '$1\n' + indent)
364
- .replace(/(\{\{\s*end\s*\}\})/g, '\n$1');
365
- }
366
- function formatLoopBlocks(yaml, indentSize) {
367
- const indent = ' '.repeat(indentSize);
368
- return yaml
369
- .replace(/(\{\{\s*range\s+[^}]+\}\})/g, '$1\n' + indent)
370
- .replace(/(\{\{\s*with\s+[^}]+\}\})/g, '$1\n' + indent);
371
- }
372
- function wrapLongLines(yaml, maxLineWidth) {
373
- if (maxLineWidth <= 0)
374
- return yaml;
375
- const lines = yaml.split('\n');
376
- const wrapped = [];
377
- for (const line of lines) {
378
- if (line.length <= maxLineWidth) {
379
- wrapped.push(line);
380
- continue;
381
- }
382
- let remaining = line;
383
- while (remaining.length > maxLineWidth) {
384
- let wrapIndex = remaining.lastIndexOf(' ', maxLineWidth);
385
- if (wrapIndex === -1)
386
- wrapIndex = maxLineWidth;
387
- wrapped.push(remaining.slice(0, wrapIndex));
388
- remaining = remaining.slice(wrapIndex).trim();
389
- }
390
- if (remaining.length > 0) {
391
- wrapped.push(remaining);
392
- }
393
- }
394
- return wrapped.join('\n');
832
+ export function preprocessHelmExpressions(obj) {
833
+ return preprocessHelmConstructs(obj);
395
834
  }
396
- function sortYamlKeys(yaml) {
835
+ export function postProcessHelmExpressions(yaml) {
397
836
  return yaml;
398
837
  }
399
- export { createHelmExpression, isHelmExpression, detectHelmExpressions, preprocessHelmExpressions, postProcessHelmExpressions, };