timonel 2.8.3 → 2.9.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.
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';
@@ -6,14 +6,20 @@ export class HelmChartWriter {
6
6
  static write(opts) {
7
7
  const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
8
8
  const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
9
+ console.log('📝 Validated outDir:', validatedOutDir);
9
10
  this.createDirectories(validatedOutDir);
11
+ console.log('📝 Directories created');
10
12
  this.writeChartYaml(validatedOutDir, meta);
13
+ console.log('📝 Chart.yaml written');
11
14
  this.writeValuesFiles(validatedOutDir, defaultValues, envValues);
15
+ console.log('📝 Values files written');
12
16
  this.writeAssets(validatedOutDir, assets);
17
+ console.log('📝 Assets written');
13
18
  this.writeHelpers(validatedOutDir, helpersTpl);
14
19
  this.writeNotes(validatedOutDir, notesTpl);
15
20
  this.writeSchema(validatedOutDir, valuesSchema);
16
21
  this.writeHelmIgnore(validatedOutDir);
22
+ console.log('📝 HelmChartWriter.write completed');
17
23
  }
18
24
  static createDirectories(outDir) {
19
25
  fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
@@ -106,12 +112,20 @@ function splitDocs(yamlStr) {
106
112
  .filter((p) => p.length);
107
113
  }
108
114
  function writeAssets(outDir, assets) {
115
+ console.log('🔍 writeAssets called with', assets.length, 'assets');
109
116
  for (const asset of assets) {
110
117
  const sanitizedId = asset.id.replace(/[^a-zA-Z0-9-_]/g, '');
111
118
  if (sanitizedId !== asset.id) {
112
119
  throw new Error(`Invalid asset ID: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
113
120
  }
114
121
  const targetDir = getTargetDirectory(asset.target);
122
+ console.log(`🔍 Processing asset: ${asset.id} -> ${sanitizedId}, singleFile: ${asset.singleFile}, target: ${asset.target}`);
123
+ if (asset.id === 'ingress') {
124
+ console.log('🔍 Ingress asset YAML preview:', asset.yaml.substring(0, 200));
125
+ if (asset.yaml.includes('number:')) {
126
+ console.log('🔍 Ingress contains number field:', asset.yaml.split('\n').filter((line) => line.includes('number:')));
127
+ }
128
+ }
115
129
  if (asset.singleFile) {
116
130
  writeSingleAssetFile(outDir, targetDir, sanitizedId, asset.yaml);
117
131
  }
@@ -124,14 +138,27 @@ function getTargetDirectory(target) {
124
138
  return target === 'crds' ? 'crds' : 'templates';
125
139
  }
126
140
  function writeSingleAssetFile(outDir, targetDir, assetId, yaml) {
141
+ if (assetId === 'ingress' && yaml.includes('number:')) {
142
+ console.log('🔍 Writing ingress file with content:');
143
+ const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
144
+ console.log('Number lines:', numberLines);
145
+ }
127
146
  const filename = `${assetId}.yaml`;
128
147
  fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
129
148
  fs.writeFileSync(path.join(outDir, targetDir, filename), yaml + '\n');
130
149
  }
131
150
  function writeMultipleAssetFiles(outDir, targetDir, assetId, yaml) {
151
+ if (assetId === 'ingress' && yaml.includes('number:')) {
152
+ console.log('🔍 Writing ingress file (multiple) with content:');
153
+ const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
154
+ console.log('Number lines:', numberLines);
155
+ }
132
156
  const parts = splitDocs(yaml);
133
157
  parts.forEach((doc, index) => {
134
158
  const filename = `${assetId}${parts.length > 1 ? `-${index + 1}` : ''}.yaml`;
159
+ if (assetId === 'ingress' && doc.includes('number:')) {
160
+ console.log(`🔍 Writing document ${index + 1} for ingress:`, doc.split('\n').filter((line) => line.includes('number:')));
161
+ }
135
162
  fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
136
163
  fs.writeFileSync(path.join(outDir, targetDir, filename), doc + '\n');
137
164
  });
@@ -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,4 +1,5 @@
1
1
  import { ApiObject, App, Chart, Testing } from 'cdk8s';
2
+ import Handlebars from 'handlebars';
2
3
  import YAML from 'yaml';
3
4
  import { helm, include } from './helm.js';
4
5
  import { HelmChartWriter } from './helmChartWriter.js';
@@ -8,10 +9,10 @@ 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;
13
12
  this.defaultValues = props.defaultValues ?? {};
14
13
  this.envValues = props.envValues ?? {};
14
+ this.meta = props.meta;
15
+ this.props = props;
15
16
  this.app = new App();
16
17
  this.chart = new Chart(this.app, props.meta.name, {
17
18
  ...props.chartProps,
@@ -74,6 +75,19 @@ export class Rutter {
74
75
  spec: manifestObject['spec'],
75
76
  });
76
77
  }
78
+ addTemplateManifest(yamlTemplate, id) {
79
+ const templateAsset = {
80
+ id,
81
+ yaml: yamlTemplate,
82
+ target: 'templates',
83
+ };
84
+ this.assets.push(templateAsset);
85
+ return new ApiObject(this.chart, id, {
86
+ apiVersion: 'v1',
87
+ kind: 'ConfigMap',
88
+ metadata: { name: id },
89
+ });
90
+ }
77
91
  addConditionalManifest(manifestObject, condition, id) {
78
92
  if (!condition || typeof condition !== 'string') {
79
93
  throw new Error('Condition must be a non-empty string');
@@ -86,13 +100,32 @@ export class Rutter {
86
100
  else {
87
101
  helmCondition = `.Values.${condition}`;
88
102
  }
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, {
103
+ try {
104
+ const yamlContent = YAML.stringify(manifestObject, {
105
+ lineWidth: 0,
106
+ doubleQuotedAsJSON: false,
107
+ simpleKeys: true,
108
+ });
109
+ const conditionalTemplate = `\\{{- if ${helmCondition} }}
110
+ {{{manifestYaml}}}
111
+ \\{{- end }}`;
112
+ const template = Handlebars.compile(conditionalTemplate, {
113
+ noEscape: true,
114
+ });
115
+ const conditionalYaml = template({
116
+ manifestYaml: yamlContent.trim(),
117
+ });
118
+ const conditionalAsset = {
119
+ id,
120
+ yaml: conditionalYaml,
121
+ target: 'templates',
122
+ };
123
+ this.assets.push(conditionalAsset);
124
+ }
125
+ catch (error) {
126
+ throw new Error(`Failed to compile Handlebars template for manifest '${id}': ${error instanceof Error ? error.message : 'Unknown error'}`);
127
+ }
128
+ return new ApiObject(this.chart, `${id}-placeholder`, {
96
129
  apiVersion: manifestObject['apiVersion'],
97
130
  kind: manifestObject['kind'],
98
131
  metadata: {
@@ -100,6 +133,7 @@ export class Rutter {
100
133
  annotations: {
101
134
  ...(manifestObject['metadata']?.['annotations'] || {}),
102
135
  'timonel.sh/conditional': condition,
136
+ 'timonel.sh/placeholder': 'true',
103
137
  },
104
138
  },
105
139
  spec: manifestObject['spec'],
@@ -148,13 +182,22 @@ export class Rutter {
148
182
  return [...this.assets];
149
183
  }
150
184
  toSynthArray() {
185
+ console.log('🔍 toSynthArray called');
151
186
  const apiObjectIds = [];
152
187
  for (const child of this.chart.node.children) {
153
- if (child instanceof ApiObject) {
188
+ if (child instanceof ApiObject && !child.node.id.endsWith('-placeholder')) {
154
189
  apiObjectIds.push(child.node.id);
155
190
  }
156
191
  }
157
- const manifestObjs = Testing.synth(this.chart);
192
+ const allManifestObjs = Testing.synth(this.chart);
193
+ const manifestObjs = allManifestObjs.filter((obj) => {
194
+ if (obj && typeof obj === 'object') {
195
+ const o = obj;
196
+ const annotations = o.metadata?.annotations || {};
197
+ return annotations['timonel.sh/placeholder'] !== 'true';
198
+ }
199
+ return true;
200
+ });
158
201
  const enriched = manifestObjs.map((obj) => {
159
202
  if (obj && typeof obj === 'object') {
160
203
  const o = obj;
@@ -178,6 +221,7 @@ export class Rutter {
178
221
  return obj;
179
222
  });
180
223
  const synthAssets = [];
224
+ console.log('🔍 singleManifestFile:', this.props.singleManifestFile);
181
225
  if (this.props.singleManifestFile) {
182
226
  const combinedYaml = enriched
183
227
  .map((obj) => this.processHelmTemplates(YAML.stringify(obj).trim()))
@@ -188,10 +232,11 @@ export class Rutter {
188
232
  }
189
233
  else {
190
234
  enriched.forEach((obj, index) => {
235
+ const apiObjectId = apiObjectIds[index];
236
+ const manifestId = apiObjectId || `manifest-${index + 1}`;
237
+ console.log(`🔍 Processing asset ${manifestId} (index ${index})`);
191
238
  const yaml = this.processHelmTemplates(YAML.stringify(obj).trim());
192
239
  if (yaml) {
193
- const apiObjectId = apiObjectIds[index];
194
- const manifestId = apiObjectId || `manifest-${index + 1}`;
195
240
  synthAssets.push({ id: manifestId, yaml });
196
241
  }
197
242
  });
@@ -202,13 +247,229 @@ export class Rutter {
202
247
  return synthAssets;
203
248
  }
204
249
  processHelmTemplates(yaml) {
205
- return yaml
206
- .replace(/": "\{\{([^}]+)\}\}"/g, ': {{ $1 }}')
207
- .replace(/: "\{\{([^}]+)\}\}"/g, ': {{ $1 }}')
208
- .replace(/"\{\{([^}]+)\}\}"/g, '{{ $1 }}')
209
- .replace(/": "\.nan"/g, ': .nan');
250
+ console.log('🔍 processHelmTemplates called with YAML length:', yaml.length);
251
+ if (yaml.includes('{{ .Values.port }}')) {
252
+ console.log('🎯 Found YAML with port template:');
253
+ const portIndex = yaml.indexOf('{{ .Values.port }}');
254
+ console.log(yaml.substring(Math.max(0, portIndex - 50), portIndex + 100));
255
+ }
256
+ let processed = this.fixCharacterMappingIssues(yaml);
257
+ processed = this.applyHelmTemplateReplacements(processed);
258
+ console.log('✅ processHelmTemplates completed');
259
+ return processed;
260
+ }
261
+ fixCharacterMappingIssues(yaml) {
262
+ const lines = yaml.split('\n');
263
+ const fixedLines = [];
264
+ let i = 0;
265
+ while (i < lines.length) {
266
+ const line = lines[i];
267
+ if (!line) {
268
+ i++;
269
+ continue;
270
+ }
271
+ const charKeyMatch = line.match(/^(\s*)"(\d+)":\s*(.+)$/);
272
+ if (charKeyMatch && charKeyMatch[2] && parseInt(charKeyMatch[2]) === 0) {
273
+ const indent = charKeyMatch[1] || '';
274
+ const result = this.processCharacterMapping(lines, i, indent);
275
+ if (result.reconstructed) {
276
+ fixedLines.push(result.reconstructed);
277
+ i = result.nextIndex;
278
+ continue;
279
+ }
280
+ }
281
+ if (line) {
282
+ fixedLines.push(line);
283
+ }
284
+ i++;
285
+ }
286
+ return fixedLines.join('\n');
287
+ }
288
+ processCharacterMapping(lines, startIndex, indent) {
289
+ const charMappings = [];
290
+ let j = startIndex;
291
+ while (j < lines.length) {
292
+ const currentLine = lines[j];
293
+ if (!currentLine) {
294
+ break;
295
+ }
296
+ const currentMatch = currentLine.match(/^(\s*)"(\d+)":\s*(.+)$/);
297
+ if (currentMatch && currentMatch[1] === indent && currentMatch[2] && currentMatch[3]) {
298
+ const index = parseInt(currentMatch[2]);
299
+ let char = currentMatch[3];
300
+ if (char && char.startsWith('"') && char.endsWith('"')) {
301
+ char = char.slice(1, -1);
302
+ }
303
+ charMappings.push({ index, char });
304
+ j++;
305
+ }
306
+ else {
307
+ break;
308
+ }
309
+ }
310
+ if (charMappings.length > 3) {
311
+ charMappings.sort((a, b) => a.index - b.index);
312
+ const reconstructed = charMappings.map((m) => m.char).join('');
313
+ if (reconstructed.includes('{{') && reconstructed.includes('}}')) {
314
+ return { reconstructed: `${indent}${reconstructed}`, nextIndex: j };
315
+ }
316
+ }
317
+ return { nextIndex: j };
318
+ }
319
+ applyHelmTemplateReplacements(processed) {
320
+ processed = this.fixIncludeStatements(processed);
321
+ processed = this.fixFunctionCalls(processed);
322
+ processed = this.fixChartReferences(processed);
323
+ processed = this.removeHelmExpressionQuotes(processed);
324
+ if (processed.includes('number:')) {
325
+ console.log('DEBUG: Before fixConditionalBlocks:', processed.split('\n').filter((line) => line.includes('number:')));
326
+ }
327
+ processed = this.fixConditionalBlocks(processed);
328
+ if (processed.includes('number:')) {
329
+ console.log('DEBUG: After fixConditionalBlocks:', processed.split('\n').filter((line) => line.includes('number:')));
330
+ }
331
+ if (processed.includes('number:')) {
332
+ console.log('DEBUG: Before fixPipeExpressions:', processed.split('\n').filter((line) => line.includes('number:')));
333
+ }
334
+ processed = this.fixPipeExpressions(processed);
335
+ if (processed.includes('number:')) {
336
+ console.log('DEBUG: After fixPipeExpressions:', processed.split('\n').filter((line) => line.includes('number:')));
337
+ }
338
+ if (processed.includes('number:')) {
339
+ console.log('DEBUG: Before fixNestedQuotes:', processed.split('\n').filter((line) => line.includes('number:')));
340
+ }
341
+ processed = this.fixNestedQuotes(processed);
342
+ if (processed.includes('number:')) {
343
+ console.log('DEBUG: After fixNestedQuotes:', processed.split('\n').filter((line) => line.includes('number:')));
344
+ }
345
+ if (processed.includes('number:')) {
346
+ console.log('DEBUG: Before cleanupArtifacts:', processed.split('\n').filter((line) => line.includes('number:')));
347
+ }
348
+ processed = this.cleanupArtifacts(processed);
349
+ if (processed.includes('number:')) {
350
+ console.log('DEBUG: After cleanupArtifacts:', processed.split('\n').filter((line) => line.includes('number:')));
351
+ }
352
+ return processed;
353
+ }
354
+ fixIncludeStatements(processed) {
355
+ processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \| nindent (\d+) \}\}/g, '{{ include "$1" . | nindent $2 }}');
356
+ processed = processed.replace(/\{\{ include \\"([^"]+)\\" \. \}\}/g, '{{ include "$1" . }}');
357
+ return processed;
358
+ }
359
+ fixFunctionCalls(processed) {
360
+ return processed.replace(/\{\{ (\w+) \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ $1 "$2" $3 }}');
361
+ }
362
+ fixChartReferences(processed) {
363
+ return processed.replace(/\{\{ \.Chart\.(\w+) \| replace \\"([^"]*)\\" \\"([^"]*)\\" ([^}]*) \}\}/g, '{{ .Chart.$1 | replace "$2" "$3" $4 }}');
364
+ }
365
+ removeHelmExpressionQuotes(processed) {
366
+ const HELM_EXPRESSION_REPLACEMENT = '$1$2: {{$3}}';
367
+ console.log('🔧 removeHelmExpressionQuotes called');
368
+ if (processed.includes('number:')) {
369
+ console.log('DEBUG: removeHelmExpressionQuotes input contains number:', processed.split('\n').filter((line) => line.includes('number:')));
370
+ }
371
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\|\s*(?:int|float|bool|number)[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
372
+ processed = processed.replace(/^(\s*)(port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
373
+ processed = processed.replace(/^(\s*)(port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
374
+ processed = processed.replace(/^(\s*)(service\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
375
+ processed = processed.replace(/^(\s*)(backend\.service\.port\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
376
+ processed = processed.replace(/^(\s*)(spec\.port):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
377
+ processed = processed.replace(/^(\s*)([^:\s]*\.number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
378
+ const numberMatches = processed.match(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm);
379
+ if (numberMatches) {
380
+ console.log('DEBUG: Found number pattern matches:', numberMatches);
381
+ }
382
+ else {
383
+ const anyNumberFields = processed.match(/number:\s*"[^"]*"/gm);
384
+ if (anyNumberFields) {
385
+ console.log('DEBUG: Found number fields but regex did not match:', anyNumberFields);
386
+ const lines = processed.split('\n');
387
+ const numberLines = lines.filter((line) => line.includes('number:'));
388
+ console.log('DEBUG: Number field lines:', numberLines);
389
+ }
390
+ }
391
+ processed = processed.replace(/^(\s*)(number):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
392
+ if (processed.includes('number:')) {
393
+ console.log('DEBUG: removeHelmExpressionQuotes after number replacement:', processed.split('\n').filter((line) => line.includes('number:')));
394
+ }
395
+ processed = processed.replace(/^(\s*)(replicas):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
396
+ processed = processed.replace(/^(\s*)(targetPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
397
+ processed = processed.replace(/^(\s*)(nodePort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
398
+ processed = processed.replace(/^(\s*)(containerPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
399
+ processed = processed.replace(/^(\s*)(hostPort):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
400
+ processed = processed.replace(/^(\s*)(weight):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
401
+ processed = processed.replace(/^(\s*)(priority):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
402
+ processed = processed.replace(/^(\s*)(timeoutSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
403
+ processed = processed.replace(/^(\s*)(periodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
404
+ processed = processed.replace(/^(\s*)(successThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
405
+ processed = processed.replace(/^(\s*)(failureThreshold):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
406
+ processed = processed.replace(/^(\s*)(initialDelaySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
407
+ processed = processed.replace(/^(\s*)(terminationGracePeriodSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
408
+ processed = processed.replace(/^(\s*)(activeDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
409
+ processed = processed.replace(/^(\s*)(backoffLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
410
+ processed = processed.replace(/^(\s*)(parallelism):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
411
+ processed = processed.replace(/^(\s*)(completions):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
412
+ processed = processed.replace(/^(\s*)(revisionHistoryLimit):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
413
+ processed = processed.replace(/^(\s*)(progressDeadlineSeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
414
+ processed = processed.replace(/^(\s*)(minReadySeconds):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
415
+ processed = processed.replace(/^(\s*)(maxUnavailable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
416
+ processed = processed.replace(/^(\s*)(maxSurge):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
417
+ processed = processed.replace(/^(\s*)(cpu):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
418
+ processed = processed.replace(/^(\s*)(memory):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
419
+ processed = processed.replace(/^(\s*)(enabled):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
420
+ processed = processed.replace(/^(\s*)(create):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
421
+ processed = processed.replace(/^(\s*)(allow):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
422
+ processed = processed.replace(/^(\s*)(disable):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
423
+ processed = processed.replace(/^(\s*)(force):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
424
+ processed = processed.replace(/^(\s*)(required):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
425
+ processed = processed.replace(/^(\s*)(optional):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
426
+ processed = processed.replace(/^(\s*)(readOnly):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
427
+ processed = processed.replace(/^(\s*)(runAsNonRoot):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
428
+ processed = processed.replace(/^(\s*)(privileged):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
429
+ processed = processed.replace(/^(\s*)(allowPrivilegeEscalation):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
430
+ processed = processed.replace(/^(\s*)(readOnlyRootFilesystem):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
431
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*true[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
432
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*false[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
433
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\d+[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
434
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\beq\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
435
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bne\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
436
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\blt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
437
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\ble\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
438
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bgt\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
439
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bge\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
440
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\band\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
441
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bor\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
442
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*\bnot\b[^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
443
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[+][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
444
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[-][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
445
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*[*][^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
446
+ processed = processed.replace(/:\s*"([^"]*\{\{[^}]+\}\}[^"]*)"/g, ': $1');
447
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(true|false)"$/gm, '$1$2: $3');
448
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+)"$/gm, '$1$2: $3');
449
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.\d+)"$/gm, '$1$2: $3');
450
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"(-?\d+\.?\d*[eE][+-]?\d+)"$/gm, '$1$2: $3');
451
+ processed = processed.replace(/^(\s*)([^:\s]+):\s*"\{\{([^}]*)\}\}"/gm, HELM_EXPRESSION_REPLACEMENT);
452
+ processed = processed.replace(/"\{\{([^}]*)\}\}"/g, '{{$1}}');
453
+ processed = processed.replace(/^(\s*)-\s*"\{\{([^}]*)\}\}"/gm, '$1- {{$2}}');
454
+ return processed;
455
+ }
456
+ fixConditionalBlocks(processed) {
457
+ return processed.replace(/"\{\{-\s*(if|with|range)([^}]*)\}\}([^"]*)\{\{-\s*end\s*\}\}"/g, '{{- $1$2}}$3{{- end }}');
458
+ }
459
+ fixPipeExpressions(processed) {
460
+ return processed.replace(/"\{\{([^}]*\|[^}]*)\}\}"/g, '{{$1}}');
461
+ }
462
+ fixNestedQuotes(processed) {
463
+ return processed.replace(/\{\{([^}]*)\\"([^"]*)\\"([^}]*)\}\}/g, '{{$1"$2"$3}}');
464
+ }
465
+ cleanupArtifacts(processed) {
466
+ processed = processed.replace(/": "\.nan"/g, ': .nan');
467
+ processed = processed.replace(/: \{\{([^}]+)\}\}$/gm, ': {{$1}}');
468
+ return processed;
210
469
  }
211
470
  write(outDir) {
471
+ console.log('🚀 Rutter.write called with outDir:', outDir);
472
+ console.log('📊 Assets count:', this.assets.length);
212
473
  let helpersContent;
213
474
  if (this.props.helpersTpl) {
214
475
  if (typeof this.props.helpersTpl === 'string') {
@@ -228,14 +489,17 @@ ${helper.template}
228
489
  else {
229
490
  helpersContent = generateHelpersTemplate(this.props.cloudProvider);
230
491
  }
492
+ const synthAssets = this.toSynthArray();
493
+ console.log('📦 Generated synth assets:', synthAssets.length);
231
494
  HelmChartWriter.write({
232
495
  outDir,
233
496
  meta: this.meta,
234
497
  defaultValues: this.defaultValues,
235
498
  envValues: this.envValues,
236
- assets: this.toSynthArray(),
499
+ assets: synthAssets,
237
500
  helpersTpl: helpersContent,
238
501
  });
502
+ console.log('✅ Rutter.write completed');
239
503
  }
240
504
  }
241
505
  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;