timonel 2.8.3 → 2.9.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/dist/index.d.ts CHANGED
@@ -3,7 +3,10 @@ export * from './lib/helmChartWriter.js';
3
3
  export * from './lib/rutter.js';
4
4
  export * from './lib/security.js';
5
5
  export * from './lib/umbrella.js';
6
+ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
7
+ export { Subchart } from './lib/templates/subchart.js';
8
+ export { BasicChart } from './lib/templates/basic-chart.js';
6
9
  export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './lib/resources/cloud/aws/awsResources.js';
7
10
  export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec, } from './lib/resources/cloud/aws/karpenterResources.js';
8
11
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
9
- export { AWS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, STANDARD_HELPERS, type HelperDefinition, } from './lib/utils/helmHelpers.js';
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';
package/dist/index.js CHANGED
@@ -3,5 +3,8 @@ export * from './lib/helmChartWriter.js';
3
3
  export * from './lib/rutter.js';
4
4
  export * from './lib/security.js';
5
5
  export * from './lib/umbrella.js';
6
+ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
7
+ export { Subchart } from './lib/templates/subchart.js';
8
+ export { BasicChart } from './lib/templates/basic-chart.js';
6
9
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
7
- export { AWS_HELPERS, formatHelpers, generateHelpersTemplate, getDefaultHelpers, STANDARD_HELPERS, } from './lib/utils/helmHelpers.js';
10
+ 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';
@@ -5,6 +5,7 @@ import { SecurityUtils } from './security.js';
5
5
  export class HelmChartWriter {
6
6
  static write(opts) {
7
7
  const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
8
+ console.log(`📝 Writing Helm chart: ${meta.name} v${meta.version} to ${outDir}`);
8
9
  const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
9
10
  this.createDirectories(validatedOutDir);
10
11
  this.writeChartYaml(validatedOutDir, meta);
@@ -14,6 +15,7 @@ export class HelmChartWriter {
14
15
  this.writeNotes(validatedOutDir, notesTpl);
15
16
  this.writeSchema(validatedOutDir, valuesSchema);
16
17
  this.writeHelmIgnore(validatedOutDir);
18
+ console.log(`✅ Helm chart written successfully to ${validatedOutDir}`);
17
19
  }
18
20
  static createDirectories(outDir) {
19
21
  fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
@@ -109,14 +111,21 @@ function writeAssets(outDir, assets) {
109
111
  for (const asset of assets) {
110
112
  const sanitizedId = asset.id.replace(/[^a-zA-Z0-9-_]/g, '');
111
113
  if (sanitizedId !== asset.id) {
114
+ console.error(`❌ Invalid asset ID detected: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
112
115
  throw new Error(`Invalid asset ID: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
113
116
  }
114
117
  const targetDir = getTargetDirectory(asset.target);
115
- if (asset.singleFile) {
116
- writeSingleAssetFile(outDir, targetDir, sanitizedId, asset.yaml);
118
+ try {
119
+ if (asset.singleFile) {
120
+ writeSingleAssetFile(outDir, targetDir, sanitizedId, asset.yaml);
121
+ }
122
+ else {
123
+ writeMultipleAssetFiles(outDir, targetDir, sanitizedId, asset.yaml);
124
+ }
117
125
  }
118
- else {
119
- writeMultipleAssetFiles(outDir, targetDir, sanitizedId, asset.yaml);
126
+ catch (error) {
127
+ console.error(`❌ Failed to write asset ${asset.id}:`, error);
128
+ throw error;
120
129
  }
121
130
  }
122
131
  }
@@ -124,14 +133,27 @@ function getTargetDirectory(target) {
124
133
  return target === 'crds' ? 'crds' : 'templates';
125
134
  }
126
135
  function writeSingleAssetFile(outDir, targetDir, assetId, yaml) {
136
+ if (assetId === 'ingress' && yaml.includes('number:')) {
137
+ console.log('🔍 Writing ingress file with content:');
138
+ const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
139
+ console.log('Number lines:', numberLines);
140
+ }
127
141
  const filename = `${assetId}.yaml`;
128
142
  fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
129
143
  fs.writeFileSync(path.join(outDir, targetDir, filename), yaml + '\n');
130
144
  }
131
145
  function writeMultipleAssetFiles(outDir, targetDir, assetId, yaml) {
146
+ if (assetId === 'ingress' && yaml.includes('number:')) {
147
+ console.log('🔍 Writing ingress file (multiple) with content:');
148
+ const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
149
+ console.log('Number lines:', numberLines);
150
+ }
132
151
  const parts = splitDocs(yaml);
133
152
  parts.forEach((doc, index) => {
134
153
  const filename = `${assetId}${parts.length > 1 ? `-${index + 1}` : ''}.yaml`;
154
+ if (assetId === 'ingress' && doc.includes('number:')) {
155
+ console.log(`🔍 Writing document ${index + 1} for ingress:`, doc.split('\n').filter((line) => line.includes('number:')));
156
+ }
135
157
  fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
136
158
  fs.writeFileSync(path.join(outDir, targetDir, filename), doc + '\n');
137
159
  });
@@ -1,21 +1,21 @@
1
1
  import { ApiObject } from 'cdk8s';
2
2
  import type { ChartProps } from 'cdk8s';
3
+ import type { Ingress, ServiceAccount } from 'cdk8s-plus-33';
3
4
  import type { Construct } from 'constructs';
4
- import type { ServiceAccount, Ingress } from 'cdk8s-plus-33';
5
5
  import type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSECRServiceAccountSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec } from './resources/cloud/aws/awsResources.js';
6
6
  import type { KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec } from './resources/cloud/aws/karpenterResources.js';
7
7
  import type { HelperDefinition } from './utils/helmHelpers.js';
8
8
  export declare class Rutter {
9
9
  private static readonly HELPER_NAME;
10
10
  private readonly app;
11
- private readonly chart;
11
+ private readonly assets;
12
12
  private readonly awsResources;
13
- private readonly karpenterResources;
14
- private readonly meta;
13
+ private readonly chart;
15
14
  private readonly defaultValues;
16
15
  private readonly envValues;
16
+ private readonly karpenterResources;
17
+ private readonly meta;
17
18
  private readonly props;
18
- private readonly assets;
19
19
  constructor(props: RutterProps);
20
20
  addAWSEBSStorageClass(spec: AWSEBSStorageClassSpec): ApiObject;
21
21
  addAWSEFSStorageClass(spec: AWSEFSStorageClassSpec): ApiObject;
@@ -79,6 +79,7 @@ export declare class Rutter {
79
79
  }): ApiObject;
80
80
  addAWSECRServiceAccount(spec: AWSECRServiceAccountSpec): ServiceAccount;
81
81
  addManifest(yamlOrObject: string | Record<string, unknown>, id: string): ApiObject;
82
+ addTemplateManifest(yamlTemplate: string, id: string): ApiObject;
82
83
  addConditionalManifest(manifestObject: Record<string, unknown>, condition: string, id: string): ApiObject;
83
84
  private validateManifestStructure;
84
85
  getMeta(): ChartMetadata;
@@ -91,31 +92,42 @@ export declare class Rutter {
91
92
  }>;
92
93
  private toSynthArray;
93
94
  private processHelmTemplates;
95
+ private fixCharacterMappingIssues;
96
+ private processCharacterMapping;
97
+ private applyHelmTemplateReplacements;
98
+ private fixIncludeStatements;
99
+ private fixFunctionCalls;
100
+ private fixChartReferences;
101
+ private removeHelmExpressionQuotes;
102
+ private fixConditionalBlocks;
103
+ private fixPipeExpressions;
104
+ private fixNestedQuotes;
105
+ private cleanupArtifacts;
94
106
  write(outDir: string): void;
95
107
  }
96
108
  export interface ChartMetadata {
97
- name: string;
98
- version: string;
99
109
  description?: string;
100
- keywords?: string[];
101
110
  home?: string;
102
- sources?: string[];
111
+ keywords?: string[];
103
112
  maintainers?: Array<{
104
- name: string;
105
113
  email?: string;
114
+ name: string;
106
115
  url?: string;
107
116
  }>;
117
+ name: string;
118
+ sources?: string[];
119
+ version: string;
108
120
  }
109
121
  export interface RutterProps {
110
- scope?: Construct;
111
- meta: ChartMetadata;
122
+ chartProps?: ChartProps;
123
+ cloudProvider?: 'aws';
112
124
  defaultValues?: Record<string, unknown>;
113
125
  envValues?: Record<string, Record<string, unknown>>;
114
- namespace?: string;
115
- chartProps?: ChartProps;
116
126
  helpersTpl?: string | HelperDefinition[];
117
- cloudProvider?: 'aws';
118
127
  manifestPrefix?: string;
128
+ meta: ChartMetadata;
129
+ namespace?: string;
130
+ scope?: Construct;
119
131
  singleManifestFile?: boolean;
120
132
  }
121
- export type { AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, AWSALBIngressSpec, } from './resources/cloud/aws/awsResources.js';
133
+ export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './resources/cloud/aws/awsResources.js';
@@ -1,6 +1,7 @@
1
1
  import { ApiObject, App, Chart, Testing } from 'cdk8s';
2
+ import Handlebars from 'handlebars';
2
3
  import YAML from 'yaml';
3
- import { helm, include } from './helm.js';
4
+ import { include } from './helm.js';
4
5
  import { HelmChartWriter } from './helmChartWriter.js';
5
6
  import { AWSResources } from './resources/cloud/aws/awsResources.js';
6
7
  import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
@@ -8,10 +9,11 @@ import { generateHelpersTemplate } from './utils/helmHelpers.js';
8
9
  export class Rutter {
9
10
  constructor(props) {
10
11
  this.assets = [];
11
- this.props = props;
12
- this.meta = props.meta;
12
+ console.log(`🚀 Initializing Rutter chart: ${props.meta.name} v${props.meta.version}`);
13
13
  this.defaultValues = props.defaultValues ?? {};
14
14
  this.envValues = props.envValues ?? {};
15
+ this.meta = props.meta;
16
+ this.props = props;
15
17
  this.app = new App();
16
18
  this.chart = new Chart(this.app, props.meta.name, {
17
19
  ...props.chartProps,
@@ -74,6 +76,19 @@ export class Rutter {
74
76
  spec: manifestObject['spec'],
75
77
  });
76
78
  }
79
+ addTemplateManifest(yamlTemplate, id) {
80
+ const templateAsset = {
81
+ id,
82
+ yaml: yamlTemplate,
83
+ target: 'templates',
84
+ };
85
+ this.assets.push(templateAsset);
86
+ return new ApiObject(this.chart, id, {
87
+ apiVersion: 'v1',
88
+ kind: 'ConfigMap',
89
+ metadata: { name: id },
90
+ });
91
+ }
77
92
  addConditionalManifest(manifestObject, condition, id) {
78
93
  if (!condition || typeof condition !== 'string') {
79
94
  throw new Error('Condition must be a non-empty string');
@@ -86,13 +101,32 @@ export class Rutter {
86
101
  else {
87
102
  helmCondition = `.Values.${condition}`;
88
103
  }
89
- const conditionalAsset = {
90
- id,
91
- yaml: `{{- if ${helmCondition} }}\n${YAML.stringify(manifestObject)}{{- end }}`,
92
- target: 'templates',
93
- };
94
- this.assets.push(conditionalAsset);
95
- return new ApiObject(this.chart, id, {
104
+ try {
105
+ const yamlContent = YAML.stringify(manifestObject, {
106
+ lineWidth: 0,
107
+ doubleQuotedAsJSON: false,
108
+ simpleKeys: true,
109
+ });
110
+ const conditionalTemplate = `\\{{- if ${helmCondition} }}
111
+ {{{manifestYaml}}}
112
+ \\{{- end }}`;
113
+ const template = Handlebars.compile(conditionalTemplate, {
114
+ noEscape: true,
115
+ });
116
+ const conditionalYaml = template({
117
+ manifestYaml: yamlContent.trim(),
118
+ });
119
+ const conditionalAsset = {
120
+ id,
121
+ yaml: conditionalYaml,
122
+ target: 'templates',
123
+ };
124
+ this.assets.push(conditionalAsset);
125
+ }
126
+ catch (error) {
127
+ throw new Error(`Failed to compile Handlebars template for manifest '${id}': ${error instanceof Error ? error.message : 'Unknown error'}`);
128
+ }
129
+ return new ApiObject(this.chart, `${id}-placeholder`, {
96
130
  apiVersion: manifestObject['apiVersion'],
97
131
  kind: manifestObject['kind'],
98
132
  metadata: {
@@ -100,39 +134,33 @@ export class Rutter {
100
134
  annotations: {
101
135
  ...(manifestObject['metadata']?.['annotations'] || {}),
102
136
  'timonel.sh/conditional': condition,
137
+ 'timonel.sh/placeholder': 'true',
103
138
  },
104
139
  },
105
140
  spec: manifestObject['spec'],
106
141
  });
107
142
  }
108
143
  validateManifestStructure(manifest) {
109
- if (!manifest['apiVersion'] || typeof manifest['apiVersion'] !== 'string') {
110
- throw new Error('Kubernetes manifest must have a valid apiVersion');
144
+ if (!manifest.apiVersion) {
145
+ console.error(' Manifest validation failed: Missing apiVersion');
146
+ throw new Error('Manifest must have an apiVersion');
111
147
  }
112
- if (!manifest['kind'] || typeof manifest['kind'] !== 'string') {
113
- throw new Error('Kubernetes manifest must have a valid kind');
148
+ if (!manifest.kind) {
149
+ console.error(' Manifest validation failed: Missing kind');
150
+ throw new Error('Manifest must have a kind');
114
151
  }
115
- if (!manifest['metadata'] ||
116
- typeof manifest['metadata'] !== 'object' ||
117
- manifest['metadata'] === null) {
118
- throw new Error('Kubernetes manifest must have valid metadata');
152
+ if (!manifest.metadata) {
153
+ console.error(' Manifest validation failed: Missing metadata');
154
+ throw new Error('Manifest must have metadata');
119
155
  }
120
- const metadata = manifest['metadata'];
121
- if (!metadata['name'] || typeof metadata['name'] !== 'string') {
122
- throw new Error('Kubernetes manifest metadata must have a valid name');
156
+ const metadata = manifest.metadata;
157
+ if (!metadata.name) {
158
+ console.error(' Manifest validation failed: Missing metadata.name');
159
+ throw new Error('Manifest metadata must have a name');
123
160
  }
124
- const kind = manifest['kind'];
125
- if (kind === 'CustomResourceDefinition') {
126
- if (!manifest['spec'] || typeof manifest['spec'] !== 'object' || manifest['spec'] === null) {
127
- throw new Error('CustomResourceDefinition must have a valid spec');
128
- }
129
- const spec = manifest['spec'];
130
- if (!spec['group'] || typeof spec['group'] !== 'string') {
131
- throw new Error('CustomResourceDefinition spec must have a valid group');
132
- }
133
- if (!Array.isArray(spec['versions']) || spec['versions'].length === 0) {
134
- throw new Error('CustomResourceDefinition must have at least one version in spec.versions');
135
- }
161
+ if (typeof metadata.name !== 'string') {
162
+ console.error('❌ Manifest validation failed: metadata.name must be a string');
163
+ throw new Error('Manifest metadata.name must be a string');
136
164
  }
137
165
  }
138
166
  getMeta() {
@@ -148,13 +176,23 @@ export class Rutter {
148
176
  return [...this.assets];
149
177
  }
150
178
  toSynthArray() {
179
+ console.log(`📦 Synthesizing chart assets for: ${this.meta.name}`);
151
180
  const apiObjectIds = [];
152
181
  for (const child of this.chart.node.children) {
153
- if (child instanceof ApiObject) {
182
+ if (child instanceof ApiObject && !child.node.id.endsWith('-placeholder')) {
154
183
  apiObjectIds.push(child.node.id);
155
184
  }
156
185
  }
157
- const manifestObjs = Testing.synth(this.chart);
186
+ const allManifestObjs = Testing.synth(this.chart);
187
+ const manifestObjs = allManifestObjs.filter((obj) => {
188
+ if (obj && typeof obj === 'object') {
189
+ const o = obj;
190
+ const annotations = o.metadata?.annotations || {};
191
+ return annotations['timonel.sh/placeholder'] !== 'true';
192
+ }
193
+ return true;
194
+ });
195
+ console.log(`📋 Found ${manifestObjs.length} manifest objects to process`);
158
196
  const enriched = manifestObjs.map((obj) => {
159
197
  if (obj && typeof obj === 'object') {
160
198
  const o = obj;
@@ -162,12 +200,12 @@ export class Rutter {
162
200
  o.metadata.labels = o.metadata.labels ?? {};
163
201
  const labels = o.metadata.labels;
164
202
  const defaults = {
165
- 'helm.sh/chart': '{{ .Chart.Name }}-{{ .Chart.Version }}',
166
- 'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
167
- 'app.kubernetes.io/instance': helm.releaseName,
168
- 'app.kubernetes.io/version': helm.chartVersion,
203
+ 'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
204
+ 'app.kubernetes.io/name': `{{ ${include} "${Rutter.HELPER_NAME}" . }}`,
205
+ 'app.kubernetes.io/instance': '{{ .Release.Name }}',
206
+ 'app.kubernetes.io/version': '{{ .Chart.Version }}',
169
207
  'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
170
- 'app.kubernetes.io/part-of': helm.chartName,
208
+ 'app.kubernetes.io/part-of': '{{ .Chart.Name }}',
171
209
  };
172
210
  for (const [key, value] of Object.entries(defaults)) {
173
211
  if (!(key in labels)) {
@@ -188,10 +226,10 @@ export class Rutter {
188
226
  }
189
227
  else {
190
228
  enriched.forEach((obj, index) => {
229
+ const apiObjectId = apiObjectIds[index];
230
+ const manifestId = apiObjectId || `manifest-${index + 1}`;
191
231
  const yaml = this.processHelmTemplates(YAML.stringify(obj).trim());
192
232
  if (yaml) {
193
- const apiObjectId = apiObjectIds[index];
194
- const manifestId = apiObjectId || `manifest-${index + 1}`;
195
233
  synthAssets.push({ id: manifestId, yaml });
196
234
  }
197
235
  });
@@ -202,13 +240,174 @@ export class Rutter {
202
240
  return synthAssets;
203
241
  }
204
242
  processHelmTemplates(yaml) {
205
- return yaml
206
- .replace(/": "\{\{([^}]+)\}\}"/g, ': {{ $1 }}')
207
- .replace(/: "\{\{([^}]+)\}\}"/g, ': {{ $1 }}')
208
- .replace(/"\{\{([^}]+)\}\}"/g, '{{ $1 }}')
209
- .replace(/": "\.nan"/g, ': .nan');
243
+ let processed = this.fixCharacterMappingIssues(yaml);
244
+ processed = this.applyHelmTemplateReplacements(processed);
245
+ return processed;
246
+ }
247
+ fixCharacterMappingIssues(yaml) {
248
+ const lines = yaml.split('\n');
249
+ const fixedLines = [];
250
+ let i = 0;
251
+ while (i < lines.length) {
252
+ const line = lines[i];
253
+ if (!line) {
254
+ i++;
255
+ continue;
256
+ }
257
+ const charKeyMatch = line.match(/^(\s*)"(\d+)":\s*(.+)$/);
258
+ if (charKeyMatch && charKeyMatch[2] && parseInt(charKeyMatch[2]) === 0) {
259
+ const indent = charKeyMatch[1] || '';
260
+ const result = this.processCharacterMapping(lines, i, indent);
261
+ if (result.reconstructed) {
262
+ fixedLines.push(result.reconstructed);
263
+ i = result.nextIndex;
264
+ continue;
265
+ }
266
+ }
267
+ if (line) {
268
+ fixedLines.push(line);
269
+ }
270
+ i++;
271
+ }
272
+ return fixedLines.join('\n');
273
+ }
274
+ processCharacterMapping(lines, startIndex, indent) {
275
+ const charMappings = [];
276
+ let j = startIndex;
277
+ while (j < lines.length) {
278
+ const currentLine = lines[j];
279
+ if (!currentLine) {
280
+ break;
281
+ }
282
+ const currentMatch = currentLine.match(/^(\s*)"(\d+)":\s*(.+)$/);
283
+ if (currentMatch && currentMatch[1] === indent && currentMatch[2] && currentMatch[3]) {
284
+ const index = parseInt(currentMatch[2]);
285
+ let char = currentMatch[3];
286
+ if (char && char.startsWith('"') && char.endsWith('"')) {
287
+ char = char.slice(1, -1);
288
+ }
289
+ charMappings.push({ index, char });
290
+ j++;
291
+ }
292
+ else {
293
+ break;
294
+ }
295
+ }
296
+ if (charMappings.length > 3) {
297
+ charMappings.sort((a, b) => a.index - b.index);
298
+ const reconstructed = charMappings.map((m) => m.char).join('');
299
+ if (reconstructed.includes('{{') && reconstructed.includes('}}')) {
300
+ return { reconstructed: `${indent}${reconstructed}`, nextIndex: j };
301
+ }
302
+ }
303
+ return { nextIndex: j };
304
+ }
305
+ applyHelmTemplateReplacements(processed) {
306
+ processed = this.fixConditionalBlocks(processed);
307
+ processed = this.fixPipeExpressions(processed);
308
+ processed = this.fixNestedQuotes(processed);
309
+ processed = this.cleanupArtifacts(processed);
310
+ processed = this.removeHelmExpressionQuotes(processed);
311
+ return processed;
312
+ }
313
+ fixIncludeStatements(processed) {
314
+ processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \| nindent (\d+) \}\}/g, '{{ include "$1" . | nindent $2 }}');
315
+ processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \}\}/g, '{{ include "$1" . }}');
316
+ return processed;
317
+ }
318
+ fixFunctionCalls(processed) {
319
+ return processed.replace(/\{\{ (\w+) \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ $1 "$2" $3 }}');
320
+ }
321
+ fixChartReferences(processed) {
322
+ return processed.replace(/\{\{ \.Chart\.(\w+) \| replace \\"([^"]*)\\" \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ .Chart.$1 | replace "$2" "$3" $4 }}');
323
+ }
324
+ removeHelmExpressionQuotes(processed) {
325
+ const HELM_EXPRESSION_REPLACEMENT = '$1$2: {{$3}}';
326
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\|\s*(?:int|float|bool|number)[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
327
+ processed = processed.replace(/^(\s*)(port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
328
+ processed = processed.replace(/^(\s*)(port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
329
+ processed = processed.replace(/^(\s*)(service\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
330
+ processed = processed.replace(/^(\s*)(backend\.service\.port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
331
+ processed = processed.replace(/^(\s*)(spec\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
332
+ processed = processed.replace(/^(\s*)([^:\s]*\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
333
+ processed = processed.replace(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
334
+ processed = processed.replace(/^(\s*)(replicas):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
335
+ processed = processed.replace(/^(\s*)(targetPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
336
+ processed = processed.replace(/^(\s*)(nodePort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
337
+ processed = processed.replace(/^(\s*)(containerPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
338
+ processed = processed.replace(/^(\s*)(hostPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
339
+ processed = processed.replace(/^(\s*)(weight):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
340
+ processed = processed.replace(/^(\s*)(priority):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
341
+ processed = processed.replace(/^(\s*)(timeoutSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
342
+ processed = processed.replace(/^(\s*)(periodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
343
+ processed = processed.replace(/^(\s*)(successThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
344
+ processed = processed.replace(/^(\s*)(failureThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
345
+ processed = processed.replace(/^(\s*)(initialDelaySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
346
+ processed = processed.replace(/^(\s*)(terminationGracePeriodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
347
+ processed = processed.replace(/^(\s*)(activeDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
348
+ processed = processed.replace(/^(\s*)(backoffLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
349
+ processed = processed.replace(/^(\s*)(parallelism):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
350
+ processed = processed.replace(/^(\s*)(completions):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
351
+ processed = processed.replace(/^(\s*)(revisionHistoryLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
352
+ processed = processed.replace(/^(\s*)(progressDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
353
+ processed = processed.replace(/^(\s*)(minReadySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
354
+ processed = processed.replace(/^(\s*)(maxUnavailable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
355
+ processed = processed.replace(/^(\s*)(maxSurge):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
356
+ processed = processed.replace(/^(\s*)(cpu):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
357
+ processed = processed.replace(/^(\s*)(memory):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
358
+ processed = processed.replace(/^(\s*)(enabled):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
359
+ processed = processed.replace(/^(\s*)(create):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
360
+ processed = processed.replace(/^(\s*)(allow):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
361
+ processed = processed.replace(/^(\s*)(disable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
362
+ processed = processed.replace(/^(\s*)(force):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
363
+ processed = processed.replace(/^(\s*)(required):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
364
+ processed = processed.replace(/^(\s*)(optional):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
365
+ processed = processed.replace(/^(\s*)(readOnly):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
366
+ processed = processed.replace(/^(\s*)(runAsNonRoot):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
367
+ processed = processed.replace(/^(\s*)(privileged):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
368
+ processed = processed.replace(/^(\s*)(allowPrivilegeEscalation):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
369
+ processed = processed.replace(/^(\s*)(readOnlyRootFilesystem):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
370
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*true[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
371
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*false[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
372
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\d+[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
373
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\beq\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
374
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bne\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
375
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\blt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
376
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\ble\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
377
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bgt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
378
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bge\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
379
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\band\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
380
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bor\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
381
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bnot\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
382
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[+][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
383
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[-][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
384
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[*][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
385
+ processed = processed.replace(/:\s*"([^"]*\{\{[^}]+\}\}[^"]*)"/g, ': $1');
386
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(true|false)"$/gm, '$1$2: $3');
387
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+)"$/gm, '$1$2: $3');
388
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.\d+)"$/gm, '$1$2: $3');
389
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.?\d*[eE][+-]?\d+)"$/gm, '$1$2: $3');
390
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
391
+ processed = processed.replace(/"\{\{([^}]*)\}\}"/g, '{{$1}}');
392
+ processed = processed.replace(/^(\s*)-\s*"\{\{([^}]*)\}\}"/gm, '$1- {{$2}}');
393
+ return processed;
394
+ }
395
+ fixConditionalBlocks(processed) {
396
+ return processed.replace(/"\{\{-\s*(if|with|range)([^}]*)\}\}([^"]*)\{\{-\s*end\s*\}\}"/g, '{{- $1$2}}$3{{- end }}');
397
+ }
398
+ fixPipeExpressions(processed) {
399
+ return processed.replace(/"\{\{([^}]*\|[^}]*)\}\}"/g, '{{$1}}');
400
+ }
401
+ fixNestedQuotes(processed) {
402
+ return processed.replace(/\{\{([^}]*)\\"([^"]*)\\"([^}]*)\}\}/g, '{{$1"$2"$3}}');
403
+ }
404
+ cleanupArtifacts(processed) {
405
+ processed = processed.replace(/": "\.nan"/g, ': .nan');
406
+ processed = processed.replace(/: \{\{([^}]+)\}\}$/gm, ': {{$1}}');
407
+ return processed;
210
408
  }
211
409
  write(outDir) {
410
+ console.log(`✍️ Writing Helm chart '${this.meta.name}' to: ${outDir}`);
212
411
  let helpersContent;
213
412
  if (this.props.helpersTpl) {
214
413
  if (typeof this.props.helpersTpl === 'string') {
@@ -228,14 +427,17 @@ ${helper.template}
228
427
  else {
229
428
  helpersContent = generateHelpersTemplate(this.props.cloudProvider);
230
429
  }
430
+ const synthAssets = this.toSynthArray();
431
+ console.log(`📄 Generated ${synthAssets.length} assets for chart`);
231
432
  HelmChartWriter.write({
232
433
  outDir,
233
434
  meta: this.meta,
234
435
  defaultValues: this.defaultValues,
235
436
  envValues: this.envValues,
236
- assets: this.toSynthArray(),
437
+ assets: synthAssets,
237
438
  helpersTpl: helpersContent,
238
439
  });
440
+ console.log(`✅ Helm chart '${this.meta.name}' written successfully`);
239
441
  }
240
442
  }
241
443
  Rutter.HELPER_NAME = 'chart.name';
@@ -0,0 +1,19 @@
1
+ import { Chart } from 'cdk8s';
2
+ import type { ChartProps } from 'cdk8s';
3
+ import type { Construct } from 'constructs';
4
+ export interface BasicChartProps extends ChartProps {
5
+ appName?: string;
6
+ image?: string;
7
+ port?: number;
8
+ replicas?: number;
9
+ createNamespace?: boolean;
10
+ }
11
+ export declare class BasicChart extends Chart {
12
+ private rutter;
13
+ private props;
14
+ constructor(scope: Construct, id: string, props?: BasicChartProps);
15
+ private initializeRutter;
16
+ writeHelmChart(outDir: string): void;
17
+ private generateKubernetesManifests;
18
+ }
19
+ export declare function generateBasicChart(appName?: string): string;