timonel 2.13.0-beta.1 → 2.14.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.
- package/CHANGELOG.md +12 -0
- package/README.md +70 -26
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/lib/rutter.d.ts +0 -12
- package/dist/lib/rutter.js +16 -184
- package/dist/lib/templates/flexible-subchart.js +23 -29
- package/dist/lib/templates/umbrella-chart.js +7 -7
- package/dist/lib/utils/helmConstructSerializer.d.ts +3 -0
- package/dist/lib/utils/helmConstructSerializer.js +61 -0
- package/dist/lib/utils/helmControlStructures.d.ts +34 -0
- package/dist/lib/utils/helmControlStructures.js +112 -0
- package/dist/lib/utils/helmYamlSerializer.d.ts +8 -28
- package/dist/lib/utils/helmYamlSerializer.js +167 -202
- package/package.json +10 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
# [2.14.0-beta.1](https://github.com/KenkoGeek/timonel/compare/v2.13.0...v2.14.0-beta.1) (2025-11-28)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
- **cli:** add Helm helpers (helmIf, helmWith) to templates and new release ([#201](https://github.com/KenkoGeek/timonel/issues/201)) ([b5ecb0f](https://github.com/KenkoGeek/timonel/commit/b5ecb0f29b207f7ff225b971c6e698bbc2d26073))
|
|
6
|
+
|
|
7
|
+
# [2.13.0](https://github.com/KenkoGeek/timonel/compare/v2.12.2...v2.13.0) (2025-11-24)
|
|
8
|
+
|
|
9
|
+
### Features
|
|
10
|
+
|
|
11
|
+
- **core:** exporting createHelmExpression() from helmYamlSerializer ([#196](https://github.com/KenkoGeek/timonel/issues/196)) ([e9ee2d0](https://github.com/KenkoGeek/timonel/commit/e9ee2d08b6ad305e0652c53e7756240793987861))
|
|
12
|
+
|
|
1
13
|
# [2.13.0-beta.1](https://github.com/KenkoGeek/timonel/compare/v2.12.2...v2.13.0-beta.1) (2025-11-24)
|
|
2
14
|
|
|
3
15
|
### Features
|
package/README.md
CHANGED
|
@@ -18,6 +18,8 @@ directory.
|
|
|
18
18
|
## ✨ Key Features
|
|
19
19
|
|
|
20
20
|
- **🔒 Type-safe API** with strict TypeScript and cdk8s constructs
|
|
21
|
+
- **🎯 Type-Safe Helm Helpers** with 9 composable template helpers (`helmIf`, `helmRange`,
|
|
22
|
+
`helmWith`, `helmInclude`, etc.)
|
|
21
23
|
- **🔧 Flexible resource creation** with built-in methods and `addManifest()` for custom resources
|
|
22
24
|
- **🌍 Multi-environment support** with automatic values files generation
|
|
23
25
|
- **☂️ Umbrella Charts** for managing multiple subcharts as a single unit
|
|
@@ -72,33 +74,58 @@ tl umbrella synth
|
|
|
72
74
|
### Simple Web Application
|
|
73
75
|
|
|
74
76
|
```typescript
|
|
75
|
-
import {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
77
|
+
import { Rutter, helmInclude, createHelmExpression as helm } from 'timonel';
|
|
78
|
+
|
|
79
|
+
const chart = new Rutter({
|
|
80
|
+
meta: {
|
|
81
|
+
name: 'web-app',
|
|
82
|
+
version: '1.0.0',
|
|
83
|
+
description: 'Simple web application',
|
|
84
|
+
},
|
|
85
|
+
defaultValues: {
|
|
86
|
+
replicas: 3,
|
|
87
|
+
image: {
|
|
88
|
+
repository: 'nginx',
|
|
89
|
+
tag: 'latest',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Add Deployment with type-safe helpers
|
|
95
|
+
chart.addManifest(
|
|
96
|
+
{
|
|
97
|
+
apiVersion: 'apps/v1',
|
|
98
|
+
kind: 'Deployment',
|
|
99
|
+
metadata: {
|
|
100
|
+
name: helmInclude('chart.fullname', '.'),
|
|
101
|
+
labels: helmInclude('chart.labels', '.', { pipe: 'nindent 4' }),
|
|
102
|
+
},
|
|
103
|
+
spec: {
|
|
104
|
+
replicas: helm('{{ .Values.replicas }}'),
|
|
105
|
+
selector: {
|
|
106
|
+
matchLabels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 6' }),
|
|
107
|
+
},
|
|
108
|
+
template: {
|
|
109
|
+
metadata: {
|
|
110
|
+
labels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 8' }),
|
|
91
111
|
},
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
112
|
+
spec: {
|
|
113
|
+
containers: [
|
|
114
|
+
{
|
|
115
|
+
name: 'web',
|
|
116
|
+
image: helm('{{ .Values.image.repository }}:{{ .Values.image.tag }}'),
|
|
117
|
+
ports: [{ containerPort: 80, name: 'http' }],
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
'deployment',
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Generate the chart
|
|
128
|
+
chart.write('./dist');
|
|
102
129
|
```
|
|
103
130
|
|
|
104
131
|
### Umbrella Chart with Multiple Services
|
|
@@ -126,6 +153,23 @@ const umbrellaConfig = {
|
|
|
126
153
|
export const umbrella = new UmbrellaChartTemplate(umbrellaConfig);
|
|
127
154
|
```
|
|
128
155
|
|
|
156
|
+
### Type-Safe Helm Helpers
|
|
157
|
+
|
|
158
|
+
Timonel provides 9 composable, type-safe helpers for Helm template generation: `helmIf`,
|
|
159
|
+
`helmRange`, `helmWith`, `helmInclude`, `helmDefine`, `helmVar`, `helmBlock`, `helmComment`, and
|
|
160
|
+
`helmFragment`.
|
|
161
|
+
|
|
162
|
+
**Benefits:**
|
|
163
|
+
|
|
164
|
+
- ✅ 100% Type-Safe - catch errors at compile time
|
|
165
|
+
- ✅ No Raw Strings - eliminate manual template interpolation
|
|
166
|
+
- ✅ Composable - nest and combine helpers freely
|
|
167
|
+
- ✅ Full IDE Support - autocomplete and type hints
|
|
168
|
+
|
|
169
|
+
**Learn more:** See the
|
|
170
|
+
[Type-Safe Helm Helpers Guide](https://github.com/KenkoGeek/timonel/wiki/Helm-Helpers-System) for
|
|
171
|
+
complete documentation, examples, and best practices.
|
|
172
|
+
|
|
129
173
|
## 📚 Documentation
|
|
130
174
|
|
|
131
175
|
- **[API Reference](https://github.com/KenkoGeek/timonel/wiki/API-Reference)** - Complete API
|
package/dist/index.d.ts
CHANGED
|
@@ -11,4 +11,6 @@ export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeCl
|
|
|
11
11
|
export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
|
|
12
12
|
export { AWS_HELPERS, createHelper, FILE_ACCESS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, getHelpersByCategory, KUBERNETES_HELPERS, SPRIG_HELPERS, STANDARD_HELPERS, TEMPLATE_FUNCTION_HELPERS, type HelperDefinition, } from './lib/utils/helmHelpers.js';
|
|
13
13
|
export { createLogger, logger, LogLevel, TimonelLogger, type LogContext, type LoggerConfig, } from './lib/utils/logger.js';
|
|
14
|
-
export {
|
|
14
|
+
export { UmbrellaRutter } from './lib/umbrellaRutter.js';
|
|
15
|
+
export { helmIf, helmRange, helmWith, helmInclude, helmDefine, helmVar, helmBlock, helmComment, helmFragment, createHelmExpression, isHelmConstruct, isHelmExpression, type HelmConstruct, type HelmExpression, type HelmContent, type HelmWhitespaceOptions, } from './lib/utils/helmControlStructures.js';
|
|
16
|
+
export { dumpHelmAwareYaml, validateHelmYaml, type HelmValidationError, type HelmValidationResult, } from './lib/utils/helmYamlSerializer.js';
|
package/dist/index.js
CHANGED
|
@@ -8,4 +8,6 @@ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella
|
|
|
8
8
|
export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
|
|
9
9
|
export { AWS_HELPERS, createHelper, FILE_ACCESS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, getHelpersByCategory, KUBERNETES_HELPERS, SPRIG_HELPERS, STANDARD_HELPERS, TEMPLATE_FUNCTION_HELPERS, } from './lib/utils/helmHelpers.js';
|
|
10
10
|
export { createLogger, logger, LogLevel, TimonelLogger, } from './lib/utils/logger.js';
|
|
11
|
-
export {
|
|
11
|
+
export { UmbrellaRutter } from './lib/umbrellaRutter.js';
|
|
12
|
+
export { helmIf, helmRange, helmWith, helmInclude, helmDefine, helmVar, helmBlock, helmComment, helmFragment, createHelmExpression, isHelmConstruct, isHelmExpression, } from './lib/utils/helmControlStructures.js';
|
|
13
|
+
export { dumpHelmAwareYaml, validateHelmYaml, } from './lib/utils/helmYamlSerializer.js';
|
package/dist/lib/rutter.d.ts
CHANGED
|
@@ -93,18 +93,6 @@ export declare class Rutter {
|
|
|
93
93
|
target: string;
|
|
94
94
|
}>;
|
|
95
95
|
private toSynthArray;
|
|
96
|
-
private processHelmTemplates;
|
|
97
|
-
private fixCharacterMappingIssues;
|
|
98
|
-
private processCharacterMapping;
|
|
99
|
-
private applyHelmTemplateReplacements;
|
|
100
|
-
private fixIncludeStatements;
|
|
101
|
-
private fixFunctionCalls;
|
|
102
|
-
private fixChartReferences;
|
|
103
|
-
private removeHelmExpressionQuotes;
|
|
104
|
-
private fixConditionalBlocks;
|
|
105
|
-
private fixPipeExpressions;
|
|
106
|
-
private fixNestedQuotes;
|
|
107
|
-
private cleanupArtifacts;
|
|
108
96
|
write(outDir: string): void;
|
|
109
97
|
}
|
|
110
98
|
export interface ChartMetadata {
|
package/dist/lib/rutter.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ApiObject, App, Chart, Testing } from 'cdk8s';
|
|
2
|
-
import
|
|
2
|
+
import { parse } from 'yaml';
|
|
3
3
|
import { include } from './helm.js';
|
|
4
4
|
import { HelmChartWriter } from './helmChartWriter.js';
|
|
5
5
|
import { AWSResources } from './resources/cloud/aws/awsResources.js';
|
|
6
6
|
import { createLogger } from './utils/logger.js';
|
|
7
7
|
import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
|
|
8
|
+
import { isHelmExpression, isHelmConstruct } from './utils/helmControlStructures.js';
|
|
8
9
|
import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
|
|
9
10
|
import { generateHelpersTemplate } from './utils/helmHelpers.js';
|
|
10
11
|
export class Rutter {
|
|
@@ -63,7 +64,7 @@ export class Rutter {
|
|
|
63
64
|
let manifestObject;
|
|
64
65
|
if (typeof yamlOrObject === 'string') {
|
|
65
66
|
try {
|
|
66
|
-
manifestObject =
|
|
67
|
+
manifestObject = parse(yamlOrObject);
|
|
67
68
|
}
|
|
68
69
|
catch (error) {
|
|
69
70
|
throw new Error(`Invalid YAML provided to addManifest(): ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
@@ -76,12 +77,13 @@ export class Rutter {
|
|
|
76
77
|
throw new Error('addManifest() requires either a YAML string or an object');
|
|
77
78
|
}
|
|
78
79
|
this.validateManifestStructure(manifestObject);
|
|
79
|
-
|
|
80
|
-
apiVersion: manifestObject
|
|
81
|
-
kind: manifestObject
|
|
82
|
-
metadata: manifestObject
|
|
83
|
-
|
|
84
|
-
}
|
|
80
|
+
const apiObjectProps = {
|
|
81
|
+
apiVersion: manifestObject.apiVersion,
|
|
82
|
+
kind: manifestObject.kind,
|
|
83
|
+
metadata: manifestObject.metadata,
|
|
84
|
+
...manifestObject,
|
|
85
|
+
};
|
|
86
|
+
return new ApiObject(this.chart, id, apiObjectProps);
|
|
85
87
|
}
|
|
86
88
|
addTemplateManifest(yamlTemplate, id) {
|
|
87
89
|
const templateAsset = {
|
|
@@ -169,13 +171,10 @@ ${yamlContent.trim()}
|
|
|
169
171
|
});
|
|
170
172
|
throw new Error('Manifest metadata must have a name');
|
|
171
173
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
actual_type: typeof metadata.name,
|
|
177
|
-
});
|
|
178
|
-
throw new Error('Manifest metadata.name must be a string');
|
|
174
|
+
const name = manifest['metadata']?.['name'];
|
|
175
|
+
if (typeof name !== 'string' && !isHelmExpression(name) && !isHelmConstruct(name)) {
|
|
176
|
+
const actualType = Array.isArray(name) ? 'array' : name === null ? 'null' : typeof name;
|
|
177
|
+
throw new Error(`Manifest metadata.name must be a string, HelmExpression, or HelmConstruct. Got ${actualType}`);
|
|
179
178
|
}
|
|
180
179
|
}
|
|
181
180
|
getMeta() {
|
|
@@ -242,7 +241,7 @@ ${yamlContent.trim()}
|
|
|
242
241
|
const synthAssets = [];
|
|
243
242
|
if (this.props.singleManifestFile) {
|
|
244
243
|
const combinedYaml = enriched
|
|
245
|
-
.map((obj) =>
|
|
244
|
+
.map((obj) => dumpHelmAwareYaml(obj).trim())
|
|
246
245
|
.filter(Boolean)
|
|
247
246
|
.join('\n---\n');
|
|
248
247
|
const manifestId = this.props.manifestPrefix ?? 'manifests';
|
|
@@ -252,7 +251,7 @@ ${yamlContent.trim()}
|
|
|
252
251
|
enriched.forEach((obj, index) => {
|
|
253
252
|
const apiObjectId = apiObjectIds[index];
|
|
254
253
|
const manifestId = apiObjectId || `manifest-${index + 1}`;
|
|
255
|
-
const yaml =
|
|
254
|
+
const yaml = dumpHelmAwareYaml(obj).trim();
|
|
256
255
|
if (yaml) {
|
|
257
256
|
synthAssets.push({ id: manifestId, yaml });
|
|
258
257
|
}
|
|
@@ -270,173 +269,6 @@ ${yamlContent.trim()}
|
|
|
270
269
|
timer();
|
|
271
270
|
return synthAssets;
|
|
272
271
|
}
|
|
273
|
-
processHelmTemplates(yaml) {
|
|
274
|
-
let processed = this.fixCharacterMappingIssues(yaml);
|
|
275
|
-
processed = this.applyHelmTemplateReplacements(processed);
|
|
276
|
-
return processed;
|
|
277
|
-
}
|
|
278
|
-
fixCharacterMappingIssues(yaml) {
|
|
279
|
-
const lines = yaml.split('\n');
|
|
280
|
-
const fixedLines = [];
|
|
281
|
-
let i = 0;
|
|
282
|
-
while (i < lines.length) {
|
|
283
|
-
const line = lines[i];
|
|
284
|
-
if (!line) {
|
|
285
|
-
i++;
|
|
286
|
-
continue;
|
|
287
|
-
}
|
|
288
|
-
const charKeyMatch = line.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
289
|
-
if (charKeyMatch && charKeyMatch[2] && parseInt(charKeyMatch[2]) === 0) {
|
|
290
|
-
const indent = charKeyMatch[1] || '';
|
|
291
|
-
const result = this.processCharacterMapping(lines, i, indent);
|
|
292
|
-
if (result.reconstructed) {
|
|
293
|
-
fixedLines.push(result.reconstructed);
|
|
294
|
-
i = result.nextIndex;
|
|
295
|
-
continue;
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
if (line) {
|
|
299
|
-
fixedLines.push(line);
|
|
300
|
-
}
|
|
301
|
-
i++;
|
|
302
|
-
}
|
|
303
|
-
return fixedLines.join('\n');
|
|
304
|
-
}
|
|
305
|
-
processCharacterMapping(lines, startIndex, indent) {
|
|
306
|
-
const charMappings = [];
|
|
307
|
-
let j = startIndex;
|
|
308
|
-
while (j < lines.length) {
|
|
309
|
-
const currentLine = lines[j];
|
|
310
|
-
if (!currentLine) {
|
|
311
|
-
break;
|
|
312
|
-
}
|
|
313
|
-
const currentMatch = currentLine.match(/^(\s*)"(\d+)":\s*(.+)$/);
|
|
314
|
-
if (currentMatch && currentMatch[1] === indent && currentMatch[2] && currentMatch[3]) {
|
|
315
|
-
const index = parseInt(currentMatch[2]);
|
|
316
|
-
let char = currentMatch[3];
|
|
317
|
-
if (char && char.startsWith('"') && char.endsWith('"')) {
|
|
318
|
-
char = char.slice(1, -1);
|
|
319
|
-
}
|
|
320
|
-
charMappings.push({ index, char });
|
|
321
|
-
j++;
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
if (charMappings.length > 3) {
|
|
328
|
-
charMappings.sort((a, b) => a.index - b.index);
|
|
329
|
-
const reconstructed = charMappings.map((m) => m.char).join('');
|
|
330
|
-
if (reconstructed.includes('{{') && reconstructed.includes('}}')) {
|
|
331
|
-
return { reconstructed: `${indent}${reconstructed}`, nextIndex: j };
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return { nextIndex: j };
|
|
335
|
-
}
|
|
336
|
-
applyHelmTemplateReplacements(processed) {
|
|
337
|
-
processed = this.fixConditionalBlocks(processed);
|
|
338
|
-
processed = this.fixPipeExpressions(processed);
|
|
339
|
-
processed = this.fixNestedQuotes(processed);
|
|
340
|
-
processed = this.cleanupArtifacts(processed);
|
|
341
|
-
processed = this.removeHelmExpressionQuotes(processed);
|
|
342
|
-
return processed;
|
|
343
|
-
}
|
|
344
|
-
fixIncludeStatements(processed) {
|
|
345
|
-
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \| nindent (\d+) \}\}/g, '{{ include "$1" . | nindent $2 }}');
|
|
346
|
-
processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \}\}/g, '{{ include "$1" . }}');
|
|
347
|
-
return processed;
|
|
348
|
-
}
|
|
349
|
-
fixFunctionCalls(processed) {
|
|
350
|
-
return processed.replace(/\{\{ (\w+) \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ $1 "$2" $3 }}');
|
|
351
|
-
}
|
|
352
|
-
fixChartReferences(processed) {
|
|
353
|
-
return processed.replace(/\{\{ \.Chart\.(\w+) \| replace \\"([^"]*)\\" \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ .Chart.$1 | replace "$2" "$3" $4 }}');
|
|
354
|
-
}
|
|
355
|
-
removeHelmExpressionQuotes(processed) {
|
|
356
|
-
const HELM_EXPRESSION_REPLACEMENT = '$1$2: {{$3}}';
|
|
357
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\|\s*(?:int|float|bool|number)[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
358
|
-
processed = processed.replace(/^(\s*)(port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
359
|
-
processed = processed.replace(/^(\s*)(port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
360
|
-
processed = processed.replace(/^(\s*)(service\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
361
|
-
processed = processed.replace(/^(\s*)(backend\.service\.port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
362
|
-
processed = processed.replace(/^(\s*)(spec\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
363
|
-
processed = processed.replace(/^(\s*)([^:\s]*\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
364
|
-
processed = processed.replace(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
365
|
-
processed = processed.replace(/^(\s*)(replicas):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
366
|
-
processed = processed.replace(/^(\s*)(targetPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
367
|
-
processed = processed.replace(/^(\s*)(nodePort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
368
|
-
processed = processed.replace(/^(\s*)(containerPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
369
|
-
processed = processed.replace(/^(\s*)(hostPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
370
|
-
processed = processed.replace(/^(\s*)(weight):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
371
|
-
processed = processed.replace(/^(\s*)(priority):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
372
|
-
processed = processed.replace(/^(\s*)(timeoutSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
373
|
-
processed = processed.replace(/^(\s*)(periodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
374
|
-
processed = processed.replace(/^(\s*)(successThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
375
|
-
processed = processed.replace(/^(\s*)(failureThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
376
|
-
processed = processed.replace(/^(\s*)(initialDelaySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
377
|
-
processed = processed.replace(/^(\s*)(terminationGracePeriodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
378
|
-
processed = processed.replace(/^(\s*)(activeDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
379
|
-
processed = processed.replace(/^(\s*)(backoffLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
380
|
-
processed = processed.replace(/^(\s*)(parallelism):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
381
|
-
processed = processed.replace(/^(\s*)(completions):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
382
|
-
processed = processed.replace(/^(\s*)(revisionHistoryLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
383
|
-
processed = processed.replace(/^(\s*)(progressDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
384
|
-
processed = processed.replace(/^(\s*)(minReadySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
385
|
-
processed = processed.replace(/^(\s*)(maxUnavailable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
386
|
-
processed = processed.replace(/^(\s*)(maxSurge):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
387
|
-
processed = processed.replace(/^(\s*)(cpu):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
388
|
-
processed = processed.replace(/^(\s*)(memory):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
389
|
-
processed = processed.replace(/^(\s*)(enabled):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
390
|
-
processed = processed.replace(/^(\s*)(create):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
391
|
-
processed = processed.replace(/^(\s*)(allow):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
392
|
-
processed = processed.replace(/^(\s*)(disable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
393
|
-
processed = processed.replace(/^(\s*)(force):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
394
|
-
processed = processed.replace(/^(\s*)(required):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
395
|
-
processed = processed.replace(/^(\s*)(optional):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
396
|
-
processed = processed.replace(/^(\s*)(readOnly):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
397
|
-
processed = processed.replace(/^(\s*)(runAsNonRoot):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
398
|
-
processed = processed.replace(/^(\s*)(privileged):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
399
|
-
processed = processed.replace(/^(\s*)(allowPrivilegeEscalation):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
400
|
-
processed = processed.replace(/^(\s*)(readOnlyRootFilesystem):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
401
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*true[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
402
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*false[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
403
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\d+[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
404
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\beq\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
405
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bne\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
406
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\blt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
407
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\ble\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
408
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bgt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
409
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bge\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
410
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\band\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
411
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bor\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
412
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bnot\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
413
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[+][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
414
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[-][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
415
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[*][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
416
|
-
processed = processed.replace(/:\s*"([^"]*\{\{[^}]+\}\}[^"]*)"/g, ': $1');
|
|
417
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(true|false)"$/gm, '$1$2: $3');
|
|
418
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+)"$/gm, '$1$2: $3');
|
|
419
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.\d+)"$/gm, '$1$2: $3');
|
|
420
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.?\d*[eE][+-]?\d+)"$/gm, '$1$2: $3');
|
|
421
|
-
processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
|
|
422
|
-
processed = processed.replace(/"\{\{([^}]*)\}\}"/g, '{{$1}}');
|
|
423
|
-
processed = processed.replace(/^(\s*)-\s*"\{\{([^}]*)\}\}"/gm, '$1- {{$2}}');
|
|
424
|
-
return processed;
|
|
425
|
-
}
|
|
426
|
-
fixConditionalBlocks(processed) {
|
|
427
|
-
return processed.replace(/"\{\{-\s*(if|with|range)([^}]*)\}\}([^"]*)\{\{-\s*end\s*\}\}"/g, '{{- $1$2}}$3{{- end }}');
|
|
428
|
-
}
|
|
429
|
-
fixPipeExpressions(processed) {
|
|
430
|
-
return processed.replace(/"\{\{([^}]*\|[^}]*)\}\}"/g, '{{$1}}');
|
|
431
|
-
}
|
|
432
|
-
fixNestedQuotes(processed) {
|
|
433
|
-
return processed.replace(/\{\{([^}]*)\\"([^"]*)\\"([^}]*)\}\}/g, '{{$1"$2"$3}}');
|
|
434
|
-
}
|
|
435
|
-
cleanupArtifacts(processed) {
|
|
436
|
-
processed = processed.replace(/": "\.nan"/g, ': .nan');
|
|
437
|
-
processed = processed.replace(/: \{\{([^}]+)\}\}$/gm, ': {{$1}}');
|
|
438
|
-
return processed;
|
|
439
|
-
}
|
|
440
272
|
write(outDir) {
|
|
441
273
|
const timer = this.logger.time('chart_write');
|
|
442
274
|
this.logger.info('Starting chart write operation', {
|
|
@@ -107,10 +107,10 @@ export function createFlexibleSubchart(scope, id, config) {
|
|
|
107
107
|
}
|
|
108
108
|
export function generateFlexibleSubchartTemplate(name) {
|
|
109
109
|
return `import { App } from 'cdk8s';
|
|
110
|
-
import { Rutter } from 'timonel';
|
|
110
|
+
import { Rutter, helmInclude, helmIf, helmWith, createHelmExpression as helm } from 'timonel';
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
|
-
* Creates a new chart with
|
|
113
|
+
* Creates a new chart with type-safe Helm helpers
|
|
114
114
|
* @returns Rutter instance for Helm chart generation
|
|
115
115
|
* @since 2.11.0
|
|
116
116
|
*/
|
|
@@ -126,45 +126,43 @@ export default function createChart() {
|
|
|
126
126
|
scope: app,
|
|
127
127
|
defaultValues: {
|
|
128
128
|
appName: '${name}',
|
|
129
|
-
image:
|
|
129
|
+
image: {
|
|
130
|
+
repository: 'nginx',
|
|
131
|
+
tag: 'latest'
|
|
132
|
+
},
|
|
130
133
|
port: 80,
|
|
131
134
|
replicas: 1,
|
|
132
135
|
},
|
|
133
136
|
});
|
|
134
137
|
|
|
135
|
-
// Add
|
|
138
|
+
// Add Deployment with type-safe helpers
|
|
136
139
|
rutter.addManifest({
|
|
137
140
|
apiVersion: 'apps/v1',
|
|
138
141
|
kind: 'Deployment',
|
|
139
142
|
metadata: {
|
|
140
|
-
name: '
|
|
141
|
-
labels: {
|
|
142
|
-
'app.kubernetes.io/name': '${name}'
|
|
143
|
-
}
|
|
143
|
+
name: helmInclude('chart.fullname', '.'),
|
|
144
|
+
labels: helmInclude('chart.labels', '.', { pipe: 'nindent 4' })
|
|
144
145
|
},
|
|
145
146
|
spec: {
|
|
146
|
-
replicas: 1,
|
|
147
|
+
replicas: helmIf('.Values.autoscaling.enabled', '{{ .Values.replicas }}', '1'),
|
|
147
148
|
selector: {
|
|
148
|
-
matchLabels: {
|
|
149
|
-
'app.kubernetes.io/name': '${name}'
|
|
150
|
-
}
|
|
149
|
+
matchLabels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 6' })
|
|
151
150
|
},
|
|
152
151
|
template: {
|
|
153
152
|
metadata: {
|
|
154
|
-
labels: {
|
|
155
|
-
|
|
156
|
-
}
|
|
153
|
+
labels: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 8' }),
|
|
154
|
+
annotations: helmWith('.Values.podAnnotations', helm('{{- toYaml . | nindent 8 }}'))
|
|
157
155
|
},
|
|
158
156
|
spec: {
|
|
159
157
|
containers: [{
|
|
160
158
|
name: '${name}',
|
|
161
|
-
image: '
|
|
159
|
+
image: helm('{{ .Values.image.repository }}:{{ .Values.image.tag }}'),
|
|
162
160
|
ports: [{
|
|
163
|
-
containerPort:
|
|
161
|
+
containerPort: helm('{{ .Values.port }}')
|
|
164
162
|
}],
|
|
165
163
|
env: [
|
|
166
|
-
{ name: 'APP_NAME', value: '
|
|
167
|
-
{ name: 'PORT', value: '
|
|
164
|
+
{ name: 'APP_NAME', value: helm('{{ .Values.appName }}') },
|
|
165
|
+
{ name: 'PORT', value: helm('{{ .Values.port | toString }}') }
|
|
168
166
|
]
|
|
169
167
|
}]
|
|
170
168
|
}
|
|
@@ -172,27 +170,23 @@ export default function createChart() {
|
|
|
172
170
|
}
|
|
173
171
|
}, 'deployment');
|
|
174
172
|
|
|
175
|
-
// Add Service
|
|
173
|
+
// Add Service with type-safe helpers
|
|
176
174
|
rutter.addManifest({
|
|
177
175
|
apiVersion: 'v1',
|
|
178
176
|
kind: 'Service',
|
|
179
177
|
metadata: {
|
|
180
|
-
name: '
|
|
181
|
-
labels: {
|
|
182
|
-
'app.kubernetes.io/name': '${name}'
|
|
183
|
-
}
|
|
178
|
+
name: helmInclude('chart.fullname', '.'),
|
|
179
|
+
labels: helmInclude('chart.labels', '.', { pipe: 'nindent 4' })
|
|
184
180
|
},
|
|
185
181
|
spec: {
|
|
186
182
|
type: 'ClusterIP',
|
|
187
183
|
ports: [{
|
|
188
|
-
port:
|
|
189
|
-
targetPort:
|
|
184
|
+
port: helm('{{ .Values.port }}'),
|
|
185
|
+
targetPort: helm('{{ .Values.port }}'),
|
|
190
186
|
protocol: 'TCP',
|
|
191
187
|
name: 'http'
|
|
192
188
|
}],
|
|
193
|
-
selector: {
|
|
194
|
-
'app.kubernetes.io/name': '${name}'
|
|
195
|
-
}
|
|
189
|
+
selector: helmInclude('chart.selectorLabels', '.', { pipe: 'nindent 4' })
|
|
196
190
|
}
|
|
197
191
|
}, 'service');
|
|
198
192
|
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { writeFileSync, mkdirSync, existsSync, copyFileSync, readdirSync, rmSync } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import
|
|
3
|
+
import { parse } from 'yaml';
|
|
4
4
|
import { App, Chart, ApiObject } from 'cdk8s';
|
|
5
5
|
import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
|
|
6
6
|
import { generateHelpersTemplate } from '../utils/helmHelpers.js';
|
|
7
7
|
import { createFlexibleSubchart } from './flexible-subchart.js';
|
|
8
8
|
export function generateUmbrellaChart(name) {
|
|
9
9
|
return `import { App } from 'cdk8s';
|
|
10
|
-
import { Rutter } from 'timonel';
|
|
10
|
+
import { Rutter, helmInclude, helmIf, helmWith, createHelmExpression as helm } from 'timonel';
|
|
11
11
|
import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
|
|
12
12
|
import { join } from 'path';
|
|
13
|
-
import
|
|
13
|
+
import { parse, stringify } from 'yaml';
|
|
14
14
|
// Import subcharts - add your subchart imports here
|
|
15
15
|
|
|
16
16
|
type SynthMode = 'dependencies' | 'inline';
|
|
@@ -42,7 +42,7 @@ function readYamlFile(filePath: string): Record<string, unknown> {
|
|
|
42
42
|
if (!existsSync(filePath)) {
|
|
43
43
|
return {};
|
|
44
44
|
}
|
|
45
|
-
const content =
|
|
45
|
+
const content = parse(readFileSync(filePath, 'utf8'));
|
|
46
46
|
return content && typeof content === 'object' ? (content as Record<string, unknown>) : {};
|
|
47
47
|
}
|
|
48
48
|
|
|
@@ -147,8 +147,8 @@ export function synth(outDir: string, options?: SynthOptions) {
|
|
|
147
147
|
});
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
writeFileSync(chartPath,
|
|
151
|
-
writeFileSync(valuesPath,
|
|
150
|
+
writeFileSync(chartPath, stringify(chartDoc));
|
|
151
|
+
writeFileSync(valuesPath, stringify(valuesDoc));
|
|
152
152
|
|
|
153
153
|
app.synth();
|
|
154
154
|
|
|
@@ -327,7 +327,7 @@ export class UmbrellaChartTemplate extends Chart {
|
|
|
327
327
|
const assets = getAssets?.() || [];
|
|
328
328
|
assets.forEach((asset, assetIndex) => {
|
|
329
329
|
try {
|
|
330
|
-
const manifest =
|
|
330
|
+
const manifest = parse(asset.yaml);
|
|
331
331
|
if (manifest?.apiVersion && manifest?.kind) {
|
|
332
332
|
new ApiObject(flexibleSubchart, `${String(manifest.kind).toLowerCase()}-${assetIndex}`, {
|
|
333
333
|
apiVersion: String(manifest.apiVersion),
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { isHelmConstruct } from './helmControlStructures.js';
|
|
2
|
+
export function serializeHelmConstruct(construct, indent = 0) {
|
|
3
|
+
const trimLeft = construct.options?.trimLeft ?? true;
|
|
4
|
+
const trimRight = construct.options?.trimRight ?? true;
|
|
5
|
+
const openTag = `{{${trimLeft ? '-' : ''} `;
|
|
6
|
+
const closeTag = ` ${trimRight ? '-' : ''}}}`;
|
|
7
|
+
switch (construct.type) {
|
|
8
|
+
case 'if': {
|
|
9
|
+
const data = construct.data;
|
|
10
|
+
let result = `${openTag}if ${data.condition}${closeTag}\n`;
|
|
11
|
+
result += serializeHelmContent(data.then, indent);
|
|
12
|
+
if (data.else !== undefined) {
|
|
13
|
+
result += `\n${openTag}else${closeTag}\n`;
|
|
14
|
+
result += serializeHelmContent(data.else, indent);
|
|
15
|
+
}
|
|
16
|
+
result += `\n${openTag}end${closeTag}`;
|
|
17
|
+
return result;
|
|
18
|
+
}
|
|
19
|
+
default:
|
|
20
|
+
throw new Error(`Unknown Helm construct type: ${construct.type}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function serializeHelmContent(content, indent = 0) {
|
|
24
|
+
const indentStr = ' '.repeat(indent);
|
|
25
|
+
if (isHelmConstruct(content)) {
|
|
26
|
+
return serializeHelmConstruct(content, indent);
|
|
27
|
+
}
|
|
28
|
+
if (content === null || content === undefined) {
|
|
29
|
+
return '';
|
|
30
|
+
}
|
|
31
|
+
if (typeof content === 'string' || typeof content === 'number' || typeof content === 'boolean') {
|
|
32
|
+
return String(content);
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(content)) {
|
|
35
|
+
return content
|
|
36
|
+
.map((item) => {
|
|
37
|
+
if (isHelmConstruct(item)) {
|
|
38
|
+
return serializeHelmConstruct(item, indent);
|
|
39
|
+
}
|
|
40
|
+
return serializeHelmContent(item, indent);
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
}
|
|
44
|
+
if (typeof content === 'object') {
|
|
45
|
+
const lines = [];
|
|
46
|
+
for (const [key, value] of Object.entries(content)) {
|
|
47
|
+
if (isHelmConstruct(value)) {
|
|
48
|
+
lines.push(`${indentStr}${key}: ${serializeHelmConstruct(value, indent + 1)}`);
|
|
49
|
+
}
|
|
50
|
+
else if (typeof value === 'object' && value !== null) {
|
|
51
|
+
lines.push(`${indentStr}${key}:`);
|
|
52
|
+
lines.push(serializeHelmContent(value, indent + 1));
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
lines.push(`${indentStr}${key}: ${value}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return lines.join('\n');
|
|
59
|
+
}
|
|
60
|
+
return '';
|
|
61
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface HelmConstruct {
|
|
2
|
+
__helmConstruct: true;
|
|
3
|
+
type: 'if' | 'range' | 'with' | 'include' | 'define' | 'var' | 'block' | 'comment' | 'fragment';
|
|
4
|
+
data: unknown;
|
|
5
|
+
options?: {
|
|
6
|
+
trimLeft?: boolean;
|
|
7
|
+
trimRight?: boolean;
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export declare function helmFragment(...contents: HelmContent[]): HelmConstruct;
|
|
11
|
+
export interface HelmExpression {
|
|
12
|
+
__helmExpression: true;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function createHelmExpression(value: string): HelmExpression;
|
|
16
|
+
export declare function isHelmExpression(value: unknown): value is HelmExpression;
|
|
17
|
+
export declare function helmRange(vars: string, collection: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
18
|
+
export declare function helmWith(scope: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
19
|
+
export declare function helmInclude(templateName: string, scope?: string, options?: {
|
|
20
|
+
pipe?: string;
|
|
21
|
+
} & HelmWhitespaceOptions): HelmConstruct;
|
|
22
|
+
export declare function helmDefine(name: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
23
|
+
export declare function helmVar(name: string, value: string, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
24
|
+
export declare function helmBlock(name: string, content: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
25
|
+
export declare function helmComment(text: string): HelmConstruct;
|
|
26
|
+
export type HelmContent = string | number | boolean | null | undefined | HelmConstruct | HelmExpression | {
|
|
27
|
+
[key: string]: HelmContent;
|
|
28
|
+
} | HelmContent[];
|
|
29
|
+
export interface HelmWhitespaceOptions {
|
|
30
|
+
trimLeft?: boolean;
|
|
31
|
+
trimRight?: boolean;
|
|
32
|
+
}
|
|
33
|
+
export declare function helmIf(condition: string, thenContent: HelmContent, elseContent?: HelmContent, options?: HelmWhitespaceOptions): HelmConstruct;
|
|
34
|
+
export declare function isHelmConstruct(value: unknown): value is HelmConstruct;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export function helmFragment(...contents) {
|
|
2
|
+
return {
|
|
3
|
+
__helmConstruct: true,
|
|
4
|
+
type: 'fragment',
|
|
5
|
+
data: contents,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
export function createHelmExpression(value) {
|
|
9
|
+
return {
|
|
10
|
+
__helmExpression: true,
|
|
11
|
+
value,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
export function isHelmExpression(value) {
|
|
15
|
+
return (typeof value === 'object' &&
|
|
16
|
+
value !== null &&
|
|
17
|
+
'__helmExpression' in value &&
|
|
18
|
+
value.__helmExpression === true);
|
|
19
|
+
}
|
|
20
|
+
export function helmRange(vars, collection, content, options) {
|
|
21
|
+
return {
|
|
22
|
+
__helmConstruct: true,
|
|
23
|
+
type: 'range',
|
|
24
|
+
data: {
|
|
25
|
+
vars,
|
|
26
|
+
collection,
|
|
27
|
+
content,
|
|
28
|
+
},
|
|
29
|
+
...(options ? { options } : {}),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function helmWith(scope, content, options) {
|
|
33
|
+
return {
|
|
34
|
+
__helmConstruct: true,
|
|
35
|
+
type: 'with',
|
|
36
|
+
data: {
|
|
37
|
+
scope,
|
|
38
|
+
content,
|
|
39
|
+
},
|
|
40
|
+
...(options ? { options } : {}),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function helmInclude(templateName, scope = '.', options) {
|
|
44
|
+
return {
|
|
45
|
+
__helmConstruct: true,
|
|
46
|
+
type: 'include',
|
|
47
|
+
data: {
|
|
48
|
+
templateName,
|
|
49
|
+
scope,
|
|
50
|
+
pipe: options?.pipe,
|
|
51
|
+
},
|
|
52
|
+
...(options ? { options } : {}),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function helmDefine(name, content, options) {
|
|
56
|
+
return {
|
|
57
|
+
__helmConstruct: true,
|
|
58
|
+
type: 'define',
|
|
59
|
+
data: {
|
|
60
|
+
name,
|
|
61
|
+
content,
|
|
62
|
+
},
|
|
63
|
+
...(options ? { options } : {}),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function helmVar(name, value, options) {
|
|
67
|
+
return {
|
|
68
|
+
__helmConstruct: true,
|
|
69
|
+
type: 'var',
|
|
70
|
+
data: {
|
|
71
|
+
name,
|
|
72
|
+
value,
|
|
73
|
+
},
|
|
74
|
+
...(options ? { options } : {}),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function helmBlock(name, content, options) {
|
|
78
|
+
return {
|
|
79
|
+
__helmConstruct: true,
|
|
80
|
+
type: 'block',
|
|
81
|
+
data: {
|
|
82
|
+
name,
|
|
83
|
+
content,
|
|
84
|
+
},
|
|
85
|
+
...(options ? { options } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
export function helmComment(text) {
|
|
89
|
+
return {
|
|
90
|
+
__helmConstruct: true,
|
|
91
|
+
type: 'comment',
|
|
92
|
+
data: { text },
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
export function helmIf(condition, thenContent, elseContent, options) {
|
|
96
|
+
return {
|
|
97
|
+
__helmConstruct: true,
|
|
98
|
+
type: 'if',
|
|
99
|
+
data: {
|
|
100
|
+
condition,
|
|
101
|
+
then: thenContent,
|
|
102
|
+
else: elseContent,
|
|
103
|
+
},
|
|
104
|
+
...(options ? { options } : {}),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
export function isHelmConstruct(value) {
|
|
108
|
+
return (typeof value === 'object' &&
|
|
109
|
+
value !== null &&
|
|
110
|
+
'__helmConstruct' in value &&
|
|
111
|
+
value.__helmConstruct === true);
|
|
112
|
+
}
|
|
@@ -1,26 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type: HelmExpressionType;
|
|
5
|
-
expression: string;
|
|
6
|
-
start: number;
|
|
7
|
-
end: number;
|
|
8
|
-
}
|
|
9
|
-
export interface HelmExpression {
|
|
10
|
-
__helmExpression: true;
|
|
11
|
-
value: string;
|
|
12
|
-
}
|
|
13
|
-
declare function createHelmExpression(value: string): HelmExpression;
|
|
14
|
-
declare function isHelmExpression(value: unknown): value is HelmExpression;
|
|
15
|
-
declare function detectHelmExpressions(str: string): HelmExpressionMatch[];
|
|
16
|
-
declare function preprocessHelmExpressions(obj: unknown, depth?: number): unknown;
|
|
17
|
-
declare function postProcessHelmExpressions(yaml: string): string;
|
|
18
|
-
export declare function dumpHelmAwareYaml(obj: unknown, options?: jsYaml.DumpOptions): string;
|
|
1
|
+
export declare function dumpHelmAwareYaml(obj: unknown, options?: {
|
|
2
|
+
lineWidth?: number;
|
|
3
|
+
}): string;
|
|
19
4
|
export declare function stringify(obj: unknown, options?: {
|
|
20
5
|
lineWidth?: number;
|
|
21
6
|
doubleQuotedAsJSON?: boolean;
|
|
22
7
|
simpleKeys?: boolean;
|
|
23
8
|
}): string;
|
|
9
|
+
type HelmExpressionType = 'block' | 'nested' | 'comment' | 'action-trimmed' | 'raw' | 'include-context' | 'generic';
|
|
24
10
|
export interface HelmValidationError {
|
|
25
11
|
type: 'syntax' | 'semantic' | 'reference' | 'type';
|
|
26
12
|
message: string;
|
|
@@ -48,13 +34,7 @@ export declare function parseHelmExpressions(content: string): Array<{
|
|
|
48
34
|
endLine: number;
|
|
49
35
|
endCol: number;
|
|
50
36
|
}>;
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
maxLineWidth?: number;
|
|
56
|
-
sortKeys?: boolean;
|
|
57
|
-
groupHelpers?: boolean;
|
|
58
|
-
}
|
|
59
|
-
export declare function prettifyHelmTemplate(yaml: string, options?: PrettifyOptions): string;
|
|
60
|
-
export { createHelmExpression, isHelmExpression, detectHelmExpressions, preprocessHelmExpressions, postProcessHelmExpressions, };
|
|
37
|
+
export declare function detectHelmExpressions(str: string): unknown[];
|
|
38
|
+
export declare function preprocessHelmExpressions(obj: unknown): unknown;
|
|
39
|
+
export declare function postProcessHelmExpressions(yaml: string): string;
|
|
40
|
+
export {};
|
|
@@ -1,111 +1,184 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
{
|
|
15
|
-
];
|
|
16
|
-
function createHelmExpression(value) {
|
|
17
|
-
return {
|
|
18
|
-
__helmExpression: true,
|
|
19
|
-
value,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
function isHelmExpression(value) {
|
|
23
|
-
return (typeof value === 'object' &&
|
|
24
|
-
value !== null &&
|
|
25
|
-
'__helmExpression' in value &&
|
|
26
|
-
value.__helmExpression === true);
|
|
1
|
+
import { Document, Scalar, isMap, isScalar, visit } from 'yaml';
|
|
2
|
+
import { isHelmConstruct, isHelmExpression, createHelmExpression, } from './helmControlStructures.js';
|
|
3
|
+
function serializeElseIfChain(elseContent, openTag, closeTag) {
|
|
4
|
+
let result = '';
|
|
5
|
+
let currentElse = elseContent;
|
|
6
|
+
if (isHelmConstruct(currentElse) && currentElse.type === 'if') {
|
|
7
|
+
while (isHelmConstruct(currentElse) && currentElse.type === 'if') {
|
|
8
|
+
const elseIfData = currentElse.data;
|
|
9
|
+
result += `\n${openTag}else if ${elseIfData.condition}${closeTag}\n`;
|
|
10
|
+
result += serializeHelmContent(elseIfData.then);
|
|
11
|
+
currentElse = elseIfData.else;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return { result, remainingElse: currentElse };
|
|
27
15
|
}
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
function serializeHelmConstruct(construct) {
|
|
17
|
+
const trimLeft = construct.options?.trimLeft ?? true;
|
|
18
|
+
const trimRight = construct.options?.trimRight ?? true;
|
|
19
|
+
const openTag = `{{${trimLeft ? '-' : ''} `;
|
|
20
|
+
const closeTag = ` ${trimRight ? '-' : ''}}}`;
|
|
21
|
+
switch (construct.type) {
|
|
22
|
+
case 'if': {
|
|
23
|
+
const data = construct.data;
|
|
24
|
+
let result = `${openTag}if ${data.condition}${closeTag}\n`;
|
|
25
|
+
result += serializeHelmContent(data.then);
|
|
26
|
+
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);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
result += `\n${openTag}end${closeTag}`;
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
case 'fragment': {
|
|
38
|
+
const data = construct.data;
|
|
39
|
+
return data.map((item) => serializeHelmContent(item)).join('\n');
|
|
40
|
+
}
|
|
41
|
+
case 'range': {
|
|
42
|
+
const data = construct.data;
|
|
43
|
+
let result = `${openTag}range ${data.vars} := ${data.collection}${closeTag}\n`;
|
|
44
|
+
result += serializeHelmContent(data.content);
|
|
45
|
+
result += `\n${openTag}end${closeTag}`;
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
case 'with': {
|
|
49
|
+
const data = construct.data;
|
|
50
|
+
let result = `${openTag}with ${data.scope}${closeTag}\n`;
|
|
51
|
+
result += serializeHelmContent(data.content);
|
|
52
|
+
result += `\n${openTag}end${closeTag}`;
|
|
53
|
+
return result;
|
|
42
54
|
}
|
|
55
|
+
case 'include': {
|
|
56
|
+
const data = construct.data;
|
|
57
|
+
let result = `${openTag}include "${data.templateName}" ${data.scope}`;
|
|
58
|
+
if (data.pipe) {
|
|
59
|
+
result += ` | ${data.pipe}`;
|
|
60
|
+
}
|
|
61
|
+
result += `${closeTag}`;
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
case 'define': {
|
|
65
|
+
const data = construct.data;
|
|
66
|
+
let result = `${openTag}define "${data.name}"${closeTag}\n`;
|
|
67
|
+
result += serializeHelmContent(data.content);
|
|
68
|
+
result += `\n${openTag}end${closeTag}`;
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
case 'var': {
|
|
72
|
+
const data = construct.data;
|
|
73
|
+
return `${openTag}${data.name} := ${data.value}${closeTag}`;
|
|
74
|
+
}
|
|
75
|
+
case 'block': {
|
|
76
|
+
const data = construct.data;
|
|
77
|
+
let result = `${openTag}block "${data.name}" .${closeTag}\n`;
|
|
78
|
+
result += serializeHelmContent(data.content);
|
|
79
|
+
result += `\n${openTag}end${closeTag}`;
|
|
80
|
+
return result;
|
|
81
|
+
}
|
|
82
|
+
case 'comment': {
|
|
83
|
+
const data = construct.data;
|
|
84
|
+
return `{{/* ${data.text} */}}`;
|
|
85
|
+
}
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unknown Helm construct type: ${construct.type}`);
|
|
43
88
|
}
|
|
44
|
-
return matches.sort((a, b) => a.start - b.start);
|
|
45
89
|
}
|
|
46
|
-
function
|
|
47
|
-
if (
|
|
48
|
-
|
|
90
|
+
function serializeHelmContent(content) {
|
|
91
|
+
if (isHelmConstruct(content)) {
|
|
92
|
+
return serializeHelmConstruct(content);
|
|
49
93
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
return
|
|
94
|
+
if (isHelmExpression(content)) {
|
|
95
|
+
return content.value;
|
|
96
|
+
}
|
|
97
|
+
if (content === null || content === undefined) {
|
|
98
|
+
return '';
|
|
99
|
+
}
|
|
100
|
+
if (typeof content === 'string') {
|
|
101
|
+
return content;
|
|
102
|
+
}
|
|
103
|
+
if (typeof content === 'number' || typeof content === 'boolean') {
|
|
104
|
+
return String(content);
|
|
105
|
+
}
|
|
106
|
+
if (typeof content === 'object') {
|
|
107
|
+
const doc = new Document(content);
|
|
108
|
+
let yaml = doc.toString({ lineWidth: 0 });
|
|
109
|
+
yaml = yaml.trim();
|
|
110
|
+
return yaml;
|
|
111
|
+
}
|
|
112
|
+
return '';
|
|
113
|
+
}
|
|
114
|
+
function preprocessHelmConstructs(obj) {
|
|
115
|
+
if (isHelmConstruct(obj)) {
|
|
116
|
+
const helmTemplate = serializeHelmConstruct(obj);
|
|
117
|
+
return createHelmExpression(helmTemplate);
|
|
55
118
|
}
|
|
56
119
|
if (Array.isArray(obj)) {
|
|
57
|
-
return obj.map((item) =>
|
|
120
|
+
return obj.map((item) => preprocessHelmConstructs(item));
|
|
58
121
|
}
|
|
59
|
-
if (typeof obj === 'object'
|
|
122
|
+
if (obj !== null && typeof obj === 'object') {
|
|
123
|
+
if (isHelmExpression(obj)) {
|
|
124
|
+
return obj;
|
|
125
|
+
}
|
|
60
126
|
const result = {};
|
|
61
127
|
for (const [key, value] of Object.entries(obj)) {
|
|
62
|
-
|
|
63
|
-
value: preprocessHelmExpressions(value, depth + 1),
|
|
64
|
-
writable: true,
|
|
65
|
-
enumerable: true,
|
|
66
|
-
configurable: true,
|
|
67
|
-
});
|
|
128
|
+
result[key] = preprocessHelmConstructs(value);
|
|
68
129
|
}
|
|
69
130
|
return result;
|
|
70
131
|
}
|
|
71
132
|
return obj;
|
|
72
133
|
}
|
|
73
|
-
function helmAwareReplacer(key, value) {
|
|
74
|
-
if (isHelmExpression(value)) {
|
|
75
|
-
return value.value;
|
|
76
|
-
}
|
|
77
|
-
return value;
|
|
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
|
-
}
|
|
88
134
|
export function dumpHelmAwareYaml(obj, options = {}) {
|
|
89
|
-
const preprocessed =
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
135
|
+
const preprocessed = preprocessHelmConstructs(obj);
|
|
136
|
+
const doc = new Document(preprocessed);
|
|
137
|
+
visit(doc, (_key, node) => {
|
|
138
|
+
if (isMap(node)) {
|
|
139
|
+
const isHelmExpr = node.items.some((pair) => isScalar(pair.key) &&
|
|
140
|
+
pair.key.value === '__helmExpression' &&
|
|
141
|
+
isScalar(pair.value) &&
|
|
142
|
+
pair.value.value === true);
|
|
143
|
+
if (isHelmExpr) {
|
|
144
|
+
const valuePair = node.items.find((pair) => isScalar(pair.key) && pair.key.value === 'value');
|
|
145
|
+
if (valuePair && isScalar(valuePair.value)) {
|
|
146
|
+
const value = String(valuePair.value.value);
|
|
147
|
+
const scalar = new Scalar(value);
|
|
148
|
+
if (value.trim().startsWith('{{') && value.includes('\n')) {
|
|
149
|
+
scalar.type = 'BLOCK_LITERAL';
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
scalar.type = 'QUOTE_DOUBLE';
|
|
153
|
+
}
|
|
154
|
+
return scalar;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
});
|
|
160
|
+
const toStringOptions = {};
|
|
161
|
+
if (options.lineWidth !== undefined) {
|
|
162
|
+
toStringOptions.lineWidth = options.lineWidth;
|
|
163
|
+
}
|
|
164
|
+
let result = doc.toString(toStringOptions);
|
|
165
|
+
result = result.replace(/\\"/g, '"');
|
|
166
|
+
result = result.replace(/\\\\/g, '');
|
|
167
|
+
result = result.replace(/:\s*\|[-+]?\s*\n(\s*\{\{)/g, ':\n$1');
|
|
168
|
+
result = result.replace(/"(\{\{[\s\S]*?\}\})"/g, '$1');
|
|
169
|
+
return result;
|
|
100
170
|
}
|
|
101
171
|
export function stringify(obj, options = {}) {
|
|
102
|
-
|
|
103
|
-
lineWidth: options.lineWidth ?? 0,
|
|
104
|
-
quotingType: options.doubleQuotedAsJSON ? '"' : "'",
|
|
105
|
-
forceQuotes: options.doubleQuotedAsJSON,
|
|
106
|
-
};
|
|
107
|
-
return dumpHelmAwareYaml(obj, jsYamlOptions);
|
|
172
|
+
return dumpHelmAwareYaml(obj, options.lineWidth !== undefined ? { lineWidth: options.lineWidth } : {});
|
|
108
173
|
}
|
|
174
|
+
const COMPILED_PATTERNS = [
|
|
175
|
+
{ regex: /\{\{-?\s*define\s+[^}]+\s*-?\}\}[\s\S]*?\{\{-?\s*end\s*-?\}\}/g, type: 'block' },
|
|
176
|
+
{ regex: /\{\{[^}]*\{\{[^}]*\}\}[^}]*\}\}/g, type: 'nested' },
|
|
177
|
+
{ regex: /\{\{\/\*[\s\S]*?\*\/\}\}/g, type: 'comment' },
|
|
178
|
+
{ regex: /\{\{-?[\s\S]*?-?\}\}/g, type: 'action-trimmed' },
|
|
179
|
+
{ regex: /\{\{`[\s\S]*?`\}\}/g, type: 'raw' },
|
|
180
|
+
{ regex: /\{\{\s*include\s+"[^"]+"\s+[^}]+\s*\}\}/g, type: 'include-context' },
|
|
181
|
+
];
|
|
109
182
|
export function validateHelmYaml(yaml) {
|
|
110
183
|
const errors = [];
|
|
111
184
|
const warnings = [];
|
|
@@ -277,120 +350,12 @@ function calculateComplexity(expressions) {
|
|
|
277
350
|
}
|
|
278
351
|
return score;
|
|
279
352
|
}
|
|
280
|
-
export function
|
|
281
|
-
|
|
282
|
-
let formatted = yaml;
|
|
283
|
-
if (groupHelpers) {
|
|
284
|
-
formatted = groupHelmHelpers(formatted);
|
|
285
|
-
}
|
|
286
|
-
if (alignExpressions) {
|
|
287
|
-
formatted = alignTemplateExpressions(formatted, indentSize);
|
|
288
|
-
}
|
|
289
|
-
formatted = formatConditionalBlocks(formatted, indentSize);
|
|
290
|
-
formatted = formatLoopBlocks(formatted, indentSize);
|
|
291
|
-
formatted = wrapLongLines(formatted, maxLineWidth);
|
|
292
|
-
if (sortKeys) {
|
|
293
|
-
formatted = sortYamlKeys(formatted);
|
|
294
|
-
}
|
|
295
|
-
return formatted;
|
|
353
|
+
export function detectHelmExpressions(str) {
|
|
354
|
+
return parseHelmExpressions(str);
|
|
296
355
|
}
|
|
297
|
-
function
|
|
298
|
-
|
|
299
|
-
const aligned = [];
|
|
300
|
-
for (const line of lines) {
|
|
301
|
-
const expressions = line.match(/\{\{[^}]*\}\}/g);
|
|
302
|
-
if (expressions && expressions.length > 1) {
|
|
303
|
-
const baseIndent = (line.match(/^\s*/) || [''])[0];
|
|
304
|
-
const content = line.trim();
|
|
305
|
-
const parts = content.split(/(\{\{[^}]*\}\})/);
|
|
306
|
-
let alignedLine = baseIndent;
|
|
307
|
-
for (let i = 0; i < parts.length; i++) {
|
|
308
|
-
alignedLine += parts[i] || '';
|
|
309
|
-
const nextPart = parts[i + 1];
|
|
310
|
-
if (i < parts.length - 1 && nextPart && nextPart.startsWith('{{')) {
|
|
311
|
-
alignedLine += ' ';
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
aligned.push(alignedLine);
|
|
315
|
-
}
|
|
316
|
-
else {
|
|
317
|
-
aligned.push(line);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
return aligned.join('\n');
|
|
321
|
-
}
|
|
322
|
-
function groupHelmHelpers(yaml) {
|
|
323
|
-
const lines = yaml.split('\n');
|
|
324
|
-
const groups = {
|
|
325
|
-
comments: [],
|
|
326
|
-
definitions: [],
|
|
327
|
-
conditionals: [],
|
|
328
|
-
loops: [],
|
|
329
|
-
other: [],
|
|
330
|
-
};
|
|
331
|
-
for (const line of lines) {
|
|
332
|
-
if (line.includes('{{/*')) {
|
|
333
|
-
groups.comments.push(line);
|
|
334
|
-
}
|
|
335
|
-
else if (line.includes('{{- define')) {
|
|
336
|
-
groups.definitions.push(line);
|
|
337
|
-
}
|
|
338
|
-
else if (/\{\{\s*(if|else|end)\s/.test(line)) {
|
|
339
|
-
groups.conditionals.push(line);
|
|
340
|
-
}
|
|
341
|
-
else if (/\{\{\s*(range|with)\s/.test(line)) {
|
|
342
|
-
groups.loops.push(line);
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
groups.other.push(line);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
return [
|
|
349
|
-
...groups.comments,
|
|
350
|
-
...groups.definitions,
|
|
351
|
-
...groups.conditionals,
|
|
352
|
-
...groups.loops,
|
|
353
|
-
...groups.other,
|
|
354
|
-
].join('\n');
|
|
355
|
-
}
|
|
356
|
-
function formatConditionalBlocks(yaml, indentSize) {
|
|
357
|
-
const indent = ' '.repeat(indentSize);
|
|
358
|
-
return yaml
|
|
359
|
-
.replace(/(\{\{\s*if\s+[^}]+\}\})/g, '$1\n' + indent)
|
|
360
|
-
.replace(/(\{\{\s*else\s*\}\})/g, '$1\n' + indent)
|
|
361
|
-
.replace(/(\{\{\s*end\s*\}\})/g, '\n$1');
|
|
362
|
-
}
|
|
363
|
-
function formatLoopBlocks(yaml, indentSize) {
|
|
364
|
-
const indent = ' '.repeat(indentSize);
|
|
365
|
-
return yaml
|
|
366
|
-
.replace(/(\{\{\s*range\s+[^}]+\}\})/g, '$1\n' + indent)
|
|
367
|
-
.replace(/(\{\{\s*with\s+[^}]+\}\})/g, '$1\n' + indent);
|
|
368
|
-
}
|
|
369
|
-
function wrapLongLines(yaml, maxLineWidth) {
|
|
370
|
-
if (maxLineWidth <= 0)
|
|
371
|
-
return yaml;
|
|
372
|
-
const lines = yaml.split('\n');
|
|
373
|
-
const wrapped = [];
|
|
374
|
-
for (const line of lines) {
|
|
375
|
-
if (line.length <= maxLineWidth) {
|
|
376
|
-
wrapped.push(line);
|
|
377
|
-
continue;
|
|
378
|
-
}
|
|
379
|
-
let remaining = line;
|
|
380
|
-
while (remaining.length > maxLineWidth) {
|
|
381
|
-
let wrapIndex = remaining.lastIndexOf(' ', maxLineWidth);
|
|
382
|
-
if (wrapIndex === -1)
|
|
383
|
-
wrapIndex = maxLineWidth;
|
|
384
|
-
wrapped.push(remaining.slice(0, wrapIndex));
|
|
385
|
-
remaining = remaining.slice(wrapIndex).trim();
|
|
386
|
-
}
|
|
387
|
-
if (remaining.length > 0) {
|
|
388
|
-
wrapped.push(remaining);
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
return wrapped.join('\n');
|
|
356
|
+
export function preprocessHelmExpressions(obj) {
|
|
357
|
+
return preprocessHelmConstructs(obj);
|
|
392
358
|
}
|
|
393
|
-
function
|
|
359
|
+
export function postProcessHelmExpressions(yaml) {
|
|
394
360
|
return yaml;
|
|
395
361
|
}
|
|
396
|
-
export { createHelmExpression, isHelmExpression, detectHelmExpressions, preprocessHelmExpressions, postProcessHelmExpressions, };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "timonel",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.14.0-beta.1",
|
|
5
5
|
"description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
|
|
6
6
|
"bin": {
|
|
7
7
|
"timonel": "dist/cli.js",
|
|
@@ -12,18 +12,27 @@
|
|
|
12
12
|
"exports": {
|
|
13
13
|
".": {
|
|
14
14
|
"types": "./dist/index.d.ts",
|
|
15
|
+
"import": "./dist/index.js",
|
|
15
16
|
"default": "./dist/index.js"
|
|
16
17
|
},
|
|
17
18
|
"./lib/helm": {
|
|
18
19
|
"types": "./dist/lib/helm.d.ts",
|
|
20
|
+
"import": "./dist/lib/helm.js",
|
|
19
21
|
"default": "./dist/lib/helm.js"
|
|
20
22
|
},
|
|
23
|
+
"./lib/utils/helmControlStructures": {
|
|
24
|
+
"types": "./dist/lib/utils/helmControlStructures.d.ts",
|
|
25
|
+
"import": "./dist/lib/utils/helmControlStructures.js",
|
|
26
|
+
"default": "./dist/lib/utils/helmControlStructures.js"
|
|
27
|
+
},
|
|
21
28
|
"./lib/utils/logger": {
|
|
22
29
|
"types": "./dist/lib/utils/logger.d.ts",
|
|
30
|
+
"import": "./dist/lib/utils/logger.js",
|
|
23
31
|
"default": "./dist/lib/utils/logger.js"
|
|
24
32
|
},
|
|
25
33
|
"./lib/utils/helmYamlSerializer": {
|
|
26
34
|
"types": "./dist/lib/utils/helmYamlSerializer.d.ts",
|
|
35
|
+
"import": "./dist/lib/utils/helmYamlSerializer.js",
|
|
27
36
|
"default": "./dist/lib/utils/helmYamlSerializer.js"
|
|
28
37
|
}
|
|
29
38
|
},
|
|
@@ -95,7 +104,6 @@
|
|
|
95
104
|
"cdk8s-plus-33": "^2.4.6",
|
|
96
105
|
"constructs": "^10.4.3",
|
|
97
106
|
"handlebars": "^4.7.8",
|
|
98
|
-
"js-yaml": "^4.1.1",
|
|
99
107
|
"pino": "^10.1.0",
|
|
100
108
|
"pino-pretty": "^13.1.2",
|
|
101
109
|
"ts-node": "^10.9.2",
|
|
@@ -108,7 +116,6 @@
|
|
|
108
116
|
"@semantic-release/changelog": "^6.0.3",
|
|
109
117
|
"@semantic-release/exec": "^7.1.0",
|
|
110
118
|
"@semantic-release/git": "^10.0.1",
|
|
111
|
-
"@types/js-yaml": "^4.0.9",
|
|
112
119
|
"@types/node": "^24.10.1",
|
|
113
120
|
"@typescript-eslint/eslint-plugin": "^8.47.0",
|
|
114
121
|
"@typescript-eslint/parser": "^8.47.0",
|