timonel 2.9.2 β†’ 2.10.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/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ # [2.10.0](https://github.com/KenkoGeek/timonel/compare/v2.9.2...v2.10.0) (2025-09-20)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **ci:** and beside or ([2901953](https://github.com/KenkoGeek/timonel/commit/2901953363ff81c03ad95e57fbf61129bffe4138))
6
+ - **ci:** change CodeQL build-mode to 'none' for JavaScript/TypeScript ([017ec93](https://github.com/KenkoGeek/timonel/commit/017ec930940780fca9e6f93e02372067f899675f))
7
+ - **ci:** clean up workflows and fix CodeQL configuration ([4c3ac55](https://github.com/KenkoGeek/timonel/commit/4c3ac55e04210d77ad694aef1fab261482a7f5e9))
8
+ - **ci:** correct CodeQL workflow configuration ([25813d3](https://github.com/KenkoGeek/timonel/commit/25813d3b4ce649da5fa3b1cf29f825369c17b196))
9
+ - **ci:** correct CodeQL workflow name in release.yaml ([cb38608](https://github.com/KenkoGeek/timonel/commit/cb3860881b527f5016dcdc891f848c22ea464868))
10
+ - **ci:** correct job dependencies and summary references ([d37cc9c](https://github.com/KenkoGeek/timonel/commit/d37cc9ccf05407f28b85bd7eeb5710b2460b72ef))
11
+ - **ci:** ensure real release on main branch ([c06a5f7](https://github.com/KenkoGeek/timonel/commit/c06a5f7f9d0d45261c95e677f04382e0e20de58d))
12
+ - **ci:** lint errors ([08fbb64](https://github.com/KenkoGeek/timonel/commit/08fbb645b3661a2bc4e6bf33b309ab88cd707fef))
13
+ - **ci:** remove explicit pnpm version from CodeQL workflow ([25a8a6c](https://github.com/KenkoGeek/timonel/commit/25a8a6cd9556ae614bb84bfcc147aadb17850f48))
14
+ - **ci:** remove explicit pnpm version from workflows ([b517e1e](https://github.com/KenkoGeek/timonel/commit/b517e1eb35b3ab57673428c7d80fa25d427bf046))
15
+ - **ci:** reorder pnpm setup before Node.js in CodeQL workflow ([904579c](https://github.com/KenkoGeek/timonel/commit/904579ca507ef2cad5f11887e35431e6a15a59f5))
16
+ - **ci:** resolve pnpm version conflict in workflows ([a2ee153](https://github.com/KenkoGeek/timonel/commit/a2ee153934c5710e58f8e5bd85da6ea70e1c8959))
17
+ - **ci:** simple but effective release ([a6b2014](https://github.com/KenkoGeek/timonel/commit/a6b201476903cedf8e1d1dd7bbf36c2f02fa750b))
18
+ - **ci:** simple but effective release, security.md updated ([6911d21](https://github.com/KenkoGeek/timonel/commit/6911d2131971787d8afce1123ed4969e0ae8e077))
19
+ - **ci:** use exact pnpm version 10.15.0 in workflows ([2634e7d](https://github.com/KenkoGeek/timonel/commit/2634e7d30b58dcea182605a52a1e34daca0cf6bb))
20
+ - **core:** correct TypeScript errors and improve version handling ([3945d8b](https://github.com/KenkoGeek/timonel/commit/3945d8b6e3f84141579e0a08b48bdfd480e64168))
21
+ - **core:** resolve issue [#114](https://github.com/KenkoGeek/timonel/issues/114) - invalid Helm template generation ([eac0bdb](https://github.com/KenkoGeek/timonel/commit/eac0bdbbe4946e0dcf29346b956451f4d1f4a016))
22
+ - **docs:** apply prettier formatting to README.md ([0ad15aa](https://github.com/KenkoGeek/timonel/commit/0ad15aa8add475268b6b7c9af9bdcd3b6a82bb80))
23
+ - **release:** align required checks with ruleset configuration ([1b4d1dd](https://github.com/KenkoGeek/timonel/commit/1b4d1dd245974f80d3fb00096d48dbaaa79419f1))
24
+ - **release:** update required checks in release workflow ([1811247](https://github.com/KenkoGeek/timonel/commit/1811247fa1562885b8bd43909bd2127e23de225d))
25
+ - resolve issue [#114](https://github.com/KenkoGeek/timonel/issues/114) - Helm template generation bug and enhance documentation ([#116](https://github.com/KenkoGeek/timonel/issues/116)) ([92782f8](https://github.com/KenkoGeek/timonel/commit/92782f840571e036cc235d1b7739b00d8ac8d56c))
26
+ - **umbrella:** remove unused \_subchartInstance variables ([73b3a8a](https://github.com/KenkoGeek/timonel/commit/73b3a8a99b30ef4576f60331f66b30164bd8213e))
27
+
28
+ ### Features
29
+
30
+ - **ci:** optimize GitHub workflows with enhanced performance and reliability ([09d0470](https://github.com/KenkoGeek/timonel/commit/09d04700b011e8dc4ae3875c4a46d835b88bb13d)), closes [#114](https://github.com/KenkoGeek/timonel/issues/114)
31
+ - **core:** enhance ChartProps for better flexibility ([385f966](https://github.com/KenkoGeek/timonel/commit/385f96607c27a1dd58ff8e4ceb60d8a56ece8422))
32
+
33
+ ### Reverts
34
+
35
+ - **ci:** restore original release workflow ([f2f9b27](https://github.com/KenkoGeek/timonel/commit/f2f9b27e1950e8c9496f0faf6b874c0be5ad02e9))
36
+
1
37
  ## [2.9.2](https://github.com/KenkoGeek/timonel/compare/v2.9.1...v2.9.2) (2025-09-19)
2
38
 
3
39
  ### Bug Fixes
package/README.md CHANGED
@@ -10,39 +10,141 @@
10
10
  [![TypeScript][ts-badge]][ts-url]
11
11
  [![Maintained by KenkoGeek][maintained-badge]][maintained-url]
12
12
 
13
- Timonel (Spanish for "helmsman") is a TypeScript library to programmatically generate Helm charts
14
- using cdk8s. Define Kubernetes resources with classes and synthesize a full Helm chart with
15
- `Chart.yaml`, `values.yaml`, per‑environment values files, and `templates/`.
13
+ **Timonel** (Spanish for "helmsman") is a powerful TypeScript library that programmatically generates
14
+ Helm charts using cdk8s. Define Kubernetes resources with type-safe classes and synthesize complete
15
+ Helm charts with `Chart.yaml`, `values.yaml`, environment-specific values files, and `templates/`
16
+ directory.
17
+
18
+ > πŸš€ **New in v2.9.2**: Enhanced umbrella chart support with flexible subchart templates, improved
19
+ > CLI version handling, and optimized GitHub workflows.
16
20
 
17
21
  ## ✨ Key Features
18
22
 
19
- - **Type-safe API** with strict TypeScript and cdk8s constructs.
20
- - **Flexible resource creation** with built-in methods and `addManifest()` for custom resources.
21
- - **Multi-environment support** with automatic values files generation.
22
- - **Umbrella Charts** for managing multiple subcharts as a single unit.
23
- - **AWS integrations**:
24
- - **AWS**: EBS/EFS StorageClass, ALB Ingress, IRSA ServiceAccount.
25
- - **Security-first approach** with NetworkPolicies and best practices.
26
- - **Minimal CLI** (`tl`) for scaffolding and chart generation.
23
+ - **πŸ”’ Type-safe API** with strict TypeScript and cdk8s constructs
24
+ - **πŸ”§ Flexible resource creation** with built-in methods and `addManifest()` for custom resources
25
+ - **🌍 Multi-environment support** with automatic values files generation
26
+ - **β˜‚οΈ Umbrella Charts** for managing multiple subcharts as a single unit
27
+ - **☁️ 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
31
+ - **πŸ›‘οΈ Security-first approach** with NetworkPolicies and best practices
32
+ - **⚑ Minimal CLI** (`tl`) for scaffolding and chart generation
33
+ - **πŸ“¦ Flexible subchart templates** supporting cdk8s, cdk8s-plus-33, and raw manifests
27
34
 
28
35
  ## πŸš€ Quick Start
29
36
 
37
+ ### Installation
38
+
30
39
  ```bash
31
40
  # Install Timonel globally
32
41
  npm install -g timonel
33
42
 
43
+ # Or use with pnpm
44
+ pnpm add -g timonel
45
+
46
+ # Verify installation
47
+ tl --version
48
+ ```
49
+
50
+ ### Basic Usage
51
+
52
+ ```bash
34
53
  # Create your first chart
35
54
  tl init my-app
36
55
 
37
56
  # Generate Helm chart
38
- tl synth charts/my-app charts/my-app-dist
57
+ tl synth my-app my-app-dist
39
58
 
40
59
  # Use with Helm
41
- helm install my-app charts/my-app-dist
60
+ helm install my-app my-app-dist
61
+ ```
62
+
63
+ ### Umbrella Charts
64
+
65
+ ```bash
66
+ # Create umbrella chart structure
67
+ tl umbrella init my-umbrella-app
68
+
69
+ # Add subcharts
70
+ tl umbrella add frontend
71
+ tl umbrella add backend
72
+
73
+ # Generate umbrella chart
74
+ tl umbrella synth
75
+ ```
76
+
77
+ ## πŸ’‘ Examples
78
+
79
+ ### Simple Web Application
80
+
81
+ ```typescript
82
+ import { Chart } from 'cdk8s';
83
+ import { Deployment, Service } from 'cdk8s-plus-33';
84
+
85
+ export class WebAppChart extends Chart {
86
+ constructor(scope: Chart, id: string) {
87
+ super(scope, id);
88
+
89
+ // Create deployment
90
+ new Deployment(this, 'web-deployment', {
91
+ containers: [
92
+ {
93
+ image: 'nginx:latest',
94
+ port: 80,
95
+ env: {
96
+ NGINX_PORT: '80',
97
+ },
98
+ },
99
+ ],
100
+ });
101
+
102
+ // Create service
103
+ new Service(this, 'web-service', {
104
+ selector: { app: 'web' },
105
+ ports: [{ port: 80, targetPort: 80 }],
106
+ });
107
+ }
108
+ }
109
+ ```
110
+
111
+ ### Umbrella Chart with Multiple Services
112
+
113
+ ```typescript
114
+ import { UmbrellaChartTemplate } from 'timonel';
115
+
116
+ const umbrellaConfig = {
117
+ name: 'my-umbrella-app',
118
+ version: '1.0.0',
119
+ subcharts: [
120
+ {
121
+ name: 'frontend',
122
+ version: '1.0.0',
123
+ chart: frontendChartFunction,
124
+ },
125
+ {
126
+ name: 'backend',
127
+ version: '1.0.0',
128
+ chart: backendChartFunction,
129
+ },
130
+ ],
131
+ };
132
+
133
+ export const umbrella = new UmbrellaChartTemplate(umbrellaConfig);
42
134
  ```
43
135
 
44
136
  ## πŸ“š Documentation
45
137
 
138
+ - **[API Reference](https://github.com/KenkoGeek/timonel/wiki/API-Reference)** - Complete API
139
+ documentation
140
+ - **[CLI Reference](https://github.com/KenkoGeek/timonel/wiki/CLI-Reference)** - Command-line
141
+ interface guide
142
+ - **[Examples](https://github.com/KenkoGeek/timonel/wiki/Examples)** - Real-world usage examples
143
+ - **[Best Practices](https://github.com/KenkoGeek/timonel/wiki/Best-Practices)** - Recommended
144
+ patterns and practices
145
+ - **[Contributing](https://github.com/KenkoGeek/timonel/wiki/Contributing)** - Development setup
146
+ and guidelines
147
+
46
148
  ## πŸ”§ Troubleshooting
47
149
 
48
150
  ### CDK8s Module Not Found Error
@@ -59,13 +161,13 @@ If you get `Error: Cannot find module 'cdk8s'` when running `tl umbrella synth`:
59
161
  "version": "1.0.0",
60
162
  "type": "module",
61
163
  "dependencies": {
62
- "cdk8s": "^2.70.11",
63
- "cdk8s-plus-33": "^2.3.5",
164
+ "cdk8s": "^2.70.15",
165
+ "cdk8s-plus-33": "^2.3.6",
64
166
  "constructs": "^10.4.2",
65
- "timonel": "^2.9.0"
167
+ "timonel": "^2.9.2"
66
168
  },
67
169
  "devDependencies": {
68
- "@types/node": "^24.3.0",
170
+ "@types/node": "^24.5.2",
69
171
  "typescript": "^5.9.2"
70
172
  }
71
173
  }
@@ -78,6 +180,45 @@ npm install
78
180
  tl umbrella synth # Now it works!
79
181
  ```
80
182
 
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
+
81
222
  ## 🀝 Contributing
82
223
 
83
224
  See our [Contributing Guide](https://github.com/KenkoGeek/timonel/wiki/Contributing) for development
@@ -104,6 +245,6 @@ MIT
104
245
  [maintained-badge]: https://img.shields.io/badge/maintained%20by-KenkoGeek-6C78AF?style=flat
105
246
  [maintained-url]: https://github.com/kenkogeek/
106
247
  [ci-badge]: https://github.com/KenkoGeek/timonel/actions/workflows/test.yaml/badge.svg?branch=main
107
- [ci-url]: https://github.com/KenkoGeek/timonel/actions/workflows/teast.yaml
248
+ [ci-url]: https://github.com/KenkoGeek/timonel/actions/workflows/test.yaml
108
249
  [codeql-badge]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yaml/badge.svg
109
250
  [codeql-url]: https://github.com/KenkoGeek/timonel/actions/workflows/codeql.yaml
package/SECURITY.md CHANGED
@@ -14,24 +14,40 @@
14
14
  - Steps to reproduce
15
15
  - Potential impact assessment
16
16
  - Suggested mitigation (if any)
17
+ - Affected versions
17
18
 
18
19
  **Response timeline:**
19
20
 
20
21
  - Initial response: 48 hours
21
22
  - Triage completion: 5 business days
22
23
  - Status updates: Every 10 business days
24
+ - Fix timeline: Depends on severity (Critical: 72h, High: 1 week, Medium: 2 weeks)
25
+
26
+ **Severity levels:**
27
+
28
+ - **Critical**: Remote code execution, privilege escalation, data breach
29
+ - **High**: Authentication bypass, significant data exposure
30
+ - **Medium**: Information disclosure, denial of service
31
+ - **Low**: Minor information leakage, cosmetic issues
23
32
 
24
33
  ## Supported Versions
25
34
 
26
35
  We provide security updates for the following versions:
27
36
 
28
- | Version | Supported | Notes |
29
- | ------- | -------------- | ---------------------------- |
30
- | 2.4.0+ | βœ… Current | Enhanced security features |
31
- | 2.3.x | βœ… Supported | Enhanced security features |
32
- | 2.0-2.2 | ⚠️ Limited | Critical security fixes only |
33
- | 1.x.x | ❌ End of life | No security updates |
34
- | < 1.0 | ❌ End of life | No security updates |
37
+ | Version | Supported | Security Updates | End of Life |
38
+ | ------- | -------------- | ---------------- | ----------- |
39
+ | 2.4.0+ | βœ… Current | Full support | TBD |
40
+ | 2.3.x | βœ… Supported | Full support | TBD |
41
+ | 2.0-2.2 | ⚠️ Limited | Critical only | 2024-12-31 |
42
+ | 1.x.x | ❌ End of life | None | 2024-06-30 |
43
+ | < 1.0 | ❌ End of life | None | 2024-01-01 |
44
+
45
+ **Update policy:**
46
+
47
+ - **Current versions**: All security updates within 48 hours
48
+ - **Supported versions**: Critical updates within 1 week
49
+ - **Limited versions**: Critical updates only, within 2 weeks
50
+ - **End of life**: No updates provided
35
51
 
36
52
  ## Development Security
37
53
 
@@ -57,6 +73,10 @@ We provide security updates for the following versions:
57
73
  - **Multi-layer scanning**: ESLint security plugin + CodeQL + pnpm audit
58
74
  - **Branch protection**: Required status checks and reviews
59
75
  - **Secrets management**: No hardcoded credentials
76
+ - **Native dependencies**: Uses `workflow_run` for secure pipeline dependencies
77
+ - **Environment protection**: Production releases require manual approval
78
+ - **Automated security**: Security scans run on every PR and push
79
+ - **Dependency provenance**: NPM_CONFIG_PROVENANCE enabled for package integrity
60
80
 
61
81
  ### Security Features
62
82
 
@@ -77,3 +97,29 @@ We provide security updates for the following versions:
77
97
  - **Values files**: Avoid hardcoded secrets in values
78
98
  - **Network policies**: Use provided NetworkPolicy helpers
79
99
  - **RBAC**: Apply principle of least privilege in Kubernetes
100
+
101
+ ## Security Best Practices
102
+
103
+ ### For Developers
104
+
105
+ - **Keep dependencies updated**: Run `pnpm audit` regularly
106
+ - **Review PRs carefully**: Check for security implications
107
+ - **Use security tools**: Enable IDE security plugins
108
+ - **Follow secure coding**: Avoid dangerous patterns (eval, innerHTML, etc.)
109
+ - **Test security features**: Verify input validation and sanitization
110
+
111
+ ### For Users
112
+
113
+ - **Update regularly**: Always use the latest stable version
114
+ - **Review configurations**: Check generated Helm charts before deployment
115
+ - **Use secrets management**: Never hardcode credentials
116
+ - **Monitor deployments**: Watch for unusual behavior
117
+ - **Apply patches**: Install security updates promptly
118
+
119
+ ## Security Tools Integration
120
+
121
+ - **GitHub Dependabot**: Automated dependency updates
122
+ - **CodeQL**: Static analysis for security vulnerabilities
123
+ - **ESLint Security**: Runtime security checks
124
+ - **pnpm audit**: Dependency vulnerability scanning
125
+ - **Semantic Release**: Automated versioning with security considerations
package/dist/cli.js CHANGED
@@ -8,6 +8,16 @@ const __filename = fileURLToPath(import.meta.url);
8
8
  const __dirname = path.dirname(__filename);
9
9
  const UMBRELLA_CONFIG_FILE = 'umbrella.config.json';
10
10
  const UMBRELLA_FILE_NAME = 'umbrella.ts';
11
+ function getVersion() {
12
+ try {
13
+ const packagePath = path.join(__dirname, '..', 'package.json');
14
+ const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
15
+ return packageJson.version || '0.1.0';
16
+ }
17
+ catch {
18
+ return '0.1.0';
19
+ }
20
+ }
11
21
  function log(msg, silent = false) {
12
22
  if (!silent) {
13
23
  console.log(msg);
@@ -214,7 +224,7 @@ async function cmdUmbrellaInit(name, silent = false) {
214
224
  fs.writeFileSync(umbrellaFile, generateUmbrellaChart(validName));
215
225
  const config = {
216
226
  name: validName,
217
- version: '0.1.0',
227
+ version: getVersion(),
218
228
  description: `${validName} umbrella chart`,
219
229
  subcharts: [],
220
230
  };
@@ -315,7 +325,7 @@ async function cmdUmbrellaAdd(subchartPath, silent = false) {
315
325
  fs.writeFileSync(chartFile, subchartContent);
316
326
  config.subcharts.push({
317
327
  name: subchartName,
318
- version: '0.1.0',
328
+ version: getVersion(),
319
329
  path: `./charts/${validSubchartPath}/chart.ts`,
320
330
  });
321
331
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
@@ -397,7 +407,7 @@ function parseFlags(args) {
397
407
  case '--version':
398
408
  case '-v':
399
409
  if (!flags.silent) {
400
- console.log('Timonel v0.1.0');
410
+ console.log(`Timonel v${getVersion()}`);
401
411
  }
402
412
  process.exit(0);
403
413
  break;
@@ -448,7 +458,7 @@ async function main() {
448
458
  }
449
459
  if (args.includes('--version') || args.includes('-v')) {
450
460
  if (!isSilent) {
451
- console.log('Timonel v0.1.0');
461
+ console.log(`Timonel v${getVersion()}`);
452
462
  }
453
463
  process.exit(0);
454
464
  }
package/dist/index.d.ts CHANGED
@@ -3,9 +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 { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
7
- export { Subchart } from './lib/templates/subchart.js';
8
6
  export { BasicChart } from './lib/templates/basic-chart.js';
7
+ export { FlexibleSubchart, createFlexibleSubchart } from './lib/templates/flexible-subchart.js';
8
+ export { Subchart } from './lib/templates/subchart.js';
9
+ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
10
+ export type { ChartProps, SubchartProps } from './lib/types.js';
9
11
  export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './lib/resources/cloud/aws/awsResources.js';
10
12
  export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec, } from './lib/resources/cloud/aws/karpenterResources.js';
11
13
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
package/dist/index.js CHANGED
@@ -3,8 +3,9 @@ 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
6
  export { BasicChart } from './lib/templates/basic-chart.js';
7
+ export { FlexibleSubchart, createFlexibleSubchart } from './lib/templates/flexible-subchart.js';
8
+ export { Subchart } from './lib/templates/subchart.js';
9
+ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella-chart.js';
9
10
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
10
11
  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,7 +5,7 @@ import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.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
+ console.log(`Writing chart: ${meta.name} v${meta.version} to ${outDir}`);
9
9
  const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
10
10
  this.createDirectories(validatedOutDir);
11
11
  this.writeChartYaml(validatedOutDir, meta);
@@ -15,7 +15,7 @@ export class HelmChartWriter {
15
15
  this.writeNotes(validatedOutDir, notesTpl);
16
16
  this.writeSchema(validatedOutDir, valuesSchema);
17
17
  this.writeHelmIgnore(validatedOutDir);
18
- console.log(`βœ… Helm chart written successfully to ${validatedOutDir}`);
18
+ console.log(`Chart written successfully to ${validatedOutDir}`);
19
19
  }
20
20
  static createDirectories(outDir) {
21
21
  fs.mkdirSync(path.join(outDir, 'templates'), { recursive: true });
@@ -111,7 +111,7 @@ function writeAssets(outDir, assets) {
111
111
  for (const asset of assets) {
112
112
  const sanitizedId = asset.id.replace(/[^a-zA-Z0-9-_]/g, '');
113
113
  if (sanitizedId !== asset.id) {
114
- console.error(`❌ Invalid asset ID detected: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
114
+ console.error(`Invalid asset ID detected: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
115
115
  throw new Error(`Invalid asset ID: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
116
116
  }
117
117
  const targetDir = getTargetDirectory(asset.target);
@@ -124,7 +124,7 @@ function writeAssets(outDir, assets) {
124
124
  }
125
125
  }
126
126
  catch (error) {
127
- console.error(`❌ Failed to write asset ${asset.id}:`, error);
127
+ console.error(`Failed to write asset ${asset.id}:`, error);
128
128
  throw error;
129
129
  }
130
130
  }
@@ -134,7 +134,7 @@ function getTargetDirectory(target) {
134
134
  }
135
135
  function writeSingleAssetFile(outDir, targetDir, assetId, yaml) {
136
136
  if (assetId === 'ingress' && yaml.includes('number:')) {
137
- console.log('πŸ” Writing ingress file with content:');
137
+ console.log('Writing ingress file with number fields');
138
138
  const numberLines = yaml.split('\n').filter((line) => line.includes('number:'));
139
139
  console.log('Number lines:', numberLines);
140
140
  }
@@ -2,14 +2,14 @@ import { ApiObject, App, Chart, Testing } from 'cdk8s';
2
2
  import * as jsYaml from 'js-yaml';
3
3
  import { include } from './helm.js';
4
4
  import { HelmChartWriter } from './helmChartWriter.js';
5
- import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
6
5
  import { AWSResources } from './resources/cloud/aws/awsResources.js';
7
6
  import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
7
+ import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
8
8
  import { generateHelpersTemplate } from './utils/helmHelpers.js';
9
9
  export class Rutter {
10
10
  constructor(props) {
11
11
  this.assets = [];
12
- console.log(`πŸš€ Initializing Rutter chart: ${props.meta.name} v${props.meta.version}`);
12
+ console.log(`Initializing chart: ${props.meta.name} v${props.meta.version}`);
13
13
  this.defaultValues = props.defaultValues ?? {};
14
14
  this.envValues = props.envValues ?? {};
15
15
  this.meta = props.meta;
@@ -134,24 +134,24 @@ ${yamlContent.trim()}
134
134
  }
135
135
  validateManifestStructure(manifest) {
136
136
  if (!manifest.apiVersion) {
137
- console.error('❌ Manifest validation failed: Missing apiVersion');
137
+ console.error('Validation failed: Missing apiVersion');
138
138
  throw new Error('Manifest must have an apiVersion');
139
139
  }
140
140
  if (!manifest.kind) {
141
- console.error('❌ Manifest validation failed: Missing kind');
141
+ console.error('Validation failed: Missing kind');
142
142
  throw new Error('Manifest must have a kind');
143
143
  }
144
144
  if (!manifest.metadata) {
145
- console.error('❌ Manifest validation failed: Missing metadata');
145
+ console.error('Validation failed: Missing metadata');
146
146
  throw new Error('Manifest must have metadata');
147
147
  }
148
148
  const metadata = manifest.metadata;
149
149
  if (!metadata.name) {
150
- console.error('❌ Manifest validation failed: Missing metadata.name');
150
+ console.error('Validation failed: Missing metadata.name');
151
151
  throw new Error('Manifest metadata must have a name');
152
152
  }
153
153
  if (typeof metadata.name !== 'string') {
154
- console.error('❌ Manifest validation failed: metadata.name must be a string');
154
+ console.error('Validation failed: metadata.name must be a string');
155
155
  throw new Error('Manifest metadata.name must be a string');
156
156
  }
157
157
  }
@@ -193,7 +193,7 @@ ${yamlContent.trim()}
193
193
  const labels = o.metadata.labels;
194
194
  const defaults = {
195
195
  'helm.sh/chart': `{{ .Chart.Name }}-{{ .Chart.Version }}`,
196
- 'app.kubernetes.io/name': `{{ ${include} "${Rutter.HELPER_NAME}" . }}`,
196
+ 'app.kubernetes.io/name': include(Rutter.HELPER_NAME),
197
197
  'app.kubernetes.io/instance': '{{ .Release.Name }}',
198
198
  'app.kubernetes.io/version': '{{ .Chart.Version }}',
199
199
  'app.kubernetes.io/managed-by': '{{ .Release.Service }}',
@@ -399,7 +399,7 @@ ${yamlContent.trim()}
399
399
  return processed;
400
400
  }
401
401
  write(outDir) {
402
- console.log(`✍️ Writing Helm chart '${this.meta.name}' to: ${outDir}`);
402
+ console.log(`Writing chart '${this.meta.name}' to: ${outDir}`);
403
403
  let helpersContent;
404
404
  if (this.props.helpersTpl) {
405
405
  if (typeof this.props.helpersTpl === 'string') {
@@ -429,7 +429,7 @@ ${helper.template}
429
429
  assets: synthAssets,
430
430
  helpersTpl: helpersContent,
431
431
  });
432
- console.log(`βœ… Helm chart '${this.meta.name}' written successfully`);
432
+ console.log(`Chart '${this.meta.name}' written successfully`);
433
433
  }
434
434
  }
435
435
  Rutter.HELPER_NAME = 'chart.name';
@@ -0,0 +1,27 @@
1
+ import { Chart } from 'cdk8s';
2
+ export declare class FlexibleSubchart extends Chart {
3
+ private readonly config;
4
+ private _constructs;
5
+ private _manifests;
6
+ constructor(scope: Chart, id: string, config: {
7
+ name: string;
8
+ version?: string;
9
+ description?: string;
10
+ construct?: unknown;
11
+ manifest?: unknown;
12
+ [key: string]: unknown;
13
+ });
14
+ addConstruct(construct: unknown, id?: string): void;
15
+ addManifest(manifest: unknown, id?: string): void;
16
+ private configure;
17
+ writeHelmChart(outputDir: string): void;
18
+ private _generateManifestTemplate;
19
+ getConstructs(): unknown[];
20
+ getManifests(): unknown[];
21
+ }
22
+ export declare function createFlexibleSubchart(scope: Chart, id: string, config: {
23
+ name: string;
24
+ version?: string;
25
+ description?: string;
26
+ [key: string]: unknown;
27
+ }): FlexibleSubchart;
@@ -0,0 +1,107 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { Chart } from 'cdk8s';
4
+ import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
5
+ import { generateHelpersTemplate } from '../utils/helmHelpers.js';
6
+ export class FlexibleSubchart extends Chart {
7
+ constructor(scope, id, config) {
8
+ super(scope, id);
9
+ this.config = config;
10
+ this._constructs = [];
11
+ this._manifests = [];
12
+ this.configure();
13
+ }
14
+ addConstruct(construct, id) {
15
+ if (construct) {
16
+ this._constructs.push({ construct, id: id || `construct-${this._constructs.length}` });
17
+ }
18
+ }
19
+ addManifest(manifest, id) {
20
+ if (manifest) {
21
+ this._manifests.push({ manifest, id: id || `manifest-${this._manifests.length}` });
22
+ }
23
+ }
24
+ configure() {
25
+ if (this.config.construct) {
26
+ this.addConstruct(this.config.construct);
27
+ }
28
+ if (this.config.manifest) {
29
+ this.addManifest(this.config.manifest);
30
+ }
31
+ Object.entries(this.config).forEach(([key, value]) => {
32
+ if (key !== 'name' &&
33
+ key !== 'version' &&
34
+ key !== 'description' &&
35
+ key !== 'construct' &&
36
+ key !== 'manifest' &&
37
+ value) {
38
+ this.addConstruct(value, key);
39
+ }
40
+ });
41
+ }
42
+ writeHelmChart(outputDir) {
43
+ if (!existsSync(outputDir)) {
44
+ mkdirSync(outputDir, { recursive: true });
45
+ }
46
+ const subchartYaml = {
47
+ apiVersion: 'v2',
48
+ name: this.config.name,
49
+ description: this.config.description || `${this.config.name} subchart`,
50
+ type: 'application',
51
+ version: this.config.version || '1.0.0',
52
+ appVersion: this.config.version || '1.0.0',
53
+ };
54
+ writeFileSync(join(outputDir, 'Chart.yaml'), dumpHelmAwareYaml(subchartYaml));
55
+ const subchartValues = {
56
+ enabled: true,
57
+ ...Object.fromEntries(Object.entries(this.config).filter(([key, value]) => key !== 'chart' && typeof value !== 'function' && value !== null && value !== undefined)),
58
+ };
59
+ writeFileSync(join(outputDir, 'values.yaml'), dumpHelmAwareYaml(subchartValues));
60
+ const templatesDir = join(outputDir, 'templates');
61
+ if (!existsSync(templatesDir)) {
62
+ mkdirSync(templatesDir, { recursive: true });
63
+ }
64
+ const helpersTpl = generateHelpersTemplate('aws', undefined, {
65
+ includeKubernetes: true,
66
+ includeSprig: true,
67
+ });
68
+ writeFileSync(join(templatesDir, '_helpers.tpl'), helpersTpl);
69
+ this._manifests.forEach((item) => {
70
+ if (item && typeof item === 'object' && 'manifest' in item && 'id' in item) {
71
+ const { manifest, id } = item;
72
+ if (manifest && typeof manifest === 'object') {
73
+ const templateContent = this._generateManifestTemplate(manifest, id);
74
+ writeFileSync(join(templatesDir, `${id}.yaml`), templateContent);
75
+ }
76
+ }
77
+ });
78
+ console.log(`Created flexible Helm chart for subchart: ${this.config.name}`);
79
+ }
80
+ _generateManifestTemplate(manifest, id) {
81
+ if (typeof manifest !== 'object' || manifest === null) {
82
+ return dumpHelmAwareYaml({});
83
+ }
84
+ const manifestObj = manifest;
85
+ const template = {
86
+ apiVersion: manifestObj.apiVersion || 'v1',
87
+ kind: manifestObj.kind || 'Manifest',
88
+ metadata: {
89
+ name: `{{ include "${id}.fullname" . }}`,
90
+ labels: `{{ include "${id}.labels" . }}`,
91
+ ...(manifestObj.metadata || {}),
92
+ },
93
+ spec: manifestObj.spec || {},
94
+ ...manifestObj,
95
+ };
96
+ return dumpHelmAwareYaml(template);
97
+ }
98
+ getConstructs() {
99
+ return this._constructs;
100
+ }
101
+ getManifests() {
102
+ return this._manifests;
103
+ }
104
+ }
105
+ export function createFlexibleSubchart(scope, id, config) {
106
+ return new FlexibleSubchart(scope, id, config);
107
+ }
@@ -9,5 +9,6 @@ export declare class UmbrellaChartTemplate extends Chart {
9
9
  private configure;
10
10
  private _addService;
11
11
  private _addSubchart;
12
+ private _createBasicSubchart;
12
13
  writeHelmChart(outputDir: string): void;
13
14
  }
@@ -3,6 +3,7 @@ import { join } from 'path';
3
3
  import { Chart, ApiObject } from 'cdk8s';
4
4
  import { dumpHelmAwareYaml } from '../utils/helmYamlSerializer.js';
5
5
  import { generateHelpersTemplate } from '../utils/helmHelpers.js';
6
+ import { createFlexibleSubchart } from './flexible-subchart.js';
6
7
  export function generateUmbrellaChart(name) {
7
8
  return `import { App } from 'cdk8s';
8
9
  import { UmbrellaChart } from 'timonel';
@@ -92,7 +93,87 @@ export class UmbrellaChartTemplate extends Chart {
92
93
  },
93
94
  });
94
95
  }
95
- _addSubchart(_subchart, _index) {
96
+ _addSubchart(subchart, index) {
97
+ try {
98
+ let _subchartInstance;
99
+ const { name: _name, version: _version, description: _description, ...subchartProps } = subchart;
100
+ const flexibleSubchart = createFlexibleSubchart(this, `${subchart.name}-${index}`, {
101
+ name: subchart.name,
102
+ version: subchart.version || '1.0.0',
103
+ description: subchart.description || `${subchart.name} subchart`,
104
+ ...subchartProps,
105
+ });
106
+ if (typeof subchart.chart === 'function') {
107
+ try {
108
+ const subchartInstance = subchart.chart(this, `${subchart.name}-${index}`);
109
+ if (subchartInstance.node.children) {
110
+ subchartInstance.node.children.forEach((child) => {
111
+ if (child instanceof Chart) {
112
+ flexibleSubchart.addConstruct(child);
113
+ }
114
+ });
115
+ }
116
+ }
117
+ catch {
118
+ const subchartInstance = subchart.chart();
119
+ if (subchartInstance.node.children) {
120
+ subchartInstance.node.children.forEach((child) => {
121
+ if (child instanceof Chart) {
122
+ flexibleSubchart.addConstruct(child);
123
+ }
124
+ });
125
+ }
126
+ }
127
+ }
128
+ else if (subchart.chart && typeof subchart.chart === 'object') {
129
+ if (subchart.chart.constructor && subchart.chart.constructor.name !== 'Object') {
130
+ flexibleSubchart.addConstruct(subchart.chart);
131
+ }
132
+ else {
133
+ flexibleSubchart.addManifest(subchart.chart);
134
+ }
135
+ }
136
+ console.log(`Added flexible subchart: ${subchart.name}`);
137
+ }
138
+ catch (_error) {
139
+ console.warn(`Failed to add subchart ${subchart.name}:`, _error);
140
+ const _fallbackSubchart = createFlexibleSubchart(this, `${subchart.name}-fallback-${index}`, {
141
+ name: subchart.name,
142
+ version: subchart.version || '1.0.0',
143
+ description: `${subchart.name} subchart (fallback)`,
144
+ });
145
+ console.log(`Created fallback subchart: ${subchart.name}`);
146
+ }
147
+ }
148
+ _createBasicSubchart(subchartName, subchartDir) {
149
+ const subchartYaml = {
150
+ apiVersion: 'v2',
151
+ name: subchartName,
152
+ description: `${subchartName} subchart`,
153
+ type: 'application',
154
+ version: '1.0.0',
155
+ appVersion: '1.0.0',
156
+ };
157
+ writeFileSync(join(subchartDir, 'Chart.yaml'), dumpHelmAwareYaml(subchartYaml));
158
+ const subchartValues = {
159
+ enabled: true,
160
+ replicas: 1,
161
+ image: {
162
+ repository: 'nginx',
163
+ tag: 'latest',
164
+ },
165
+ };
166
+ writeFileSync(join(subchartDir, 'values.yaml'), dumpHelmAwareYaml(subchartValues));
167
+ const templatesDir = join(subchartDir, 'templates');
168
+ if (!existsSync(templatesDir)) {
169
+ mkdirSync(templatesDir, { recursive: true });
170
+ }
171
+ const helpersTpl = generateHelpersTemplate('aws', undefined, {
172
+ includeKubernetes: true,
173
+ includeSprig: true,
174
+ });
175
+ writeFileSync(join(templatesDir, '_helpers.tpl'), helpersTpl);
176
+ console.log(`Created basic Helm chart structure for subchart: ${subchartName}`);
96
177
  }
97
178
  writeHelmChart(outputDir) {
98
179
  if (!existsSync(outputDir)) {
@@ -133,21 +214,50 @@ export class UmbrellaChartTemplate extends Chart {
133
214
  if (!existsSync(subchartDir)) {
134
215
  mkdirSync(subchartDir, { recursive: true });
135
216
  }
136
- let subchartInstance = null;
217
+ const { name: _name, version: _version, description: _description, ...subchartProps } = subchart;
218
+ const flexibleSubchart = createFlexibleSubchart(this, `${subchart.name}-chart`, {
219
+ name: subchart.name,
220
+ version: subchart.version || '1.0.0',
221
+ description: subchart.description || `${subchart.name} subchart`,
222
+ ...subchartProps,
223
+ });
137
224
  if (typeof subchart.chart === 'function') {
138
225
  try {
139
- subchartInstance = subchart.chart();
226
+ const chartInstance = subchart.chart(this, `${subchart.name}-chart`);
227
+ if (chartInstance && typeof chartInstance.writeHelmChart === 'function') {
228
+ chartInstance.writeHelmChart(subchartDir);
229
+ }
230
+ else {
231
+ flexibleSubchart.writeHelmChart(subchartDir);
232
+ }
140
233
  }
141
- catch (error) {
142
- console.warn(`Failed to create subchart ${subchart.name}:`, error);
143
- return;
234
+ catch {
235
+ try {
236
+ const chartInstance = subchart.chart();
237
+ if (chartInstance && typeof chartInstance.writeHelmChart === 'function') {
238
+ chartInstance.writeHelmChart(subchartDir);
239
+ }
240
+ else {
241
+ flexibleSubchart.writeHelmChart(subchartDir);
242
+ }
243
+ }
244
+ catch (fallbackError) {
245
+ console.warn(`Failed to create subchart ${subchart.name}:`, fallbackError);
246
+ flexibleSubchart.writeHelmChart(subchartDir);
247
+ }
144
248
  }
145
249
  }
146
- else {
147
- subchartInstance = subchart.chart;
250
+ else if (subchart.chart && typeof subchart.chart === 'object') {
251
+ if (subchart.chart.constructor && subchart.chart.constructor.name !== 'Object') {
252
+ flexibleSubchart.addConstruct(subchart.chart);
253
+ }
254
+ else {
255
+ flexibleSubchart.addManifest(subchart.chart);
256
+ }
257
+ flexibleSubchart.writeHelmChart(subchartDir);
148
258
  }
149
- if (subchartInstance && typeof subchartInstance.writeHelmChart === 'function') {
150
- subchartInstance.writeHelmChart(subchartDir);
259
+ else {
260
+ flexibleSubchart.writeHelmChart(subchartDir);
151
261
  }
152
262
  });
153
263
  const templatesDir = join(outputDir, 'templates');
@@ -3,6 +3,9 @@ export interface ChartProps {
3
3
  name: string;
4
4
  version: string;
5
5
  description: string;
6
+ namespace?: string;
7
+ labels?: Record<string, string>;
8
+ annotations?: Record<string, string>;
6
9
  services?: Array<{
7
10
  name: string;
8
11
  port: number;
@@ -10,8 +13,12 @@ export interface ChartProps {
10
13
  }>;
11
14
  subcharts?: Array<{
12
15
  name: string;
13
- chart: Chart | (() => Chart);
16
+ chart: Chart | ((scope: Chart, id: string) => Chart) | unknown;
17
+ version?: string;
18
+ enabled?: boolean;
19
+ [key: string]: unknown;
14
20
  }>;
21
+ [key: string]: unknown;
15
22
  }
16
23
  export interface SubchartProps {
17
24
  name: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "2.9.2",
4
+ "version": "2.10.0",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",
@@ -81,29 +81,29 @@
81
81
  ]
82
82
  },
83
83
  "dependencies": {
84
- "cdk8s": "^2.70.11",
85
- "cdk8s-plus-33": "^2.3.5",
84
+ "cdk8s": "^2.70.15",
85
+ "cdk8s-plus-33": "^2.3.6",
86
86
  "constructs": "^10.4.2",
87
87
  "handlebars": "^4.7.8",
88
88
  "js-yaml": "^4.1.0",
89
- "timonel": "^2.9.0",
89
+ "timonel": "^2.9.2",
90
90
  "ts-node": "^10.9.2",
91
91
  "yaml": "^2.8.1"
92
92
  },
93
93
  "devDependencies": {
94
94
  "@commitlint/cli": "^19.8.1",
95
95
  "@commitlint/config-conventional": "^19.8.1",
96
- "@eslint/js": "^9.35.0",
96
+ "@eslint/js": "^9.36.0",
97
97
  "@semantic-release/changelog": "^6.0.3",
98
98
  "@semantic-release/exec": "^7.1.0",
99
99
  "@semantic-release/git": "^10.0.1",
100
100
  "@types/js-yaml": "^4.0.9",
101
- "@types/node": "^24.3.0",
102
- "@typescript-eslint/eslint-plugin": "^8.42.0",
103
- "@typescript-eslint/parser": "^8.42.0",
104
- "@vitest/coverage-v8": "^2.1.9",
101
+ "@types/node": "^24.5.2",
102
+ "@typescript-eslint/eslint-plugin": "^8.44.0",
103
+ "@typescript-eslint/parser": "^8.44.0",
104
+ "@vitest/coverage-v8": "^3.2.4",
105
105
  "conventional-changelog-cli": "^5.0.0",
106
- "eslint": "^9.35.0",
106
+ "eslint": "^9.36.0",
107
107
  "eslint-config-prettier": "^10.1.8",
108
108
  "eslint-import-resolver-typescript": "^4.4.4",
109
109
  "eslint-plugin-import": "^2.32.0",
@@ -115,10 +115,10 @@
115
115
  "markdownlint": "^0.38.0",
116
116
  "markdownlint-cli": "^0.45.0",
117
117
  "prettier": "^3.6.2",
118
- "semantic-release": "^24.2.7",
118
+ "semantic-release": "^24.2.9",
119
119
  "tsx": "^4.20.5",
120
120
  "typescript": "^5.9.2",
121
- "vitest": "^2.1.9"
121
+ "vitest": "^3.2.4"
122
122
  },
123
123
  "files": [
124
124
  "dist",