timonel 2.10.2 → 2.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +8 -54
  3. package/dist/cli.js +65 -52
  4. package/dist/index.d.ts +1 -2
  5. package/dist/index.js +1 -2
  6. package/dist/lib/helmChartWriter.js +17 -2
  7. package/dist/lib/rutter.d.ts +1 -0
  8. package/dist/lib/rutter.js +64 -11
  9. package/dist/lib/templates/flexible-subchart.d.ts +1 -0
  10. package/dist/lib/templates/flexible-subchart.js +101 -0
  11. package/dist/lib/templates/umbrella-chart.d.ts +13 -2
  12. package/dist/lib/templates/umbrella-chart.js +128 -42
  13. package/dist/lib/utils/helmHelpers/envHelpers.d.ts +2 -0
  14. package/dist/lib/utils/helmHelpers/envHelpers.js +97 -0
  15. package/dist/lib/utils/helmHelpers/gitopsHelpers.d.ts +2 -0
  16. package/dist/lib/utils/helmHelpers/gitopsHelpers.js +132 -0
  17. package/dist/lib/utils/helmHelpers/index.d.ts +23 -0
  18. package/dist/lib/utils/helmHelpers/index.js +132 -0
  19. package/dist/lib/utils/helmHelpers/observabilityHelpers.d.ts +2 -0
  20. package/dist/lib/utils/helmHelpers/observabilityHelpers.js +221 -0
  21. package/dist/lib/utils/helmHelpers/types.d.ts +16 -0
  22. package/dist/lib/utils/helmHelpers/types.js +1 -0
  23. package/dist/lib/utils/helmHelpers/validationHelpers.d.ts +2 -0
  24. package/dist/lib/utils/helmHelpers/validationHelpers.js +201 -0
  25. package/dist/lib/utils/helmHelpers.d.ts +11 -1
  26. package/dist/lib/utils/helmHelpers.js +20 -7
  27. package/dist/lib/utils/helmYamlSerializer.d.ts +53 -1
  28. package/dist/lib/utils/helmYamlSerializer.js +325 -29
  29. package/dist/lib/utils/logger.d.ts +49 -0
  30. package/dist/lib/utils/logger.js +418 -0
  31. package/package.json +21 -11
  32. package/dist/lib/templates/basic-chart.d.ts +0 -19
  33. package/dist/lib/templates/basic-chart.js +0 -210
  34. package/dist/lib/templates/subchart.d.ts +0 -27
  35. package/dist/lib/templates/subchart.js +0 -219
package/CHANGELOG.md CHANGED
@@ -1,3 +1,20 @@
1
+ # [2.12.0](https://github.com/KenkoGeek/timonel/compare/v2.11.0...v2.12.0) (2025-09-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **cli:** harden flag handling and path validation ([#130](https://github.com/KenkoGeek/timonel/issues/130)) ([2186d88](https://github.com/KenkoGeek/timonel/commit/2186d886428752025e7bf531c4a6541dade683a6))
6
+
7
+ ### Features
8
+
9
+ - **docs:** update references and CLI [skip ci] ([23704d3](https://github.com/KenkoGeek/timonel/commit/23704d3164cfbb92d183af40c70e27a32b566d06))
10
+ - **docs:** update references and CLI [skip ci] ([be4bcdc](https://github.com/KenkoGeek/timonel/commit/be4bcdca62cb3a9339e5ad70efc0ba31a6d9d5be))
11
+
12
+ # [2.11.0](https://github.com/KenkoGeek/timonel/compare/v2.10.2...v2.11.0) (2025-09-26)
13
+
14
+ ### Features
15
+
16
+ - **helm:** add comprehensive helm helpers with Helm template validation ([#123](https://github.com/KenkoGeek/timonel/issues/123)) ([bda8115](https://github.com/KenkoGeek/timonel/commit/bda811546d308eaa3ee5aec7fd8356b4a508940e))
17
+
1
18
  ## [2.10.2](https://github.com/KenkoGeek/timonel/compare/v2.10.1...v2.10.2) (2025-09-20)
2
19
 
3
20
  ### Bug Fixes
package/README.md CHANGED
@@ -15,22 +15,18 @@ Helm charts using cdk8s. Define Kubernetes resources with type-safe classes and
15
15
  Helm charts with `Chart.yaml`, `values.yaml`, environment-specific values files, and `templates/`
16
16
  directory.
17
17
 
18
- > 🚀 **New in v2.9.2**: Enhanced umbrella chart support with flexible subchart templates, improved
19
- > CLI version handling, and optimized GitHub workflows.
20
-
21
18
  ## ✨ Key Features
22
19
 
23
20
  - **🔒 Type-safe API** with strict TypeScript and cdk8s constructs
24
21
  - **🔧 Flexible resource creation** with built-in methods and `addManifest()` for custom resources
25
22
  - **🌍 Multi-environment support** with automatic values files generation
26
23
  - **☂️ Umbrella Charts** for managing multiple subcharts as a single unit
24
+ - **🛠️ Enhanced Helm Helpers** with environment, GitOps, observability, and validation helpers
27
25
  - **☁️ Cloud integrations**:
28
- - **AWS**: EBS/EFS StorageClass, ALB Ingress, IRSA ServiceAccount
29
- - **Azure**: Azure Disk, Azure Files, AKS integrations
30
- - **GCP**: GCE Persistent Disk, GKE integrations
26
+ - **AWS**: EBS/EFS StorageClass, ALB Ingress, IRSA ServiceAccount, ECR, and Karpenter
31
27
  - **🛡️ Security-first approach** with NetworkPolicies and best practices
32
28
  - **⚡ Minimal CLI** (`tl`) for scaffolding and chart generation
33
- - **📦 Flexible subchart templates** supporting cdk8s, cdk8s-plus-33, and raw manifests
29
+ - **📦 Flexible subchart templates** supporting cdk8s and cdk8s-plus-33
34
30
 
35
31
  ## 🚀 Quick Start
36
32
 
@@ -42,9 +38,6 @@ npm install -g timonel
42
38
 
43
39
  # Or use with pnpm
44
40
  pnpm add -g timonel
45
-
46
- # Verify installation
47
- tl --version
48
41
  ```
49
42
 
50
43
  ### Basic Usage
@@ -161,10 +154,10 @@ If you get `Error: Cannot find module 'cdk8s'` when running `tl umbrella synth`:
161
154
  "version": "1.0.0",
162
155
  "type": "module",
163
156
  "dependencies": {
164
- "cdk8s": "^2.70.15",
165
- "cdk8s-plus-33": "^2.3.6",
157
+ "cdk8s": "^2.70.16",
158
+ "cdk8s-plus-33": "^2.3.8",
166
159
  "constructs": "^10.4.2",
167
- "timonel": "^2.9.2"
160
+ "timonel": "^2.11.0"
168
161
  },
169
162
  "devDependencies": {
170
163
  "@types/node": "^24.5.2",
@@ -180,45 +173,6 @@ npm install
180
173
  tl umbrella synth # Now it works!
181
174
  ```
182
175
 
183
- ### Version Display Issues
184
-
185
- If the CLI shows an incorrect version:
186
-
187
- **Problem**: The CLI might be using cached or hardcoded version information.
188
-
189
- **Solution**: Rebuild the project:
190
-
191
- ```bash
192
- # In the Timonel project directory
193
- pnpm build
194
- tl --version # Should show correct version
195
- ```
196
-
197
- ### TypeScript Compilation Errors
198
-
199
- If you encounter TypeScript errors when using umbrella charts:
200
-
201
- **Problem**: Type mismatches in subchart configurations.
202
-
203
- **Solution**: Ensure proper type casting for version and description fields:
204
-
205
- ```typescript
206
- // ✅ Correct
207
- const subchart = {
208
- name: 'my-service',
209
- version: '1.0.0' as string,
210
- description: 'My service' as string,
211
- chart: myChartFunction,
212
- };
213
-
214
- // ❌ Incorrect
215
- const subchart = {
216
- name: 'my-service',
217
- version: someUnknownValue, // This will cause TypeScript errors
218
- chart: myChartFunction,
219
- };
220
- ```
221
-
222
176
  ## 🤝 Contributing
223
177
 
224
178
  See our [Contributing Guide](https://github.com/KenkoGeek/timonel/wiki/Contributing) for development
@@ -246,5 +200,5 @@ MIT
246
200
  [maintained-url]: https://github.com/kenkogeek/
247
201
  [ci-badge]: https://github.com/KenkoGeek/timonel/actions/workflows/test.yaml/badge.svg?branch=main
248
202
  [ci-url]: https://github.com/KenkoGeek/timonel/actions/workflows/test.yaml
249
- [codeql-badge]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yaml/badge.svg
250
- [codeql-url]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yaml
203
+ [codeql-badge]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yml/badge.svg
204
+ [codeql-url]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yml
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { SecurityUtils } from './lib/security.js';
7
+ import { createLogger } from './lib/utils/logger.js';
7
8
  const __filename = fileURLToPath(import.meta.url);
8
9
  const __dirname = path.dirname(__filename);
9
10
  const UMBRELLA_CONFIG_FILE = 'umbrella.config.json';
@@ -11,46 +12,30 @@ const UMBRELLA_FILE_NAME = 'umbrella.ts';
11
12
  const PACKAGE_JSON_FILE = 'package.json';
12
13
  function getVersion() {
13
14
  try {
14
- const possiblePaths = [
15
- path.resolve(__dirname, '..', PACKAGE_JSON_FILE),
16
- path.resolve(__dirname, '..', '..', PACKAGE_JSON_FILE),
17
- path.resolve(process.cwd(), PACKAGE_JSON_FILE),
18
- path.resolve(__dirname, PACKAGE_JSON_FILE),
19
- ];
20
- for (const packagePath of possiblePaths) {
21
- try {
22
- const validatedPath = SecurityUtils.validatePath(packagePath, process.cwd());
23
- if (fs.existsSync(validatedPath)) {
24
- try {
25
- const packageJson = JSON.parse(fs.readFileSync(validatedPath, 'utf8'));
26
- return packageJson.version || 'unknown';
27
- }
28
- catch {
29
- continue;
30
- }
31
- }
32
- }
33
- catch {
34
- continue;
35
- }
15
+ const packagePath = path.resolve(__dirname, '..', PACKAGE_JSON_FILE);
16
+ const allowedBase = path.resolve(__dirname, '..');
17
+ const validatedPath = SecurityUtils.validatePath(packagePath, allowedBase);
18
+ const packageJson = JSON.parse(fs.readFileSync(validatedPath, 'utf8'));
19
+ if (packageJson.version) {
20
+ return packageJson.version;
36
21
  }
37
- if (process.env.npm_package_version) {
38
- return process.env.npm_package_version;
39
- }
40
- return 'unknown';
41
22
  }
42
23
  catch {
43
- return 'unknown';
44
24
  }
25
+ if (process.env.npm_package_version) {
26
+ return process.env.npm_package_version;
27
+ }
28
+ return 'unknown';
45
29
  }
30
+ const cliLogger = createLogger('cli');
46
31
  function log(msg, silent = false) {
47
32
  if (!silent) {
48
- console.log(msg);
33
+ cliLogger.info(msg, { operation: 'cli_log' });
49
34
  }
50
35
  }
51
36
  function logError(msg, silent = false) {
52
37
  if (!silent) {
53
- console.error(SecurityUtils.sanitizeLogMessage(msg));
38
+ cliLogger.error(msg, { operation: 'cli_error' });
54
39
  }
55
40
  }
56
41
  function usageAndExit(msg, silent = false) {
@@ -80,7 +65,6 @@ function usageAndExit(msg, silent = false) {
80
65
  ' --env <environment> Use environment-specific values',
81
66
  ' --set <key=value> Override values (can be used multiple times)',
82
67
  ' --help, -h Show this help message',
83
- ' --version, -v Show version information',
84
68
  '',
85
69
  'Examples:',
86
70
  ' tl init my-app',
@@ -104,8 +88,8 @@ async function cmdInit(name, silent = false) {
104
88
  const base = path.join(process.cwd(), validName);
105
89
  const chartFile = path.join(base, 'chart.ts');
106
90
  fs.mkdirSync(base, { recursive: true });
107
- const { generateBasicChart } = await import('./lib/templates/basic-chart.js');
108
- fs.writeFileSync(chartFile, generateBasicChart(validName));
91
+ const { generateFlexibleSubchartTemplate } = await import('./lib/templates/flexible-subchart.js');
92
+ fs.writeFileSync(chartFile, generateFlexibleSubchartTemplate(validName));
109
93
  log(`Chart created at ${base}`, silent);
110
94
  log(`Generated chart.ts file`, silent);
111
95
  log(`Run 'tl synth ${validName}' to generate complete Helm chart`, silent);
@@ -161,7 +145,23 @@ await import(pathToFileURL('${tempChartFile}').href);
161
145
  }
162
146
  }
163
147
  async function cmdValidate(flags) {
164
- const result = spawnSync('helm', ['lint', '.'], {
148
+ const lintArgs = ['lint', '.'];
149
+ if (flags?.env) {
150
+ try {
151
+ const sanitizedEnv = SecurityUtils.sanitizeEnvironmentName(flags.env);
152
+ lintArgs.push('-f', `values-${sanitizedEnv}.yaml`);
153
+ }
154
+ catch (error) {
155
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
156
+ process.exit(1);
157
+ }
158
+ }
159
+ if (flags?.set) {
160
+ for (const setValue of flags.set) {
161
+ lintArgs.push('--set', setValue);
162
+ }
163
+ }
164
+ const result = spawnSync('helm', lintArgs, {
165
165
  stdio: flags?.silent ? 'pipe' : 'inherit',
166
166
  encoding: 'utf8',
167
167
  });
@@ -179,6 +179,21 @@ async function cmdDeploy(release, namespace, flags) {
179
179
  if (namespace) {
180
180
  args.push('--namespace', namespace);
181
181
  }
182
+ if (flags?.env) {
183
+ try {
184
+ const sanitizedEnv = SecurityUtils.sanitizeEnvironmentName(flags.env);
185
+ args.push('-f', `values-${sanitizedEnv}.yaml`);
186
+ }
187
+ catch (error) {
188
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
189
+ process.exit(1);
190
+ }
191
+ }
192
+ if (flags?.set) {
193
+ for (const setValue of flags.set) {
194
+ args.push('--set', setValue);
195
+ }
196
+ }
182
197
  const result = spawnSync('helm', args.filter((arg) => Boolean(arg)), {
183
198
  stdio: flags?.silent ? 'pipe' : 'inherit',
184
199
  encoding: 'utf8',
@@ -342,20 +357,31 @@ async function cmdUmbrellaAdd(subchartPath, silent = false) {
342
357
  }
343
358
  const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
344
359
  const subchartName = path.basename(validSubchartPath);
345
- const subchartDir = path.join(process.cwd(), 'charts', validSubchartPath);
360
+ const chartsRoot = path.join(process.cwd(), 'charts');
361
+ const targetSubchartPath = path.join(chartsRoot, validSubchartPath);
362
+ let subchartDir;
363
+ try {
364
+ subchartDir = SecurityUtils.validatePath(targetSubchartPath, chartsRoot);
365
+ }
366
+ catch (error) {
367
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
368
+ process.exit(1);
369
+ }
346
370
  fs.mkdirSync(subchartDir, { recursive: true });
371
+ const relativeSubchartPath = path.relative(chartsRoot, subchartDir) || subchartName;
372
+ const normalizedSubchartPath = relativeSubchartPath.split(path.sep).join('/');
347
373
  const chartFile = path.join(subchartDir, 'chart.ts');
348
- const { generateSubchartTemplate } = await import('./lib/templates/subchart.js');
349
- const subchartContent = generateSubchartTemplate(subchartName);
374
+ const { generateFlexibleSubchartTemplate } = await import('./lib/templates/flexible-subchart.js');
375
+ const subchartContent = generateFlexibleSubchartTemplate(subchartName);
350
376
  fs.writeFileSync(chartFile, subchartContent);
351
377
  config.subcharts.push({
352
378
  name: subchartName,
353
379
  version: getVersion(),
354
- path: `./charts/${validSubchartPath}/chart.ts`,
380
+ path: `./charts/${normalizedSubchartPath}/chart.ts`,
355
381
  });
356
382
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
357
- updateUmbrellaTs(validSubchartPath, subchartName);
358
- log(`Subchart ${subchartName} added to umbrella at path '${validSubchartPath}'`, silent);
383
+ updateUmbrellaTs(normalizedSubchartPath, subchartName);
384
+ log(`Subchart ${subchartName} added to umbrella at path '${normalizedSubchartPath}'`, silent);
359
385
  }
360
386
  async function cmdUmbrellaSynth(outDir, flags) {
361
387
  const umbrellaFile = path.join(process.cwd(), UMBRELLA_FILE_NAME);
@@ -429,13 +455,6 @@ function parseFlags(args) {
429
455
  }
430
456
  break;
431
457
  }
432
- case '--version':
433
- case '-v':
434
- if (!flags.silent) {
435
- console.log(`Timonel v${getVersion()}`);
436
- }
437
- process.exit(0);
438
- break;
439
458
  case '--help':
440
459
  case '-h':
441
460
  usageAndExit(undefined, flags.silent);
@@ -481,12 +500,6 @@ async function main() {
481
500
  usageAndExit(undefined, isSilent);
482
501
  return;
483
502
  }
484
- if (args.includes('--version') || args.includes('-v')) {
485
- if (!isSilent) {
486
- console.log(`Timonel v${getVersion()}`);
487
- }
488
- process.exit(0);
489
- }
490
503
  const command = args.shift();
491
504
  const flags = parseFlags(args);
492
505
  await executeCommand(command, args, flags);
package/dist/index.d.ts CHANGED
@@ -3,12 +3,11 @@ 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 { BasicChart } from './lib/templates/basic-chart.js';
7
6
  export { FlexibleSubchart, createFlexibleSubchart } from './lib/templates/flexible-subchart.js';
8
- export { Subchart } from './lib/templates/subchart.js';
9
7
  export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
10
8
  export type { ChartProps, SubchartProps } from './lib/types.js';
11
9
  export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './lib/resources/cloud/aws/awsResources.js';
12
10
  export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec, } from './lib/resources/cloud/aws/karpenterResources.js';
13
11
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
14
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
+ export { createLogger, logger, LogLevel, TimonelLogger, type LogContext, type LoggerConfig, } from './lib/utils/logger.js';
package/dist/index.js CHANGED
@@ -3,9 +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 { BasicChart } from './lib/templates/basic-chart.js';
7
6
  export { FlexibleSubchart, createFlexibleSubchart } from './lib/templates/flexible-subchart.js';
8
- export { Subchart } from './lib/templates/subchart.js';
9
7
  export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
10
8
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
11
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
+ export { createLogger, logger, LogLevel, TimonelLogger, } from './lib/utils/logger.js';
@@ -1,11 +1,20 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { SecurityUtils } from './security.js';
4
+ import { createLogger } from './utils/logger.js';
4
5
  import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
5
6
  export class HelmChartWriter {
6
7
  static write(opts) {
7
8
  const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
8
- console.log(`Writing chart: ${meta.name} v${meta.version} to ${outDir}`);
9
+ const logger = createLogger('helm-chart-writer');
10
+ const timer = logger.time('helm_chart_write');
11
+ logger.info('Starting Helm chart write operation', {
12
+ chartName: meta.name,
13
+ version: meta.version,
14
+ outputDirectory: outDir,
15
+ assetCount: assets.length,
16
+ operation: 'helm_write_start',
17
+ });
9
18
  const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
10
19
  this.createDirectories(validatedOutDir);
11
20
  this.writeChartYaml(validatedOutDir, meta);
@@ -15,7 +24,13 @@ export class HelmChartWriter {
15
24
  this.writeNotes(validatedOutDir, notesTpl);
16
25
  this.writeSchema(validatedOutDir, valuesSchema);
17
26
  this.writeHelmIgnore(validatedOutDir);
18
- console.log(`Chart written successfully to ${validatedOutDir}`);
27
+ logger.info('Helm chart write operation completed successfully', {
28
+ chartName: meta.name,
29
+ version: meta.version,
30
+ outputDirectory: validatedOutDir,
31
+ operation: 'helm_write_complete',
32
+ });
33
+ timer();
19
34
  }
20
35
  static createDirectories(outDir) {
21
36
  fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
@@ -16,6 +16,7 @@ export declare class Rutter {
16
16
  private readonly karpenterResources;
17
17
  private readonly meta;
18
18
  private readonly props;
19
+ private readonly logger;
19
20
  constructor(props: RutterProps);
20
21
  addAWSEBSStorageClass(spec: AWSEBSStorageClassSpec): ApiObject;
21
22
  addAWSEFSStorageClass(spec: AWSEFSStorageClassSpec): ApiObject;
@@ -3,17 +3,24 @@ import * as jsYaml from 'js-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
+ import { createLogger } from './utils/logger.js';
6
7
  import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
7
8
  import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
8
9
  import { generateHelpersTemplate } from './utils/helmHelpers.js';
9
10
  export class Rutter {
10
11
  constructor(props) {
11
12
  this.assets = [];
12
- console.log(`Initializing chart: ${props.meta.name} v${props.meta.version}`);
13
+ this.logger = createLogger('rutter');
13
14
  this.defaultValues = props.defaultValues ?? {};
14
15
  this.envValues = props.envValues ?? {};
15
16
  this.meta = props.meta;
16
17
  this.props = props;
18
+ this.logger.info('Initializing chart', {
19
+ chartName: props.meta.name,
20
+ version: props.meta.version,
21
+ namespace: props.namespace,
22
+ operation: 'chart_initialization',
23
+ });
17
24
  this.app = new App();
18
25
  this.chart = new Chart(this.app, props.meta.name, {
19
26
  ...props.chartProps,
@@ -134,24 +141,40 @@ ${yamlContent.trim()}
134
141
  }
135
142
  validateManifestStructure(manifest) {
136
143
  if (!manifest.apiVersion) {
137
- console.error('Validation failed: Missing apiVersion');
144
+ this.logger.error('Validation failed: Missing apiVersion', {
145
+ operation: 'manifest_validation',
146
+ issue: 'missing_api_version',
147
+ });
138
148
  throw new Error('Manifest must have an apiVersion');
139
149
  }
140
150
  if (!manifest.kind) {
141
- console.error('Validation failed: Missing kind');
151
+ this.logger.error('Validation failed: Missing kind', {
152
+ operation: 'manifest_validation',
153
+ issue: 'missing_kind',
154
+ });
142
155
  throw new Error('Manifest must have a kind');
143
156
  }
144
157
  if (!manifest.metadata) {
145
- console.error('Validation failed: Missing metadata');
158
+ this.logger.error('Validation failed: Missing metadata', {
159
+ operation: 'manifest_validation',
160
+ issue: 'missing_metadata',
161
+ });
146
162
  throw new Error('Manifest must have metadata');
147
163
  }
148
164
  const metadata = manifest.metadata;
149
165
  if (!metadata.name) {
150
- console.error('Validation failed: Missing metadata.name');
166
+ this.logger.error('Validation failed: Missing metadata.name', {
167
+ operation: 'manifest_validation',
168
+ issue: 'missing_metadata_name',
169
+ });
151
170
  throw new Error('Manifest metadata must have a name');
152
171
  }
153
172
  if (typeof metadata.name !== 'string') {
154
- console.error('Validation failed: metadata.name must be a string');
173
+ this.logger.error('Validation failed: metadata.name must be a string', {
174
+ operation: 'manifest_validation',
175
+ issue: 'invalid_metadata_name_type',
176
+ actual_type: typeof metadata.name,
177
+ });
155
178
  throw new Error('Manifest metadata.name must be a string');
156
179
  }
157
180
  }
@@ -168,7 +191,11 @@ ${yamlContent.trim()}
168
191
  return [...this.assets];
169
192
  }
170
193
  toSynthArray() {
171
- console.log(`Synthesizing chart assets for: ${this.meta.name}`);
194
+ const timer = this.logger.time('chart_synthesis');
195
+ this.logger.debug('Starting chart synthesis', {
196
+ chartName: this.meta.name,
197
+ operation: 'synthesis_start',
198
+ });
172
199
  const apiObjectIds = [];
173
200
  for (const child of this.chart.node.children) {
174
201
  if (child instanceof ApiObject && !child.node.id.endsWith('-placeholder')) {
@@ -184,7 +211,12 @@ ${yamlContent.trim()}
184
211
  }
185
212
  return true;
186
213
  });
187
- console.log(`Found ${manifestObjs.length} manifest objects to process`);
214
+ this.logger.info('Processing manifest objects', {
215
+ chartName: this.meta.name,
216
+ manifestCount: manifestObjs.length,
217
+ apiObjectCount: apiObjectIds.length,
218
+ operation: 'manifest_processing',
219
+ });
188
220
  const enriched = manifestObjs.map((obj) => {
189
221
  if (obj && typeof obj === 'object') {
190
222
  const o = obj;
@@ -229,6 +261,13 @@ ${yamlContent.trim()}
229
261
  this.assets.forEach((asset) => {
230
262
  synthAssets.push({ id: asset.id, yaml: asset.yaml });
231
263
  });
264
+ this.logger.info('Chart synthesis completed', {
265
+ chartName: this.meta.name,
266
+ totalAssets: synthAssets.length,
267
+ additionalAssets: this.assets.length,
268
+ operation: 'synthesis_complete',
269
+ });
270
+ timer();
232
271
  return synthAssets;
233
272
  }
234
273
  processHelmTemplates(yaml) {
@@ -399,7 +438,12 @@ ${yamlContent.trim()}
399
438
  return processed;
400
439
  }
401
440
  write(outDir) {
402
- console.log(`Writing chart '${this.meta.name}' to: ${outDir}`);
441
+ const timer = this.logger.time('chart_write');
442
+ this.logger.info('Starting chart write operation', {
443
+ chartName: this.meta.name,
444
+ outputDirectory: outDir,
445
+ operation: 'chart_write_start',
446
+ });
403
447
  let helpersContent;
404
448
  if (this.props.helpersTpl) {
405
449
  if (typeof this.props.helpersTpl === 'string') {
@@ -420,7 +464,11 @@ ${helper.template}
420
464
  helpersContent = generateHelpersTemplate(this.props.cloudProvider);
421
465
  }
422
466
  const synthAssets = this.toSynthArray();
423
- console.log(`Generated ${synthAssets.length} assets for chart`);
467
+ this.logger.info('Generated assets for chart', {
468
+ chartName: this.meta.name,
469
+ assetCount: synthAssets.length,
470
+ operation: 'assets_generated',
471
+ });
424
472
  HelmChartWriter.write({
425
473
  outDir,
426
474
  meta: this.meta,
@@ -429,7 +477,12 @@ ${helper.template}
429
477
  assets: synthAssets,
430
478
  helpersTpl: helpersContent,
431
479
  });
432
- console.log(`Chart '${this.meta.name}' written successfully`);
480
+ this.logger.info('Chart write operation completed successfully', {
481
+ chartName: this.meta.name,
482
+ outputDirectory: outDir,
483
+ operation: 'chart_write_complete',
484
+ });
485
+ timer();
433
486
  }
434
487
  }
435
488
  Rutter.HELPER_NAME = 'chart.name';
@@ -25,3 +25,4 @@ export declare function createFlexibleSubchart(scope: Chart, id: string, config:
25
25
  description?: string;
26
26
  [key: string]: unknown;
27
27
  }): FlexibleSubchart;
28
+ export declare function generateFlexibleSubchartTemplate(name: string): string;
@@ -105,3 +105,104 @@ export class FlexibleSubchart extends Chart {
105
105
  export function createFlexibleSubchart(scope, id, config) {
106
106
  return new FlexibleSubchart(scope, id, config);
107
107
  }
108
+ export function generateFlexibleSubchartTemplate(name) {
109
+ return `import { App } from 'cdk8s';
110
+ import { Rutter } from 'timonel';
111
+
112
+ /**
113
+ * Creates a new chart with simple Rutter implementation
114
+ * @returns Rutter instance for Helm chart generation
115
+ * @since 2.11.0
116
+ */
117
+ export default function createChart() {
118
+ const app = new App({ outdir: 'dist' });
119
+
120
+ const rutter = new Rutter({
121
+ meta: {
122
+ name: '${name}',
123
+ version: '1.0.0',
124
+ description: '${name} subchart'
125
+ },
126
+ scope: app,
127
+ defaultValues: {
128
+ appName: '${name}',
129
+ image: 'nginx:latest',
130
+ port: 80,
131
+ replicas: 1,
132
+ },
133
+ });
134
+
135
+ // Add simple deployment
136
+ rutter.addManifest({
137
+ apiVersion: 'apps/v1',
138
+ kind: 'Deployment',
139
+ metadata: {
140
+ name: '${name}',
141
+ labels: {
142
+ 'app.kubernetes.io/name': '${name}'
143
+ }
144
+ },
145
+ spec: {
146
+ replicas: 1,
147
+ selector: {
148
+ matchLabels: {
149
+ 'app.kubernetes.io/name': '${name}'
150
+ }
151
+ },
152
+ template: {
153
+ metadata: {
154
+ labels: {
155
+ 'app.kubernetes.io/name': '${name}'
156
+ }
157
+ },
158
+ spec: {
159
+ containers: [{
160
+ name: '${name}',
161
+ image: 'nginx:latest',
162
+ ports: [{
163
+ containerPort: 80
164
+ }],
165
+ env: [
166
+ { name: 'APP_NAME', value: '${name}' },
167
+ { name: 'PORT', value: '80' }
168
+ ]
169
+ }]
170
+ }
171
+ }
172
+ }
173
+ }, 'deployment');
174
+
175
+ // Add Service
176
+ rutter.addManifest({
177
+ apiVersion: 'v1',
178
+ kind: 'Service',
179
+ metadata: {
180
+ name: '${name}',
181
+ labels: {
182
+ 'app.kubernetes.io/name': '${name}'
183
+ }
184
+ },
185
+ spec: {
186
+ type: 'ClusterIP',
187
+ ports: [{
188
+ port: 80,
189
+ targetPort: 80,
190
+ protocol: 'TCP',
191
+ name: 'http'
192
+ }],
193
+ selector: {
194
+ 'app.kubernetes.io/name': '${name}'
195
+ }
196
+ }
197
+ }, 'service');
198
+
199
+ return rutter;
200
+ }
201
+
202
+ // Auto-execute when run directly
203
+ if (import.meta.url === new URL(import.meta.url).href) {
204
+ const chart = createChart();
205
+ chart.write('dist');
206
+ }
207
+ `;
208
+ }