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