timonel 2.12.1 → 2.12.2-beta.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.2-beta.1](https://github.com/KenkoGeek/timonel/compare/v2.12.1...v2.12.2-beta.1) (2025-10-04)
2
+
3
+ ### Bug Fixes
4
+
5
+ - **cli:** handle flags after positional arguments ([#142](https://github.com/KenkoGeek/timonel/issues/142)) ([36ac024](https://github.com/KenkoGeek/timonel/commit/36ac024fa7c2a454a184394337a65ebae46a65f9))
6
+
1
7
  ## [2.12.1](https://github.com/KenkoGeek/timonel/compare/v2.12.0...v2.12.1) (2025-09-28)
2
8
 
3
9
  ### Bug Fixes
package/dist/cli.js CHANGED
@@ -3,10 +3,66 @@ import { spawnSync } from 'child_process';
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
+ import { createRequire } from 'module';
6
7
  import { SecurityUtils } from './lib/security.js';
7
8
  import { createLogger } from './lib/utils/logger.js';
8
9
  const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = path.dirname(__filename);
11
+ const esmRequire = createRequire(import.meta.url);
12
+ function getLocalTsxCliPath() {
13
+ try {
14
+ return esmRequire.resolve('tsx/cli');
15
+ }
16
+ catch (error) {
17
+ const sanitizedMessage = SecurityUtils.sanitizeLogMessage('Unable to locate the local tsx CLI module required to execute commands.');
18
+ console.error(sanitizedMessage);
19
+ if (error instanceof Error && error.message) {
20
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
21
+ }
22
+ process.exit(1);
23
+ }
24
+ }
25
+ function escapeForSingleQuotedLiteral(value) {
26
+ return value
27
+ .replace(/\\/g, '\\\\')
28
+ .replace(/'/g, "\\'")
29
+ .replace(/\r/g, '\\r')
30
+ .replace(/\n/g, '\\n')
31
+ .replace(/\t/g, '\\t');
32
+ }
33
+ function resolveOutputDirectory(requestedOutDir, baseDir, silent) {
34
+ let validatedOutDir;
35
+ try {
36
+ validatedOutDir = SecurityUtils.validatePath(requestedOutDir, baseDir, {
37
+ allowAbsolute: true,
38
+ });
39
+ }
40
+ catch (error) {
41
+ if (!silent) {
42
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
43
+ }
44
+ process.exit(1);
45
+ }
46
+ if (fs.existsSync(validatedOutDir)) {
47
+ try {
48
+ const stats = fs.statSync(validatedOutDir);
49
+ if (!stats.isDirectory()) {
50
+ if (!silent) {
51
+ console.error(`Invalid output path: ${SecurityUtils.sanitizeLogMessage(validatedOutDir)} must be a directory`);
52
+ }
53
+ process.exit(1);
54
+ }
55
+ }
56
+ catch (error) {
57
+ if (!silent) {
58
+ console.error(SecurityUtils.sanitizeLogMessage(error.message));
59
+ }
60
+ process.exit(1);
61
+ }
62
+ }
63
+ return validatedOutDir;
64
+ }
65
+ const TSX_CLI_PATH = getLocalTsxCliPath();
10
66
  const UMBRELLA_CONFIG_FILE = 'umbrella.config.json';
11
67
  const UMBRELLA_FILE_NAME = 'umbrella.ts';
12
68
  const PACKAGE_JSON_FILE = 'package.json';
@@ -95,7 +151,7 @@ async function cmdInit(name, silent = false) {
95
151
  log(`Generated chart.ts file`, silent);
96
152
  log(`Run 'tl synth ${validName}' to generate complete Helm chart`, silent);
97
153
  }
98
- async function cmdSynth(chartDirOrOutDir, flags) {
154
+ async function cmdSynth(chartDirOrOutDir, flags, explicitOutDir) {
99
155
  let chartDir = process.cwd();
100
156
  let outDir;
101
157
  if (chartDirOrOutDir && fs.existsSync(path.join(chartDirOrOutDir, 'chart.ts'))) {
@@ -104,27 +160,35 @@ async function cmdSynth(chartDirOrOutDir, flags) {
104
160
  else {
105
161
  outDir = chartDirOrOutDir;
106
162
  }
163
+ if (explicitOutDir) {
164
+ outDir = explicitOutDir;
165
+ }
107
166
  const chartFile = path.join(chartDir, 'chart.ts');
108
167
  const defaultOutDir = path.join(chartDir, 'dist');
109
168
  if (!fs.existsSync(chartFile)) {
110
169
  console.error('chart.ts not found. Run `tl init` first.');
111
170
  process.exit(1);
112
171
  }
113
- const resolvedOutDir = outDir ? path.resolve(outDir) : defaultOutDir;
172
+ const requestedOutDir = outDir ?? defaultOutDir;
173
+ const validatedOutDir = resolveOutputDirectory(requestedOutDir, chartDir, flags?.silent);
114
174
  const originalContent = fs.readFileSync(chartFile, 'utf8');
115
- const modifiedContent = originalContent.replace(/chart\.writeHelmChart\(['"][^'"]*['"]\)/, `chart.writeHelmChart('${resolvedOutDir}')`);
175
+ const safeOutDirLiteral = escapeForSingleQuotedLiteral(validatedOutDir);
176
+ let modifiedContent = originalContent.replace(/chart\.writeHelmChart\(['"][^'"]*['"]\)/, `chart.writeHelmChart('${safeOutDirLiteral}')`);
177
+ if (modifiedContent === originalContent) {
178
+ modifiedContent = originalContent.replace(/chart\.write\(['"][^'"]*['"]\)/, `chart.write('${safeOutDirLiteral}')`);
179
+ }
116
180
  const tempChartFile = path.join(chartDir, '.timonel-temp-chart.ts');
117
181
  fs.writeFileSync(tempChartFile, modifiedContent);
118
182
  const wrapperScript = `
119
183
  import { pathToFileURL } from 'url';
120
184
 
121
185
  // Import the modified chart file which should execute the synthesis directly
122
- await import(pathToFileURL('${tempChartFile}').href);
186
+ await import(pathToFileURL(${JSON.stringify(tempChartFile)}).href);
123
187
  `;
124
188
  const wrapperFile = path.join(chartDir, '.timonel-wrapper.mjs');
125
189
  try {
126
190
  fs.writeFileSync(wrapperFile, wrapperScript);
127
- const result = spawnSync('npx', ['tsx', wrapperFile].filter(Boolean), {
191
+ const result = spawnSync(process.execPath, [TSX_CLI_PATH, wrapperFile], {
128
192
  stdio: flags?.silent ? 'pipe' : 'inherit',
129
193
  encoding: 'utf8',
130
194
  cwd: chartDir,
@@ -421,26 +485,27 @@ async function cmdUmbrellaSynth(outDir, flags) {
421
485
  console.error('umbrella.ts not found. Run `tl umbrella init` first.');
422
486
  process.exit(1);
423
487
  }
424
- const resolvedOutDir = outDir ? path.resolve(outDir) : defaultOutDir;
425
488
  const synthMode = flags?.mode ?? 'dependencies';
426
489
  if (!UMBRELLA_SYNTH_MODES.includes(synthMode)) {
427
490
  usageAndExit('Invalid mode. Use "dependencies" or "inline".', flags?.silent);
428
491
  }
429
- await executeTypeScriptUmbrella(umbrellaFile, resolvedOutDir, synthMode, flags);
492
+ await executeTypeScriptUmbrella(umbrellaFile, outDir ?? defaultOutDir, synthMode, flags);
430
493
  }
431
494
  async function executeTypeScriptUmbrella(resolvedPath, outDir, mode, flags) {
495
+ const umbrellaBase = path.dirname(resolvedPath);
496
+ const validatedOutDir = resolveOutputDirectory(outDir, umbrellaBase, flags?.silent);
432
497
  const wrapperScript = `
433
498
  import { pathToFileURL } from 'url';
434
499
 
435
- const mod = await import(pathToFileURL('${resolvedPath}').href);
500
+ const mod = await import(pathToFileURL(${JSON.stringify(resolvedPath)}).href);
436
501
  const runner = mod.default || mod.run || mod.synth;
437
502
  if (typeof runner !== 'function') {
438
503
  console.error('umbrella.ts must export a default/run/synth function');
439
504
  process.exit(1);
440
505
  }
441
506
 
442
- const output = '${outDir}';
443
- const synthOptions = { mode: '${mode}' };
507
+ const output = ${JSON.stringify(validatedOutDir)};
508
+ const synthOptions = { mode: ${JSON.stringify(mode)} };
444
509
  const fs = await import('fs');
445
510
  fs.mkdirSync(output, { recursive: true });
446
511
  await Promise.resolve(runner(output, synthOptions));
@@ -449,7 +514,7 @@ console.log('Umbrella chart written to ' + output);
449
514
  const wrapperFile = path.join(process.cwd(), '.timonel-umbrella-wrapper.mjs');
450
515
  try {
451
516
  fs.writeFileSync(wrapperFile, wrapperScript);
452
- const result = spawnSync('npx', ['tsx', wrapperFile].filter(Boolean), {
517
+ const result = spawnSync(process.execPath, [TSX_CLI_PATH, wrapperFile], {
453
518
  stdio: flags?.silent ? 'pipe' : 'inherit',
454
519
  encoding: 'utf8',
455
520
  });
@@ -468,45 +533,81 @@ console.log('Umbrella chart written to ' + output);
468
533
  }
469
534
  function parseFlags(args) {
470
535
  const flags = {};
471
- while (args.length > 0 && args[0]?.startsWith('-')) {
472
- const flag = args.shift();
473
- switch (flag) {
474
- case '--dry-run':
536
+ const positionalArguments = [];
537
+ const takeValue = (flagName) => {
538
+ const value = args.shift();
539
+ if (value === undefined) {
540
+ usageAndExit(`Missing value for ${flagName}`, flags.silent);
541
+ return '';
542
+ }
543
+ return value;
544
+ };
545
+ const flagHandlers = new Map([
546
+ [
547
+ '--dry-run',
548
+ () => {
475
549
  flags.dryRun = true;
476
- break;
477
- case '--silent':
550
+ },
551
+ ],
552
+ [
553
+ '--silent',
554
+ () => {
478
555
  flags.silent = true;
479
- break;
480
- case '--env': {
481
- const envValue = args.shift();
482
- if (envValue)
556
+ },
557
+ ],
558
+ [
559
+ '--env',
560
+ () => {
561
+ const envValue = takeValue('--env');
562
+ if (envValue) {
483
563
  flags.env = envValue;
484
- break;
485
- }
486
- case '--set': {
487
- const setValue = args.shift();
564
+ }
565
+ },
566
+ ],
567
+ [
568
+ '--set',
569
+ () => {
570
+ const setValue = takeValue('--set');
488
571
  if (setValue) {
489
572
  flags.set = flags.set || [];
490
573
  flags.set.push(setValue);
491
574
  }
492
- break;
493
- }
494
- case '--mode': {
495
- const modeValue = args.shift();
496
- if (!modeValue || !UMBRELLA_SYNTH_MODES.includes(modeValue)) {
575
+ },
576
+ ],
577
+ [
578
+ '--mode',
579
+ () => {
580
+ const modeValue = takeValue('--mode');
581
+ if (!UMBRELLA_SYNTH_MODES.includes(modeValue)) {
497
582
  usageAndExit('Invalid mode. Use "dependencies" or "inline".', flags.silent);
498
583
  }
499
584
  flags.mode = modeValue;
500
- break;
501
- }
502
- case '--help':
503
- case '-h':
504
- usageAndExit(undefined, flags.silent);
505
- break;
506
- default:
507
- usageAndExit(`Unknown flag: ${flag}`, flags.silent);
585
+ },
586
+ ],
587
+ ]);
588
+ const helpFlags = new Set(['--help', '-h']);
589
+ while (args.length > 0) {
590
+ const token = args.shift();
591
+ if (token === undefined) {
592
+ break;
593
+ }
594
+ if (!token.startsWith('-')) {
595
+ positionalArguments.push(token);
596
+ continue;
597
+ }
598
+ if (helpFlags.has(token)) {
599
+ usageAndExit(undefined, flags.silent);
600
+ return flags;
601
+ }
602
+ const handler = flagHandlers.get(token);
603
+ if (handler) {
604
+ handler();
605
+ continue;
508
606
  }
607
+ usageAndExit(`Unknown flag: ${token}`, flags.silent);
509
608
  }
609
+ args.length = 0;
610
+ args.push(...positionalArguments);
510
611
  return flags;
511
612
  }
512
613
  async function executeCommand(command, args, flags) {
@@ -515,7 +616,7 @@ async function executeCommand(command, args, flags) {
515
616
  await cmdInit(args[0], flags.silent);
516
617
  break;
517
618
  case 'synth':
518
- await cmdSynth(args[0], flags);
619
+ await cmdSynth(args[0], flags, args[1]);
519
620
  break;
520
621
  case 'validate':
521
622
  await cmdValidate(flags);
@@ -1,3 +1,5 @@
1
+ import type { HelperDefinition as ExternalHelperDefinition } from './utils/helmHelpers/types.js';
2
+ export type HelperDefinition = ExternalHelperDefinition;
1
3
  export interface HelmChartMeta {
2
4
  name: string;
3
5
  version: string;
@@ -43,10 +45,6 @@ export interface HelmChartWriteOptions {
43
45
  notesTpl?: string;
44
46
  valuesSchema?: Record<string, unknown>;
45
47
  }
46
- export interface HelperDefinition {
47
- name: string;
48
- body: string;
49
- }
50
48
  export declare class HelmChartWriter {
51
49
  static write(opts: HelmChartWriteOptions): void;
52
50
  private static createDirectories;
@@ -15,7 +15,9 @@ export class HelmChartWriter {
15
15
  assetCount: assets.length,
16
16
  operation: 'helm_write_start',
17
17
  });
18
- const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
18
+ const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd(), {
19
+ allowAbsolute: true,
20
+ });
19
21
  this.createDirectories(validatedOutDir);
20
22
  this.writeChartYaml(validatedOutDir, meta);
21
23
  this.writeValuesFiles(validatedOutDir, defaultValues, envValues);
@@ -72,7 +74,7 @@ export class HelmChartWriter {
72
74
  }
73
75
  else if (Array.isArray(helpersTpl)) {
74
76
  content = helpersTpl
75
- .map((h) => [`{{- define "${h.name}" -}}`, h.body.trimEnd(), '{{- end }}', ''].join('\n'))
77
+ .map((h) => [`{{- define "${h.name}" -}}`, h.template.trimEnd(), '{{- end }}', ''].join('\n'))
76
78
  .join('\n');
77
79
  }
78
80
  fs.writeFileSync(path.join(outDir, 'templates', '_helpers.tpl'), content);
@@ -124,18 +126,14 @@ function splitDocs(yamlStr) {
124
126
  }
125
127
  function writeAssets(outDir, assets) {
126
128
  for (const asset of assets) {
127
- const sanitizedId = asset.id.replace(/[^a-zA-Z0-9-_]/g, '');
128
- if (sanitizedId !== asset.id) {
129
- console.error(`Invalid asset ID detected: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
130
- throw new Error(`Invalid asset ID: ${SecurityUtils.sanitizeLogMessage(asset.id)}`);
131
- }
132
129
  const targetDir = getTargetDirectory(asset.target);
133
130
  try {
131
+ const { directorySegments, fileBaseName } = resolveAssetPath(asset.id);
134
132
  if (asset.singleFile) {
135
- writeSingleAssetFile(outDir, targetDir, sanitizedId, asset.yaml);
133
+ writeSingleAssetFile(outDir, targetDir, directorySegments, fileBaseName, asset.yaml);
136
134
  }
137
135
  else {
138
- writeMultipleAssetFiles(outDir, targetDir, sanitizedId, asset.yaml);
136
+ writeMultipleAssetFiles(outDir, targetDir, directorySegments, fileBaseName, asset.yaml);
139
137
  }
140
138
  }
141
139
  catch (error) {
@@ -147,16 +145,72 @@ function writeAssets(outDir, assets) {
147
145
  function getTargetDirectory(target) {
148
146
  return target === 'crds' ? 'crds' : 'templates';
149
147
  }
150
- function writeSingleAssetFile(outDir, targetDir, assetId, yaml) {
151
- const filename = `${assetId}.yaml`;
152
- fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
153
- fs.writeFileSync(path.join(outDir, targetDir, filename), yaml + '\n');
148
+ function writeSingleAssetFile(outDir, targetDir, directorySegments, fileBaseName, yaml) {
149
+ const chartSubdir = path.join(outDir, targetDir, ...directorySegments);
150
+ SecurityUtils.validatePath(chartSubdir, outDir);
151
+ fs.mkdirSync(chartSubdir, { recursive: true });
152
+ const filename = `${fileBaseName}.yaml`;
153
+ const absolutePath = path.join(chartSubdir, filename);
154
+ SecurityUtils.validatePath(absolutePath, outDir);
155
+ fs.writeFileSync(absolutePath, yaml.endsWith('\n') ? yaml : `${yaml}\n`);
154
156
  }
155
- function writeMultipleAssetFiles(outDir, targetDir, assetId, yaml) {
157
+ function writeMultipleAssetFiles(outDir, targetDir, directorySegments, fileBaseName, yaml) {
158
+ const chartSubdir = path.join(outDir, targetDir, ...directorySegments);
159
+ SecurityUtils.validatePath(chartSubdir, outDir);
160
+ fs.mkdirSync(chartSubdir, { recursive: true });
156
161
  const parts = splitDocs(yaml);
157
162
  parts.forEach((doc, index) => {
158
- const filename = `${assetId}${parts.length > 1 ? `-${index + 1}` : ''}.yaml`;
159
- fs.mkdirSync(path.join(outDir, targetDir), { recursive: true });
160
- fs.writeFileSync(path.join(outDir, targetDir, filename), doc + '\n');
163
+ const suffix = parts.length > 1 ? `-${index + 1}` : '';
164
+ const filename = `${fileBaseName}${suffix}.yaml`;
165
+ const absolutePath = path.join(chartSubdir, filename);
166
+ SecurityUtils.validatePath(absolutePath, outDir);
167
+ fs.writeFileSync(absolutePath, doc.endsWith('\n') ? doc : `${doc}\n`);
168
+ });
169
+ }
170
+ function resolveAssetPath(assetId) {
171
+ if (!assetId || typeof assetId !== 'string') {
172
+ throw new Error('Asset ID must be a non-empty string');
173
+ }
174
+ if (assetId.includes('\0')) {
175
+ throw new Error('Asset ID cannot contain null bytes');
176
+ }
177
+ const normalized = assetId.replace(/\\+/g, '/');
178
+ const rawSegments = normalized.split('/');
179
+ if (rawSegments.length === 0) {
180
+ throw new Error('Asset ID must resolve to at least one segment');
181
+ }
182
+ const segments = rawSegments.map((segment) => {
183
+ if (!segment || segment.trim().length === 0) {
184
+ throw new Error('Asset path segments cannot be empty');
185
+ }
186
+ if (segment === '.' || segment === '..') {
187
+ throw new Error('Asset path segments cannot be relative references');
188
+ }
189
+ if (/[<>:"|?*]/.test(segment)) {
190
+ throw new Error('Asset path contains unsupported characters');
191
+ }
192
+ const hasControlCharacters = Array.from(segment).some((character) => {
193
+ const codePoint = character.codePointAt(0);
194
+ if (typeof codePoint !== 'number') {
195
+ return false;
196
+ }
197
+ if (codePoint < 0x20 || codePoint === 0x7f) {
198
+ return true;
199
+ }
200
+ if (codePoint >= 0x80 && codePoint <= 0x9f) {
201
+ return true;
202
+ }
203
+ return false;
204
+ });
205
+ if (hasControlCharacters) {
206
+ throw new Error('Asset path contains control characters');
207
+ }
208
+ return segment;
161
209
  });
210
+ const fileBaseName = segments[segments.length - 1];
211
+ const directorySegments = segments.slice(0, -1);
212
+ if (!fileBaseName) {
213
+ throw new Error('Asset ID must include a filename segment');
214
+ }
215
+ return { directorySegments, fileBaseName };
162
216
  }
@@ -1,5 +1,8 @@
1
+ export interface PathValidationOptions {
2
+ allowAbsolute?: boolean;
3
+ }
1
4
  export declare class SecurityUtils {
2
- static validatePath(inputPath: string, allowedBasePath: string): string;
5
+ static validatePath(inputPath: string, allowedBasePath: string, options?: PathValidationOptions): string;
3
6
  static sanitizeLogMessage(message: string): string;
4
7
  static sanitizeEnvironmentName(env: string): string;
5
8
  static isValidTypeScriptFile(filePath: string): boolean;
@@ -1,18 +1,68 @@
1
1
  import * as path from 'path';
2
2
  export class SecurityUtils {
3
- static validatePath(inputPath, allowedBasePath) {
3
+ static validatePath(inputPath, allowedBasePath, options) {
4
4
  if (!inputPath || typeof inputPath !== 'string') {
5
5
  throw new Error('Invalid path: path must be a non-empty string');
6
6
  }
7
- if (inputPath.includes('../') ||
8
- inputPath.includes('..\\') ||
9
- inputPath.includes('%2e%2e%2f') ||
10
- inputPath.includes('%2e%2e%5c')) {
11
- throw new Error('Invalid path: path traversal sequences detected');
7
+ if (inputPath.includes('\0')) {
8
+ throw new Error('Invalid path: null bytes are not permitted');
9
+ }
10
+ const allowAbsolute = Boolean(options?.allowAbsolute);
11
+ const candidates = new Set();
12
+ const registerCandidate = (value) => {
13
+ if (typeof value === 'string' && value.length > 0) {
14
+ candidates.add(value);
15
+ }
16
+ };
17
+ const addCandidate = (value) => {
18
+ if (typeof value !== 'string' || value.length === 0) {
19
+ return;
20
+ }
21
+ registerCandidate(value);
22
+ try {
23
+ const normalized = value.normalize('NFKC');
24
+ registerCandidate(normalized);
25
+ }
26
+ catch {
27
+ }
28
+ };
29
+ addCandidate(inputPath);
30
+ let decoded = inputPath;
31
+ for (let iteration = 0; iteration < 5; iteration += 1) {
32
+ try {
33
+ const nextDecoded = decodeURIComponent(decoded);
34
+ if (nextDecoded === decoded) {
35
+ break;
36
+ }
37
+ decoded = nextDecoded;
38
+ addCandidate(decoded);
39
+ }
40
+ catch {
41
+ break;
42
+ }
43
+ }
44
+ const containsTraversalSequence = (candidate) => {
45
+ const lowered = candidate.toLowerCase();
46
+ if (lowered.includes('%2e%2f') || lowered.includes('%2e%5c') || lowered.includes('%2f%2e')) {
47
+ return true;
48
+ }
49
+ const normalized = candidate.replace(/\\+/g, '/');
50
+ const segments = normalized.split('/');
51
+ return segments.some((segment) => {
52
+ const loweredSegment = segment.toLowerCase();
53
+ return loweredSegment === '..' || loweredSegment === '%2e%2e';
54
+ });
55
+ };
56
+ for (const candidate of candidates) {
57
+ if (containsTraversalSequence(candidate)) {
58
+ throw new Error('Invalid path: path traversal sequences detected');
59
+ }
12
60
  }
13
61
  const resolvedInput = path.resolve(inputPath);
14
62
  const resolvedBase = path.resolve(allowedBasePath);
15
- if (!resolvedInput.startsWith(resolvedBase + path.sep) && resolvedInput !== resolvedBase) {
63
+ const relativePath = path.relative(resolvedBase, resolvedInput);
64
+ const isInsideBase = relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
65
+ if (!(allowAbsolute && path.isAbsolute(inputPath)) && !isInsideBase) {
16
66
  throw new Error(`Invalid path: path must be within ${resolvedBase}`);
17
67
  }
18
68
  return resolvedInput;
@@ -41,7 +91,7 @@ export class SecurityUtils {
41
91
  return sanitized;
42
92
  }
43
93
  static isValidTypeScriptFile(filePath) {
44
- const allowedExtensions = ['.ts', '.tsx'];
94
+ const allowedExtensions = ['.ts', '.tsx', '.cts', '.mts'];
45
95
  const ext = path.extname(filePath);
46
96
  return allowedExtensions.includes(ext);
47
97
  }
@@ -16,7 +16,9 @@ export class UmbrellaRutter {
16
16
  }
17
17
  }
18
18
  write(outDir) {
19
- const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd());
19
+ const validatedOutDir = SecurityUtils.validatePath(outDir, process.cwd(), {
20
+ allowAbsolute: true,
21
+ });
20
22
  mkdirSync(validatedOutDir, { recursive: true });
21
23
  mkdirSync(join(validatedOutDir, 'charts'), { recursive: true });
22
24
  for (const subchart of this.props.subcharts) {
@@ -1,7 +1,5 @@
1
- export interface HelperDefinition {
2
- name: string;
3
- template: string;
4
- }
1
+ import type { HelperDefinition } from './helmHelpers/types.js';
2
+ export type { HelperDefinition } from './helmHelpers/types.js';
5
3
  export declare const STANDARD_HELPERS: HelperDefinition[];
6
4
  export declare const FILE_ACCESS_HELPERS: HelperDefinition[];
7
5
  export declare const TEMPLATE_FUNCTION_HELPERS: HelperDefinition[];
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "timonel",
3
3
  "type": "module",
4
- "version": "2.12.1",
4
+ "version": "2.12.2-beta.1",
5
5
  "description": "Timonel: programmatic Helm chart generator using cdk8s (TypeScript)",
6
6
  "bin": {
7
7
  "timonel": "dist/cli.js",
@@ -92,7 +92,7 @@
92
92
  "constructs": "^10.4.2",
93
93
  "handlebars": "^4.7.8",
94
94
  "js-yaml": "^4.1.0",
95
- "pino": "^9.11.0",
95
+ "pino": "^10.0.0",
96
96
  "pino-pretty": "^13.1.1",
97
97
  "ts-node": "^10.9.2",
98
98
  "yaml": "^2.8.1"