timonel 0.4.0 → 2.0.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,59 @@
1
+ # [2.0.0](https://github.com/KenkoGeek/timonel/compare/v1.0.0...v2.0.0) (2025-09-07)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **ci:** remove redundant publish workflow and configure release token ([fcea159](https://github.com/KenkoGeek/timonel/commit/fcea15914617e372a2aafc8a30001cc54ec8bff6))
6
+ - **examples:** update timonel dependency to ^1.0.0 ([3d1f692](https://github.com/KenkoGeek/timonel/commit/3d1f6922448648c7efe57cc1fdde9a5808e04d7e))
7
+ - **helm:** remove automatic numbering from template files ([#20](https://github.com/KenkoGeek/timonel/issues/20)) ([f75748a](https://github.com/KenkoGeek/timonel/commit/f75748aee974a254f576807f26396b026262b42c))
8
+
9
+ ### Features
10
+
11
+ - **ci:** add automatic CHANGELOG.md formatting to semantic-release ([6846b4d](https://github.com/KenkoGeek/timonel/commit/6846b4de2cfd411b7b9b9724d67830cf192ad2b9))
12
+ - **core:** add Azure Application Gateway Ingress Controller (AGIC) support ([#19](https://github.com/KenkoGeek/timonel/issues/19)) ([75bbb99](https://github.com/KenkoGeek/timonel/commit/75bbb998981ca6492b09ad12c54e03050b307c27))
13
+ - **core:** add Azure Disk StorageClass helper for AKS ([#18](https://github.com/KenkoGeek/timonel/issues/18)) ([5fb29de](https://github.com/KenkoGeek/timonel/commit/5fb29de260c4cabf6732520b0834cf0d0421a33a))
14
+
15
+ ### BREAKING CHANGES
16
+
17
+ - **helm:** Template files no longer have automatic numbering prefixes
18
+
19
+ - refactor(helm): unify template naming strategy and add breaking change notice
20
+
21
+ * Simplify file naming logic with unified approach
22
+ * Use consistent naming strategy for single and multiple documents
23
+ * Add breaking change warning in README for numbered filename dependencies
24
+ * Improve code maintainability with cleaner implementation
25
+
26
+ Addresses PR review feedback for better consistency and user awareness.
27
+
28
+ # [1.0.0](https://github.com/KenkoGeek/timonel/compare/v0.4.0...v1.0.0) (2025-09-07)
29
+
30
+ ### Bug Fixes
31
+
32
+ - **ci:** configure GitHub App token for semantic-release bypass - Use RELEASE_TOKEN from npm-prd environment for checkout and semantic-release ([0d385d1](https://github.com/KenkoGeek/timonel/commit/0d385d1084a45de3ab731094e87196239811e6fb))
33
+ - **ci:** disable body-max-line-length for semantic-release compatibility - Set body-max-line-length to 0 (disabled) to allow long changelog entries - Fixes semantic-release commit validation failures - Maintains other commitlint rules for developer commits ([b215f65](https://github.com/KenkoGeek/timonel/commit/b215f65bccb69a1cfca79084e6a9c96954fb478a))
34
+ - **ci:** disable footer line length limit for semantic-release commits ([d5ae4dc](https://github.com/KenkoGeek/timonel/commit/d5ae4dc83e68f8fade4a8289d1ce4e2bae5046dd))
35
+ - **ci:** disable footer-leading-blank for semantic-release compatibility - Set footer-leading-blank to 0 (disabled) to allow semantic-release footer format ([2f8c6c8](https://github.com/KenkoGeek/timonel/commit/2f8c6c863c6aabd1d3f3d8022b43fefecb02f010))
36
+ - **ci:** replace CodeQL with security check in release validation ([#15](https://github.com/KenkoGeek/timonel/issues/15)) ([b8cecea](https://github.com/KenkoGeek/timonel/commit/b8cecea1eaf0bb6af3e6fd2f6bc38c2fb2c09f4d))
37
+ - **ci:** resolve release workflow issues ([#16](https://github.com/KenkoGeek/timonel/issues/16)) ([ed5dc9b](https://github.com/KenkoGeek/timonel/commit/ed5dc9b3d86b531d4495d4d05dd913340333322c))
38
+
39
+ ### Features
40
+
41
+ - add manual release workflow with GitHub Actions ([1035b2e](https://github.com/KenkoGeek/timonel/commit/1035b2e18fbb808e57438353c865ecd129d9e04d))
42
+ - add NetworkPolicy helpers for Zero Trust network security ([#13](https://github.com/KenkoGeek/timonel/issues/13)) ([cee3b58](https://github.com/KenkoGeek/timonel/commit/cee3b5876de839ca3cf16dd6ca6e3b76626392ce))
43
+ - **ci:** add required status checks validation to release workflow ([#14](https://github.com/KenkoGeek/timonel/issues/14)) ([7f8665f](https://github.com/KenkoGeek/timonel/commit/7f8665fa908215cc92a4dbefb7fec00de98464bb))
44
+ - **ci:** implement automated semantic release with conventional commits ([02ce189](https://github.com/KenkoGeek/timonel/commit/02ce18943efd8019168b5f9af29be0140a9e1524))
45
+ - **ci:** implement automated semantic release with conventional commits ([bb94fda](https://github.com/KenkoGeek/timonel/commit/bb94fda3e80bf3e010e7b53bdd1794f88060408a))
46
+ - **core:** add NetworkPolicy helpers for Zero Trust network security ([34ac6bd](https://github.com/KenkoGeek/timonel/commit/34ac6bda3956e5fe326b72e9831bd4bb8bc660da))
47
+
48
+ ### Performance Improvements
49
+
50
+ - **ci:** homogenize CI/CD workflows and optimize release pipeline ([#17](https://github.com/KenkoGeek/timonel/issues/17)) ([4622d11](https://github.com/KenkoGeek/timonel/commit/4622d113e5753a5ff69c82a1e3784ecf61c34689))
51
+
52
+ ### BREAKING CHANGES
53
+
54
+ - **ci:** ESLint configuration migrated to flat config format
55
+ - **ci:** ESLint configuration migrated to flat config format
56
+
1
57
  # Changelog
2
58
 
3
59
  All notable changes to this project will be documented in this file.
package/README.md CHANGED
@@ -186,6 +186,11 @@ rutter.write('dist/charts/my-app');
186
186
 
187
187
  ### Custom Manifest Naming
188
188
 
189
+ > **⚠️ Breaking Change Notice**: Starting from v1.1.0, template files no longer include
190
+ > automatic numbering prefixes (0000-, 0001-, etc.). Files now use descriptive names
191
+ > following Helm best practices. If your scripts or tools depend on numbered filenames,
192
+ > please update them accordingly.
193
+
189
194
  Timonel provides flexible options for naming your Kubernetes manifest files:
190
195
 
191
196
  #### Single Manifest File
@@ -210,9 +215,9 @@ const rutter = new Rutter({
210
215
  });
211
216
 
212
217
  // Generates:
213
- // templates/0000-my-app-deployment-web.yaml
214
- // templates/0001-my-app-service-web.yaml
215
- // templates/0002-my-app-configmap-config.yaml
218
+ // templates/my-app-deployment-web.yaml
219
+ // templates/my-app-service-web.yaml
220
+ // templates/my-app-configmap-config.yaml
216
221
  ```
217
222
 
218
223
  #### Default Behavior
@@ -224,9 +229,9 @@ const rutter = new Rutter({
224
229
  });
225
230
 
226
231
  // Generates:
227
- // templates/0000-deployment-web.yaml
228
- // templates/0001-service-web.yaml
229
- // templates/0002-configmap-config.yaml
232
+ // templates/deployment-web.yaml
233
+ // templates/service-web.yaml
234
+ // templates/configmap-config.yaml
230
235
  ```
231
236
 
232
237
  ## Umbrella Charts
@@ -315,9 +320,13 @@ shared values, and multi-environment support.
315
320
  (`toString`, `float64`).
316
321
  - For numeric fields (ports, replicas), use literal values or template strings with proper casting.
317
322
 
318
- ## AWS Multi-Cloud Support
323
+ ## Multi-Cloud Support
324
+
325
+ Timonel provides comprehensive cloud-specific helpers for major Kubernetes platforms:
326
+
327
+ ### AWS Multi-Cloud Support
319
328
 
320
- Timonel provides comprehensive AWS-specific helpers for EKS deployments:
329
+ Comprehensive AWS-specific helpers for EKS deployments:
321
330
 
322
331
  ### IRSA (IAM Roles for Service Accounts)
323
332
 
@@ -429,6 +438,331 @@ rutter.addAWSSecretProviderClass({
429
438
  });
430
439
  ```
431
440
 
441
+ ### Azure Multi-Cloud Support
442
+
443
+ Azure-specific helpers for AKS deployments:
444
+
445
+ #### Azure Disk Storage Classes
446
+
447
+ ```typescript
448
+ // Premium ZRS disk for production workloads
449
+ rutter.addAzureDiskStorageClass({
450
+ name: 'azure-premium-zrs',
451
+ skuName: 'Premium_ZRS',
452
+ fsType: 'ext4',
453
+ cachingMode: 'ReadWrite',
454
+ allowVolumeExpansion: true,
455
+ volumeBindingMode: 'WaitForFirstConsumer',
456
+ tags: {
457
+ Environment: 'production',
458
+ Application: 'database',
459
+ },
460
+ });
461
+
462
+ // Standard SSD for cost-optimized workloads
463
+ rutter.addAzureDiskStorageClass({
464
+ name: 'azure-standard-ssd',
465
+ skuName: 'StandardSSD_LRS',
466
+ fsType: 'ext4',
467
+ cachingMode: 'ReadOnly',
468
+ });
469
+
470
+ // Ultra SSD for high-performance workloads
471
+ rutter.addAzureDiskStorageClass({
472
+ name: 'azure-ultra-ssd',
473
+ skuName: 'UltraSSD_LRS',
474
+ fsType: 'ext4',
475
+ cachingMode: 'None',
476
+ diskIOPSReadWrite: 2000,
477
+ diskMBpsReadWrite: 200,
478
+ logicalSectorSize: 4096,
479
+ });
480
+ ```
481
+
482
+ #### Azure Application Gateway Ingress Controller (AGIC)
483
+
484
+ Comprehensive AGIC support for AKS deployments with advanced features:
485
+
486
+ ```typescript
487
+ // AGIC Ingress with SSL and health checks
488
+ rutter.addAzureAGICIngress({
489
+ name: 'app-ingress',
490
+ rules: [
491
+ {
492
+ host: 'app.example.com',
493
+ paths: [
494
+ {
495
+ path: '/',
496
+ pathType: 'Prefix',
497
+ backend: { service: { name: 'app-service', port: { number: 80 } } },
498
+ },
499
+ ],
500
+ },
501
+ ],
502
+ sslRedirect: true,
503
+ backendProtocol: 'https',
504
+ healthProbePath: '/health',
505
+ healthProbeInterval: 30,
506
+ cookieBasedAffinity: true,
507
+ requestTimeout: 60,
508
+ appgwSslCertificate: 'my-ssl-cert',
509
+ wafPolicyForPath:
510
+ '/subscriptions/sub-id/resourceGroups/rg/providers/Microsoft.Network/applicationGatewayWebApplicationFirewallPolicies/waf-policy',
511
+ });
512
+
513
+ // Advanced AGIC features
514
+ rutter.addAzureAGICIngress({
515
+ name: 'advanced-ingress',
516
+ rules: [
517
+ /* rules */
518
+ ],
519
+ backendPathPrefix: '/api/v1',
520
+ backendHostname: 'internal.example.com',
521
+ usePrivateIp: true,
522
+ overrideFrontendPort: 8080,
523
+ connectionDraining: true,
524
+ connectionDrainingTimeout: 60,
525
+ hostnameExtension: ['api.example.com', 'admin.example.com'],
526
+ appgwTrustedRootCertificate: ['root-cert-1', 'root-cert-2'],
527
+ rewriteRuleSet: 'custom-rewrite-rules',
528
+ rulePriority: 100,
529
+ });
530
+ ```
531
+
532
+ ## Network Security with NetworkPolicies
533
+
534
+ Timonel provides comprehensive NetworkPolicy helpers for implementing Zero Trust network security
535
+ in Kubernetes:
536
+
537
+ ### Zero Trust Network Security
538
+
539
+ Implement defense-in-depth with deny-by-default policies:
540
+
541
+ ```typescript
542
+ // 1. Deny all traffic by default (recommended starting point)
543
+ rutter.addDenyAllNetworkPolicy('default-deny-all');
544
+
545
+ // 2. Allow specific traffic as needed
546
+ rutter.addAllowFromPodsNetworkPolicy({
547
+ name: 'allow-frontend-to-backend',
548
+ targetPodSelector: { app: 'backend' },
549
+ sourcePodSelector: { app: 'frontend' },
550
+ ports: [{ protocol: 'TCP', port: 8080 }],
551
+ });
552
+ ```
553
+
554
+ ### Advanced NetworkPolicy Examples
555
+
556
+ #### Multi-tier Application Security
557
+
558
+ ```typescript
559
+ // Web tier - allow external traffic on port 80/443
560
+ rutter.addNetworkPolicy({
561
+ name: 'web-tier-policy',
562
+ podSelector: { matchLabels: { tier: 'web' } },
563
+ policyTypes: ['Ingress', 'Egress'],
564
+ ingress: [
565
+ {
566
+ ports: [
567
+ { protocol: 'TCP', port: 80 },
568
+ { protocol: 'TCP', port: 443 },
569
+ ],
570
+ },
571
+ ],
572
+ egress: [
573
+ // Allow access to app tier
574
+ {
575
+ to: [{ podSelector: { matchLabels: { tier: 'app' } } }],
576
+ ports: [{ protocol: 'TCP', port: 8080 }],
577
+ },
578
+ // Allow DNS resolution
579
+ {
580
+ to: [{ namespaceSelector: { matchLabels: { name: 'kube-system' } } }],
581
+ ports: [
582
+ { protocol: 'UDP', port: 53 },
583
+ { protocol: 'TCP', port: 53 },
584
+ ],
585
+ },
586
+ ],
587
+ });
588
+
589
+ // App tier - only allow traffic from web tier
590
+ rutter.addNetworkPolicy({
591
+ name: 'app-tier-policy',
592
+ podSelector: { matchLabels: { tier: 'app' } },
593
+ policyTypes: ['Ingress', 'Egress'],
594
+ ingress: [
595
+ {
596
+ from: [{ podSelector: { matchLabels: { tier: 'web' } } }],
597
+ ports: [{ protocol: 'TCP', port: 8080 }],
598
+ },
599
+ ],
600
+ egress: [
601
+ // Allow access to database
602
+ {
603
+ to: [{ podSelector: { matchLabels: { tier: 'database' } } }],
604
+ ports: [{ protocol: 'TCP', port: 5432 }],
605
+ },
606
+ // Allow external API calls (with CIDR restrictions)
607
+ {
608
+ to: [
609
+ {
610
+ ipBlock: {
611
+ cidr: '0.0.0.0/0',
612
+ except: ['10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'],
613
+ },
614
+ },
615
+ ],
616
+ ports: [{ protocol: 'TCP', port: 443 }],
617
+ },
618
+ ],
619
+ });
620
+
621
+ // Database tier - most restrictive
622
+ rutter.addNetworkPolicy({
623
+ name: 'database-tier-policy',
624
+ podSelector: { matchLabels: { tier: 'database' } },
625
+ policyTypes: ['Ingress', 'Egress'],
626
+ ingress: [
627
+ {
628
+ from: [{ podSelector: { matchLabels: { tier: 'app' } } }],
629
+ ports: [{ protocol: 'TCP', port: 5432 }],
630
+ },
631
+ ],
632
+ egress: [
633
+ // Only allow DNS resolution
634
+ {
635
+ to: [{ namespaceSelector: { matchLabels: { name: 'kube-system' } } }],
636
+ ports: [
637
+ { protocol: 'UDP', port: 53 },
638
+ { protocol: 'TCP', port: 53 },
639
+ ],
640
+ },
641
+ ],
642
+ });
643
+ ```
644
+
645
+ #### Cross-Namespace Communication
646
+
647
+ ```typescript
648
+ // Allow traffic from monitoring namespace
649
+ rutter.addAllowFromNamespaceNetworkPolicy({
650
+ name: 'allow-monitoring',
651
+ targetPodSelector: { app: 'backend' },
652
+ sourceNamespaceSelector: { name: 'monitoring' },
653
+ ports: [{ protocol: 'TCP', port: 9090 }], // Prometheus metrics
654
+ });
655
+
656
+ // Allow traffic to shared services namespace
657
+ rutter.addNetworkPolicy({
658
+ name: 'allow-to-shared-services',
659
+ podSelector: { matchLabels: { app: 'backend' } },
660
+ policyTypes: ['Egress'],
661
+ egress: [
662
+ {
663
+ to: [
664
+ {
665
+ namespaceSelector: { matchLabels: { name: 'shared-services' } },
666
+ podSelector: { matchLabels: { app: 'redis' } },
667
+ },
668
+ ],
669
+ ports: [{ protocol: 'TCP', port: 6379 }],
670
+ },
671
+ ],
672
+ });
673
+ ```
674
+
675
+ ### Security Best Practices
676
+
677
+ #### 1. Start with Deny-All Policies
678
+
679
+ ```typescript
680
+ // Always start with deny-all for maximum security
681
+ rutter.addDenyAllNetworkPolicy('default-deny-all');
682
+
683
+ // Then add specific allow rules
684
+ rutter.addAllowFromPodsNetworkPolicy({
685
+ name: 'allow-specific-communication',
686
+ targetPodSelector: { app: 'api' },
687
+ sourcePodSelector: { app: 'frontend' },
688
+ ports: [{ protocol: 'TCP', port: 8080 }],
689
+ });
690
+ ```
691
+
692
+ #### 2. Separate Ingress and Egress Policies
693
+
694
+ ```typescript
695
+ // Separate policies for better maintainability
696
+ rutter.addDenyAllIngressNetworkPolicy('deny-all-ingress');
697
+ rutter.addDenyAllEgressNetworkPolicy('deny-all-egress');
698
+ ```
699
+
700
+ #### 3. Use CIDR Blocks for External Access
701
+
702
+ ```typescript
703
+ // Restrict external access to specific IP ranges
704
+ rutter.addNetworkPolicy({
705
+ name: 'external-api-access',
706
+ podSelector: { matchLabels: { app: 'backend' } },
707
+ policyTypes: ['Egress'],
708
+ egress: [
709
+ {
710
+ to: [
711
+ {
712
+ ipBlock: {
713
+ cidr: '203.0.113.0/24', // Specific external service
714
+ },
715
+ },
716
+ ],
717
+ ports: [{ protocol: 'TCP', port: 443 }],
718
+ },
719
+ ],
720
+ });
721
+ ```
722
+
723
+ #### 4. Include DNS Resolution
724
+
725
+ ```typescript
726
+ // Always allow DNS for name resolution
727
+ const dnsEgressRule = {
728
+ to: [{ namespaceSelector: { matchLabels: { name: 'kube-system' } } }],
729
+ ports: [
730
+ { protocol: 'UDP', port: 53 },
731
+ { protocol: 'TCP', port: 53 },
732
+ ],
733
+ };
734
+
735
+ rutter.addNetworkPolicy({
736
+ name: 'app-with-dns',
737
+ podSelector: { matchLabels: { app: 'backend' } },
738
+ policyTypes: ['Egress'],
739
+ egress: [dnsEgressRule /* other rules */],
740
+ });
741
+ ```
742
+
743
+ ### Basic NetworkPolicy
744
+
745
+ ```typescript
746
+ // Custom NetworkPolicy with full control
747
+ rutter.addNetworkPolicy({
748
+ name: 'custom-policy',
749
+ podSelector: { matchLabels: { app: 'backend' } },
750
+ policyTypes: ['Ingress', 'Egress'],
751
+ ingress: [
752
+ {
753
+ from: [{ podSelector: { matchLabels: { app: 'frontend' } } }],
754
+ ports: [{ protocol: 'TCP', port: 8080 }],
755
+ },
756
+ ],
757
+ egress: [
758
+ {
759
+ to: [{ podSelector: { matchLabels: { app: 'database' } } }],
760
+ ports: [{ protocol: 'TCP', port: 5432 }],
761
+ },
762
+ ],
763
+ });
764
+ ```
765
+
432
766
  ## Multi-environment values
433
767
 
434
768
  Provide `envValues` in the `Rutter` constructor to automatically create
@@ -457,7 +791,7 @@ Provide `envValues` in the `Rutter` constructor to automatically create
457
791
  - ✅ Auto-scaling helpers (HPA, VPA, PodDisruptionBudget)
458
792
  - ✅ AWS multi-cloud support (EBS, EFS, ALB, IRSA, Secrets Manager, Parameter Store)
459
793
  - ✅ Custom manifest naming and file organization
460
- - Azure multi-cloud support (AKS-specific helpers)
794
+ - Azure multi-cloud support (Azure Disk StorageClass, AGIC)
461
795
  - GCP multi-cloud support (GKE-specific helpers)
462
796
  - Richer CLI (resource generators, diff)
463
797
  - Template validation and testing utilities
package/dist/cli.js CHANGED
@@ -1,42 +1,7 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- const fs = __importStar(require("fs"));
38
- const path = __importStar(require("path"));
39
- const cp = __importStar(require("child_process"));
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as cp from 'child_process';
40
5
  const HELM_INSTALL_URL = 'https://helm.sh/docs/intro/install/';
41
6
  const HELM_ENV_VAR_MSG = 'Or set HELM_BIN environment variable to helm binary path.';
42
7
  function usageAndExit(msg) {
@@ -139,7 +104,6 @@ async function cmdValidate(projectDir, silent = false) {
139
104
  }
140
105
  finally {
141
106
  // Cleanup temp directory
142
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
143
107
  if (fs.existsSync(tempDir)) {
144
108
  fs.rmSync(tempDir, { recursive: true, force: true });
145
109
  }
@@ -158,10 +122,11 @@ async function cmdSynth(projectDir, out, flags) {
158
122
  process.exit(1);
159
123
  }
160
124
  // Enable TS runtime with specific config
161
- require('ts-node').register({
125
+ const { register } = await import('ts-node');
126
+ register({
162
127
  transpileOnly: true,
163
128
  compilerOptions: {
164
- module: 'CommonJS',
129
+ module: 'ESNext',
165
130
  moduleResolution: 'node',
166
131
  },
167
132
  });
@@ -172,29 +137,34 @@ async function cmdSynth(projectDir, out, flags) {
172
137
  console.error('Module path must be within current working directory');
173
138
  process.exit(1);
174
139
  }
175
- // eslint-disable-next-line security/detect-non-literal-require -- CLI tool needs dynamic module loading
176
- const mod = require(resolvedPath);
177
- const runner = mod.default || mod.run || mod.synth;
178
- if (typeof runner !== 'function') {
179
- console.error('chart.ts must export a default/run/synth function');
180
- process.exit(1);
181
- }
182
- // Apply --set overrides if provided
183
- if (flags?.set &&
184
- Object.keys(flags.set).length > 0 &&
185
- mod.rutter &&
186
- typeof mod.rutter.setValues === 'function') {
187
- mod.rutter.setValues(flags.set);
140
+ try {
141
+ const mod = await import(resolvedPath);
142
+ const runner = mod.default || mod.run || mod.synth;
143
+ if (typeof runner !== 'function') {
144
+ console.error('chart.ts must export a default/run/synth function');
145
+ process.exit(1);
146
+ }
147
+ // Apply --set overrides if provided
148
+ if (flags?.set &&
149
+ Object.keys(flags.set).length > 0 &&
150
+ mod.rutter &&
151
+ typeof mod.rutter.setValues === 'function') {
152
+ mod.rutter.setValues(flags.set);
153
+ }
154
+ const outDir = out || path.join(path.dirname(chartTs), 'dist');
155
+ if (flags?.dryRun) {
156
+ log(`[DRY RUN] Would write chart to ${outDir}`, flags.silent);
157
+ return;
158
+ }
159
+ // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
160
+ fs.mkdirSync(outDir, { recursive: true });
161
+ await Promise.resolve(runner(outDir));
162
+ log(`Chart written to ${outDir}`, flags?.silent);
188
163
  }
189
- const outDir = out || path.join(path.dirname(chartTs), 'dist');
190
- if (flags?.dryRun) {
191
- log(`[DRY RUN] Would write chart to ${outDir}`, flags.silent);
192
- return;
164
+ catch (error) {
165
+ console.error(`Failed to import module: ${error instanceof Error ? error.message : error}`);
166
+ process.exit(1);
193
167
  }
194
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
195
- fs.mkdirSync(outDir, { recursive: true });
196
- await Promise.resolve(runner(outDir));
197
- log(`Chart written to ${outDir}`, flags?.silent);
198
168
  }
199
169
  async function cmdDiff(projectDir, chartDir, silent = false) {
200
170
  if (!projectDir || !chartDir)
@@ -218,7 +188,6 @@ async function cmdDiff(projectDir, chartDir, silent = false) {
218
188
  }
219
189
  finally {
220
190
  // Cleanup temp directory
221
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
222
191
  if (fs.existsSync(tempDir)) {
223
192
  fs.rmSync(tempDir, { recursive: true, force: true });
224
193
  }
@@ -268,7 +237,6 @@ async function cmdDeploy(projectDir, releaseName, flags) {
268
237
  }
269
238
  finally {
270
239
  // Cleanup temp directory
271
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
272
240
  if (fs.existsSync(tempDir)) {
273
241
  fs.rmSync(tempDir, { recursive: true, force: true });
274
242
  }
@@ -324,12 +292,10 @@ async function cmdUmbrellaAdd(subchartName, silent = false) {
324
292
  if (!subchartName)
325
293
  usageAndExit(MISSING_SUBCHART_MSG);
326
294
  const configFile = path.join(process.cwd(), UMBRELLA_CONFIG_FILE);
327
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
328
295
  if (!fs.existsSync(configFile)) {
329
296
  console.error(`${UMBRELLA_CONFIG_FILE} not found. Run \`tl umbrella init\` first.`);
330
297
  process.exit(1);
331
298
  }
332
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
333
299
  const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
334
300
  // Create subchart directory and scaffold
335
301
  const subchartDir = path.join(process.cwd(), 'charts', subchartName);
@@ -344,19 +310,16 @@ async function cmdUmbrellaAdd(subchartName, silent = false) {
344
310
  version: '0.1.0',
345
311
  path: `./charts/${subchartName}/chart.ts`,
346
312
  });
347
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
348
313
  fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
349
314
  log(`Subchart ${subchartName} added to umbrella`, silent);
350
315
  }
351
316
  async function cmdUmbrellaSynth(outDir, flags) {
352
317
  const configFile = path.join(process.cwd(), UMBRELLA_CONFIG_FILE);
353
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
354
318
  if (!fs.existsSync(configFile)) {
355
319
  console.error(`${UMBRELLA_CONFIG_FILE} not found. Run \`tl umbrella init\` first.`);
356
320
  process.exit(1);
357
321
  }
358
322
  const umbrellaFile = path.join(process.cwd(), 'umbrella.ts');
359
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
360
323
  if (!fs.existsSync(umbrellaFile)) {
361
324
  console.error('umbrella.ts not found.');
362
325
  process.exit(1);
@@ -376,7 +339,6 @@ async function cmdUmbrellaSynth(outDir, flags) {
376
339
  console.error('Module path must be within current working directory');
377
340
  process.exit(1);
378
341
  }
379
- // eslint-disable-next-line security/detect-non-literal-require -- CLI tool needs dynamic module loading
380
342
  const mod = require(resolvedPath);
381
343
  const runner = mod.default || mod.run || mod.synth;
382
344
  if (typeof runner !== 'function') {
@@ -622,10 +584,8 @@ function parseArgs() {
622
584
  return { cmd, args, flags };
623
585
  }
624
586
  function showVersion() {
625
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI needs to read package.json
626
587
  const packagePath = path.join(__dirname, '..', 'package.json');
627
588
  try {
628
- // eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI needs to read package.json
629
589
  const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
630
590
  console.log(`timonel v${packageJson.version}`);
631
591
  }