timonel 2.8.1 → 2.9.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 +12 -0
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +274 -634
- package/dist/index.d.ts +4 -6
- package/dist/index.js +4 -7
- package/dist/lib/helm.d.ts +0 -472
- package/dist/lib/helm.js +0 -481
- package/dist/lib/helmChartWriter.d.ts +0 -178
- package/dist/lib/helmChartWriter.js +27 -180
- package/dist/lib/resources/baseResourceProvider.d.ts +0 -46
- package/dist/lib/resources/baseResourceProvider.js +1 -47
- package/dist/lib/resources/cloud/aws/awsResources.d.ts +0 -144
- package/dist/lib/resources/cloud/aws/awsResources.js +1 -163
- package/dist/lib/resources/cloud/aws/karpenterResources.d.ts +0 -132
- package/dist/lib/resources/cloud/aws/karpenterResources.js +0 -75
- package/dist/lib/rutter.d.ts +28 -340
- package/dist/lib/rutter.js +291 -371
- package/dist/lib/security.d.ts +0 -119
- package/dist/lib/security.js +4 -160
- package/dist/lib/templates/basic-chart.d.ts +19 -0
- package/dist/lib/templates/basic-chart.js +189 -0
- package/dist/lib/templates/subchart.d.ts +27 -0
- package/dist/lib/templates/subchart.js +224 -0
- package/dist/lib/templates/umbrella-chart.d.ts +13 -0
- package/dist/lib/templates/umbrella-chart.js +222 -0
- package/dist/lib/types.d.ts +20 -0
- package/dist/lib/types.js +1 -0
- package/dist/lib/umbrella.d.ts +0 -24
- package/dist/lib/umbrella.js +0 -24
- package/dist/lib/umbrellaRutter.d.ts +0 -71
- package/dist/lib/umbrellaRutter.js +2 -82
- package/dist/lib/utils/helmHelpers.d.ts +18 -41
- package/dist/lib/utils/helmHelpers.js +246 -35
- package/package.json +9 -7
- package/README.md +0 -74
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/lib/helm.d.ts.map +0 -1
- package/dist/lib/helm.js.map +0 -1
- package/dist/lib/helmChartWriter.d.ts.map +0 -1
- package/dist/lib/helmChartWriter.js.map +0 -1
- package/dist/lib/resources/baseResourceProvider.d.ts.map +0 -1
- package/dist/lib/resources/baseResourceProvider.js.map +0 -1
- package/dist/lib/resources/cloud/aws/awsResources.d.ts.map +0 -1
- package/dist/lib/resources/cloud/aws/awsResources.js.map +0 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.d.ts.map +0 -1
- package/dist/lib/resources/cloud/aws/karpenterResources.js.map +0 -1
- package/dist/lib/rutter.d.ts.map +0 -1
- package/dist/lib/rutter.js.map +0 -1
- package/dist/lib/security.d.ts.map +0 -1
- package/dist/lib/security.js.map +0 -1
- package/dist/lib/umbrella.d.ts.map +0 -1
- package/dist/lib/umbrella.js.map +0 -1
- package/dist/lib/umbrellaRutter.d.ts.map +0 -1
- package/dist/lib/umbrellaRutter.js.map +0 -1
- package/dist/lib/utils/helmHelpers.d.ts.map +0 -1
- package/dist/lib/utils/helmHelpers.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,30 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
6
|
import { SecurityUtils } from './lib/security.js';
|
|
7
|
-
// ES modules equivalent of __dirname
|
|
8
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
8
|
const __dirname = path.dirname(__filename);
|
|
10
|
-
const
|
|
11
|
-
const
|
|
9
|
+
const UMBRELLA_CONFIG_FILE = 'umbrella.config.json';
|
|
10
|
+
const UMBRELLA_FILE_NAME = 'umbrella.ts';
|
|
11
|
+
function log(msg, silent = false) {
|
|
12
|
+
if (!silent) {
|
|
13
|
+
console.log(msg);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
12
16
|
function usageAndExit(msg) {
|
|
13
|
-
if (msg)
|
|
14
|
-
console.error(
|
|
17
|
+
if (msg) {
|
|
18
|
+
console.error(`Error: ${msg}`);
|
|
19
|
+
}
|
|
15
20
|
console.log([
|
|
16
|
-
'
|
|
21
|
+
'Usage: tl <command> [options]',
|
|
17
22
|
'',
|
|
18
|
-
'
|
|
19
|
-
' tl init <chart-name>
|
|
20
|
-
' tl synth
|
|
21
|
-
' tl validate
|
|
22
|
-
' tl
|
|
23
|
-
' tl
|
|
24
|
-
' tl package <chartDir> [outDir] Run `helm package` into outDir (requires Helm)',
|
|
23
|
+
'Commands:',
|
|
24
|
+
' tl init <chart-name> Create new chart',
|
|
25
|
+
' tl synth [outDir] Generate Helm chart',
|
|
26
|
+
' tl validate Validate chart',
|
|
27
|
+
' tl deploy <release> [namespace] Deploy chart',
|
|
28
|
+
' tl templates List available templates',
|
|
25
29
|
'',
|
|
26
30
|
'Umbrella Charts:',
|
|
27
|
-
' tl umbrella init <
|
|
31
|
+
' tl umbrella init <n> Create umbrella chart structure',
|
|
28
32
|
' tl umbrella add <subchart> Add subchart to umbrella',
|
|
29
33
|
' tl umbrella synth [outDir] Generate umbrella chart',
|
|
30
34
|
'',
|
|
@@ -38,130 +42,64 @@ function usageAndExit(msg) {
|
|
|
38
42
|
'',
|
|
39
43
|
'Examples:',
|
|
40
44
|
' tl init my-app',
|
|
41
|
-
' tl synth
|
|
42
|
-
' tl validate
|
|
43
|
-
' tl deploy
|
|
44
|
-
' tl synth
|
|
45
|
-
' tl synth
|
|
46
|
-
' tl deploy
|
|
45
|
+
' tl synth my-app my-app/dist',
|
|
46
|
+
' tl validate my-app',
|
|
47
|
+
' tl deploy my-app my-release --env prod',
|
|
48
|
+
' tl synth my-app --dry-run --silent',
|
|
49
|
+
' tl synth my-app --set replicas=5 --set image.tag=v2.0.0',
|
|
50
|
+
' tl deploy my-app my-release --set service.port=8080',
|
|
47
51
|
].join('\n'));
|
|
48
52
|
process.exit(msg ? 1 : 0);
|
|
49
53
|
}
|
|
50
54
|
async function cmdInit(name, silent = false) {
|
|
51
55
|
if (!name)
|
|
52
56
|
usageAndExit('Missing <chart-name>');
|
|
53
|
-
|
|
54
|
-
if (!SecurityUtils.isValidChartName(
|
|
55
|
-
usageAndExit('Invalid chart name
|
|
57
|
+
const validName = name;
|
|
58
|
+
if (!SecurityUtils.isValidChartName(validName)) {
|
|
59
|
+
usageAndExit('Invalid chart name. Must be lowercase, start with a letter, and contain only letters, numbers, and dashes.');
|
|
56
60
|
}
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
const file = path.join(base, 'chart.ts');
|
|
60
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
61
|
+
const base = path.join(process.cwd(), validName);
|
|
62
|
+
const chartFile = path.join(base, 'chart.ts');
|
|
61
63
|
fs.mkdirSync(base, { recursive: true });
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
}
|
|
67
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
68
|
-
fs.writeFileSync(file, exampleChartTs(name));
|
|
69
|
-
log(SecurityUtils.sanitizeLogMessage(`Scaffold created at ${file}`), silent);
|
|
64
|
+
const { generateBasicChart } = await import('./lib/templates/basic-chart.js');
|
|
65
|
+
fs.writeFileSync(chartFile, generateBasicChart(validName));
|
|
66
|
+
log(`Chart created at ${base}`, silent);
|
|
67
|
+
log(`Generated chart.ts file`, silent);
|
|
68
|
+
log(`Run 'tl synth ${validName}' to generate complete Helm chart`, silent);
|
|
70
69
|
}
|
|
71
|
-
async function
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
// Check if helm is available
|
|
86
|
-
const helm = process.env['HELM_BIN'] || 'helm';
|
|
87
|
-
try {
|
|
88
|
-
cp.execSync(`${helm} version --short`, { stdio: 'ignore' });
|
|
89
|
-
}
|
|
90
|
-
catch {
|
|
91
|
-
console.error('✗ Helm not found. Install Helm to enable chart validation.');
|
|
92
|
-
console.error(` Install: ${HELM_INSTALL_URL}`);
|
|
93
|
-
console.error(` ${HELM_ENV_VAR_MSG}`);
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
const tempDir = path.join(process.cwd(), '.timonel-validate');
|
|
97
|
-
try {
|
|
98
|
-
// Generate chart to temp directory for validation
|
|
99
|
-
await cmdSynth(projectDir, tempDir, { dryRun: false, silent: true, set: {} });
|
|
100
|
-
// Run helm lint on generated chart
|
|
101
|
-
const lintResult = cp.spawnSync(helm, ['lint', tempDir], {
|
|
102
|
-
stdio: silent ? 'pipe' : 'inherit',
|
|
103
|
-
encoding: 'utf8',
|
|
104
|
-
});
|
|
105
|
-
if (lintResult.status !== 0) {
|
|
106
|
-
if (silent && lintResult.stderr) {
|
|
107
|
-
console.error(SecurityUtils.sanitizeLogMessage(lintResult.stderr));
|
|
108
|
-
}
|
|
109
|
-
console.error('✗ Chart validation failed: Helm lint errors found');
|
|
110
|
-
process.exit(1);
|
|
111
|
-
}
|
|
112
|
-
log('✓ Chart validation passed', silent);
|
|
113
|
-
}
|
|
114
|
-
catch (error) {
|
|
115
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
116
|
-
console.error('✗ Chart validation failed:', SecurityUtils.sanitizeLogMessage(errorMsg));
|
|
70
|
+
async function cmdSynth(chartDirOrOutDir, flags) {
|
|
71
|
+
let chartDir = process.cwd();
|
|
72
|
+
let outDir;
|
|
73
|
+
if (chartDirOrOutDir && fs.existsSync(path.join(chartDirOrOutDir, 'chart.ts'))) {
|
|
74
|
+
chartDir = path.resolve(chartDirOrOutDir);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
outDir = chartDirOrOutDir;
|
|
78
|
+
}
|
|
79
|
+
const chartFile = path.join(chartDir, 'chart.ts');
|
|
80
|
+
const defaultOutDir = path.join(chartDir, 'dist');
|
|
81
|
+
if (!fs.existsSync(chartFile)) {
|
|
82
|
+
console.error('chart.ts not found. Run `tl init` first.');
|
|
117
83
|
process.exit(1);
|
|
118
84
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
|
-
* Execute TypeScript chart using tsx with proper error handling.
|
|
128
|
-
* @param resolvedPath - Resolved path to the TypeScript file
|
|
129
|
-
* @param outDir - Output directory for the chart
|
|
130
|
-
* @param flags - CLI flags
|
|
131
|
-
* @since 2.7.3
|
|
132
|
-
*/
|
|
133
|
-
async function executeTypeScriptChart(resolvedPath, outDir, flags) {
|
|
85
|
+
const resolvedOutDir = outDir ? path.resolve(outDir) : defaultOutDir;
|
|
86
|
+
const originalContent = fs.readFileSync(chartFile, 'utf8');
|
|
87
|
+
const modifiedContent = originalContent.replace(/chart\.writeHelmChart\(['"][^'"]*['"]\)/, `chart.writeHelmChart('${resolvedOutDir}')`);
|
|
88
|
+
const tempChartFile = path.join(chartDir, '.timonel-temp-chart.ts');
|
|
89
|
+
fs.writeFileSync(tempChartFile, modifiedContent);
|
|
134
90
|
const wrapperScript = `
|
|
135
91
|
import { pathToFileURL } from 'url';
|
|
136
92
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (typeof runner !== 'function') {
|
|
140
|
-
console.error('chart.ts must export a default/run/synth function');
|
|
141
|
-
process.exit(1);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
${flags?.set && Object.keys(flags.set).length > 0
|
|
145
|
-
? `
|
|
146
|
-
// Apply --set overrides if provided
|
|
147
|
-
if (mod.rutter && typeof mod.rutter.setValues === 'function') {
|
|
148
|
-
mod.rutter.setValues(${JSON.stringify(flags.set)});
|
|
149
|
-
}
|
|
150
|
-
`
|
|
151
|
-
: ''}
|
|
152
|
-
|
|
153
|
-
const outDir = '${outDir}';
|
|
154
|
-
const fs = await import('fs');
|
|
155
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
156
|
-
await Promise.resolve(runner(outDir));
|
|
157
|
-
console.log('Chart written to ' + outDir);
|
|
93
|
+
// Import the modified chart file which should execute the synthesis directly
|
|
94
|
+
await import(pathToFileURL('${tempChartFile}').href);
|
|
158
95
|
`;
|
|
159
|
-
const wrapperFile = path.join(
|
|
96
|
+
const wrapperFile = path.join(chartDir, '.timonel-wrapper.mjs');
|
|
160
97
|
try {
|
|
161
98
|
fs.writeFileSync(wrapperFile, wrapperScript);
|
|
162
|
-
const result =
|
|
99
|
+
const result = spawnSync('npx', ['tsx', wrapperFile].filter(Boolean), {
|
|
163
100
|
stdio: flags?.silent ? 'pipe' : 'inherit',
|
|
164
101
|
encoding: 'utf8',
|
|
102
|
+
cwd: chartDir,
|
|
165
103
|
});
|
|
166
104
|
if (result.status !== 0) {
|
|
167
105
|
if (flags?.silent && result.stderr) {
|
|
@@ -171,127 +109,77 @@ console.log('Chart written to ' + outDir);
|
|
|
171
109
|
}
|
|
172
110
|
}
|
|
173
111
|
finally {
|
|
174
|
-
// Cleanup wrapper file
|
|
175
112
|
if (fs.existsSync(wrapperFile)) {
|
|
176
113
|
fs.unlinkSync(wrapperFile);
|
|
177
114
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
async function cmdSynth(projectDir, out, flags) {
|
|
181
|
-
if (!projectDir)
|
|
182
|
-
usageAndExit('Missing <projectDir>');
|
|
183
|
-
const proj = projectDir;
|
|
184
|
-
// Validate and secure the project directory path
|
|
185
|
-
const basePath = path.isAbsolute(proj) ? path.dirname(proj) : process.cwd();
|
|
186
|
-
const chartTs = path.isAbsolute(proj)
|
|
187
|
-
? SecurityUtils.validatePath(path.join(proj, 'chart.ts'), basePath)
|
|
188
|
-
: SecurityUtils.validatePath(path.join(process.cwd(), proj, 'chart.ts'), process.cwd());
|
|
189
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
190
|
-
if (!fs.existsSync(chartTs)) {
|
|
191
|
-
console.error(SecurityUtils.sanitizeLogMessage(`chart.ts not found at ${chartTs}`));
|
|
192
|
-
process.exit(1);
|
|
193
|
-
}
|
|
194
|
-
// Validate TypeScript file extension
|
|
195
|
-
if (!SecurityUtils.isValidTypeScriptFile(chartTs)) {
|
|
196
|
-
console.error('Security: Only TypeScript files (.ts) are allowed');
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
// Additional path validation is already done by SecurityUtils.validatePath above
|
|
200
|
-
const resolvedPath = path.resolve(chartTs);
|
|
201
|
-
const outDir = out || path.join(path.dirname(chartTs), 'dist');
|
|
202
|
-
if (flags?.dryRun) {
|
|
203
|
-
log(`[DRY RUN] Would write chart to ${outDir}`, flags.silent);
|
|
204
|
-
return;
|
|
205
|
-
}
|
|
206
|
-
try {
|
|
207
|
-
await executeTypeScriptChart(resolvedPath, outDir, flags);
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
211
|
-
console.error(SecurityUtils.sanitizeLogMessage(`Failed to execute chart: ${errorMsg}`));
|
|
212
|
-
process.exit(1);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
async function cmdDiff(projectDir, chartDir, silent = false) {
|
|
216
|
-
if (!projectDir || !chartDir)
|
|
217
|
-
usageAndExit('Missing <projectDir> or <chartDir>');
|
|
218
|
-
const tempDir = path.join(process.cwd(), '.timonel-temp');
|
|
219
|
-
try {
|
|
220
|
-
// Generate chart to temp directory
|
|
221
|
-
await cmdSynth(projectDir, tempDir);
|
|
222
|
-
// Compare with existing chart
|
|
223
|
-
const diffCmd = process.platform === 'win32' ? 'fc' : 'diff';
|
|
224
|
-
const args = process.platform === 'win32' ? ['/N', chartDir, tempDir] : ['-r', chartDir, tempDir];
|
|
225
|
-
log(`> ${diffCmd} ${args.join(' ')}`, silent);
|
|
226
|
-
const res = cp.spawnSync(diffCmd, args, { stdio: 'inherit' });
|
|
227
|
-
if (res.status === 0) {
|
|
228
|
-
log('✓ No differences found', silent);
|
|
229
|
-
}
|
|
230
|
-
else if (res.status === 1) {
|
|
231
|
-
log('⚠ Differences found', silent);
|
|
232
|
-
process.exit(1);
|
|
115
|
+
if (fs.existsSync(tempChartFile)) {
|
|
116
|
+
fs.unlinkSync(tempChartFile);
|
|
233
117
|
}
|
|
234
118
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
119
|
+
}
|
|
120
|
+
async function cmdValidate(flags) {
|
|
121
|
+
const result = spawnSync('helm', ['lint', '.'], {
|
|
122
|
+
stdio: flags?.silent ? 'pipe' : 'inherit',
|
|
123
|
+
encoding: 'utf8',
|
|
124
|
+
});
|
|
125
|
+
if (result.status !== 0) {
|
|
126
|
+
if (flags?.silent && result.stderr) {
|
|
127
|
+
console.error(SecurityUtils.sanitizeLogMessage(result.stderr));
|
|
239
128
|
}
|
|
129
|
+
process.exit(result.status ?? 1);
|
|
240
130
|
}
|
|
241
131
|
}
|
|
242
|
-
async function cmdDeploy(
|
|
243
|
-
if (!
|
|
244
|
-
usageAndExit('Missing <
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
const tempDir = path.join(process.cwd(), '.timonel-deploy');
|
|
257
|
-
try {
|
|
258
|
-
// Generate chart
|
|
259
|
-
await cmdSynth(projectDir, tempDir, flags);
|
|
260
|
-
// Check if release exists
|
|
261
|
-
const checkCmd = `${helm} status ${releaseName}`;
|
|
262
|
-
const releaseExists = cp.spawnSync('sh', ['-c', checkCmd], { stdio: 'ignore' }).status === 0;
|
|
263
|
-
// Build helm command
|
|
264
|
-
const action = releaseExists ? 'upgrade' : 'install';
|
|
265
|
-
const args = [action, releaseName, tempDir];
|
|
266
|
-
// Add environment-specific values if specified
|
|
267
|
-
if (flags?.env) {
|
|
268
|
-
const valuesFile = path.join(tempDir, `values-${flags.env}.yaml`);
|
|
269
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
270
|
-
if (fs.existsSync(valuesFile)) {
|
|
271
|
-
args.push('-f', valuesFile);
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
if (flags?.dryRun) {
|
|
275
|
-
args.push('--dry-run');
|
|
276
|
-
}
|
|
277
|
-
log(`> ${helm} ${args.join(' ')}`, flags?.silent);
|
|
278
|
-
const res = cp.spawnSync(helm, args, { stdio: 'inherit' });
|
|
279
|
-
if (res.status !== 0) {
|
|
280
|
-
process.exit(res.status ?? 1);
|
|
132
|
+
async function cmdDeploy(release, namespace, flags) {
|
|
133
|
+
if (!release)
|
|
134
|
+
usageAndExit('Missing <release>');
|
|
135
|
+
const args = ['upgrade', '--install', release, '.'];
|
|
136
|
+
if (namespace) {
|
|
137
|
+
args.push('--namespace', namespace);
|
|
138
|
+
}
|
|
139
|
+
const result = spawnSync('helm', args.filter((arg) => Boolean(arg)), {
|
|
140
|
+
stdio: flags?.silent ? 'pipe' : 'inherit',
|
|
141
|
+
encoding: 'utf8',
|
|
142
|
+
});
|
|
143
|
+
if (result.status !== 0) {
|
|
144
|
+
if (flags?.silent && result.stderr) {
|
|
145
|
+
console.error(SecurityUtils.sanitizeLogMessage(result.stderr));
|
|
281
146
|
}
|
|
282
|
-
|
|
147
|
+
process.exit(result.status ?? 1);
|
|
283
148
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
149
|
+
}
|
|
150
|
+
async function cmdTemplates(flags) {
|
|
151
|
+
const templates = [
|
|
152
|
+
{
|
|
153
|
+
name: 'basic-chart',
|
|
154
|
+
description: 'Basic chart with deployment and service',
|
|
155
|
+
usage: 'tl init my-app',
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'umbrella-chart',
|
|
159
|
+
description: 'Umbrella chart for managing multiple subcharts',
|
|
160
|
+
usage: 'tl umbrella init my-app',
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: 'subchart',
|
|
164
|
+
description: 'Subchart for use with umbrella charts',
|
|
165
|
+
usage: 'tl umbrella add my-subchart',
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
if (flags?.silent) {
|
|
169
|
+
console.log(JSON.stringify(templates));
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
console.log('Available templates:');
|
|
173
|
+
templates.forEach((t) => {
|
|
174
|
+
console.log(`\n${t.name}`);
|
|
175
|
+
console.log(` Description: ${t.description}`);
|
|
176
|
+
console.log(` Usage: ${t.usage}`);
|
|
177
|
+
});
|
|
289
178
|
}
|
|
290
179
|
}
|
|
291
180
|
async function cmdUmbrella(subcommand, args, flags) {
|
|
292
|
-
const UMBRELLA_USAGE_MSG = 'Missing umbrella subcommand (init|add|synth)';
|
|
293
181
|
if (!subcommand)
|
|
294
|
-
usageAndExit(
|
|
182
|
+
usageAndExit('Missing umbrella subcommand');
|
|
295
183
|
switch (subcommand) {
|
|
296
184
|
case 'init':
|
|
297
185
|
await cmdUmbrellaInit(args?.[0], flags?.silent);
|
|
@@ -306,66 +194,137 @@ async function cmdUmbrella(subcommand, args, flags) {
|
|
|
306
194
|
usageAndExit(`Unknown umbrella subcommand: ${subcommand}`);
|
|
307
195
|
}
|
|
308
196
|
}
|
|
309
|
-
const UMBRELLA_CONFIG_FILE = 'umbrella.config.json';
|
|
310
197
|
async function cmdUmbrellaInit(name, silent = false) {
|
|
311
198
|
const MISSING_NAME_MSG = 'Missing umbrella chart name';
|
|
312
199
|
if (!name)
|
|
313
200
|
usageAndExit(MISSING_NAME_MSG);
|
|
314
|
-
const
|
|
315
|
-
const
|
|
201
|
+
const validName = name;
|
|
202
|
+
const base = path.join(process.cwd(), validName);
|
|
203
|
+
const umbrellaFile = path.join(base, UMBRELLA_FILE_NAME);
|
|
316
204
|
const configFile = path.join(base, UMBRELLA_CONFIG_FILE);
|
|
317
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
318
205
|
fs.mkdirSync(base, { recursive: true });
|
|
319
|
-
|
|
320
|
-
fs.
|
|
321
|
-
// Create umbrella.ts
|
|
322
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
323
|
-
fs.writeFileSync(umbrellaFile, exampleUmbrellaTs(name));
|
|
324
|
-
// Create config file
|
|
206
|
+
const { generateUmbrellaChart } = await import('./lib/templates/umbrella-chart.js');
|
|
207
|
+
fs.writeFileSync(umbrellaFile, generateUmbrellaChart(validName));
|
|
325
208
|
const config = {
|
|
326
|
-
name,
|
|
209
|
+
name: validName,
|
|
327
210
|
version: '0.1.0',
|
|
328
|
-
description: `${
|
|
211
|
+
description: `${validName} umbrella chart`,
|
|
329
212
|
subcharts: [],
|
|
330
213
|
};
|
|
331
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
332
214
|
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
215
|
+
const chartsDir = path.join(base, 'charts');
|
|
216
|
+
fs.mkdirSync(chartsDir, { recursive: true });
|
|
333
217
|
log(`Umbrella chart structure created at ${base}`, silent);
|
|
334
218
|
log(`Add subcharts with: tl umbrella add <subchart-name>`, silent);
|
|
335
219
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
220
|
+
function toCamelCase(str) {
|
|
221
|
+
return str.replace(/-([a-z])/g, (g) => g[1]?.toUpperCase() || '');
|
|
222
|
+
}
|
|
223
|
+
function findLastImportIndex(lines) {
|
|
224
|
+
let lastImportIndex = -1;
|
|
225
|
+
for (let i = 0; i < lines.length; i++) {
|
|
226
|
+
if (lines[i]?.trim().startsWith('import ')) {
|
|
227
|
+
lastImportIndex = i;
|
|
228
|
+
}
|
|
229
|
+
else if (lines[i]?.trim() && !lines[i]?.trim().startsWith('import ')) {
|
|
230
|
+
break;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return lastImportIndex;
|
|
234
|
+
}
|
|
235
|
+
function addImportStatement(content, importStatement) {
|
|
236
|
+
if (content.includes(importStatement)) {
|
|
237
|
+
return content;
|
|
238
|
+
}
|
|
239
|
+
const lines = content.split('\n');
|
|
240
|
+
const lastImportIndex = findLastImportIndex(lines);
|
|
241
|
+
if (lastImportIndex >= 0) {
|
|
242
|
+
lines.splice(lastImportIndex + 1, 0, importStatement);
|
|
243
|
+
}
|
|
244
|
+
else {
|
|
245
|
+
lines.splice(0, 0, importStatement);
|
|
246
|
+
}
|
|
247
|
+
return lines.join('\n');
|
|
248
|
+
}
|
|
249
|
+
function buildSubchartsContent(subchartsContent, subchartEntry) {
|
|
250
|
+
const hasExistingEntries = /\{\s*name:\s*['"]/.test(subchartsContent || '');
|
|
251
|
+
if (!hasExistingEntries) {
|
|
252
|
+
return `\n // Add your subcharts here:\n ${subchartEntry},\n `;
|
|
253
|
+
}
|
|
254
|
+
const trimmedContent = subchartsContent?.trimEnd() ?? '';
|
|
255
|
+
const needsComma = !trimmedContent.endsWith(',');
|
|
256
|
+
const comma = needsComma ? ',' : '';
|
|
257
|
+
return trimmedContent + `${comma}\n ${subchartEntry}`;
|
|
258
|
+
}
|
|
259
|
+
function addSubchartToArray(content, chartName, camelCaseName) {
|
|
260
|
+
const subchartsRegex = /subcharts:\s*\[([\s\S]*?)\]/;
|
|
261
|
+
const match = content.match(subchartsRegex);
|
|
262
|
+
if (!match) {
|
|
263
|
+
return content;
|
|
264
|
+
}
|
|
265
|
+
const subchartsContent = match[1];
|
|
266
|
+
if (subchartsContent?.includes(`name: '${chartName}'`)) {
|
|
267
|
+
return content;
|
|
268
|
+
}
|
|
269
|
+
const subchartEntry = `{ name: '${chartName}', chart: ${camelCaseName} }`;
|
|
270
|
+
const newSubchartsContent = buildSubchartsContent(subchartsContent, subchartEntry);
|
|
271
|
+
return content.replace(subchartsRegex, `subcharts: [${newSubchartsContent}\n ]`);
|
|
272
|
+
}
|
|
273
|
+
function updateUmbrellaTs(subchartPath, chartName) {
|
|
274
|
+
const umbrellaFile = path.join(process.cwd(), UMBRELLA_FILE_NAME);
|
|
275
|
+
const content = processUmbrellaFile(umbrellaFile, subchartPath, chartName);
|
|
276
|
+
fs.writeFileSync(umbrellaFile, content);
|
|
277
|
+
}
|
|
278
|
+
function processUmbrellaFile(umbrellaFile, subchartPath, chartName) {
|
|
279
|
+
let content = fs.readFileSync(umbrellaFile, 'utf8');
|
|
280
|
+
const importData = createImportData(subchartPath, chartName);
|
|
281
|
+
content = addImportStatement(content, importData.importStatement);
|
|
282
|
+
content = addSubchartToArray(content, chartName, importData.camelCaseName);
|
|
283
|
+
return content;
|
|
284
|
+
}
|
|
285
|
+
function createImportData(subchartPath, chartName) {
|
|
286
|
+
const importPath = `./charts/${subchartPath}/chart`;
|
|
287
|
+
const camelCaseName = toCamelCase(chartName);
|
|
288
|
+
const importStatement = `import ${camelCaseName} from '${importPath}';`;
|
|
289
|
+
return { importPath, camelCaseName, importStatement };
|
|
290
|
+
}
|
|
291
|
+
async function cmdUmbrellaAdd(subchartPath, silent = false) {
|
|
292
|
+
const MISSING_SUBCHART_MSG = 'Missing subchart name or path';
|
|
293
|
+
if (!subchartPath)
|
|
339
294
|
usageAndExit(MISSING_SUBCHART_MSG);
|
|
295
|
+
const validSubchartPath = subchartPath;
|
|
340
296
|
const configFile = path.join(process.cwd(), UMBRELLA_CONFIG_FILE);
|
|
341
297
|
if (!fs.existsSync(configFile)) {
|
|
342
298
|
console.error(`${UMBRELLA_CONFIG_FILE} not found. Run \`tl umbrella init\` first.`);
|
|
343
299
|
process.exit(1);
|
|
344
300
|
}
|
|
345
301
|
const config = JSON.parse(fs.readFileSync(configFile, 'utf8'));
|
|
346
|
-
|
|
347
|
-
const subchartDir = path.join(process.cwd(), 'charts',
|
|
348
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
302
|
+
const subchartName = path.basename(validSubchartPath);
|
|
303
|
+
const subchartDir = path.join(process.cwd(), 'charts', validSubchartPath);
|
|
349
304
|
fs.mkdirSync(subchartDir, { recursive: true });
|
|
350
305
|
const chartFile = path.join(subchartDir, 'chart.ts');
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
306
|
+
const { generateSubchartTemplate } = await import('./lib/templates/subchart.js');
|
|
307
|
+
const subchartContent = generateSubchartTemplate(subchartName);
|
|
308
|
+
fs.writeFileSync(chartFile, subchartContent);
|
|
354
309
|
config.subcharts.push({
|
|
355
310
|
name: subchartName,
|
|
356
311
|
version: '0.1.0',
|
|
357
|
-
path: `./charts/${
|
|
312
|
+
path: `./charts/${validSubchartPath}/chart.ts`,
|
|
358
313
|
});
|
|
359
314
|
fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
|
|
360
|
-
|
|
315
|
+
updateUmbrellaTs(validSubchartPath, subchartName);
|
|
316
|
+
log(`Subchart ${subchartName} added to umbrella at path '${validSubchartPath}'`, silent);
|
|
317
|
+
}
|
|
318
|
+
async function cmdUmbrellaSynth(outDir, flags) {
|
|
319
|
+
const umbrellaFile = path.join(process.cwd(), UMBRELLA_FILE_NAME);
|
|
320
|
+
const defaultOutDir = path.join(process.cwd(), 'dist');
|
|
321
|
+
if (!fs.existsSync(umbrellaFile)) {
|
|
322
|
+
console.error('umbrella.ts not found. Run `tl umbrella init` first.');
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
const resolvedOutDir = outDir ? path.resolve(outDir) : defaultOutDir;
|
|
326
|
+
await executeTypeScriptUmbrella(umbrellaFile, resolvedOutDir, flags);
|
|
361
327
|
}
|
|
362
|
-
/**
|
|
363
|
-
* Execute TypeScript umbrella chart using tsx with proper error handling.
|
|
364
|
-
* @param resolvedPath - Resolved path to the TypeScript file
|
|
365
|
-
* @param outDir - Output directory for the chart
|
|
366
|
-
* @param flags - CLI flags
|
|
367
|
-
* @since 2.7.3
|
|
368
|
-
*/
|
|
369
328
|
async function executeTypeScriptUmbrella(resolvedPath, outDir, flags) {
|
|
370
329
|
const wrapperScript = `
|
|
371
330
|
import { pathToFileURL } from 'url';
|
|
@@ -386,7 +345,7 @@ console.log('Umbrella chart written to ' + output);
|
|
|
386
345
|
const wrapperFile = path.join(process.cwd(), '.timonel-umbrella-wrapper.mjs');
|
|
387
346
|
try {
|
|
388
347
|
fs.writeFileSync(wrapperFile, wrapperScript);
|
|
389
|
-
const result =
|
|
348
|
+
const result = spawnSync('npx', ['tsx', wrapperFile].filter(Boolean), {
|
|
390
349
|
stdio: flags?.silent ? 'pipe' : 'inherit',
|
|
391
350
|
encoding: 'utf8',
|
|
392
351
|
});
|
|
@@ -398,404 +357,85 @@ console.log('Umbrella chart written to ' + output);
|
|
|
398
357
|
}
|
|
399
358
|
}
|
|
400
359
|
finally {
|
|
401
|
-
// Cleanup wrapper file
|
|
402
360
|
if (fs.existsSync(wrapperFile)) {
|
|
403
361
|
fs.unlinkSync(wrapperFile);
|
|
404
362
|
}
|
|
405
363
|
}
|
|
406
364
|
}
|
|
407
|
-
|
|
408
|
-
const
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
// Validate path to prevent traversal attacks
|
|
424
|
-
const validatedPath = SecurityUtils.validatePath(umbrellaFile, process.cwd());
|
|
425
|
-
const resolvedPath = path.resolve(validatedPath);
|
|
426
|
-
const output = outDir || path.join(process.cwd(), 'dist');
|
|
427
|
-
if (flags?.dryRun) {
|
|
428
|
-
log(`[DRY RUN] Would write umbrella chart to ${output}`, flags.silent);
|
|
429
|
-
return;
|
|
430
|
-
}
|
|
431
|
-
try {
|
|
432
|
-
await executeTypeScriptUmbrella(resolvedPath, output, flags);
|
|
433
|
-
}
|
|
434
|
-
catch (error) {
|
|
435
|
-
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
436
|
-
console.error(SecurityUtils.sanitizeLogMessage(`Failed to execute umbrella: ${errorMsg}`));
|
|
437
|
-
process.exit(1);
|
|
438
|
-
}
|
|
439
|
-
}
|
|
440
|
-
async function cmdPackage(chartDir, out, silent = false) {
|
|
441
|
-
if (!chartDir)
|
|
442
|
-
usageAndExit('Missing <chartDir>');
|
|
443
|
-
const src = path.isAbsolute(chartDir) ? chartDir : path.join(process.cwd(), chartDir);
|
|
444
|
-
const chartYaml = path.join(src, 'Chart.yaml');
|
|
445
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
446
|
-
if (!fs.existsSync(chartYaml)) {
|
|
447
|
-
console.error(`Chart.yaml not found in ${src}`);
|
|
448
|
-
process.exit(1);
|
|
449
|
-
}
|
|
450
|
-
const outDir = out ? (path.isAbsolute(out) ? out : path.join(process.cwd(), out)) : src;
|
|
451
|
-
// eslint-disable-next-line security/detect-non-literal-fs-filename -- CLI tool needs dynamic paths
|
|
452
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
453
|
-
const helm = process.env['HELM_BIN'] || 'helm';
|
|
454
|
-
const args = ['package', src, '-d', outDir];
|
|
455
|
-
log(`> ${helm} ${args.join(' ')}`, silent);
|
|
456
|
-
const res = cp.spawnSync(helm, args, { stdio: 'inherit' });
|
|
457
|
-
if (res.error) {
|
|
458
|
-
console.error(`✗ Failed to execute Helm: ${res.error.message}`);
|
|
459
|
-
console.error(` Install: ${HELM_INSTALL_URL}`);
|
|
460
|
-
console.error(` ${HELM_ENV_VAR_MSG}`);
|
|
461
|
-
process.exit(1);
|
|
462
|
-
}
|
|
463
|
-
if (res.status !== 0) {
|
|
464
|
-
process.exit(res.status ?? 1);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
function exampleUmbrellaTs(name) {
|
|
468
|
-
return `import { createUmbrella } from 'timonel';
|
|
469
|
-
import { Rutter } from 'timonel';
|
|
470
|
-
|
|
471
|
-
// Import subcharts (these will be created with 'tl umbrella add')
|
|
472
|
-
// import { rutter as fluentBit } from './charts/fluent-bit/chart';
|
|
473
|
-
// import { rutter as openObserve } from './charts/openobserve/chart';
|
|
474
|
-
|
|
475
|
-
// Create umbrella chart
|
|
476
|
-
const umbrella = createUmbrella({
|
|
477
|
-
meta: {
|
|
478
|
-
name: '${name}',
|
|
479
|
-
version: '0.1.0',
|
|
480
|
-
description: '${name} umbrella chart',
|
|
481
|
-
appVersion: '1.0.0',
|
|
482
|
-
},
|
|
483
|
-
subcharts: [
|
|
484
|
-
// Add your subcharts here:
|
|
485
|
-
// { name: 'fluent-bit', rutter: fluentBit },
|
|
486
|
-
// { name: 'openobserve', rutter: openObserve },
|
|
487
|
-
],
|
|
488
|
-
defaultValues: {
|
|
489
|
-
global: {
|
|
490
|
-
// Global values shared across subcharts
|
|
491
|
-
},
|
|
492
|
-
},
|
|
493
|
-
envValues: {
|
|
494
|
-
dev: {
|
|
495
|
-
// Development environment overrides
|
|
496
|
-
},
|
|
497
|
-
prod: {
|
|
498
|
-
// Production environment overrides
|
|
499
|
-
},
|
|
500
|
-
},
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
export default function run(outDir: string) {
|
|
504
|
-
umbrella.write(outDir);
|
|
505
|
-
}
|
|
506
|
-
`;
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Generate TypeScript code for a subchart with correct package imports.
|
|
510
|
-
* Uses published package imports instead of relative development paths.
|
|
511
|
-
* @param name - The name of the subchart
|
|
512
|
-
* @returns TypeScript code string for the subchart
|
|
513
|
-
* @since 2.7.3
|
|
514
|
-
*/
|
|
515
|
-
function exampleSubchartTs(name) {
|
|
516
|
-
return `import { Rutter } from 'timonel';
|
|
517
|
-
import { valuesRef } from './lib/helm.js';
|
|
518
|
-
|
|
519
|
-
// Define subchart
|
|
520
|
-
const rutter = new Rutter({
|
|
521
|
-
meta: {
|
|
522
|
-
name: '${name}',
|
|
523
|
-
version: '0.1.0',
|
|
524
|
-
description: '${name} subchart',
|
|
525
|
-
appVersion: '1.0.0',
|
|
526
|
-
},
|
|
527
|
-
defaultValues: {
|
|
528
|
-
image: { repository: 'nginx', tag: '1.27' },
|
|
529
|
-
replicas: 1,
|
|
530
|
-
service: { port: 80 },
|
|
531
|
-
},
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
// Add Deployment manifest
|
|
535
|
-
rutter.addManifest({
|
|
536
|
-
apiVersion: 'apps/v1',
|
|
537
|
-
kind: 'Deployment',
|
|
538
|
-
metadata: {
|
|
539
|
-
name: '${name}-deployment',
|
|
540
|
-
labels: { app: '${name}' },
|
|
541
|
-
},
|
|
542
|
-
spec: {
|
|
543
|
-
replicas: valuesRef('replicas'),
|
|
544
|
-
selector: {
|
|
545
|
-
matchLabels: { app: '${name}' },
|
|
546
|
-
},
|
|
547
|
-
template: {
|
|
548
|
-
metadata: {
|
|
549
|
-
labels: { app: '${name}' },
|
|
550
|
-
},
|
|
551
|
-
spec: {
|
|
552
|
-
containers: [{
|
|
553
|
-
name: '${name}',
|
|
554
|
-
image: String(valuesRef('image.repository')) + ':' + String(valuesRef('image.tag')),
|
|
555
|
-
ports: [{ containerPort: 80 }],
|
|
556
|
-
}],
|
|
557
|
-
},
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
}, 'deployment');
|
|
561
|
-
|
|
562
|
-
// Add Service manifest
|
|
563
|
-
rutter.addManifest({
|
|
564
|
-
apiVersion: 'v1',
|
|
565
|
-
kind: 'Service',
|
|
566
|
-
metadata: {
|
|
567
|
-
name: '${name}-service',
|
|
568
|
-
labels: { app: '${name}' },
|
|
569
|
-
},
|
|
570
|
-
spec: {
|
|
571
|
-
type: 'ClusterIP',
|
|
572
|
-
ports: [{ port: valuesRef('service.port'), targetPort: 80 }],
|
|
573
|
-
selector: { app: '${name}' },
|
|
574
|
-
},
|
|
575
|
-
}, 'service');
|
|
576
|
-
|
|
577
|
-
export default function run(outDir: string) {
|
|
578
|
-
rutter.write(outDir);
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
// Export rutter for umbrella chart
|
|
582
|
-
export { rutter };
|
|
583
|
-
`;
|
|
584
|
-
}
|
|
585
|
-
/**
|
|
586
|
-
* Generate TypeScript code for a main chart with correct package imports.
|
|
587
|
-
* Uses published package imports instead of relative development paths.
|
|
588
|
-
* @param name - The name of the chart
|
|
589
|
-
* @returns TypeScript code string for the chart
|
|
590
|
-
* @since 2.7.3
|
|
591
|
-
*/
|
|
592
|
-
function exampleChartTs(name) {
|
|
593
|
-
return `import { Rutter } from 'timonel';
|
|
594
|
-
import { valuesRef, helm } from 'timonel/lib/helm';
|
|
595
|
-
|
|
596
|
-
// Define chart metadata and default/env values
|
|
597
|
-
const rutter = new Rutter({
|
|
598
|
-
meta: {
|
|
599
|
-
name: '${name}',
|
|
600
|
-
version: '0.1.0',
|
|
601
|
-
description: 'Example Helm chart generated with timonel + cdk8s',
|
|
602
|
-
appVersion: '1.0.0',
|
|
603
|
-
},
|
|
604
|
-
defaultValues: {
|
|
605
|
-
image: { repository: 'nginx', tag: '1.27' },
|
|
606
|
-
replicas: 1,
|
|
607
|
-
service: { port: 80 },
|
|
608
|
-
ingress: { enabled: false, host: 'example.com', className: 'nginx' },
|
|
609
|
-
},
|
|
610
|
-
envValues: {
|
|
611
|
-
dev: { replicas: 1 },
|
|
612
|
-
prod: { replicas: 3 },
|
|
613
|
-
},
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
// Add Deployment manifest
|
|
617
|
-
rutter.addManifest({
|
|
618
|
-
apiVersion: 'apps/v1',
|
|
619
|
-
kind: 'Deployment',
|
|
620
|
-
metadata: {
|
|
621
|
-
name: '${name}-deployment',
|
|
622
|
-
labels: { app: '${name}' },
|
|
623
|
-
},
|
|
624
|
-
spec: {
|
|
625
|
-
replicas: valuesRef('replicas'),
|
|
626
|
-
selector: {
|
|
627
|
-
matchLabels: { app: '${name}' },
|
|
628
|
-
},
|
|
629
|
-
template: {
|
|
630
|
-
metadata: {
|
|
631
|
-
labels: { app: '${name}' },
|
|
632
|
-
},
|
|
633
|
-
spec: {
|
|
634
|
-
containers: [{
|
|
635
|
-
name: '${name}',
|
|
636
|
-
image: String(valuesRef('image.repository')) + ':' + String(valuesRef('image.tag')),
|
|
637
|
-
ports: [{ containerPort: 80 }],
|
|
638
|
-
env: [
|
|
639
|
-
{ name: 'APP_NAME', value: '${name}' },
|
|
640
|
-
{ name: 'RELEASE', value: helm.releaseName },
|
|
641
|
-
],
|
|
642
|
-
}],
|
|
643
|
-
},
|
|
644
|
-
},
|
|
645
|
-
},
|
|
646
|
-
}, 'deployment');
|
|
647
|
-
|
|
648
|
-
// Add Service manifest
|
|
649
|
-
rutter.addManifest({
|
|
650
|
-
apiVersion: 'v1',
|
|
651
|
-
kind: 'Service',
|
|
652
|
-
metadata: {
|
|
653
|
-
name: '${name}-service',
|
|
654
|
-
labels: { app: '${name}' },
|
|
655
|
-
},
|
|
656
|
-
spec: {
|
|
657
|
-
type: 'ClusterIP',
|
|
658
|
-
ports: [{ port: valuesRef('service.port'), targetPort: 80 }],
|
|
659
|
-
selector: { app: '${name}' },
|
|
660
|
-
},
|
|
661
|
-
}, 'service');
|
|
662
|
-
|
|
663
|
-
// Add Ingress manifest
|
|
664
|
-
rutter.addManifest({
|
|
665
|
-
apiVersion: 'networking.k8s.io/v1',
|
|
666
|
-
kind: 'Ingress',
|
|
667
|
-
metadata: {
|
|
668
|
-
name: '${name}-ingress',
|
|
669
|
-
labels: { app: '${name}' },
|
|
670
|
-
},
|
|
671
|
-
spec: {
|
|
672
|
-
ingressClassName: String(valuesRef('ingress.className')),
|
|
673
|
-
rules: [{
|
|
674
|
-
host: String(valuesRef('ingress.host')),
|
|
675
|
-
http: {
|
|
676
|
-
paths: [{
|
|
677
|
-
path: '/',
|
|
678
|
-
pathType: 'Prefix',
|
|
679
|
-
backend: {
|
|
680
|
-
service: {
|
|
681
|
-
name: '${name}-service',
|
|
682
|
-
port: { number: valuesRef('service.port') },
|
|
683
|
-
},
|
|
684
|
-
},
|
|
685
|
-
}],
|
|
686
|
-
},
|
|
687
|
-
}],
|
|
688
|
-
},
|
|
689
|
-
}, 'ingress');
|
|
690
|
-
|
|
691
|
-
export default function run(outDir: string) {
|
|
692
|
-
rutter.write(outDir);
|
|
693
|
-
}
|
|
694
|
-
`;
|
|
695
|
-
}
|
|
696
|
-
// eslint-disable-next-line sonarjs/cognitive-complexity -- CLI argument parsing requires multiple conditions
|
|
697
|
-
function parseArgs() {
|
|
698
|
-
/* eslint-disable security/detect-object-injection */
|
|
699
|
-
const argv = process.argv.slice(2);
|
|
700
|
-
const flags = { dryRun: false, silent: false, set: {} };
|
|
701
|
-
const args = [];
|
|
702
|
-
let cmd = '';
|
|
703
|
-
for (let i = 0; i < argv.length; i++) {
|
|
704
|
-
const arg = argv[i];
|
|
705
|
-
if (arg === '--dry-run') {
|
|
706
|
-
flags.dryRun = true;
|
|
707
|
-
}
|
|
708
|
-
else if (arg === '--silent') {
|
|
709
|
-
flags.silent = true;
|
|
710
|
-
}
|
|
711
|
-
else if (arg === '--env' && i + 1 < argv.length) {
|
|
712
|
-
const nextArg = argv[++i];
|
|
713
|
-
if (nextArg)
|
|
714
|
-
flags.env = nextArg;
|
|
715
|
-
}
|
|
716
|
-
else if (arg === '--version-bump' && i + 1 < argv.length) {
|
|
717
|
-
const nextArg = argv[++i];
|
|
718
|
-
if (nextArg)
|
|
719
|
-
flags.versionBump = nextArg;
|
|
720
|
-
}
|
|
721
|
-
else if (arg === '--set' && i + 1 < argv.length) {
|
|
722
|
-
const setValue = argv[++i];
|
|
723
|
-
if (setValue) {
|
|
724
|
-
const [key, value] = setValue.split('=', 2);
|
|
725
|
-
if (key && value !== undefined) {
|
|
726
|
-
flags.set[key] = value;
|
|
727
|
-
}
|
|
365
|
+
function parseFlags(args) {
|
|
366
|
+
const flags = {};
|
|
367
|
+
while (args.length > 0 && args[0]?.startsWith('-')) {
|
|
368
|
+
const flag = args.shift();
|
|
369
|
+
switch (flag) {
|
|
370
|
+
case '--dry-run':
|
|
371
|
+
flags.dryRun = true;
|
|
372
|
+
break;
|
|
373
|
+
case '--silent':
|
|
374
|
+
flags.silent = true;
|
|
375
|
+
break;
|
|
376
|
+
case '--env': {
|
|
377
|
+
const envValue = args.shift();
|
|
378
|
+
if (envValue)
|
|
379
|
+
flags.env = envValue;
|
|
380
|
+
break;
|
|
728
381
|
}
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}
|
|
737
|
-
else if (arg && !arg.startsWith('--')) {
|
|
738
|
-
if (!cmd) {
|
|
739
|
-
cmd = arg;
|
|
740
|
-
}
|
|
741
|
-
else {
|
|
742
|
-
args.push(arg);
|
|
382
|
+
case '--set': {
|
|
383
|
+
const setValue = args.shift();
|
|
384
|
+
if (setValue) {
|
|
385
|
+
flags.set = flags.set || [];
|
|
386
|
+
flags.set.push(setValue);
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
743
389
|
}
|
|
390
|
+
case '--version':
|
|
391
|
+
case '-v':
|
|
392
|
+
console.log('Timonel v0.1.0');
|
|
393
|
+
process.exit(0);
|
|
394
|
+
break;
|
|
395
|
+
case '--help':
|
|
396
|
+
case '-h':
|
|
397
|
+
usageAndExit();
|
|
398
|
+
break;
|
|
399
|
+
default:
|
|
400
|
+
usageAndExit(`Unknown flag: ${flag}`);
|
|
744
401
|
}
|
|
745
402
|
}
|
|
746
|
-
|
|
747
|
-
return { cmd, args, flags };
|
|
403
|
+
return flags;
|
|
748
404
|
}
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
* Reads version from package.json using ES modules compatible path resolution.
|
|
752
|
-
* @since 2.7.3
|
|
753
|
-
*/
|
|
754
|
-
function showVersion() {
|
|
755
|
-
const packagePath = path.join(__dirname, '..', 'package.json');
|
|
756
|
-
try {
|
|
757
|
-
const packageJson = JSON.parse(fs.readFileSync(packagePath, 'utf8'));
|
|
758
|
-
console.log(`timonel v${packageJson.version}`);
|
|
759
|
-
}
|
|
760
|
-
catch {
|
|
761
|
-
console.log('timonel (version unknown)');
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
function log(message, silent = false) {
|
|
765
|
-
if (!silent) {
|
|
766
|
-
console.log(message);
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
async function main() {
|
|
770
|
-
const { cmd, args, flags } = parseArgs();
|
|
771
|
-
switch (cmd) {
|
|
405
|
+
async function executeCommand(command, args, flags) {
|
|
406
|
+
switch (command) {
|
|
772
407
|
case 'init':
|
|
773
408
|
await cmdInit(args[0], flags.silent);
|
|
774
409
|
break;
|
|
775
|
-
case 'validate':
|
|
776
|
-
await cmdValidate(args[0], flags.silent);
|
|
777
|
-
break;
|
|
778
410
|
case 'synth':
|
|
779
|
-
await cmdSynth(args[0],
|
|
411
|
+
await cmdSynth(args[0], flags);
|
|
780
412
|
break;
|
|
781
|
-
case '
|
|
782
|
-
await
|
|
413
|
+
case 'validate':
|
|
414
|
+
await cmdValidate(flags);
|
|
783
415
|
break;
|
|
784
416
|
case 'deploy':
|
|
785
417
|
await cmdDeploy(args[0], args[1], flags);
|
|
786
418
|
break;
|
|
787
|
-
case '
|
|
788
|
-
await
|
|
419
|
+
case 'templates':
|
|
420
|
+
await cmdTemplates(flags);
|
|
789
421
|
break;
|
|
790
422
|
case 'umbrella':
|
|
791
423
|
await cmdUmbrella(args[0], args.slice(1), flags);
|
|
792
424
|
break;
|
|
425
|
+
case undefined:
|
|
426
|
+
usageAndExit('Missing command');
|
|
427
|
+
break;
|
|
793
428
|
default:
|
|
794
|
-
usageAndExit();
|
|
429
|
+
usageAndExit(`Unknown command: ${command}`);
|
|
795
430
|
}
|
|
796
431
|
}
|
|
797
|
-
main()
|
|
798
|
-
|
|
432
|
+
async function main() {
|
|
433
|
+
const args = process.argv.slice(2);
|
|
434
|
+
const command = args.shift();
|
|
435
|
+
const flags = parseFlags(args);
|
|
436
|
+
await executeCommand(command, args, flags);
|
|
437
|
+
}
|
|
438
|
+
main().catch((error) => {
|
|
439
|
+
console.error('Error:', error.message);
|
|
799
440
|
process.exit(1);
|
|
800
441
|
});
|
|
801
|
-
//# sourceMappingURL=cli.js.map
|