cloudburn 0.6.2 → 0.8.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/README.md +33 -0
- package/dist/cli.js +658 -84
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -8,6 +8,36 @@ Command-line interface for Cloudburn cloud cost optimization.
|
|
|
8
8
|
npm install -g cloudburn
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
+
## Shell completion
|
|
12
|
+
|
|
13
|
+
Inspect the available completion subcommands:
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
cloudburn completion
|
|
17
|
+
cloudburn completion zsh --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Generate a completion script for your shell and source it directly:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
source <(cloudburn completion zsh)
|
|
24
|
+
source <(cloudburn completion bash)
|
|
25
|
+
cloudburn completion fish | source
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
To enable completion persistently, add one of the following lines to your shell config:
|
|
29
|
+
|
|
30
|
+
```sh
|
|
31
|
+
# ~/.zshrc
|
|
32
|
+
source <(cloudburn completion zsh)
|
|
33
|
+
|
|
34
|
+
# ~/.bashrc
|
|
35
|
+
source <(cloudburn completion bash)
|
|
36
|
+
|
|
37
|
+
# ~/.config/fish/config.fish
|
|
38
|
+
cloudburn completion fish | source
|
|
39
|
+
```
|
|
40
|
+
|
|
11
41
|
## Usage
|
|
12
42
|
|
|
13
43
|
```sh
|
|
@@ -23,6 +53,9 @@ cloudburn scan ./template.yaml
|
|
|
23
53
|
cloudburn scan ./iac
|
|
24
54
|
cloudburn discover
|
|
25
55
|
cloudburn discover --region all
|
|
56
|
+
cloudburn rules
|
|
57
|
+
cloudburn completion
|
|
58
|
+
cloudburn completion zsh
|
|
26
59
|
```
|
|
27
60
|
|
|
28
61
|
`cloudburn scan --format json` emits the lean canonical grouped result:
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,464 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
|
+
|
|
6
|
+
// src/completion/engine.ts
|
|
7
|
+
var buildCompletionTree = (command) => {
|
|
8
|
+
const help = command.createHelp();
|
|
9
|
+
return {
|
|
10
|
+
commands: help.visibleCommands(command).map((childCommand) => buildCompletionTree(childCommand)),
|
|
11
|
+
name: command.name(),
|
|
12
|
+
options: collectVisibleOptions([...help.visibleOptions(command), ...help.visibleGlobalOptions(command)])
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
var collectVisibleOptions = (options) => options.map((option) => ({
|
|
16
|
+
expectsValue: option.required || option.optional,
|
|
17
|
+
tokens: [option.short, option.long].filter((token) => token !== void 0)
|
|
18
|
+
}));
|
|
19
|
+
var flattenOptionTokens = (options) => {
|
|
20
|
+
const seen = /* @__PURE__ */ new Set();
|
|
21
|
+
const tokens = [];
|
|
22
|
+
for (const option of options) {
|
|
23
|
+
for (const token of option.tokens) {
|
|
24
|
+
if (seen.has(token)) {
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
seen.add(token);
|
|
28
|
+
tokens.push(token);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return tokens;
|
|
32
|
+
};
|
|
33
|
+
var findOption = (options, token) => options.find((option) => option.tokens.includes(token));
|
|
34
|
+
var parseOptionToken = (token) => {
|
|
35
|
+
const separatorIndex = token.indexOf("=");
|
|
36
|
+
if (separatorIndex === -1) {
|
|
37
|
+
return { hasInlineValue: false, name: token };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
hasInlineValue: true,
|
|
41
|
+
name: token.slice(0, separatorIndex)
|
|
42
|
+
};
|
|
43
|
+
};
|
|
44
|
+
var createCompletionTree = (command) => buildCompletionTree(command);
|
|
45
|
+
var resolveCompletionSuggestions = (root, words) => {
|
|
46
|
+
const committedWords = words.length === 0 ? [] : words.slice(0, -1);
|
|
47
|
+
const partialWord = words.at(-1) ?? "";
|
|
48
|
+
let currentNode = root;
|
|
49
|
+
let expectsOptionValue = false;
|
|
50
|
+
for (const word of committedWords) {
|
|
51
|
+
if (expectsOptionValue) {
|
|
52
|
+
expectsOptionValue = false;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (word.startsWith("-")) {
|
|
56
|
+
const parsedOption = parseOptionToken(word);
|
|
57
|
+
const option = findOption(currentNode.options, parsedOption.name);
|
|
58
|
+
if (option?.expectsValue && !parsedOption.hasInlineValue) {
|
|
59
|
+
expectsOptionValue = true;
|
|
60
|
+
}
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const nextNode = currentNode.commands.find((command) => command.name === word);
|
|
64
|
+
if (nextNode !== void 0) {
|
|
65
|
+
currentNode = nextNode;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (expectsOptionValue) {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
if (partialWord.startsWith("-")) {
|
|
72
|
+
const parsedOption = parseOptionToken(partialWord);
|
|
73
|
+
if (parsedOption.hasInlineValue && findOption(currentNode.options, parsedOption.name) !== void 0) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
return flattenOptionTokens(currentNode.options).filter((token) => token.startsWith(partialWord));
|
|
77
|
+
}
|
|
78
|
+
const commandSuggestions = currentNode.commands.map((command) => command.name).filter((commandName) => commandName.startsWith(partialWord));
|
|
79
|
+
if (partialWord.length > 0) {
|
|
80
|
+
return commandSuggestions;
|
|
81
|
+
}
|
|
82
|
+
return [...commandSuggestions, ...flattenOptionTokens(currentNode.options)];
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/completion/shell.ts
|
|
86
|
+
var renderZshCompletion = (commandName) => `#compdef ${commandName}
|
|
87
|
+
|
|
88
|
+
_${commandName}() {
|
|
89
|
+
local -a suggestions
|
|
90
|
+
suggestions=("\${(@f)$("\${words[1]}" __complete -- "\${words[@]:1:$((CURRENT - 1))}")}")
|
|
91
|
+
_describe 'values' suggestions
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
compdef _${commandName} ${commandName}
|
|
95
|
+
`;
|
|
96
|
+
var renderBashCompletion = (commandName) => `_${commandName}() {
|
|
97
|
+
local -a args=()
|
|
98
|
+
local line
|
|
99
|
+
local -a suggestions=()
|
|
100
|
+
|
|
101
|
+
args=("\${COMP_WORDS[@]:1:$COMP_CWORD}")
|
|
102
|
+
if [ "$COMP_CWORD" -ge "\${#COMP_WORDS[@]}" ]; then
|
|
103
|
+
args+=("")
|
|
104
|
+
fi
|
|
105
|
+
|
|
106
|
+
while IFS= read -r line; do
|
|
107
|
+
suggestions+=("$line")
|
|
108
|
+
done < <("\${COMP_WORDS[0]}" __complete -- "\${args[@]}")
|
|
109
|
+
|
|
110
|
+
COMPREPLY=("\${suggestions[@]}")
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
complete -F _${commandName} ${commandName}
|
|
114
|
+
`;
|
|
115
|
+
var renderFishCompletion = (commandName) => `function __fish_${commandName}_complete
|
|
116
|
+
set -l words (commandline -opc)
|
|
117
|
+
if test (count $words) -gt 0
|
|
118
|
+
set -e words[1]
|
|
119
|
+
end
|
|
120
|
+
set -a words (commandline -ct)
|
|
121
|
+
${commandName} __complete -- $words
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
complete -c ${commandName} -f -a "(__fish_${commandName}_complete)"
|
|
125
|
+
`;
|
|
126
|
+
var generateCompletionScript = (shell, program) => {
|
|
127
|
+
const commandName = program.name();
|
|
128
|
+
switch (shell) {
|
|
129
|
+
case "zsh":
|
|
130
|
+
return renderZshCompletion(commandName);
|
|
131
|
+
case "bash":
|
|
132
|
+
return renderBashCompletion(commandName);
|
|
133
|
+
case "fish":
|
|
134
|
+
return renderFishCompletion(commandName);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// src/help.ts
|
|
5
139
|
import { Command } from "commander";
|
|
140
|
+
var ITEM_INDENT = 2;
|
|
141
|
+
var SPACER_WIDTH = 2;
|
|
142
|
+
var INCOMPLETE_HELP_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
143
|
+
"commander.missingArgument",
|
|
144
|
+
"commander.missingMandatoryOptionValue",
|
|
145
|
+
"commander.optionMissingArgument"
|
|
146
|
+
]);
|
|
147
|
+
var buildCommandPath = (command) => {
|
|
148
|
+
const names = [];
|
|
149
|
+
let current = command;
|
|
150
|
+
while (current !== null) {
|
|
151
|
+
names.unshift(current.name());
|
|
152
|
+
current = current.parent;
|
|
153
|
+
}
|
|
154
|
+
return names.join(" ");
|
|
155
|
+
};
|
|
156
|
+
var visibleCommands = (command) => command.commands.filter((subcommand) => !subcommand._hidden);
|
|
157
|
+
var getCommandExamples = (command) => {
|
|
158
|
+
const { _cloudburnExamples } = command;
|
|
159
|
+
return _cloudburnExamples ?? [];
|
|
160
|
+
};
|
|
161
|
+
var getCommandUsageGuidance = (command) => {
|
|
162
|
+
const { _cloudburnUsageGuidance } = command;
|
|
163
|
+
return _cloudburnUsageGuidance ?? null;
|
|
164
|
+
};
|
|
165
|
+
var getHelpScenario = (command) => command._cloudburnRenderingHelpScenario ?? "explicit";
|
|
166
|
+
var inferHelpScenario = (errorCode) => errorCode !== void 0 && INCOMPLETE_HELP_ERROR_CODES.has(errorCode) ? "incomplete" : "error";
|
|
167
|
+
var buildSubcommandGuidance = (command) => {
|
|
168
|
+
const firstSubcommand = visibleCommands(command).at(0);
|
|
169
|
+
if (firstSubcommand === void 0) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return `Specify one of the available subcommands to continue.
|
|
173
|
+
Try: ${buildCommandPath(command)} ${firstSubcommand.name()}`;
|
|
174
|
+
};
|
|
175
|
+
var buildExampleGuidance = (command) => {
|
|
176
|
+
const [firstExample] = getCommandExamples(command);
|
|
177
|
+
return firstExample === void 0 ? null : `Try: ${firstExample}`;
|
|
178
|
+
};
|
|
179
|
+
var buildRecoveryGuidance = (command, scenario) => {
|
|
180
|
+
if (scenario !== "incomplete") {
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
return buildSubcommandGuidance(command) ?? buildExampleGuidance(command);
|
|
184
|
+
};
|
|
185
|
+
var formatDescriptionItem = (term, description, termWidth, helper) => {
|
|
186
|
+
const itemIndent = " ".repeat(ITEM_INDENT);
|
|
187
|
+
if (!description.includes("\n")) {
|
|
188
|
+
return helper.formatItem(term, termWidth, description, helper);
|
|
189
|
+
}
|
|
190
|
+
const paddedTerm = term.padEnd(termWidth + term.length - helper.displayWidth(term));
|
|
191
|
+
const continuationIndent = `${itemIndent}${" ".repeat(termWidth)}${" ".repeat(SPACER_WIDTH)}`;
|
|
192
|
+
const [firstLine, ...remainingLines] = description.split("\n");
|
|
193
|
+
return [
|
|
194
|
+
`${itemIndent}${paddedTerm}${" ".repeat(SPACER_WIDTH)}${firstLine}`,
|
|
195
|
+
...remainingLines.map((line) => `${continuationIndent}${line}`)
|
|
196
|
+
].join("\n");
|
|
197
|
+
};
|
|
198
|
+
var formatSection = (title, items, termWidth, helper) => {
|
|
199
|
+
if (items.length === 0) {
|
|
200
|
+
return [];
|
|
201
|
+
}
|
|
202
|
+
return [
|
|
203
|
+
helper.styleTitle(title),
|
|
204
|
+
...items.map(({ description, term }) => formatDescriptionItem(term, description, termWidth, helper)),
|
|
205
|
+
""
|
|
206
|
+
];
|
|
207
|
+
};
|
|
208
|
+
var formatExamplesSection = (examples) => {
|
|
209
|
+
if (examples.length === 0) {
|
|
210
|
+
return [];
|
|
211
|
+
}
|
|
212
|
+
return ["Examples:", ...examples.map((example) => ` ${example}`), ""];
|
|
213
|
+
};
|
|
214
|
+
var formatGuidanceBlock = (guidance) => {
|
|
215
|
+
if (guidance === null) {
|
|
216
|
+
return [];
|
|
217
|
+
}
|
|
218
|
+
return [...guidance.trim().split("\n"), ""];
|
|
219
|
+
};
|
|
220
|
+
var formatCloudBurnHelp = (command, helper) => {
|
|
221
|
+
const cloudBurnHelp = helper;
|
|
222
|
+
const helpWidth = helper.helpWidth ?? 80;
|
|
223
|
+
const scenario = getHelpScenario(command);
|
|
224
|
+
const localOptions = helper.visibleOptions(command);
|
|
225
|
+
const globalOptions = helper.visibleGlobalOptions(command);
|
|
226
|
+
const commands = helper.visibleCommands(command);
|
|
227
|
+
const examples = getCommandExamples(command);
|
|
228
|
+
const usageGuidance = getCommandUsageGuidance(command);
|
|
229
|
+
const recoveryGuidance = buildRecoveryGuidance(command, scenario);
|
|
230
|
+
const termWidth = Math.max(
|
|
231
|
+
helper.longestArgumentTermLength(command, helper),
|
|
232
|
+
helper.longestOptionTermLength(command, helper),
|
|
233
|
+
helper.longestGlobalOptionTermLength(command, helper),
|
|
234
|
+
helper.longestSubcommandTermLength(command, helper)
|
|
235
|
+
);
|
|
236
|
+
const output = [];
|
|
237
|
+
const description = helper.commandDescription(command);
|
|
238
|
+
if (!cloudBurnHelp._cloudburnIsErrorOutput && description.length > 0) {
|
|
239
|
+
output.push(helper.boxWrap(helper.styleCommandDescription(description), helpWidth), "");
|
|
240
|
+
}
|
|
241
|
+
output.push(`${helper.styleTitle("Usage:")} ${helper.styleUsage(helper.commandUsage(command))}`, "");
|
|
242
|
+
output.push(...formatExamplesSection(examples));
|
|
243
|
+
output.push(...formatGuidanceBlock(usageGuidance));
|
|
244
|
+
output.push(
|
|
245
|
+
...formatSection(
|
|
246
|
+
"Available Commands:",
|
|
247
|
+
commands.map((subcommand) => ({
|
|
248
|
+
description: helper.styleSubcommandDescription(helper.subcommandDescription(subcommand)),
|
|
249
|
+
term: helper.styleSubcommandTerm(helper.subcommandTerm(subcommand))
|
|
250
|
+
})),
|
|
251
|
+
termWidth,
|
|
252
|
+
helper
|
|
253
|
+
)
|
|
254
|
+
);
|
|
255
|
+
output.push(
|
|
256
|
+
...formatSection(
|
|
257
|
+
"Arguments:",
|
|
258
|
+
helper.visibleArguments(command).map((argument) => ({
|
|
259
|
+
description: helper.styleArgumentDescription(helper.argumentDescription(argument)),
|
|
260
|
+
term: helper.styleArgumentTerm(helper.argumentTerm(argument))
|
|
261
|
+
})),
|
|
262
|
+
termWidth,
|
|
263
|
+
helper
|
|
264
|
+
)
|
|
265
|
+
);
|
|
266
|
+
output.push(
|
|
267
|
+
...formatSection(
|
|
268
|
+
command.parent === null ? "Global Flags:" : "Flags:",
|
|
269
|
+
localOptions.map((option) => ({
|
|
270
|
+
description: helper.styleOptionDescription(helper.optionDescription(option)),
|
|
271
|
+
term: helper.styleOptionTerm(helper.optionTerm(option))
|
|
272
|
+
})),
|
|
273
|
+
termWidth,
|
|
274
|
+
helper
|
|
275
|
+
)
|
|
276
|
+
);
|
|
277
|
+
if (command.parent !== null) {
|
|
278
|
+
output.push(
|
|
279
|
+
...formatSection(
|
|
280
|
+
"Global Flags:",
|
|
281
|
+
globalOptions.map((option) => ({
|
|
282
|
+
description: helper.styleOptionDescription(helper.optionDescription(option)),
|
|
283
|
+
term: helper.styleOptionTerm(helper.optionTerm(option))
|
|
284
|
+
})),
|
|
285
|
+
termWidth,
|
|
286
|
+
helper
|
|
287
|
+
)
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
output.push(...formatGuidanceBlock(recoveryGuidance));
|
|
291
|
+
if (scenario === "error" && commands.length > 0) {
|
|
292
|
+
output.push(`Use "${buildCommandPath(command)} [command] --help" for more information about a command.`, "");
|
|
293
|
+
}
|
|
294
|
+
while (output.at(-1) === "") {
|
|
295
|
+
output.pop();
|
|
296
|
+
}
|
|
297
|
+
return `${output.join("\n")}
|
|
298
|
+
`;
|
|
299
|
+
};
|
|
300
|
+
var prepareCloudBurnHelpContext = function(contextOptions) {
|
|
301
|
+
this._cloudburnIsErrorOutput = !!contextOptions.error;
|
|
302
|
+
this.helpWidth = this.helpWidth ?? contextOptions.helpWidth ?? 80;
|
|
303
|
+
};
|
|
304
|
+
var CloudBurnCommand = class _CloudBurnCommand extends Command {
|
|
305
|
+
_cloudburnExamples;
|
|
306
|
+
_cloudburnPendingHelpScenario;
|
|
307
|
+
_cloudburnRenderingHelpScenario;
|
|
308
|
+
_cloudburnUsageGuidance;
|
|
309
|
+
/**
|
|
310
|
+
* Creates CloudBurn subcommands that inherit the scenario-aware help behavior.
|
|
311
|
+
*
|
|
312
|
+
* @param name - Optional subcommand name supplied by Commander.
|
|
313
|
+
* @returns A new CloudBurn-aware command instance.
|
|
314
|
+
*/
|
|
315
|
+
createCommand(name) {
|
|
316
|
+
return new _CloudBurnCommand(name);
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* Captures the error scenario so Commander help-after-error renders the correct layout.
|
|
320
|
+
*
|
|
321
|
+
* @param message - Error text emitted by Commander.
|
|
322
|
+
* @param errorOptions - Commander exit metadata including the error code.
|
|
323
|
+
* @returns Never returns because Commander exits or throws via exitOverride.
|
|
324
|
+
*/
|
|
325
|
+
error(message, errorOptions) {
|
|
326
|
+
this._cloudburnPendingHelpScenario = inferHelpScenario(errorOptions?.code);
|
|
327
|
+
return super.error(message, errorOptions);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Returns help text using the shared CloudBurn scenario rules.
|
|
331
|
+
*
|
|
332
|
+
* @param context - Commander help context.
|
|
333
|
+
* @returns The formatted help text.
|
|
334
|
+
*/
|
|
335
|
+
helpInformation(context) {
|
|
336
|
+
return this.renderWithHelpScenario(context, () => super.helpInformation(context));
|
|
337
|
+
}
|
|
338
|
+
renderWithHelpScenario(context, render) {
|
|
339
|
+
const previousScenario = this._cloudburnRenderingHelpScenario;
|
|
340
|
+
const pendingScenario = this._cloudburnPendingHelpScenario;
|
|
341
|
+
this._cloudburnRenderingHelpScenario = context?.error ? pendingScenario ?? "error" : pendingScenario ?? "explicit";
|
|
342
|
+
this._cloudburnPendingHelpScenario = void 0;
|
|
343
|
+
try {
|
|
344
|
+
return render();
|
|
345
|
+
} finally {
|
|
346
|
+
this._cloudburnRenderingHelpScenario = previousScenario;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
var outputScenarioHelp = (command, scenario) => {
|
|
351
|
+
const cloudBurnCommand = command;
|
|
352
|
+
const previousScenario = cloudBurnCommand._cloudburnPendingHelpScenario;
|
|
353
|
+
cloudBurnCommand._cloudburnPendingHelpScenario = scenario;
|
|
354
|
+
try {
|
|
355
|
+
command.outputHelp();
|
|
356
|
+
} finally {
|
|
357
|
+
cloudBurnCommand._cloudburnPendingHelpScenario = previousScenario;
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
var createCliCommand = () => new CloudBurnCommand();
|
|
361
|
+
var createHelpConfiguration = () => ({
|
|
362
|
+
formatHelp: formatCloudBurnHelp,
|
|
363
|
+
prepareContext: prepareCloudBurnHelpContext,
|
|
364
|
+
showGlobalOptions: true,
|
|
365
|
+
visibleCommands
|
|
366
|
+
});
|
|
367
|
+
var configureCliHelp = (program) => {
|
|
368
|
+
program.configureHelp(createHelpConfiguration());
|
|
369
|
+
program.showHelpAfterError();
|
|
370
|
+
program.showSuggestionAfterError();
|
|
371
|
+
};
|
|
372
|
+
var registerParentCommand = (parent, name, description) => parent.command(name).description(description).usage("[command]").allowExcessArguments(true).action(function() {
|
|
373
|
+
if (this.args.length > 0) {
|
|
374
|
+
this.unknownCommand();
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
outputScenarioHelp(this, "incomplete");
|
|
378
|
+
});
|
|
379
|
+
var setCommandExamples = (command, examples) => {
|
|
380
|
+
command._cloudburnExamples = examples;
|
|
381
|
+
return command;
|
|
382
|
+
};
|
|
383
|
+
var setCommandUsageGuidance = (command, guidance) => {
|
|
384
|
+
command._cloudburnUsageGuidance = guidance.trim();
|
|
385
|
+
return command;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// src/commands/completion.ts
|
|
389
|
+
var getRootCommand = (command) => {
|
|
390
|
+
let currentCommand = command;
|
|
391
|
+
while (currentCommand.parent !== null) {
|
|
392
|
+
currentCommand = currentCommand.parent;
|
|
393
|
+
}
|
|
394
|
+
return currentCommand;
|
|
395
|
+
};
|
|
396
|
+
var getCompletionHelpText = (shell) => {
|
|
397
|
+
switch (shell) {
|
|
398
|
+
case "bash":
|
|
399
|
+
return `
|
|
400
|
+
To load completions in your current shell session:
|
|
401
|
+
|
|
402
|
+
source <(cloudburn completion bash)
|
|
403
|
+
|
|
404
|
+
To load completions for every new session, add this to your shell config:
|
|
405
|
+
|
|
406
|
+
source <(cloudburn completion bash)
|
|
407
|
+
`;
|
|
408
|
+
case "fish":
|
|
409
|
+
return `
|
|
410
|
+
To load completions in your current shell session:
|
|
411
|
+
|
|
412
|
+
cloudburn completion fish | source
|
|
413
|
+
|
|
414
|
+
To load completions for every new session, add this to your shell config:
|
|
415
|
+
|
|
416
|
+
cloudburn completion fish | source
|
|
417
|
+
`;
|
|
418
|
+
case "zsh":
|
|
419
|
+
return `
|
|
420
|
+
If shell completion is not already enabled in your environment you will need
|
|
421
|
+
to enable it. You can execute the following once:
|
|
422
|
+
|
|
423
|
+
echo "autoload -U compinit; compinit" >> ~/.zshrc
|
|
424
|
+
|
|
425
|
+
To load completions in your current shell session:
|
|
426
|
+
|
|
427
|
+
source <(cloudburn completion zsh)
|
|
428
|
+
|
|
429
|
+
To load completions for every new session, add this to your shell config:
|
|
430
|
+
|
|
431
|
+
source <(cloudburn completion zsh)
|
|
432
|
+
`;
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
var registerCompletionCommand = (program) => {
|
|
436
|
+
const completionCommand = registerParentCommand(
|
|
437
|
+
program,
|
|
438
|
+
"completion",
|
|
439
|
+
"Generate shell completion scripts for CloudBurn."
|
|
440
|
+
);
|
|
441
|
+
for (const shell of ["bash", "fish", "zsh"]) {
|
|
442
|
+
setCommandUsageGuidance(
|
|
443
|
+
completionCommand.command(shell).description(`Generate the autocompletion script for the ${shell} shell.`).option("--no-descriptions", "disable completion descriptions").action(function() {
|
|
444
|
+
const output = generateCompletionScript(shell, getRootCommand(this));
|
|
445
|
+
process.stdout.write(output);
|
|
446
|
+
}),
|
|
447
|
+
getCompletionHelpText(shell)
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
program.command("__complete", { hidden: true }).argument("[words...]").allowUnknownOption().action(function(words) {
|
|
451
|
+
const suggestions = resolveCompletionSuggestions(createCompletionTree(getRootCommand(this)), words ?? []);
|
|
452
|
+
if (suggestions.length === 0) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
process.stdout.write(`${suggestions.join("\n")}
|
|
456
|
+
`);
|
|
457
|
+
});
|
|
458
|
+
};
|
|
6
459
|
|
|
7
460
|
// src/commands/discover.ts
|
|
8
461
|
import { assertValidAwsRegion, CloudBurnClient } from "@cloudburn/sdk";
|
|
9
|
-
import { InvalidArgumentError as
|
|
462
|
+
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
10
463
|
|
|
11
464
|
// src/exit-codes.ts
|
|
12
465
|
var EXIT_CODE_OK = 0;
|
|
@@ -89,7 +542,7 @@ var scanColumns = [
|
|
|
89
542
|
{ key: "startColumn", header: "StartColumn" },
|
|
90
543
|
{ key: "message", header: "Message" }
|
|
91
544
|
];
|
|
92
|
-
var formatOptionDescription = "
|
|
545
|
+
var formatOptionDescription = "Options: table: human-readable terminal output.\ntext: tab-delimited output for grep, sed, and awk.\njson: machine-readable output for automation and downstream systems.";
|
|
93
546
|
var OUTPUT_FORMAT_OPTION_DESCRIPTION = formatOptionDescription;
|
|
94
547
|
var parseOutputFormat = (value) => {
|
|
95
548
|
if (supportedOutputFormats.includes(value)) {
|
|
@@ -254,12 +707,22 @@ var renderAsciiTable = (rows, columns) => {
|
|
|
254
707
|
return [border, header, border, ...body, border].join("\n");
|
|
255
708
|
};
|
|
256
709
|
|
|
710
|
+
// src/commands/config-options.ts
|
|
711
|
+
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
712
|
+
var parseRuleIdList = (value) => {
|
|
713
|
+
const ruleIds = value.split(",").map((ruleId) => ruleId.trim()).filter((ruleId) => ruleId.length > 0);
|
|
714
|
+
if (ruleIds.length === 0) {
|
|
715
|
+
throw new InvalidArgumentError2("Provide at least one rule ID.");
|
|
716
|
+
}
|
|
717
|
+
return ruleIds;
|
|
718
|
+
};
|
|
719
|
+
|
|
257
720
|
// src/commands/discover.ts
|
|
258
721
|
var parseAwsRegion = (value) => {
|
|
259
722
|
try {
|
|
260
723
|
return assertValidAwsRegion(value);
|
|
261
724
|
} catch (err) {
|
|
262
|
-
throw new
|
|
725
|
+
throw new InvalidArgumentError3(err instanceof Error ? err.message : "Invalid AWS region.");
|
|
263
726
|
}
|
|
264
727
|
};
|
|
265
728
|
var parseDiscoverRegion = (value) => {
|
|
@@ -269,6 +732,17 @@ var parseDiscoverRegion = (value) => {
|
|
|
269
732
|
return parseAwsRegion(value);
|
|
270
733
|
};
|
|
271
734
|
var resolveDiscoveryTarget = (region) => region === void 0 ? { mode: "current" } : region === "all" ? { mode: "all" } : { mode: "region", region };
|
|
735
|
+
var toDiscoveryConfigOverride = (options) => {
|
|
736
|
+
if (options.enabledRules === void 0 && options.disabledRules === void 0) {
|
|
737
|
+
return void 0;
|
|
738
|
+
}
|
|
739
|
+
return {
|
|
740
|
+
discovery: {
|
|
741
|
+
disabledRules: options.disabledRules,
|
|
742
|
+
enabledRules: options.enabledRules
|
|
743
|
+
}
|
|
744
|
+
};
|
|
745
|
+
};
|
|
272
746
|
var runCommand = async (action) => {
|
|
273
747
|
try {
|
|
274
748
|
process.exitCode = await action() ?? EXIT_CODE_OK;
|
|
@@ -279,39 +753,49 @@ var runCommand = async (action) => {
|
|
|
279
753
|
}
|
|
280
754
|
};
|
|
281
755
|
var registerDiscoverCommand = (program) => {
|
|
282
|
-
const discoverCommand =
|
|
283
|
-
"
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
756
|
+
const discoverCommand = setCommandExamples(
|
|
757
|
+
program.command("discover").description("Run a live AWS discovery").enablePositionalOptions().option(
|
|
758
|
+
"--region <region>",
|
|
759
|
+
'Discovery region to use. Pass "all" to require an aggregator index.',
|
|
760
|
+
parseDiscoverRegion
|
|
761
|
+
).option("--config <path>", "Explicit CloudBurn config file to load").option("--enabled-rules <ruleIds>", "Comma-separated rule IDs to enable", parseRuleIdList).option("--disabled-rules <ruleIds>", "Comma-separated rule IDs to disable", parseRuleIdList).option("--exit-code", "Exit with code 1 when findings exist").action(async (options, command) => {
|
|
762
|
+
await runCommand(async () => {
|
|
763
|
+
const scanner = new CloudBurnClient();
|
|
764
|
+
const loadedConfig = await scanner.loadConfig(options.config);
|
|
765
|
+
const discoveryOptions = {
|
|
766
|
+
target: resolveDiscoveryTarget(options.region)
|
|
767
|
+
};
|
|
768
|
+
const configOverride = toDiscoveryConfigOverride(options);
|
|
769
|
+
if (configOverride !== void 0) {
|
|
770
|
+
discoveryOptions.config = configOverride;
|
|
771
|
+
}
|
|
772
|
+
if (options.config !== void 0) {
|
|
773
|
+
discoveryOptions.configPath = options.config;
|
|
774
|
+
}
|
|
775
|
+
const result = await scanner.discover(discoveryOptions);
|
|
776
|
+
const format = resolveOutputFormat(command, void 0, loadedConfig.discovery.format ?? "table");
|
|
777
|
+
const output = renderResponse({ kind: "scan-result", result }, format);
|
|
778
|
+
process.stdout.write(`${output}
|
|
303
779
|
`);
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
780
|
+
if (options.exitCode && countScanResultFindings(result) > 0) {
|
|
781
|
+
return EXIT_CODE_POLICY_VIOLATION;
|
|
782
|
+
}
|
|
783
|
+
return EXIT_CODE_OK;
|
|
784
|
+
});
|
|
785
|
+
}),
|
|
786
|
+
[
|
|
787
|
+
"cloudburn discover",
|
|
788
|
+
"cloudburn discover --region eu-central-1",
|
|
789
|
+
"cloudburn discover --region all",
|
|
790
|
+
"cloudburn discover list-enabled-regions",
|
|
791
|
+
"cloudburn discover init"
|
|
792
|
+
]
|
|
793
|
+
);
|
|
794
|
+
discoverCommand.command("list-enabled-regions").description("List AWS regions with a local or aggregator Resource Explorer index").action(async (_options, command) => {
|
|
311
795
|
await runCommand(async () => {
|
|
312
796
|
const scanner = new CloudBurnClient();
|
|
313
797
|
const regions = await scanner.listEnabledDiscoveryRegions();
|
|
314
|
-
const format = resolveOutputFormat(command
|
|
798
|
+
const format = resolveOutputFormat(command);
|
|
315
799
|
const output = renderResponse(
|
|
316
800
|
{
|
|
317
801
|
kind: "record-list",
|
|
@@ -329,14 +813,14 @@ Examples:
|
|
|
329
813
|
return EXIT_CODE_OK;
|
|
330
814
|
});
|
|
331
815
|
});
|
|
332
|
-
discoverCommand.command("init").description("Set up AWS Resource Explorer for CloudBurn").option("--
|
|
816
|
+
discoverCommand.command("init").description("Set up AWS Resource Explorer for CloudBurn").option("--region <region>", "Aggregator region to create or reuse during setup", parseAwsRegion).action(async (options, command) => {
|
|
333
817
|
await runCommand(async () => {
|
|
334
818
|
const scanner = new CloudBurnClient();
|
|
335
819
|
const parentRegion = discoverCommand.opts().region;
|
|
336
820
|
const region = options.region ?? (parentRegion === "all" ? void 0 : parentRegion);
|
|
337
821
|
const result = await scanner.initializeDiscovery({ region });
|
|
338
822
|
const message = result.status === "EXISTING" ? `Resource Explorer setup already exists in ${result.aggregatorRegion}.` : `Resource Explorer setup created in ${result.aggregatorRegion}.`;
|
|
339
|
-
const format = resolveOutputFormat(command
|
|
823
|
+
const format = resolveOutputFormat(command);
|
|
340
824
|
const output = renderResponse(
|
|
341
825
|
{
|
|
342
826
|
kind: "status",
|
|
@@ -356,11 +840,11 @@ Examples:
|
|
|
356
840
|
return EXIT_CODE_OK;
|
|
357
841
|
});
|
|
358
842
|
});
|
|
359
|
-
discoverCommand.command("supported-resource-types").description("List Resource Explorer supported AWS resource types").
|
|
843
|
+
discoverCommand.command("supported-resource-types").description("List Resource Explorer supported AWS resource types").action(async (_options, command) => {
|
|
360
844
|
await runCommand(async () => {
|
|
361
845
|
const scanner = new CloudBurnClient();
|
|
362
846
|
const resourceTypes = await scanner.listSupportedDiscoveryResourceTypes();
|
|
363
|
-
const format = resolveOutputFormat(command
|
|
847
|
+
const format = resolveOutputFormat(command);
|
|
364
848
|
const output = renderResponse(
|
|
365
849
|
{
|
|
366
850
|
kind: "record-list",
|
|
@@ -385,8 +869,8 @@ Examples:
|
|
|
385
869
|
|
|
386
870
|
// src/commands/estimate.ts
|
|
387
871
|
var registerEstimateCommand = (program) => {
|
|
388
|
-
program.command("estimate").description("Request optional pricing estimates from a self-hosted dashboard").option("--
|
|
389
|
-
const format = resolveOutputFormat(command
|
|
872
|
+
program.command("estimate").description("Request optional pricing estimates from a self-hosted dashboard").option("--server <url>", "Dashboard API base URL").action((options, command) => {
|
|
873
|
+
const format = resolveOutputFormat(command);
|
|
390
874
|
if (!options.server) {
|
|
391
875
|
const output2 = renderResponse(
|
|
392
876
|
{
|
|
@@ -422,41 +906,120 @@ var registerEstimateCommand = (program) => {
|
|
|
422
906
|
};
|
|
423
907
|
|
|
424
908
|
// src/commands/init.ts
|
|
425
|
-
|
|
426
|
-
|
|
909
|
+
import { access, writeFile } from "fs/promises";
|
|
910
|
+
import { dirname, join, resolve } from "path";
|
|
911
|
+
var CONFIG_FILENAMES = [".cloudburn.yml", ".cloudburn.yaml"];
|
|
912
|
+
var starterConfig = `# Static IaC scan configuration.
|
|
913
|
+
# enabled-rules restricts scans to only the listed rule IDs.
|
|
914
|
+
# disabled-rules removes specific rule IDs from the active set.
|
|
915
|
+
# format sets the default output format when --format is not passed.
|
|
916
|
+
iac:
|
|
917
|
+
enabled-rules:
|
|
918
|
+
- CLDBRN-AWS-EBS-1
|
|
919
|
+
disabled-rules:
|
|
920
|
+
- CLDBRN-AWS-EC2-2
|
|
921
|
+
format: table
|
|
427
922
|
|
|
428
|
-
#
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
923
|
+
# Live AWS discovery configuration.
|
|
924
|
+
# Use the same rule controls here to tune discover runs separately from IaC scans.
|
|
925
|
+
discovery:
|
|
926
|
+
enabled-rules:
|
|
927
|
+
- CLDBRN-AWS-EBS-1
|
|
928
|
+
disabled-rules:
|
|
929
|
+
- CLDBRN-AWS-S3-1
|
|
930
|
+
format: json
|
|
432
931
|
`;
|
|
932
|
+
var renderStarterConfig = (command) => renderResponse(
|
|
933
|
+
{
|
|
934
|
+
kind: "document",
|
|
935
|
+
content: starterConfig,
|
|
936
|
+
contentType: "application/yaml"
|
|
937
|
+
},
|
|
938
|
+
resolveOutputFormat(command, void 0, "text")
|
|
939
|
+
);
|
|
940
|
+
var fileExists = async (path) => {
|
|
941
|
+
try {
|
|
942
|
+
await access(path);
|
|
943
|
+
return true;
|
|
944
|
+
} catch {
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
var findProjectRoot = async (startDirectory) => {
|
|
949
|
+
let currentDirectory = resolve(startDirectory);
|
|
950
|
+
while (true) {
|
|
951
|
+
if (await fileExists(join(currentDirectory, ".git"))) {
|
|
952
|
+
return currentDirectory;
|
|
953
|
+
}
|
|
954
|
+
const parentDirectory = dirname(currentDirectory);
|
|
955
|
+
if (parentDirectory === currentDirectory) {
|
|
956
|
+
return resolve(startDirectory);
|
|
957
|
+
}
|
|
958
|
+
currentDirectory = parentDirectory;
|
|
959
|
+
}
|
|
960
|
+
};
|
|
433
961
|
var registerInitCommand = (program) => {
|
|
434
|
-
program.command("init").description("
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
962
|
+
const initCommand = program.command("init").description("Initialize CloudBurn scaffolding").usage("[command]").action(function() {
|
|
963
|
+
process.stdout.write(`${renderStarterConfig(this)}
|
|
964
|
+
`);
|
|
965
|
+
process.exitCode = EXIT_CODE_OK;
|
|
966
|
+
});
|
|
967
|
+
initCommand.command("config").description("Create a starter .cloudburn.yml configuration").option("--print", "Print the starter config instead of writing the file").action(async function(options) {
|
|
968
|
+
try {
|
|
969
|
+
if (options.print) {
|
|
970
|
+
process.stdout.write(`${renderStarterConfig(this)}
|
|
971
|
+
`);
|
|
972
|
+
process.exitCode = EXIT_CODE_OK;
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const rootDirectory = await findProjectRoot(process.cwd());
|
|
976
|
+
const existingConfigPath = (await Promise.all(
|
|
977
|
+
CONFIG_FILENAMES.map(async (filename) => {
|
|
978
|
+
const path = join(rootDirectory, filename);
|
|
979
|
+
return await fileExists(path) ? path : void 0;
|
|
980
|
+
})
|
|
981
|
+
)).find((path) => path !== void 0);
|
|
982
|
+
if (existingConfigPath) {
|
|
983
|
+
throw new Error(
|
|
984
|
+
`CloudBurn config already exists at ${existingConfigPath}. Use --print to inspect the template.`
|
|
985
|
+
);
|
|
986
|
+
}
|
|
987
|
+
const configPath = join(rootDirectory, ".cloudburn.yml");
|
|
988
|
+
await writeFile(configPath, starterConfig, { encoding: "utf8", flag: "wx" });
|
|
989
|
+
const output = renderResponse(
|
|
990
|
+
{
|
|
991
|
+
kind: "status",
|
|
992
|
+
data: {
|
|
993
|
+
message: "Created CloudBurn config.",
|
|
994
|
+
path: configPath
|
|
995
|
+
},
|
|
996
|
+
text: `Created ${configPath}.`
|
|
997
|
+
},
|
|
998
|
+
resolveOutputFormat(this)
|
|
999
|
+
);
|
|
1000
|
+
process.stdout.write(`${output}
|
|
1001
|
+
`);
|
|
1002
|
+
process.exitCode = EXIT_CODE_OK;
|
|
1003
|
+
} catch (err) {
|
|
1004
|
+
process.stderr.write(`${formatError(err)}
|
|
444
1005
|
`);
|
|
1006
|
+
process.exitCode = EXIT_CODE_RUNTIME_ERROR;
|
|
1007
|
+
}
|
|
445
1008
|
});
|
|
446
1009
|
};
|
|
447
1010
|
|
|
448
1011
|
// src/commands/rules-list.ts
|
|
449
1012
|
import { builtInRuleMetadata } from "@cloudburn/sdk";
|
|
450
1013
|
var registerRulesListCommand = (program) => {
|
|
451
|
-
const rulesCommand = program
|
|
452
|
-
rulesCommand.command("list").description("List built-in CloudBurn rules").
|
|
1014
|
+
const rulesCommand = registerParentCommand(program, "rules", "Inspect built-in CloudBurn rules");
|
|
1015
|
+
rulesCommand.command("list").description("List built-in CloudBurn rules").action(function() {
|
|
453
1016
|
const output = renderResponse(
|
|
454
1017
|
{
|
|
455
1018
|
kind: "rule-list",
|
|
456
1019
|
emptyMessage: "No built-in rules are available.",
|
|
457
1020
|
rules: builtInRuleMetadata
|
|
458
1021
|
},
|
|
459
|
-
resolveOutputFormat(
|
|
1022
|
+
resolveOutputFormat(this, void 0, "text")
|
|
460
1023
|
);
|
|
461
1024
|
process.stdout.write(`${output}
|
|
462
1025
|
`);
|
|
@@ -465,40 +1028,51 @@ var registerRulesListCommand = (program) => {
|
|
|
465
1028
|
|
|
466
1029
|
// src/commands/scan.ts
|
|
467
1030
|
import { CloudBurnClient as CloudBurnClient2 } from "@cloudburn/sdk";
|
|
1031
|
+
var toScanConfigOverride = (options) => {
|
|
1032
|
+
if (options.enabledRules === void 0 && options.disabledRules === void 0) {
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
return {
|
|
1036
|
+
iac: {
|
|
1037
|
+
disabledRules: options.disabledRules,
|
|
1038
|
+
enabledRules: options.enabledRules
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
};
|
|
468
1042
|
var registerScanCommand = (program) => {
|
|
469
|
-
|
|
470
|
-
"
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const result = await scanner.scanStatic(path ?? process.cwd());
|
|
481
|
-
const format = resolveOutputFormat(command, options.format);
|
|
482
|
-
const output = renderResponse({ kind: "scan-result", result }, format);
|
|
483
|
-
process.stdout.write(`${output}
|
|
1043
|
+
setCommandExamples(
|
|
1044
|
+
program.command("scan").description("Run an autodetected static IaC scan").argument("[path]", "Terraform file, CloudFormation template, or directory to scan").option("--config <path>", "Explicit CloudBurn config file to load").option("--enabled-rules <ruleIds>", "Comma-separated rule IDs to enable", parseRuleIdList).option("--disabled-rules <ruleIds>", "Comma-separated rule IDs to disable", parseRuleIdList).option("--exit-code", "Exit with code 1 when findings exist").action(async (path, options, command) => {
|
|
1045
|
+
try {
|
|
1046
|
+
const scanner = new CloudBurnClient2();
|
|
1047
|
+
const loadedConfig = await scanner.loadConfig(options.config);
|
|
1048
|
+
const configOverride = toScanConfigOverride(options);
|
|
1049
|
+
const scanPath = path ?? process.cwd();
|
|
1050
|
+
const result = configOverride === void 0 && options.config === void 0 ? await scanner.scanStatic(scanPath) : options.config === void 0 ? await scanner.scanStatic(scanPath, configOverride) : await scanner.scanStatic(scanPath, configOverride, { configPath: options.config });
|
|
1051
|
+
const format = resolveOutputFormat(command, void 0, loadedConfig.iac.format ?? "table");
|
|
1052
|
+
const output = renderResponse({ kind: "scan-result", result }, format);
|
|
1053
|
+
process.stdout.write(`${output}
|
|
484
1054
|
`);
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
1055
|
+
if (options.exitCode && countScanResultFindings(result) > 0) {
|
|
1056
|
+
process.exitCode = EXIT_CODE_POLICY_VIOLATION;
|
|
1057
|
+
return;
|
|
1058
|
+
}
|
|
1059
|
+
process.exitCode = EXIT_CODE_OK;
|
|
1060
|
+
} catch (err) {
|
|
1061
|
+
process.stderr.write(`${formatError(err)}
|
|
492
1062
|
`);
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
1063
|
+
process.exitCode = EXIT_CODE_RUNTIME_ERROR;
|
|
1064
|
+
}
|
|
1065
|
+
}),
|
|
1066
|
+
["cloudburn scan ./main.tf", "cloudburn scan ./template.yaml", "cloudburn scan ./iac"]
|
|
1067
|
+
);
|
|
496
1068
|
};
|
|
497
1069
|
|
|
498
1070
|
// src/cli.ts
|
|
499
1071
|
var createProgram = () => {
|
|
500
|
-
const program =
|
|
501
|
-
program.name("cloudburn").description("Know what you spend. Fix what you waste.").version("0.
|
|
1072
|
+
const program = createCliCommand();
|
|
1073
|
+
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.8.0").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
|
|
1074
|
+
configureCliHelp(program);
|
|
1075
|
+
registerCompletionCommand(program);
|
|
502
1076
|
registerDiscoverCommand(program);
|
|
503
1077
|
registerScanCommand(program);
|
|
504
1078
|
registerInitCommand(program);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudburn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Cloudburn CLI for cloud cost optimization",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"commander": "^13.1.0",
|
|
14
|
-
"@cloudburn/sdk": "0.
|
|
14
|
+
"@cloudburn/sdk": "0.12.0"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@biomejs/biome": "^2.4.6",
|