timonel 2.12.2 → 2.13.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,15 @@
1
+ # [2.13.0](https://github.com/KenkoGeek/timonel/compare/v2.12.2...v2.13.0) (2025-11-24)
2
+
3
+ ### Features
4
+
5
+ - **core:** exporting createHelmExpression() from helmYamlSerializer ([#196](https://github.com/KenkoGeek/timonel/issues/196)) ([e9ee2d0](https://github.com/KenkoGeek/timonel/commit/e9ee2d08b6ad305e0652c53e7756240793987861))
6
+
7
+ # [2.13.0-beta.1](https://github.com/KenkoGeek/timonel/compare/v2.12.2...v2.13.0-beta.1) (2025-11-24)
8
+
9
+ ### Features
10
+
11
+ - **core:** exporting createHelmExpression() from helmYamlSerializer ([#196](https://github.com/KenkoGeek/timonel/issues/196)) ([e9ee2d0](https://github.com/KenkoGeek/timonel/commit/e9ee2d08b6ad305e0652c53e7756240793987861))
12
+
1
13
  ## [2.12.2](https://github.com/KenkoGeek/timonel/compare/v2.12.1...v2.12.2) (2025-11-22)
2
14
 
3
15
  ### Bug Fixes
package/SECURITY.md CHANGED
@@ -34,6 +34,8 @@
34
34
 
35
35
  We provide security updates for the following versions:
36
36
 
37
+ <!-- markdownlint-disable MD060 -->
38
+
37
39
  | Version | Supported | Security Updates | End of Life |
38
40
  | ------- | -------------- | ---------------- | ----------- |
39
41
  | 2.4.0+ | ✅ Current | Full support | TBD |
package/dist/index.d.ts CHANGED
@@ -11,3 +11,4 @@ export type { KarpenterDisruption, KarpenterDisruptionBudget, KarpenterEC2NodeCl
11
11
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
12
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
13
  export { createLogger, logger, LogLevel, TimonelLogger, type LogContext, type LoggerConfig, } from './lib/utils/logger.js';
14
+ export { createHelmExpression, isHelmExpression, type HelmExpression, } from './lib/utils/helmYamlSerializer.js';
package/dist/index.js CHANGED
@@ -8,3 +8,4 @@ export { UmbrellaChartTemplate as UmbrellaChart } from './lib/templates/umbrella
8
8
  export { DEFAULT_TERMINATION_GRACE_PERIOD, isValidDisruptionBudget, isValidKubernetesDuration, KarpenterVersionUtils, } from './lib/resources/cloud/aws/karpenterResources.js';
9
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
10
  export { createLogger, logger, LogLevel, TimonelLogger, } from './lib/utils/logger.js';
11
+ export { createHelmExpression, isHelmExpression, } from './lib/utils/helmYamlSerializer.js';
@@ -1,3 +1,4 @@
1
+ import { type TimonelLogger } from './utils/logger.js';
1
2
  import type { HelperDefinition as ExternalHelperDefinition } from './utils/helmHelpers/types.js';
2
3
  export type HelperDefinition = ExternalHelperDefinition;
3
4
  export interface HelmChartMeta {
@@ -44,6 +45,7 @@ export interface HelmChartWriteOptions {
44
45
  helpersTpl?: string | HelperDefinition[];
45
46
  notesTpl?: string;
46
47
  valuesSchema?: Record<string, unknown>;
48
+ logger?: TimonelLogger;
47
49
  }
48
50
  export declare class HelmChartWriter {
49
51
  static write(opts: HelmChartWriteOptions): void;
@@ -5,8 +5,8 @@ import { createLogger } from './utils/logger.js';
5
5
  import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
6
6
  export class HelmChartWriter {
7
7
  static write(opts) {
8
- const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, } = opts;
9
- const logger = createLogger('helm-chart-writer');
8
+ const { outDir, meta, defaultValues = {}, envValues = {}, assets, helpersTpl, notesTpl, valuesSchema, logger: customLogger, } = opts;
9
+ const logger = customLogger ?? createLogger('helm-chart-writer');
10
10
  const timer = logger.time('helm_chart_write');
11
11
  logger.info('Starting Helm chart write operation', {
12
12
  chartName: meta.name,
@@ -2,6 +2,7 @@ import { ApiObject } from 'cdk8s';
2
2
  import type { ChartProps } from 'cdk8s';
3
3
  import type { Ingress, ServiceAccount } from 'cdk8s-plus-33';
4
4
  import type { Construct } from 'constructs';
5
+ import { type TimonelLogger } from './utils/logger.js';
5
6
  import type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSECRServiceAccountSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec } from './resources/cloud/aws/awsResources.js';
6
7
  import type { KarpenterEC2NodeClassSpec, KarpenterNodeClaimSpec, KarpenterNodePoolSpec } from './resources/cloud/aws/karpenterResources.js';
7
8
  import type { HelperDefinition } from './utils/helmHelpers.js';
@@ -130,5 +131,6 @@ export interface RutterProps {
130
131
  namespace?: string;
131
132
  scope?: Construct;
132
133
  singleManifestFile?: boolean;
134
+ logger?: TimonelLogger;
133
135
  }
134
136
  export type { AWSALBIngressSpec, AWSEBSStorageClassSpec, AWSEFSStorageClassSpec, AWSIRSAServiceAccountSpec, } from './resources/cloud/aws/awsResources.js';
@@ -5,16 +5,16 @@ import { HelmChartWriter } from './helmChartWriter.js';
5
5
  import { AWSResources } from './resources/cloud/aws/awsResources.js';
6
6
  import { createLogger } from './utils/logger.js';
7
7
  import { KarpenterResources } from './resources/cloud/aws/karpenterResources.js';
8
- import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
8
+ import { dumpHelmAwareYaml, isHelmExpression } from './utils/helmYamlSerializer.js';
9
9
  import { generateHelpersTemplate } from './utils/helmHelpers.js';
10
10
  export class Rutter {
11
11
  constructor(props) {
12
12
  this.assets = [];
13
- this.logger = createLogger('rutter');
14
13
  this.defaultValues = props.defaultValues ?? {};
15
14
  this.envValues = props.envValues ?? {};
16
15
  this.meta = props.meta;
17
16
  this.props = props;
17
+ this.logger = props.logger ?? createLogger('rutter');
18
18
  this.logger.info('Initializing chart', {
19
19
  chartName: props.meta.name,
20
20
  version: props.meta.version,
@@ -169,13 +169,13 @@ ${yamlContent.trim()}
169
169
  });
170
170
  throw new Error('Manifest metadata must have a name');
171
171
  }
172
- if (typeof metadata.name !== 'string') {
173
- this.logger.error('Validation failed: metadata.name must be a string', {
172
+ if (typeof metadata.name !== 'string' && !isHelmExpression(metadata.name)) {
173
+ this.logger.error('Validation failed: metadata.name must be a string or HelmExpression', {
174
174
  operation: 'manifest_validation',
175
175
  issue: 'invalid_metadata_name_type',
176
176
  actual_type: typeof metadata.name,
177
177
  });
178
- throw new Error('Manifest metadata.name must be a string');
178
+ throw new Error('Manifest metadata.name must be a string or HelmExpression');
179
179
  }
180
180
  }
181
181
  getMeta() {
@@ -476,6 +476,7 @@ ${helper.template}
476
476
  envValues: this.envValues,
477
477
  assets: synthAssets,
478
478
  helpersTpl: helpersContent,
479
+ logger: this.logger,
479
480
  });
480
481
  this.logger.info('Chart write operation completed successfully', {
481
482
  chartName: this.meta.name,
@@ -72,9 +72,10 @@ export class SecurityUtils {
72
72
  return String(message);
73
73
  }
74
74
  return (message
75
- .replace(/[\x00-\x1F\x7F]/g, '')
76
- .replace(/\r\n/g, ' ')
75
+ .replace(/\r\n/g, '\n')
76
+ .replace(/[\x00-\x09\x0B-\x0C\x0E-\x1F\x7F]/g, '')
77
77
  .replace(/[\r\n]/g, ' ')
78
+ .replace(/\s+/g, ' ')
78
79
  .trim());
79
80
  }
80
81
  static sanitizeEnvironmentName(env) {
@@ -105,20 +106,23 @@ export class SecurityUtils {
105
106
  if (!/^[a-zA-Z0-9]/.test(templatePath) || !/[a-zA-Z0-9]$/.test(templatePath)) {
106
107
  return false;
107
108
  }
108
- if (!/^[a-zA-Z0-9._-]+$/.test(templatePath)) {
109
+ if (!/^[a-zA-Z0-9._\-/]+$/.test(templatePath)) {
109
110
  return false;
110
111
  }
111
112
  if (templatePath.includes('..') || templatePath.includes('//')) {
112
113
  return false;
113
114
  }
114
115
  const reservedWords = ['nil', 'null', 'undefined', 'true', 'false'];
115
- const pathSegments = templatePath.split('.');
116
- for (const segment of pathSegments) {
117
- if (reservedWords.includes(segment.toLowerCase())) {
118
- return false;
119
- }
120
- if (segment.length === 0) {
121
- return false;
116
+ const pathParts = templatePath.split('/');
117
+ for (const part of pathParts) {
118
+ const segments = part.split('.');
119
+ for (const segment of segments) {
120
+ if (reservedWords.includes(segment.toLowerCase())) {
121
+ return false;
122
+ }
123
+ if (segment.length === 0 && segments.length > 1) {
124
+ continue;
125
+ }
122
126
  }
123
127
  }
124
128
  return true;
@@ -1,4 +1,5 @@
1
1
  import type { Chart } from 'cdk8s';
2
+ import type { TimonelLogger } from './utils/logger.js';
2
3
  export interface ChartProps {
3
4
  name: string;
4
5
  version: string;
@@ -19,6 +20,7 @@ export interface ChartProps {
19
20
  [key: string]: unknown;
20
21
  }>;
21
22
  [key: string]: unknown;
23
+ logger?: TimonelLogger;
22
24
  }
23
25
  export interface SubchartProps {
24
26
  name: string;
@@ -1,5 +1,6 @@
1
1
  import type { HelmChartMeta } from './helmChartWriter.js';
2
2
  import type { Rutter } from './rutter.js';
3
+ import { type TimonelLogger } from './utils/logger.js';
3
4
  export interface SubchartSpec {
4
5
  name: string;
5
6
  rutter: Rutter;
@@ -12,10 +13,12 @@ export interface UmbrellaRutterProps {
12
13
  meta: HelmChartMeta;
13
14
  subcharts: SubchartSpec[];
14
15
  defaultValues?: Record<string, unknown>;
16
+ logger?: TimonelLogger;
15
17
  envValues?: Record<string, Record<string, unknown>>;
16
18
  }
17
19
  export declare class UmbrellaRutter {
18
20
  private readonly props;
21
+ private readonly logger;
19
22
  constructor(props: UmbrellaRutterProps);
20
23
  private validateMetadata;
21
24
  write(outDir: string): void;
@@ -1,9 +1,16 @@
1
1
  import { writeFileSync, mkdirSync } from 'fs';
2
2
  import { join } from 'path';
3
+ import { createLogger } from './utils/logger.js';
3
4
  import { SecurityUtils } from './security.js';
4
5
  import { dumpHelmAwareYaml } from './utils/helmYamlSerializer.js';
5
6
  export class UmbrellaRutter {
6
7
  constructor(props) {
8
+ this.logger = props.logger ?? createLogger('umbrella-rutter');
9
+ this.logger.info('Initializing umbrella chart', {
10
+ name: props.meta.name,
11
+ version: props.meta.version,
12
+ subchartsCount: props.subcharts.length,
13
+ });
7
14
  this.validateMetadata(props.meta);
8
15
  this.props = props;
9
16
  }
@@ -6,7 +6,7 @@ interface HelmExpressionMatch {
6
6
  start: number;
7
7
  end: number;
8
8
  }
9
- interface HelmExpression {
9
+ export interface HelmExpression {
10
10
  __helmExpression: true;
11
11
  value: string;
12
12
  }
@@ -47,6 +47,9 @@ function preprocessHelmExpressions(obj, depth = 0) {
47
47
  if (depth > 100) {
48
48
  throw new Error('Maximum recursion depth exceeded during Helm preprocessing');
49
49
  }
50
+ if (isHelmExpression(obj)) {
51
+ return obj;
52
+ }
50
53
  if (typeof obj === 'string') {
51
54
  if (detectHelmExpressions(obj).length > 0) {
52
55
  return createHelmExpression(obj);
@@ -102,6 +105,7 @@ export function stringify(obj, options = {}) {
102
105
  const jsYamlOptions = {
103
106
  lineWidth: options.lineWidth ?? 0,
104
107
  quotingType: options.doubleQuotedAsJSON ? '"' : "'",
108
+ forceQuotes: options.doubleQuotedAsJSON,
105
109
  };
106
110
  return dumpHelmAwareYaml(obj, jsYamlOptions);
107
111
  }
@@ -109,6 +113,17 @@ export function validateHelmYaml(yaml) {
109
113
  const errors = [];
110
114
  const warnings = [];
111
115
  const expressions = parseHelmExpressions(yaml);
116
+ const openBraces = (yaml.match(/\{\{/g) || []).length;
117
+ const closeBraces = (yaml.match(/\}\}/g) || []).length;
118
+ if (openBraces !== closeBraces) {
119
+ errors.push({
120
+ type: 'syntax',
121
+ message: 'Unbalanced Helm template braces detected in file',
122
+ line: 0,
123
+ column: 0,
124
+ expression: '',
125
+ });
126
+ }
112
127
  for (const expr of expressions) {
113
128
  try {
114
129
  validateHelmSyntax(expr.expression);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "2.12.2",
4
+ "version": "2.13.0",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",
@@ -21,6 +21,10 @@
21
21
  "./lib/utils/logger": {
22
22
  "types": "./dist/lib/utils/logger.d.ts",
23
23
  "default": "./dist/lib/utils/logger.js"
24
+ },
25
+ "./lib/utils/helmYamlSerializer": {
26
+ "types": "./dist/lib/utils/helmYamlSerializer.d.ts",
27
+ "default": "./dist/lib/utils/helmYamlSerializer.js"
24
28
  }
25
29
  },
26
30
  "scripts": {
@@ -63,7 +67,7 @@
63
67
  "author": "Franklin García <KenkoGeek>",
64
68
  "license": "MIT",
65
69
  "engines": {
66
- "node": ">=20",
70
+ "node": ">=22",
67
71
  "pnpm": ">=9.0.0"
68
72
  },
69
73
  "repository": {
@@ -87,44 +91,44 @@
87
91
  ]
88
92
  },
89
93
  "dependencies": {
90
- "cdk8s": "^2.70.16",
91
- "cdk8s-plus-33": "^2.3.8",
92
- "constructs": "^10.4.2",
94
+ "cdk8s": "^2.70.28",
95
+ "cdk8s-plus-33": "^2.4.6",
96
+ "constructs": "^10.4.3",
93
97
  "handlebars": "^4.7.8",
94
- "js-yaml": "^4.1.0",
95
- "pino": "^10.0.0",
96
- "pino-pretty": "^13.1.1",
98
+ "js-yaml": "^4.1.1",
99
+ "pino": "^10.1.0",
100
+ "pino-pretty": "^13.1.2",
97
101
  "ts-node": "^10.9.2",
98
102
  "yaml": "^2.8.1"
99
103
  },
100
104
  "devDependencies": {
101
- "@commitlint/cli": "^20.0.0",
105
+ "@commitlint/cli": "^20.1.0",
102
106
  "@commitlint/config-conventional": "^20.0.0",
103
- "@eslint/js": "^9.36.0",
107
+ "@eslint/js": "^9.39.1",
104
108
  "@semantic-release/changelog": "^6.0.3",
105
109
  "@semantic-release/exec": "^7.1.0",
106
110
  "@semantic-release/git": "^10.0.1",
107
111
  "@types/js-yaml": "^4.0.9",
108
- "@types/node": "^24.5.2",
109
- "@typescript-eslint/eslint-plugin": "^8.44.1",
110
- "@typescript-eslint/parser": "^8.44.1",
112
+ "@types/node": "^24.10.1",
113
+ "@typescript-eslint/eslint-plugin": "^8.47.0",
114
+ "@typescript-eslint/parser": "^8.47.0",
111
115
  "@vitest/coverage-v8": "^3.2.4",
112
116
  "conventional-changelog-cli": "^5.0.0",
113
- "eslint": "^9.36.0",
117
+ "eslint": "^9.39.1",
114
118
  "eslint-config-prettier": "^10.1.8",
115
119
  "eslint-import-resolver-typescript": "^4.4.4",
116
120
  "eslint-plugin-import": "^2.32.0",
117
121
  "eslint-plugin-security": "^3.0.1",
118
122
  "eslint-plugin-sonarjs": "^3.0.5",
119
- "eslint-plugin-unused-imports": "^4.2.0",
123
+ "eslint-plugin-unused-imports": "^4.3.0",
120
124
  "husky": "^9.1.7",
121
- "lint-staged": "^16.2.1",
125
+ "lint-staged": "^16.2.7",
122
126
  "markdownlint": "^0.39.0",
123
- "markdownlint-cli": "^0.45.0",
127
+ "markdownlint-cli": "^0.46.0",
124
128
  "prettier": "^3.6.2",
125
- "semantic-release": "^24.2.9",
129
+ "semantic-release": "^25.0.2",
126
130
  "tsx": "^4.20.6",
127
- "typescript": "^5.9.2",
131
+ "typescript": "^5.9.3",
128
132
  "vitest": "^3.2.4"
129
133
  },
130
134
  "files": [