timonel 2.10.2 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +8 -54
  3. package/dist/cli.js +65 -52
  4. package/dist/index.d.ts +1 -2
  5. package/dist/index.js +1 -2
  6. package/dist/lib/helmChartWriter.js +17 -2
  7. package/dist/lib/rutter.d.ts +1 -0
  8. package/dist/lib/rutter.js +64 -11
  9. package/dist/lib/templates/flexible-subchart.d.ts +1 -0
  10. package/dist/lib/templates/flexible-subchart.js +101 -0
  11. package/dist/lib/templates/umbrella-chart.d.ts +13 -2
  12. package/dist/lib/templates/umbrella-chart.js +128 -42
  13. package/dist/lib/utils/helmHelpers/envHelpers.d.ts +2 -0
  14. package/dist/lib/utils/helmHelpers/envHelpers.js +97 -0
  15. package/dist/lib/utils/helmHelpers/gitopsHelpers.d.ts +2 -0
  16. package/dist/lib/utils/helmHelpers/gitopsHelpers.js +132 -0
  17. package/dist/lib/utils/helmHelpers/index.d.ts +23 -0
  18. package/dist/lib/utils/helmHelpers/index.js +132 -0
  19. package/dist/lib/utils/helmHelpers/observabilityHelpers.d.ts +2 -0
  20. package/dist/lib/utils/helmHelpers/observabilityHelpers.js +221 -0
  21. package/dist/lib/utils/helmHelpers/types.d.ts +16 -0
  22. package/dist/lib/utils/helmHelpers/types.js +1 -0
  23. package/dist/lib/utils/helmHelpers/validationHelpers.d.ts +2 -0
  24. package/dist/lib/utils/helmHelpers/validationHelpers.js +201 -0
  25. package/dist/lib/utils/helmHelpers.d.ts +11 -1
  26. package/dist/lib/utils/helmHelpers.js +20 -7
  27. package/dist/lib/utils/helmYamlSerializer.d.ts +53 -1
  28. package/dist/lib/utils/helmYamlSerializer.js +325 -29
  29. package/dist/lib/utils/logger.d.ts +49 -0
  30. package/dist/lib/utils/logger.js +418 -0
  31. package/package.json +21 -11
  32. package/dist/lib/templates/basic-chart.d.ts +0 -19
  33. package/dist/lib/templates/basic-chart.js +0 -210
  34. package/dist/lib/templates/subchart.d.ts +0 -27
  35. package/dist/lib/templates/subchart.js +0 -219
@@ -1,14 +1,17 @@
1
1
  import * as jsYaml from 'js-yaml';
2
- const HELM_EXPRESSION_PATTERNS = [
3
- /\{\{\s*[^}]+\s*\}\}/g,
4
- /\{\{\s*include\s+[^}]+\s*\}\}/g,
5
- /\{\{\s*if\s+[^}]+\s*\}\}/g,
6
- /\{\{\s*else\s*\}\}/g,
7
- /\{\{\s*end\s*\}\}/g,
8
- /\{\{\s*range\s+[^}]+\s*\}\}/g,
9
- /\{\{\s*with\s+[^}]+\s*\}\}/g,
10
- /\{\{\s*\$[^}]+\s*\}\}/g,
11
- /\{\{\s*[^}]*\|\s*[^}]+\s*\}\}/g,
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' },
12
15
  ];
13
16
  function createHelmExpression(value) {
14
17
  return {
@@ -22,24 +25,42 @@ function isHelmExpression(value) {
22
25
  '__helmExpression' in value &&
23
26
  value.__helmExpression === true);
24
27
  }
25
- function containsHelmExpression(str) {
26
- return HELM_EXPRESSION_PATTERNS.some((pattern) => pattern.test(str));
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++;
42
+ }
43
+ }
44
+ return matches.sort((a, b) => a.start - b.start);
27
45
  }
28
- function preprocessHelmExpressions(obj) {
46
+ function preprocessHelmExpressions(obj, depth = 0) {
47
+ if (depth > 100) {
48
+ throw new Error('Maximum recursion depth exceeded during Helm preprocessing');
49
+ }
29
50
  if (typeof obj === 'string') {
30
- if (containsHelmExpression(obj)) {
51
+ if (detectHelmExpressions(obj).length > 0) {
31
52
  return createHelmExpression(obj);
32
53
  }
33
54
  return obj;
34
55
  }
35
56
  if (Array.isArray(obj)) {
36
- return obj.map(preprocessHelmExpressions);
57
+ return obj.map((item) => preprocessHelmExpressions(item, depth + 1));
37
58
  }
38
- if (obj && typeof obj === 'object') {
59
+ if (typeof obj === 'object' && obj !== null) {
39
60
  const result = {};
40
61
  for (const [key, value] of Object.entries(obj)) {
41
62
  Object.defineProperty(result, key, {
42
- value: preprocessHelmExpressions(value),
63
+ value: preprocessHelmExpressions(value, depth + 1),
43
64
  writable: true,
44
65
  enumerable: true,
45
66
  configurable: true,
@@ -55,26 +76,28 @@ function helmAwareReplacer(key, value) {
55
76
  }
56
77
  return value;
57
78
  }
79
+ function postProcessHelmExpressions(yaml) {
80
+ let processed = yaml;
81
+ processed = processed.replace(/'(\{\{[^}]*\}\})'/g, '$1');
82
+ processed = processed.replace(/"(\{\{[^}]*\}\})"/g, '$1');
83
+ processed = processed.replace(/'(\{\{[^}]*\\"[^}]*\}\})'/g, '$1');
84
+ processed = processed.replace(/"(\{\{[^}]*\\"[^}]*\}\})"/g, '$1');
85
+ processed = processed.replace(/\\(\{\{[^}]+\}\})/g, '$1');
86
+ return processed;
87
+ }
58
88
  export function dumpHelmAwareYaml(obj, options = {}) {
59
89
  const preprocessed = preprocessHelmExpressions(obj);
60
- const helmOptions = {
90
+ const dumpOptions = {
61
91
  forceQuotes: false,
62
92
  lineWidth: options.lineWidth ?? 0,
63
93
  flowLevel: options.flowLevel ?? -1,
64
94
  replacer: helmAwareReplacer,
65
95
  ...options,
66
96
  };
67
- let yamlOutput = jsYaml.dump(preprocessed, helmOptions);
97
+ let yamlOutput = jsYaml.dump(preprocessed, dumpOptions);
68
98
  yamlOutput = postProcessHelmExpressions(yamlOutput);
69
99
  return yamlOutput;
70
100
  }
71
- function postProcessHelmExpressions(yaml) {
72
- let processed = yaml;
73
- processed = processed.replace(/'(\{\{[^}]*\}\})'/g, '$1');
74
- processed = processed.replace(/"(\{\{[^}]*\}\})"/g, '$1');
75
- processed = processed.replace(/\\(\{\{[^}]*\}\})/g, '$1');
76
- return processed;
77
- }
78
101
  export function stringify(obj, options = {}) {
79
102
  const jsYamlOptions = {
80
103
  lineWidth: options.lineWidth ?? 0,
@@ -82,7 +105,280 @@ export function stringify(obj, options = {}) {
82
105
  };
83
106
  return dumpHelmAwareYaml(obj, jsYamlOptions);
84
107
  }
85
- export function validateHelmExpressions(yaml) {
86
- const quotedHelmExpressions = [/'(\{\{[^}]*\}\})'/g, /"(\{\{[^}]*\}\})"/g];
87
- return !quotedHelmExpressions.some((pattern) => pattern.test(yaml));
108
+ export function validateHelmYaml(yaml) {
109
+ const errors = [];
110
+ const warnings = [];
111
+ const expressions = parseHelmExpressions(yaml);
112
+ for (const expr of expressions) {
113
+ try {
114
+ validateHelmSyntax(expr.expression);
115
+ }
116
+ catch (err) {
117
+ errors.push({
118
+ type: 'syntax',
119
+ message: err instanceof Error ? err.message : 'Unknown syntax error',
120
+ line: expr.startLine,
121
+ column: expr.startCol,
122
+ expression: expr.expression,
123
+ });
124
+ }
125
+ }
126
+ checkCommonIssues(yaml, warnings);
127
+ checkQuotedExpressions(yaml, warnings);
128
+ const expressionsByType = expressions.reduce((acc, expr) => {
129
+ acc[expr.type] = (acc[expr.type] || 0) + 1;
130
+ return acc;
131
+ }, {});
132
+ const complexity = calculateComplexity(expressions.map((expr) => ({
133
+ type: expr.type,
134
+ expression: expr.expression,
135
+ start: 0,
136
+ end: 0,
137
+ })));
138
+ return {
139
+ isValid: errors.length === 0,
140
+ errors,
141
+ warnings,
142
+ statistics: {
143
+ totalExpressions: expressions.length,
144
+ expressionsByType,
145
+ complexity,
146
+ },
147
+ };
148
+ }
149
+ export function parseHelmExpressions(content) {
150
+ const expressions = [];
151
+ const lines = content.split('\n');
152
+ for (let i = 0; i < lines.length; i++) {
153
+ const line = lines[i];
154
+ if (!line)
155
+ continue;
156
+ for (const { regex, type } of COMPILED_PATTERNS) {
157
+ let match;
158
+ regex.lastIndex = 0;
159
+ while ((match = regex.exec(line)) !== null) {
160
+ expressions.push({
161
+ type,
162
+ expression: match[0],
163
+ startLine: i + 1,
164
+ startCol: match.index + 1,
165
+ endLine: i + 1,
166
+ endCol: match.index + match[0].length + 1,
167
+ });
168
+ if (match.index === regex.lastIndex)
169
+ regex.lastIndex++;
170
+ }
171
+ }
172
+ }
173
+ return expressions;
174
+ }
175
+ function validateHelmSyntax(expression) {
176
+ const content = expression.replace(/^\{\{-?\s*/, '').replace(/\s*-?\}\}$/, '');
177
+ if (!isBalanced(content, '(', ')')) {
178
+ throw new Error('Unbalanced parentheses in Helm expression');
179
+ }
180
+ if (!isQuotesBalanced(content)) {
181
+ throw new Error('Unbalanced quotes in Helm expression');
182
+ }
183
+ validateFunctionCalls(content);
184
+ }
185
+ function isBalanced(str, open, close) {
186
+ let count = 0;
187
+ for (const c of str) {
188
+ if (c === open)
189
+ count++;
190
+ else if (c === close)
191
+ count--;
192
+ if (count < 0)
193
+ return false;
194
+ }
195
+ return count === 0;
196
+ }
197
+ function isQuotesBalanced(str) {
198
+ let singleQuoteOpen = false;
199
+ let doubleQuoteOpen = false;
200
+ let escaped = false;
201
+ for (const c of str) {
202
+ if (c === '\\' && !escaped) {
203
+ escaped = true;
204
+ continue;
205
+ }
206
+ if ((c === "'" && !doubleQuoteOpen && !escaped) ||
207
+ (c === '"' && !singleQuoteOpen && !escaped)) {
208
+ if (c === "'")
209
+ singleQuoteOpen = !singleQuoteOpen;
210
+ else
211
+ doubleQuoteOpen = !doubleQuoteOpen;
212
+ }
213
+ escaped = false;
214
+ }
215
+ return !singleQuoteOpen && !doubleQuoteOpen;
216
+ }
217
+ function validateFunctionCalls(_content) {
218
+ }
219
+ function checkCommonIssues(yaml, warnings) {
220
+ const deprecatedFunctions = ['template'];
221
+ for (const func of deprecatedFunctions) {
222
+ const pattern = new RegExp(`\\{\\{[^}]*\\b${func}\\b[^}]*\\}\\}`, 'g');
223
+ let match;
224
+ while ((match = pattern.exec(yaml)) !== null) {
225
+ warnings.push({
226
+ type: 'semantic',
227
+ message: `Function '${func}' is deprecated`,
228
+ expression: match[0],
229
+ suggestion: `Consider avoiding deprecated function '${func}'`,
230
+ });
231
+ }
232
+ }
233
+ }
234
+ function checkQuotedExpressions(yaml, warnings) {
235
+ const quotedPatterns = [/'(\{\{[^}]+\}\})'/g, /"(\{\{[^}]+\}\})"/g];
236
+ for (const pattern of quotedPatterns) {
237
+ let match;
238
+ pattern.lastIndex = 0;
239
+ while ((match = pattern.exec(yaml)) !== null) {
240
+ warnings.push({
241
+ type: 'semantic',
242
+ message: 'Helm expression should not be quoted',
243
+ expression: match[0],
244
+ suggestion: `Remove quotes around: ${match[1]}`,
245
+ });
246
+ }
247
+ }
248
+ }
249
+ function calculateComplexity(expressions) {
250
+ let score = 0;
251
+ for (const expr of expressions) {
252
+ switch (expr.type) {
253
+ case 'block':
254
+ score += 5;
255
+ break;
256
+ case 'nested':
257
+ score += 4;
258
+ break;
259
+ case 'comment':
260
+ score += 0;
261
+ break;
262
+ default:
263
+ score += 1;
264
+ }
265
+ }
266
+ return score;
267
+ }
268
+ export function prettifyHelmTemplate(yaml, options = {}) {
269
+ const { indentSize = 2, alignExpressions = true, preserveComments: _preserveComments = true, maxLineWidth = 120, sortKeys = false, groupHelpers = true, } = options;
270
+ let formatted = yaml;
271
+ if (groupHelpers) {
272
+ formatted = groupHelmHelpers(formatted);
273
+ }
274
+ if (alignExpressions) {
275
+ formatted = alignTemplateExpressions(formatted, indentSize);
276
+ }
277
+ formatted = formatConditionalBlocks(formatted, indentSize);
278
+ formatted = formatLoopBlocks(formatted, indentSize);
279
+ formatted = wrapLongLines(formatted, maxLineWidth);
280
+ if (sortKeys) {
281
+ formatted = sortYamlKeys(formatted);
282
+ }
283
+ return formatted;
284
+ }
285
+ function alignTemplateExpressions(yaml, _indentSize) {
286
+ const lines = yaml.split('\n');
287
+ const aligned = [];
288
+ for (const line of lines) {
289
+ const expressions = line.match(/\{\{[^}]*\}\}/g);
290
+ if (expressions && expressions.length > 1) {
291
+ const baseIndent = (line.match(/^\s*/) || [''])[0];
292
+ const content = line.trim();
293
+ const parts = content.split(/(\{\{[^}]*\}\})/);
294
+ let alignedLine = baseIndent;
295
+ for (let i = 0; i < parts.length; i++) {
296
+ alignedLine += parts[i] || '';
297
+ const nextPart = parts[i + 1];
298
+ if (i < parts.length - 1 && nextPart && nextPart.startsWith('{{')) {
299
+ alignedLine += ' ';
300
+ }
301
+ }
302
+ aligned.push(alignedLine);
303
+ }
304
+ else {
305
+ aligned.push(line);
306
+ }
307
+ }
308
+ return aligned.join('\n');
309
+ }
310
+ function groupHelmHelpers(yaml) {
311
+ const lines = yaml.split('\n');
312
+ const groups = {
313
+ comments: [],
314
+ definitions: [],
315
+ conditionals: [],
316
+ loops: [],
317
+ other: [],
318
+ };
319
+ for (const line of lines) {
320
+ if (line.includes('{{/*')) {
321
+ groups.comments.push(line);
322
+ }
323
+ else if (line.includes('{{- define')) {
324
+ groups.definitions.push(line);
325
+ }
326
+ else if (/\{\{\s*(if|else|end)\s/.test(line)) {
327
+ groups.conditionals.push(line);
328
+ }
329
+ else if (/\{\{\s*(range|with)\s/.test(line)) {
330
+ groups.loops.push(line);
331
+ }
332
+ else {
333
+ groups.other.push(line);
334
+ }
335
+ }
336
+ return [
337
+ ...groups.comments,
338
+ ...groups.definitions,
339
+ ...groups.conditionals,
340
+ ...groups.loops,
341
+ ...groups.other,
342
+ ].join('\n');
343
+ }
344
+ function formatConditionalBlocks(yaml, indentSize) {
345
+ const indent = ' '.repeat(indentSize);
346
+ return yaml
347
+ .replace(/(\{\{\s*if\s+[^}]+\}\})/g, '$1\n' + indent)
348
+ .replace(/(\{\{\s*else\s*\}\})/g, '$1\n' + indent)
349
+ .replace(/(\{\{\s*end\s*\}\})/g, '\n$1');
350
+ }
351
+ function formatLoopBlocks(yaml, indentSize) {
352
+ const indent = ' '.repeat(indentSize);
353
+ return yaml
354
+ .replace(/(\{\{\s*range\s+[^}]+\}\})/g, '$1\n' + indent)
355
+ .replace(/(\{\{\s*with\s+[^}]+\}\})/g, '$1\n' + indent);
356
+ }
357
+ function wrapLongLines(yaml, maxLineWidth) {
358
+ if (maxLineWidth <= 0)
359
+ return yaml;
360
+ const lines = yaml.split('\n');
361
+ const wrapped = [];
362
+ for (const line of lines) {
363
+ if (line.length <= maxLineWidth) {
364
+ wrapped.push(line);
365
+ continue;
366
+ }
367
+ let remaining = line;
368
+ while (remaining.length > maxLineWidth) {
369
+ let wrapIndex = remaining.lastIndexOf(' ', maxLineWidth);
370
+ if (wrapIndex === -1)
371
+ wrapIndex = maxLineWidth;
372
+ wrapped.push(remaining.slice(0, wrapIndex));
373
+ remaining = remaining.slice(wrapIndex).trim();
374
+ }
375
+ if (remaining.length > 0) {
376
+ wrapped.push(remaining);
377
+ }
378
+ }
379
+ return wrapped.join('\n');
380
+ }
381
+ function sortYamlKeys(yaml) {
382
+ return yaml;
88
383
  }
384
+ export { createHelmExpression, isHelmExpression, detectHelmExpressions, preprocessHelmExpressions, postProcessHelmExpressions, };
@@ -0,0 +1,49 @@
1
+ export declare enum LogLevel {
2
+ FATAL = 60,
3
+ ERROR = 50,
4
+ WARN = 40,
5
+ INFO = 30,
6
+ DEBUG = 20,
7
+ TRACE = 10
8
+ }
9
+ export interface LoggerConfig {
10
+ level: LogLevel;
11
+ silent: boolean;
12
+ service: string;
13
+ environment: string;
14
+ prettyPrint: boolean;
15
+ base?: Record<string, unknown>;
16
+ }
17
+ export interface LogContext {
18
+ correlationId?: string;
19
+ userId?: string;
20
+ operation?: string;
21
+ component?: string;
22
+ chartName?: string;
23
+ [key: string]: unknown;
24
+ }
25
+ export declare class TimonelLogger {
26
+ private readonly logger;
27
+ private readonly config;
28
+ private static readonly SENSITIVE_FIELDS;
29
+ constructor(config?: Partial<LoggerConfig>);
30
+ private mapLogLevel;
31
+ private requestSerializer;
32
+ private responseSerializer;
33
+ fatal(message: string, context?: LogContext): void;
34
+ error(message: string, context?: LogContext): void;
35
+ warn(message: string, context?: LogContext): void;
36
+ info(message: string, context?: LogContext): void;
37
+ debug(message: string, context?: LogContext): void;
38
+ trace(message: string, context?: LogContext): void;
39
+ private log;
40
+ private sanitizeContext;
41
+ private sanitizeNestedObject;
42
+ child(context: LogContext): TimonelLogger;
43
+ time(operation: string): () => void;
44
+ setLevel(level: LogLevel): void;
45
+ setSilent(silent: boolean): void;
46
+ flush(): Promise<void>;
47
+ }
48
+ export declare const logger: TimonelLogger;
49
+ export declare function createLogger(component: string, additionalConfig?: Partial<LoggerConfig>): TimonelLogger;