timonel 2.14.0-beta.1 → 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,5 +1,33 @@
1
- import { Document, Scalar, isMap, isScalar, visit } from 'yaml';
1
+ import { Document, Scalar, isMap, isScalar, visit, Pair } from 'yaml';
2
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 });
30
+ }
3
31
  function serializeElseIfChain(elseContent, openTag, closeTag) {
4
32
  let result = '';
5
33
  let currentElse = elseContent;
@@ -14,6 +42,9 @@ function serializeElseIfChain(elseContent, openTag, closeTag) {
14
42
  return { result, remainingElse: currentElse };
15
43
  }
16
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
+ }
17
48
  const trimLeft = construct.options?.trimLeft ?? true;
18
49
  const trimRight = construct.options?.trimRight ?? true;
19
50
  const openTag = `{{${trimLeft ? '-' : ''} `;
@@ -21,22 +52,117 @@ function serializeHelmConstruct(construct) {
21
52
  switch (construct.type) {
22
53
  case 'if': {
23
54
  const data = construct.data;
24
- let result = `${openTag}if ${data.condition}${closeTag}\n`;
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}`;
25
59
  result += serializeHelmContent(data.then);
26
60
  if (data.else !== undefined) {
27
- const { result: elseIfResult, remainingElse } = serializeElseIfChain(data.else, openTag, closeTag);
28
- result += elseIfResult;
29
- if (remainingElse !== undefined) {
30
- result += `\n${openTag}else${closeTag}\n`;
31
- result += serializeHelmContent(remainingElse);
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
+ }
32
72
  }
33
73
  }
34
- result += `\n${openTag}end${closeTag}`;
74
+ result += `${newline}${openTag}end${closeTag}`;
35
75
  return result;
36
76
  }
37
77
  case 'fragment': {
38
78
  const data = construct.data;
39
- return data.map((item) => serializeHelmContent(item)).join('\n');
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');
40
166
  }
41
167
  case 'range': {
42
168
  const data = construct.data;
@@ -58,7 +184,7 @@ function serializeHelmConstruct(construct) {
58
184
  if (data.pipe) {
59
185
  result += ` | ${data.pipe}`;
60
186
  }
61
- result += `${closeTag}`;
187
+ result += closeTag;
62
188
  return result;
63
189
  }
64
190
  case 'define': {
@@ -104,15 +230,47 @@ function serializeHelmContent(content) {
104
230
  return String(content);
105
231
  }
106
232
  if (typeof content === 'object') {
107
- const doc = new Document(content);
233
+ const preprocessed = preprocessHelmConstructs(content);
234
+ const doc = new Document(preprocessed);
108
235
  let yaml = doc.toString({ lineWidth: 0 });
109
236
  yaml = yaml.trim();
110
237
  return yaml;
111
238
  }
112
239
  return '';
113
240
  }
114
- function preprocessHelmConstructs(obj) {
241
+ export function preprocessHelmConstructs(obj) {
242
+ if (isHelmValue(obj)) {
243
+ const value = obj;
244
+ return createHelmExpression(`{{ ${value.__path} }}`);
245
+ }
246
+ if (isHelmFieldConditional(obj)) {
247
+ return obj;
248
+ }
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)) {
261
+ return obj;
262
+ }
115
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
+ }
116
274
  const helmTemplate = serializeHelmConstruct(obj);
117
275
  return createHelmExpression(helmTemplate);
118
276
  }
@@ -125,6 +283,111 @@ function preprocessHelmConstructs(obj) {
125
283
  }
126
284
  const result = {};
127
285
  for (const [key, value] of Object.entries(obj)) {
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
+ }
128
391
  result[key] = preprocessHelmConstructs(value);
129
392
  }
130
393
  return result;
@@ -144,10 +407,22 @@ export function dumpHelmAwareYaml(obj, options = {}) {
144
407
  const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
145
408
  if (valuePair && isScalar(valuePair.value)) {
146
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
+ }
147
417
  const scalar = new Scalar(value);
148
- if (value.trim().startsWith('{{') && value.includes('\n')) {
418
+ const isMultilineTemplate = value.includes('\n') && value.trim().startsWith('{{');
419
+ const isWithOrRange = value.trim().startsWith('{{- with ') || value.trim().startsWith('{{- range ');
420
+ if (isMultilineTemplate && !isWithOrRange) {
149
421
  scalar.type = 'BLOCK_LITERAL';
150
422
  }
423
+ else if (isWithOrRange) {
424
+ scalar.type = 'PLAIN';
425
+ }
151
426
  else {
152
427
  scalar.type = 'QUOTE_DOUBLE';
153
428
  }
@@ -157,13 +432,214 @@ export function dumpHelmAwareYaml(obj, options = {}) {
157
432
  }
158
433
  return undefined;
159
434
  });
160
- const toStringOptions = {};
435
+ const toStringOptions = { lineWidth: 0 };
161
436
  if (options.lineWidth !== undefined) {
162
437
  toStringOptions.lineWidth = options.lineWidth;
163
438
  }
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
+ }
164
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
+ }
165
540
  result = result.replace(/\\"/g, '"');
166
- 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;
559
+ }
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
+ }
167
643
  result = result.replace(/:\s*\|[-+]?\s*\n(\s*\{\{)/g, ':\n$1');
168
644
  result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
169
645
  return result;
@@ -0,0 +1,102 @@
1
+ import { type HelmExpression } from './helmControlStructures.js';
2
+ declare const HELM_VALUE_SYMBOL: unique symbol;
3
+ export interface HelmValue<T = unknown> {
4
+ [HELM_VALUE_SYMBOL]: true;
5
+ __path: string;
6
+ __type?: T;
7
+ eq(value: HelmValue | string | number | boolean): HelmCondition;
8
+ ne(value: HelmValue | string | number | boolean): HelmCondition;
9
+ gt(value: HelmValue | number): HelmCondition;
10
+ ge(value: HelmValue | number): HelmCondition;
11
+ lt(value: HelmValue | number): HelmCondition;
12
+ le(value: HelmValue | number): HelmCondition;
13
+ not(): HelmCondition;
14
+ and(other: HelmCondition): HelmCondition;
15
+ or(other: HelmCondition): HelmCondition;
16
+ default(defaultValue: HelmValue | string | number | boolean): HelmValue<T>;
17
+ quote(): HelmValue<string>;
18
+ upper(): HelmValue<string>;
19
+ lower(): HelmValue<string>;
20
+ title(): HelmValue<string>;
21
+ trim(): HelmValue<string>;
22
+ trimPrefix(prefix: string): HelmValue<string>;
23
+ trimSuffix(suffix: string): HelmValue<string>;
24
+ replace(old: string, newStr: string): HelmValue<string>;
25
+ contains(substr: string): HelmCondition;
26
+ hasPrefix(prefix: string): HelmCondition;
27
+ hasSuffix(suffix: string): HelmCondition;
28
+ trunc(length: number): HelmValue<string>;
29
+ kindIs(kind: 'string' | 'slice' | 'map' | 'bool' | 'int' | 'float'): HelmCondition;
30
+ hasKey(key: string): HelmCondition;
31
+ toYaml(): HelmValue<string>;
32
+ toJson(): HelmValue<string>;
33
+ nindent(spaces: number): HelmValue<string>;
34
+ indent(spaces: number): HelmValue<string>;
35
+ toExpression(): HelmExpression;
36
+ if<V>(condition: HelmCondition, thenValue: V): HelmFieldConditional<V>;
37
+ ifElse<V>(condition: HelmCondition, thenValue: V, elseValue: V): HelmValue<V>;
38
+ range<V>(callback: (item: HelmValue<T extends (infer U)[] ? U : unknown>, index: HelmValue<number>) => V): HelmRange<V>;
39
+ with<V>(callback: (ctx: HelmValue<T>) => V): HelmWith<V>;
40
+ }
41
+ export interface HelmCondition {
42
+ [HELM_VALUE_SYMBOL]: true;
43
+ __condition: string;
44
+ not(): HelmCondition;
45
+ and(other: HelmCondition): HelmCondition;
46
+ or(other: HelmCondition): HelmCondition;
47
+ toString(): string;
48
+ }
49
+ export interface HelmFieldConditional<T> {
50
+ __helmFieldConditional: true;
51
+ condition: HelmCondition;
52
+ thenValue: T;
53
+ elseValue?: T;
54
+ }
55
+ export interface HelmRange<T> {
56
+ __helmRange: true;
57
+ source: HelmValue;
58
+ callback: (item: HelmValue, index: HelmValue<number>) => T;
59
+ }
60
+ export interface HelmWith<T> {
61
+ __helmWith: true;
62
+ source: HelmValue;
63
+ callback: (ctx: HelmValue) => T;
64
+ }
65
+ export interface HelmHelpers {
66
+ include(templateName: string, context?: '.' | HelmValue): HelmValue<string>;
67
+ printf(format: string, ...args: (HelmValue | string | number)[]): HelmValue<string>;
68
+ release: {
69
+ name: HelmValue<string>;
70
+ namespace: HelmValue<string>;
71
+ service: HelmValue<string>;
72
+ isUpgrade: HelmValue<boolean>;
73
+ isInstall: HelmValue<boolean>;
74
+ revision: HelmValue<number>;
75
+ };
76
+ chart: {
77
+ name: HelmValue<string>;
78
+ version: HelmValue<string>;
79
+ appVersion: HelmValue<string>;
80
+ type: HelmValue<string>;
81
+ };
82
+ capabilities: {
83
+ kubeVersion: {
84
+ version: HelmValue<string>;
85
+ major: HelmValue<string>;
86
+ minor: HelmValue<string>;
87
+ };
88
+ apiVersions: {
89
+ has(apiVersion: string): HelmCondition;
90
+ };
91
+ };
92
+ rawCondition(condition: string): HelmCondition;
93
+ }
94
+ export declare function valuesRef<T extends Record<string, unknown>>(): HelmValue<T> & HelmHelpers;
95
+ export declare function isHelmValue(value: unknown): value is HelmValue;
96
+ export declare function isHelmCondition(value: unknown): value is HelmCondition;
97
+ export declare function isHelmFieldConditional(value: unknown): value is HelmFieldConditional<unknown>;
98
+ export declare function isHelmRange(value: unknown): value is HelmRange<unknown>;
99
+ export declare function isHelmWith(value: unknown): value is HelmWith<unknown>;
100
+ export declare function serializeHelmValue(value: HelmValue): string;
101
+ export declare function serializeHelmCondition(condition: HelmCondition): string;
102
+ export {};