timonel 2.12.0 → 2.12.1

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,9 @@
1
+ ## [2.12.1](https://github.com/KenkoGeek/timonel/compare/v2.12.0...v2.12.1) (2025-09-28)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **cli:** new feature for cli ([a8c5326](https://github.com/KenkoGeek/timonel/commit/a8c532617616bfedada20d28bcc4fe94ea165321))
6
+
1
7
  # [2.12.0](https://github.com/KenkoGeek/timonel/compare/v2.11.0...v2.12.0) (2025-09-28)
2
8
 
3
9
  ### Bug Fixes
package/README.md CHANGED
@@ -137,6 +137,8 @@ export const umbrella = new UmbrellaChartTemplate(umbrellaConfig);
137
137
  patterns and practices
138
138
  - **[Contributing](https://github.com/KenkoGeek/timonel/wiki/Contributing)** - Development setup
139
139
  and guidelines
140
+ - **[Timonel Examples Repository](https://github.com/KenkoGeek/timonel-examples)** - Curated
141
+ collection of ready-to-run Timonel sample projects
140
142
 
141
143
  ## 🔧 Troubleshooting
142
144
 
package/dist/cli.js CHANGED
@@ -64,6 +64,7 @@ function usageAndExit(msg, silent = false) {
64
64
  ' --silent Suppress output (useful for CI)',
65
65
  ' --env <environment> Use environment-specific values',
66
66
  ' --set <key=value> Override values (can be used multiple times)',
67
+ ' --mode <dependencies|inline> Umbrella synth mode (default: dependencies)',
67
68
  ' --help, -h Show this help message',
68
69
  '',
69
70
  'Examples:',
@@ -234,18 +235,22 @@ async function cmdTemplates(flags) {
234
235
  });
235
236
  }
236
237
  }
238
+ const UMBRELLA_SYNTH_MODES = ['dependencies', 'inline'];
237
239
  async function cmdUmbrella(subcommand, args, flags) {
238
240
  if (!subcommand)
239
241
  usageAndExit('Missing umbrella subcommand');
242
+ const workingArgs = [...(args ?? [])];
243
+ const subcommandFlags = parseFlags(workingArgs);
244
+ const mergedFlags = mergeCliFlags(flags, subcommandFlags);
240
245
  switch (subcommand) {
241
246
  case 'init':
242
- await cmdUmbrellaInit(args?.[0], flags?.silent);
247
+ await cmdUmbrellaInit(workingArgs[0], mergedFlags.silent);
243
248
  break;
244
249
  case 'add':
245
- await cmdUmbrellaAdd(args?.[0], flags?.silent);
250
+ await cmdUmbrellaAdd(workingArgs[0], mergedFlags.silent);
246
251
  break;
247
252
  case 'synth':
248
- await cmdUmbrellaSynth(args?.[0], flags);
253
+ await cmdUmbrellaSynth(workingArgs[0], mergedFlags);
249
254
  break;
250
255
  default:
251
256
  usageAndExit(`Unknown umbrella subcommand: ${subcommand}`);
@@ -304,28 +309,54 @@ function addImportStatement(content, importStatement) {
304
309
  return lines.join('\n');
305
310
  }
306
311
  function buildSubchartsContent(subchartsContent, subchartEntry) {
307
- const hasExistingEntries = /\{\s*name:\s*['"]/.test(subchartsContent || '');
308
- if (!hasExistingEntries) {
309
- return `\n // Add your subcharts here:\n ${subchartEntry},\n `;
310
- }
311
- const trimmedContent = subchartsContent?.trimEnd() ?? '';
312
- const needsComma = !trimmedContent.endsWith(',');
313
- const comma = needsComma ? ',' : '';
314
- return trimmedContent + `${comma}\n ${subchartEntry}`;
312
+ const existingEntries = (subchartsContent || '')
313
+ .split('\n')
314
+ .map((line) => line.trim())
315
+ .filter((line) => line && !line.startsWith('//'))
316
+ .map((line) => line.replace(/,$/, ''));
317
+ if (existingEntries.includes(subchartEntry)) {
318
+ return subchartsContent ?? '';
319
+ }
320
+ const entries = [...existingEntries, subchartEntry];
321
+ const lines = [' // Add your subcharts here:'];
322
+ for (const entry of entries) {
323
+ lines.push(` ${entry},`);
324
+ }
325
+ return `\n${lines.join('\n')}\n`;
315
326
  }
316
327
  function addSubchartToArray(content, chartName, camelCaseName) {
317
- const subchartsRegex = /subcharts:\s*\[([\s\S]*?)\]/;
328
+ const subchartsRegex = /(const SUBCHARTS[\s\S]*?=\s*\[)([\s\S]*?)(\];)/;
318
329
  const match = content.match(subchartsRegex);
319
330
  if (!match) {
320
331
  return content;
321
332
  }
322
- const subchartsContent = match[1];
323
- if (subchartsContent?.includes(`name: '${chartName}'`)) {
333
+ const [, prefix, body, suffix] = match;
334
+ if (body?.includes(`name: '${chartName}'`)) {
324
335
  return content;
325
336
  }
326
- const subchartEntry = `{ name: '${chartName}', chart: ${camelCaseName} }`;
327
- const newSubchartsContent = buildSubchartsContent(subchartsContent, subchartEntry);
328
- return content.replace(subchartsRegex, `subcharts: [${newSubchartsContent}\n ]`);
337
+ const subchartEntry = `{ name: '${chartName}', factory: ${camelCaseName} }`;
338
+ const newBody = buildSubchartsContent(body, subchartEntry);
339
+ return content.replace(subchartsRegex, `${prefix}${newBody}${suffix}`);
340
+ }
341
+ function mergeCliFlags(base, override) {
342
+ const merged = { ...(base || {}) };
343
+ if (!override) {
344
+ return merged;
345
+ }
346
+ if (override.dryRun !== undefined)
347
+ merged.dryRun = override.dryRun;
348
+ if (override.silent !== undefined)
349
+ merged.silent = override.silent;
350
+ if (override.env !== undefined)
351
+ merged.env = override.env;
352
+ if (override.mode !== undefined)
353
+ merged.mode = override.mode;
354
+ const baseSet = base?.set ?? [];
355
+ const overrideSet = override.set ?? [];
356
+ if (baseSet.length || overrideSet.length) {
357
+ merged.set = [...baseSet, ...overrideSet];
358
+ }
359
+ return merged;
329
360
  }
330
361
  function updateUmbrellaTs(subchartPath, chartName) {
331
362
  const umbrellaFile = path.join(process.cwd(), UMBRELLA_FILE_NAME);
@@ -391,9 +422,13 @@ async function cmdUmbrellaSynth(outDir, flags) {
391
422
  process.exit(1);
392
423
  }
393
424
  const resolvedOutDir = outDir ? path.resolve(outDir) : defaultOutDir;
394
- await executeTypeScriptUmbrella(umbrellaFile, resolvedOutDir, flags);
425
+ const synthMode = flags?.mode ?? 'dependencies';
426
+ if (!UMBRELLA_SYNTH_MODES.includes(synthMode)) {
427
+ usageAndExit('Invalid mode. Use "dependencies" or "inline".', flags?.silent);
428
+ }
429
+ await executeTypeScriptUmbrella(umbrellaFile, resolvedOutDir, synthMode, flags);
395
430
  }
396
- async function executeTypeScriptUmbrella(resolvedPath, outDir, flags) {
431
+ async function executeTypeScriptUmbrella(resolvedPath, outDir, mode, flags) {
397
432
  const wrapperScript = `
398
433
  import { pathToFileURL } from 'url';
399
434
 
@@ -405,9 +440,10 @@ if (typeof runner !== 'function') {
405
440
  }
406
441
 
407
442
  const output = '${outDir}';
443
+ const synthOptions = { mode: '${mode}' };
408
444
  const fs = await import('fs');
409
445
  fs.mkdirSync(output, { recursive: true });
410
- await Promise.resolve(runner(output));
446
+ await Promise.resolve(runner(output, synthOptions));
411
447
  console.log('Umbrella chart written to ' + output);
412
448
  `;
413
449
  const wrapperFile = path.join(process.cwd(), '.timonel-umbrella-wrapper.mjs');
@@ -455,6 +491,14 @@ function parseFlags(args) {
455
491
  }
456
492
  break;
457
493
  }
494
+ case '--mode': {
495
+ const modeValue = args.shift();
496
+ if (!modeValue || !UMBRELLA_SYNTH_MODES.includes(modeValue)) {
497
+ usageAndExit('Invalid mode. Use "dependencies" or "inline".', flags.silent);
498
+ }
499
+ flags.mode = modeValue;
500
+ break;
501
+ }
458
502
  case '--help':
459
503
  case '-h':
460
504
  usageAndExit(undefined, flags.silent);
@@ -7,36 +7,152 @@ import { generateHelpersTemplate } from '../utils/helmHelpers.js';
7
7
  import { createFlexibleSubchart } from './flexible-subchart.js';
8
8
  export function generateUmbrellaChart(name) {
9
9
  return `import { App } from 'cdk8s';
10
- import { UmbrellaChart } from 'timonel';
10
+ import { Rutter } from 'timonel';
11
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'fs';
12
+ import { join } from 'path';
13
+ import * as jsYaml from 'js-yaml';
11
14
  // Import subcharts - add your subchart imports here
12
15
 
13
- /**
14
- * Synthesizes the umbrella chart with all subcharts
15
- * @param outDir Output directory for generated charts
16
- * @since 2.11.0
17
- */
18
- export function synth(outDir: string) {
16
+ type SynthMode = 'dependencies' | 'inline';
17
+
18
+ interface SynthOptions {
19
+ mode?: SynthMode;
20
+ }
21
+
22
+ const DEFAULT_MODE: SynthMode = 'dependencies';
23
+
24
+ const SUBCHARTS: Array<{ name: string; factory: () => Rutter }> = [
25
+ // Add your subcharts here:
26
+ // { name: 'my-subchart', factory: mySubchart },
27
+ ];
28
+
29
+ function resolveMode(options?: SynthOptions): SynthMode {
30
+ const explicit = options?.mode;
31
+ if (explicit === 'dependencies' || explicit === 'inline') {
32
+ return explicit;
33
+ }
34
+ const envMode = process.env.TIMONEL_UMBRELLA_MODE;
35
+ if (envMode === 'dependencies' || envMode === 'inline') {
36
+ return envMode;
37
+ }
38
+ return DEFAULT_MODE;
39
+ }
40
+
41
+ function readYamlFile(filePath: string): Record<string, unknown> {
42
+ if (!existsSync(filePath)) {
43
+ return {};
44
+ }
45
+ const content = jsYaml.load(readFileSync(filePath, 'utf8'));
46
+ return content && typeof content === 'object' ? (content as Record<string, unknown>) : {};
47
+ }
48
+
49
+ export function synth(outDir: string, options?: SynthOptions) {
50
+ const mode = resolveMode(options);
19
51
  const app = new App({
20
52
  outdir: outDir,
21
53
  outputFileExtension: '.yaml',
22
- yamlOutputType: 'FILE_PER_RESOURCE'
54
+ yamlOutputType: 'FILE_PER_RESOURCE',
23
55
  });
24
56
 
25
- const chart = new UmbrellaChart(app, '${name}', {
26
- name: '${name}',
27
- version: '0.1.0',
28
- description: '${name} umbrella chart',
29
- services: [],
30
- subcharts: [
31
- // Add your subcharts here:
32
- ]
57
+ const umbrella = new Rutter({
58
+ meta: {
59
+ name: '${name}',
60
+ version: '0.1.0',
61
+ description: '${name} umbrella chart',
62
+ appVersion: '1.0.0',
63
+ type: 'application',
64
+ },
65
+ scope: app,
66
+ defaultValues: {
67
+ namespace: 'default',
68
+ createNamespace: false,
69
+ },
33
70
  });
34
71
 
35
- // Generate Helm chart files
36
- chart.writeHelmChart(outDir);
37
-
38
- // Also generate CDK8s YAML files
72
+ umbrella.addConditionalManifest(
73
+ {
74
+ apiVersion: 'v1',
75
+ kind: 'Namespace',
76
+ metadata: {
77
+ name: '{{ .Values.namespace | default .Release.Namespace }}',
78
+ },
79
+ },
80
+ 'createNamespace',
81
+ 'namespace',
82
+ );
83
+
84
+ umbrella.write(outDir);
85
+
86
+ const chartPath = join(outDir, 'Chart.yaml');
87
+ const valuesPath = join(outDir, 'values.yaml');
88
+ const templatesDir = join(outDir, 'templates');
89
+ mkdirSync(templatesDir, { recursive: true });
90
+
91
+ const chartDoc = readYamlFile(chartPath);
92
+ const valuesDoc = readYamlFile(valuesPath);
93
+
94
+ if (mode === 'dependencies') {
95
+ const chartsDir = join(outDir, 'charts');
96
+ mkdirSync(chartsDir, { recursive: true });
97
+
98
+ const dependencies: Array<{ name: string; version: string; repository: string }> = [];
99
+
100
+ SUBCHARTS.forEach((subchart) => {
101
+ const instance = subchart.factory();
102
+ const targetDir = join(chartsDir, subchart.name);
103
+ rmSync(targetDir, { recursive: true, force: true });
104
+ instance.write(targetDir);
105
+ const meta = instance.getMeta();
106
+ const version = meta.version ?? '0.1.0';
107
+ dependencies.push({
108
+ name: subchart.name,
109
+ version,
110
+ repository: 'file://./charts/' + subchart.name,
111
+ });
112
+ const subchartValues = readYamlFile(join(targetDir, 'values.yaml'));
113
+ if (Object.keys(subchartValues).length > 0) {
114
+ valuesDoc[subchart.name] = subchartValues;
115
+ }
116
+ });
117
+
118
+ chartDoc.dependencies = dependencies;
119
+ } else {
120
+ const chartsDir = join(outDir, 'charts');
121
+ if (existsSync(chartsDir)) {
122
+ rmSync(chartsDir, { recursive: true, force: true });
123
+ }
124
+ delete chartDoc.dependencies;
125
+
126
+ SUBCHARTS.forEach((subchart) => {
127
+ const instance = subchart.factory();
128
+ const tempDir = join(outDir, '.timonel-inline-' + subchart.name);
129
+ rmSync(tempDir, { recursive: true, force: true });
130
+ instance.write(tempDir);
131
+
132
+ const subTemplatesDir = join(tempDir, 'templates');
133
+ if (existsSync(subTemplatesDir)) {
134
+ const targetTemplatesDir = join(templatesDir, subchart.name);
135
+ mkdirSync(targetTemplatesDir, { recursive: true });
136
+ for (const file of readdirSync(subTemplatesDir)) {
137
+ copyFileSync(join(subTemplatesDir, file), join(targetTemplatesDir, file));
138
+ }
139
+ }
140
+
141
+ const subchartValues = readYamlFile(join(tempDir, 'values.yaml'));
142
+ if (Object.keys(subchartValues).length > 0) {
143
+ valuesDoc[subchart.name] = subchartValues;
144
+ }
145
+
146
+ rmSync(tempDir, { recursive: true, force: true });
147
+ });
148
+ }
149
+
150
+ writeFileSync(chartPath, jsYaml.dump(chartDoc));
151
+ writeFileSync(valuesPath, jsYaml.dump(valuesDoc));
152
+
39
153
  app.synth();
154
+
155
+ console.log('✅ Umbrella chart generated in ' + mode + ' mode!');
40
156
  }
41
157
 
42
158
  // Auto-execute when run directly
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "2.12.0",
4
+ "version": "2.12.1",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",