cloudburn 0.8.2 → 0.8.4
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/dist/cli.js +228 -39
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import {
|
|
4
|
+
import { realpathSync } from "fs";
|
|
5
|
+
import { resolve as resolve2 } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
5
7
|
|
|
6
8
|
// src/completion/engine.ts
|
|
7
9
|
var buildCompletionTree = (command) => {
|
|
@@ -153,6 +155,13 @@ var buildCommandPath = (command) => {
|
|
|
153
155
|
}
|
|
154
156
|
return names.join(" ");
|
|
155
157
|
};
|
|
158
|
+
var getRootCommand = (command) => {
|
|
159
|
+
let current = command;
|
|
160
|
+
while (current.parent !== null) {
|
|
161
|
+
current = current.parent;
|
|
162
|
+
}
|
|
163
|
+
return current;
|
|
164
|
+
};
|
|
156
165
|
var visibleCommands = (command) => command.commands.filter((subcommand) => !subcommand._hidden);
|
|
157
166
|
var getCommandExamples = (command) => {
|
|
158
167
|
const { _cloudburnExamples } = command;
|
|
@@ -221,8 +230,9 @@ var formatCloudBurnHelp = (command, helper) => {
|
|
|
221
230
|
const cloudBurnHelp = helper;
|
|
222
231
|
const helpWidth = helper.helpWidth ?? 80;
|
|
223
232
|
const scenario = getHelpScenario(command);
|
|
224
|
-
const
|
|
225
|
-
const
|
|
233
|
+
const rootCommand = getRootCommand(command);
|
|
234
|
+
const localOptions = helper.visibleOptions(command).filter((option) => command.options.includes(option));
|
|
235
|
+
const globalOptions = helper.visibleGlobalOptions(command).filter((option) => rootCommand.options.includes(option));
|
|
226
236
|
const commands = helper.visibleCommands(command);
|
|
227
237
|
const examples = getCommandExamples(command);
|
|
228
238
|
const usageGuidance = getCommandUsageGuidance(command);
|
|
@@ -386,7 +396,7 @@ var setCommandUsageGuidance = (command, guidance) => {
|
|
|
386
396
|
};
|
|
387
397
|
|
|
388
398
|
// src/commands/completion.ts
|
|
389
|
-
var
|
|
399
|
+
var getRootCommand2 = (command) => {
|
|
390
400
|
let currentCommand = command;
|
|
391
401
|
while (currentCommand.parent !== null) {
|
|
392
402
|
currentCommand = currentCommand.parent;
|
|
@@ -441,14 +451,14 @@ var registerCompletionCommand = (program) => {
|
|
|
441
451
|
for (const shell of ["bash", "fish", "zsh"]) {
|
|
442
452
|
setCommandUsageGuidance(
|
|
443
453
|
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,
|
|
454
|
+
const output = generateCompletionScript(shell, getRootCommand2(this));
|
|
445
455
|
process.stdout.write(output);
|
|
446
456
|
}),
|
|
447
457
|
getCompletionHelpText(shell)
|
|
448
458
|
);
|
|
449
459
|
}
|
|
450
460
|
program.command("__complete", { hidden: true }).argument("[words...]").allowUnknownOption().action(function(words) {
|
|
451
|
-
const suggestions = resolveCompletionSuggestions(createCompletionTree(
|
|
461
|
+
const suggestions = resolveCompletionSuggestions(createCompletionTree(getRootCommand2(this)), words ?? []);
|
|
452
462
|
if (suggestions.length === 0) {
|
|
453
463
|
return;
|
|
454
464
|
}
|
|
@@ -480,25 +490,26 @@ var categorize = (err) => {
|
|
|
480
490
|
if (!(err instanceof Error)) {
|
|
481
491
|
return { code: "RUNTIME_ERROR", message: "An unexpected error occurred." };
|
|
482
492
|
}
|
|
483
|
-
|
|
493
|
+
const code = "code" in err && typeof err.code === "string" ? err.code : void 0;
|
|
494
|
+
if (err.name === "CredentialsProviderError" || err.name === "ExpiredTokenException" || code === "CredentialsProviderError" || code === "ExpiredTokenException") {
|
|
484
495
|
return {
|
|
485
496
|
code: "CREDENTIALS_ERROR",
|
|
486
497
|
message: "AWS credentials not found or expired. Run 'aws sts get-caller-identity' to verify your session."
|
|
487
498
|
};
|
|
488
499
|
}
|
|
489
|
-
if (err.name.includes("AccessDenied")) {
|
|
500
|
+
if (err.name.includes("AccessDenied") || code?.includes("AccessDenied") === true) {
|
|
490
501
|
return {
|
|
491
502
|
code: "ACCESS_DENIED",
|
|
492
|
-
message: "Insufficient AWS permissions. Check your IAM role or policy."
|
|
503
|
+
message: sanitizeRuntimeErrorMessage(err.message).trim() || "Insufficient AWS permissions. Check your IAM role or policy."
|
|
493
504
|
};
|
|
494
505
|
}
|
|
495
506
|
if (err.code === "ENOENT") {
|
|
496
507
|
const path = err.path ?? "unknown";
|
|
497
508
|
return { code: "PATH_NOT_FOUND", message: `Path not found: ${path}` };
|
|
498
509
|
}
|
|
499
|
-
if (
|
|
510
|
+
if (code && isAwsDiscoveryErrorCode(code)) {
|
|
500
511
|
return {
|
|
501
|
-
code
|
|
512
|
+
code,
|
|
502
513
|
message: sanitizeRuntimeErrorMessage(err.message).trim() || "AWS Resource Explorer discovery failed."
|
|
503
514
|
};
|
|
504
515
|
}
|
|
@@ -526,6 +537,7 @@ var flattenScanResult = (result) => result.providers.flatMap(
|
|
|
526
537
|
)
|
|
527
538
|
);
|
|
528
539
|
var countScanResultFindings = (result) => flattenScanResult(result).length;
|
|
540
|
+
var getScanDiagnostics = (result) => result.diagnostics ?? [];
|
|
529
541
|
|
|
530
542
|
// src/formatters/output.ts
|
|
531
543
|
var DEFAULT_TABLE_WIDTH = 200;
|
|
@@ -574,6 +586,8 @@ var renderJson = (response) => {
|
|
|
574
586
|
switch (response.kind) {
|
|
575
587
|
case "document":
|
|
576
588
|
return JSON.stringify({ content: response.content, contentType: response.contentType }, null, 2);
|
|
589
|
+
case "discovery-status":
|
|
590
|
+
return JSON.stringify({ summary: response.summary, regions: response.rows }, null, 2);
|
|
577
591
|
case "record-list":
|
|
578
592
|
return JSON.stringify(response.rows, null, 2);
|
|
579
593
|
case "rule-list":
|
|
@@ -590,6 +604,9 @@ var renderText = (response) => {
|
|
|
590
604
|
switch (response.kind) {
|
|
591
605
|
case "document":
|
|
592
606
|
return response.content;
|
|
607
|
+
case "discovery-status":
|
|
608
|
+
return `${response.summaryText}
|
|
609
|
+
${renderTextRows(response.rows, response.columns, "No discovery status available.")}`;
|
|
593
610
|
case "record-list":
|
|
594
611
|
return renderTextRows(response.rows, response.columns, response.emptyMessage);
|
|
595
612
|
case "rule-list":
|
|
@@ -615,6 +632,19 @@ var renderTable = (response) => {
|
|
|
615
632
|
{ key: "Value", header: "Value" }
|
|
616
633
|
]
|
|
617
634
|
);
|
|
635
|
+
case "discovery-status": {
|
|
636
|
+
const summaryTable = renderAsciiTable(
|
|
637
|
+
Object.entries(response.summary).map(([field, value]) => ({ Field: field, Value: value })),
|
|
638
|
+
[
|
|
639
|
+
{ key: "Field", header: "Field" },
|
|
640
|
+
{ key: "Value", header: "Value" }
|
|
641
|
+
]
|
|
642
|
+
);
|
|
643
|
+
const regionsTable = response.rows.length === 0 ? "No discovery status available." : renderAsciiTable(response.rows, response.columns ?? inferColumns(response.rows));
|
|
644
|
+
return `${summaryTable}
|
|
645
|
+
|
|
646
|
+
${regionsTable}`;
|
|
647
|
+
}
|
|
618
648
|
case "record-list":
|
|
619
649
|
return response.rows.length === 0 ? response.emptyMessage : renderAsciiTable(response.rows, response.columns ?? inferColumns(response.rows));
|
|
620
650
|
case "rule-list":
|
|
@@ -638,19 +668,34 @@ var renderTable = (response) => {
|
|
|
638
668
|
);
|
|
639
669
|
}
|
|
640
670
|
};
|
|
641
|
-
var projectScanRows = (result) =>
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
671
|
+
var projectScanRows = (result) => [
|
|
672
|
+
...flattenScanResult(result).map(({ finding, message, provider, ruleId, service, source }) => ({
|
|
673
|
+
accountId: finding.accountId ?? "",
|
|
674
|
+
message,
|
|
675
|
+
path: finding.location?.path ?? "",
|
|
676
|
+
provider,
|
|
677
|
+
region: finding.region ?? "",
|
|
678
|
+
resourceId: finding.resourceId,
|
|
679
|
+
ruleId,
|
|
680
|
+
service,
|
|
681
|
+
source,
|
|
682
|
+
column: finding.location?.column ?? "",
|
|
683
|
+
line: finding.location?.line ?? ""
|
|
684
|
+
})),
|
|
685
|
+
...getScanDiagnostics(result).map((diagnostic) => ({
|
|
686
|
+
accountId: "",
|
|
687
|
+
column: "",
|
|
688
|
+
line: "",
|
|
689
|
+
message: diagnostic.message,
|
|
690
|
+
path: "",
|
|
691
|
+
provider: diagnostic.provider,
|
|
692
|
+
region: diagnostic.region ?? "",
|
|
693
|
+
resourceId: "",
|
|
694
|
+
ruleId: "",
|
|
695
|
+
service: diagnostic.service,
|
|
696
|
+
source: diagnostic.source
|
|
697
|
+
}))
|
|
698
|
+
];
|
|
654
699
|
var renderTextRows = (rows, columns, emptyMessage) => {
|
|
655
700
|
if (rows.length === 0) {
|
|
656
701
|
return emptyMessage;
|
|
@@ -822,6 +867,69 @@ var parseRuleIdList = (value) => {
|
|
|
822
867
|
};
|
|
823
868
|
|
|
824
869
|
// src/commands/discover.ts
|
|
870
|
+
var describeDiscoverySummary = (status) => {
|
|
871
|
+
const aggregatorSummary = status.aggregatorRegion ? ` Aggregator region: ${status.aggregatorRegion}.` : "";
|
|
872
|
+
const warningSummary = status.warning ? ` ${status.warning}` : "";
|
|
873
|
+
return `Coverage: ${status.coverage}. Indexed ${status.indexedRegionCount} of ${status.totalRegionCount} enabled regions.${aggregatorSummary}${warningSummary}`.trim();
|
|
874
|
+
};
|
|
875
|
+
var describeInitializationMessage = (result) => {
|
|
876
|
+
const baseMessage = result.indexType === "aggregator" ? result.aggregatorAction === "promoted" ? `Promoted the existing local Resource Explorer index in ${result.aggregatorRegion} to the aggregator.` : result.aggregatorAction === "created" ? `Configured ${result.aggregatorRegion} as the Resource Explorer aggregator.` : result.coverage === "full" ? `Resource Explorer aggregator already exists in ${result.aggregatorRegion}.` : `Resource Explorer aggregator already exists in ${result.aggregatorRegion}, but only ${result.observedStatus.indexedRegionCount} of ${result.observedStatus.totalRegionCount} regions are indexed.` : result.status === "EXISTING" ? `Local Resource Explorer setup already exists in ${result.aggregatorRegion}.` : `Local Resource Explorer setup created in ${result.aggregatorRegion}.`;
|
|
877
|
+
const indexSummaryParts = [];
|
|
878
|
+
if (result.createdIndexCount > 0) {
|
|
879
|
+
indexSummaryParts.push(
|
|
880
|
+
`Created ${result.createdIndexCount} ${result.createdIndexCount === 1 ? "index" : "indexes"}.`
|
|
881
|
+
);
|
|
882
|
+
}
|
|
883
|
+
if (result.reusedIndexCount > 0) {
|
|
884
|
+
indexSummaryParts.push(
|
|
885
|
+
`Reused ${result.reusedIndexCount} existing ${result.reusedIndexCount === 1 ? "index" : "indexes"}.`
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
const indexSummary = indexSummaryParts.length === 0 ? "" : ` ${indexSummaryParts.join(" ")}`;
|
|
889
|
+
const warnings = Array.from(new Set([result.warning, result.observedStatus.warning].filter(Boolean)));
|
|
890
|
+
const warning = warnings.length === 0 ? "" : ` ${warnings.join(" ")}`;
|
|
891
|
+
const convergenceNotice = result.verificationStatus === "timed_out" ? " Setup is still converging in AWS, so the observed coverage may still change." : "";
|
|
892
|
+
return `${baseMessage}${indexSummary}${warning}${convergenceNotice}`.trim();
|
|
893
|
+
};
|
|
894
|
+
var buildInitializationStatusData = (result, message, format) => {
|
|
895
|
+
const restrictedRegionCount = Math.max(
|
|
896
|
+
0,
|
|
897
|
+
result.observedStatus.totalRegionCount - result.observedStatus.accessibleRegionCount
|
|
898
|
+
);
|
|
899
|
+
if (format === "json") {
|
|
900
|
+
return {
|
|
901
|
+
aggregatorAction: result.aggregatorAction,
|
|
902
|
+
aggregatorRegion: result.aggregatorRegion,
|
|
903
|
+
coverage: result.coverage,
|
|
904
|
+
createdIndexCount: result.createdIndexCount,
|
|
905
|
+
indexType: result.indexType,
|
|
906
|
+
message,
|
|
907
|
+
observedStatus: result.observedStatus,
|
|
908
|
+
regions: result.regions,
|
|
909
|
+
reusedIndexCount: result.reusedIndexCount,
|
|
910
|
+
status: result.status,
|
|
911
|
+
taskId: result.taskId ?? "",
|
|
912
|
+
verificationStatus: result.verificationStatus,
|
|
913
|
+
...result.warning ? { warning: result.warning } : {}
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
return {
|
|
917
|
+
aggregatorAction: result.aggregatorAction,
|
|
918
|
+
aggregatorRegion: result.aggregatorRegion,
|
|
919
|
+
coverage: result.coverage,
|
|
920
|
+
createdIndexes: String(result.createdIndexCount),
|
|
921
|
+
details: "Run `cloudburn discover status` for per-region details.",
|
|
922
|
+
indexedRegions: result.regions.length === 0 ? "none" : result.regions.join(", "),
|
|
923
|
+
indexedSummary: `${result.observedStatus.indexedRegionCount} of ${result.observedStatus.totalRegionCount}`,
|
|
924
|
+
indexType: result.indexType,
|
|
925
|
+
message,
|
|
926
|
+
reusedIndexes: String(result.reusedIndexCount),
|
|
927
|
+
...restrictedRegionCount > 0 ? { restrictedRegions: String(restrictedRegionCount) } : {},
|
|
928
|
+
status: result.status,
|
|
929
|
+
taskId: result.taskId ?? "",
|
|
930
|
+
verificationStatus: result.verificationStatus
|
|
931
|
+
};
|
|
932
|
+
};
|
|
825
933
|
var parseAwsRegion = (value) => {
|
|
826
934
|
try {
|
|
827
935
|
return assertValidAwsRegion(value);
|
|
@@ -860,9 +968,17 @@ var registerDiscoverCommand = (program) => {
|
|
|
860
968
|
const discoverCommand = setCommandExamples(
|
|
861
969
|
program.command("discover").description("Run a live AWS discovery").enablePositionalOptions().option(
|
|
862
970
|
"--region <region>",
|
|
863
|
-
'Discovery region to use. Pass "all" to
|
|
971
|
+
'Discovery region to use. Defaults to the current AWS region from AWS_REGION; use this flag to override it. Pass "all" to check resources in all regions that are indexed in AWS Resource Explorer.',
|
|
864
972
|
parseDiscoverRegion
|
|
865
|
-
).option("--config <path>", "Explicit CloudBurn config file to load").option(
|
|
973
|
+
).option("--config <path>", "Explicit CloudBurn config file to load").option(
|
|
974
|
+
"--enabled-rules <ruleIds>",
|
|
975
|
+
"Comma-separated rule IDs to enable. When set, CloudBurn checks only these rules. By default, all rules are enabled.",
|
|
976
|
+
parseRuleIdList
|
|
977
|
+
).option(
|
|
978
|
+
"--disabled-rules <ruleIds>",
|
|
979
|
+
"Comma-separated rule IDs to disable. By default, all rules are enabled; use this to exclude specific rules.",
|
|
980
|
+
parseRuleIdList
|
|
981
|
+
).option("--exit-code", "Exit with code 1 when findings exist").action(async (options, command) => {
|
|
866
982
|
await runCommand(async () => {
|
|
867
983
|
const scanner = new CloudBurnClient();
|
|
868
984
|
const loadedConfig = await scanner.loadConfig(options.config);
|
|
@@ -891,10 +1007,64 @@ var registerDiscoverCommand = (program) => {
|
|
|
891
1007
|
"cloudburn discover",
|
|
892
1008
|
"cloudburn discover --region eu-central-1",
|
|
893
1009
|
"cloudburn discover --region all",
|
|
1010
|
+
"cloudburn discover status",
|
|
894
1011
|
"cloudburn discover list-enabled-regions",
|
|
895
1012
|
"cloudburn discover init"
|
|
896
1013
|
]
|
|
897
1014
|
);
|
|
1015
|
+
discoverCommand.command("status").description("Show Resource Explorer status across all enabled AWS regions").action(async (_options, command) => {
|
|
1016
|
+
await runCommand(async () => {
|
|
1017
|
+
const scanner = new CloudBurnClient();
|
|
1018
|
+
const parentRegion = discoverCommand.opts().region;
|
|
1019
|
+
const region = parentRegion === "all" ? void 0 : parentRegion;
|
|
1020
|
+
const status = await scanner.getDiscoveryStatus({ region });
|
|
1021
|
+
const format = resolveOutputFormat(command);
|
|
1022
|
+
const rows = format === "json" ? status.regions.map((regionStatus) => ({
|
|
1023
|
+
...regionStatus,
|
|
1024
|
+
notes: regionStatus.notes ?? ""
|
|
1025
|
+
})) : status.regions.map((regionStatus) => ({
|
|
1026
|
+
region: regionStatus.region,
|
|
1027
|
+
indexType: regionStatus.indexType === void 0 ? "" : regionStatus.isAggregator ? `${regionStatus.indexType} (active)` : regionStatus.indexType,
|
|
1028
|
+
notes: regionStatus.notes ?? "",
|
|
1029
|
+
status: regionStatus.status,
|
|
1030
|
+
viewStatus: regionStatus.viewStatus ?? ""
|
|
1031
|
+
}));
|
|
1032
|
+
const summary = format === "json" ? {
|
|
1033
|
+
accessibleRegionCount: status.accessibleRegionCount,
|
|
1034
|
+
coverage: status.coverage,
|
|
1035
|
+
indexedRegionCount: status.indexedRegionCount,
|
|
1036
|
+
totalRegionCount: status.totalRegionCount,
|
|
1037
|
+
...status.aggregatorRegion ? { aggregatorRegion: status.aggregatorRegion } : {},
|
|
1038
|
+
...status.warning ? { warning: status.warning } : {}
|
|
1039
|
+
} : {
|
|
1040
|
+
accessibleRegionCount: status.accessibleRegionCount,
|
|
1041
|
+
aggregatorRegion: status.aggregatorRegion ?? "",
|
|
1042
|
+
coverage: status.coverage,
|
|
1043
|
+
indexedRegionCount: status.indexedRegionCount,
|
|
1044
|
+
totalRegionCount: status.totalRegionCount,
|
|
1045
|
+
...status.warning ? { warning: status.warning } : {}
|
|
1046
|
+
};
|
|
1047
|
+
const output = renderResponse(
|
|
1048
|
+
{
|
|
1049
|
+
kind: "discovery-status",
|
|
1050
|
+
columns: [
|
|
1051
|
+
{ key: "region", header: "Region" },
|
|
1052
|
+
{ key: "indexType", header: "IndexType" },
|
|
1053
|
+
{ key: "status", header: "Status" },
|
|
1054
|
+
{ key: "viewStatus", header: "ViewStatus" },
|
|
1055
|
+
{ key: "notes", header: "Notes" }
|
|
1056
|
+
],
|
|
1057
|
+
rows,
|
|
1058
|
+
summary,
|
|
1059
|
+
summaryText: describeDiscoverySummary(status)
|
|
1060
|
+
},
|
|
1061
|
+
format
|
|
1062
|
+
);
|
|
1063
|
+
process.stdout.write(`${output}
|
|
1064
|
+
`);
|
|
1065
|
+
return EXIT_CODE_OK;
|
|
1066
|
+
});
|
|
1067
|
+
});
|
|
898
1068
|
discoverCommand.command("list-enabled-regions").description("List AWS regions with a local or aggregator Resource Explorer index").action(async (_options, command) => {
|
|
899
1069
|
await runCommand(async () => {
|
|
900
1070
|
const scanner = new CloudBurnClient();
|
|
@@ -917,24 +1087,22 @@ var registerDiscoverCommand = (program) => {
|
|
|
917
1087
|
return EXIT_CODE_OK;
|
|
918
1088
|
});
|
|
919
1089
|
});
|
|
920
|
-
discoverCommand.command("init").description("Set up AWS Resource Explorer for CloudBurn").option(
|
|
1090
|
+
discoverCommand.command("init").description("Set up AWS Resource Explorer for CloudBurn").option(
|
|
1091
|
+
"--region <region>",
|
|
1092
|
+
"Requested aggregator region to create or reuse during setup. This is the main Resource Explorer region that aggregates indexes from other regions when cross-region setup succeeds. Defaults to the current AWS region from AWS_REGION; use this flag to override it.",
|
|
1093
|
+
parseAwsRegion
|
|
1094
|
+
).action(async (options, command) => {
|
|
921
1095
|
await runCommand(async () => {
|
|
922
1096
|
const scanner = new CloudBurnClient();
|
|
923
1097
|
const parentRegion = discoverCommand.opts().region;
|
|
924
1098
|
const region = options.region ?? (parentRegion === "all" ? void 0 : parentRegion);
|
|
925
1099
|
const result = await scanner.initializeDiscovery({ region });
|
|
926
|
-
const message = result
|
|
1100
|
+
const message = describeInitializationMessage(result);
|
|
927
1101
|
const format = resolveOutputFormat(command);
|
|
928
1102
|
const output = renderResponse(
|
|
929
1103
|
{
|
|
930
1104
|
kind: "status",
|
|
931
|
-
data:
|
|
932
|
-
aggregatorRegion: result.aggregatorRegion,
|
|
933
|
-
message,
|
|
934
|
-
regions: result.regions,
|
|
935
|
-
status: result.status,
|
|
936
|
-
taskId: result.taskId ?? ""
|
|
937
|
-
},
|
|
1105
|
+
data: buildInitializationStatusData(result, message, format),
|
|
938
1106
|
text: message
|
|
939
1107
|
},
|
|
940
1108
|
format
|
|
@@ -1145,7 +1313,15 @@ var toScanConfigOverride = (options) => {
|
|
|
1145
1313
|
};
|
|
1146
1314
|
var registerScanCommand = (program) => {
|
|
1147
1315
|
setCommandExamples(
|
|
1148
|
-
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(
|
|
1316
|
+
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(
|
|
1317
|
+
"--enabled-rules <ruleIds>",
|
|
1318
|
+
"Comma-separated rule IDs to enable. When set, CloudBurn checks only these rules. By default, all rules are enabled.",
|
|
1319
|
+
parseRuleIdList
|
|
1320
|
+
).option(
|
|
1321
|
+
"--disabled-rules <ruleIds>",
|
|
1322
|
+
"Comma-separated rule IDs to disable. By default, all rules are enabled; use this to exclude specific rules.",
|
|
1323
|
+
parseRuleIdList
|
|
1324
|
+
).option("--exit-code", "Exit with code 1 when findings exist").action(async (path, options, command) => {
|
|
1149
1325
|
try {
|
|
1150
1326
|
const scanner = new CloudBurnClient2();
|
|
1151
1327
|
const loadedConfig = await scanner.loadConfig(options.config);
|
|
@@ -1172,9 +1348,22 @@ var registerScanCommand = (program) => {
|
|
|
1172
1348
|
};
|
|
1173
1349
|
|
|
1174
1350
|
// src/cli.ts
|
|
1351
|
+
var resolveEntrypointPath = (entrypointPath) => {
|
|
1352
|
+
try {
|
|
1353
|
+
return realpathSync.native(entrypointPath);
|
|
1354
|
+
} catch {
|
|
1355
|
+
return resolve2(entrypointPath);
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
var isCliEntrypoint = (moduleUrl, argvEntry = process.argv[1]) => {
|
|
1359
|
+
if (argvEntry === void 0) {
|
|
1360
|
+
return false;
|
|
1361
|
+
}
|
|
1362
|
+
return resolveEntrypointPath(fileURLToPath(moduleUrl)) === resolveEntrypointPath(argvEntry);
|
|
1363
|
+
};
|
|
1175
1364
|
var createProgram = () => {
|
|
1176
1365
|
const program = createCliCommand();
|
|
1177
|
-
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.8.
|
|
1366
|
+
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.8.4").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
|
|
1178
1367
|
configureCliHelp(program);
|
|
1179
1368
|
registerCompletionCommand(program);
|
|
1180
1369
|
registerDiscoverCommand(program);
|
|
@@ -1187,11 +1376,11 @@ var createProgram = () => {
|
|
|
1187
1376
|
var runCli = async () => {
|
|
1188
1377
|
await createProgram().parseAsync(process.argv);
|
|
1189
1378
|
};
|
|
1190
|
-
|
|
1191
|
-
if (isMain) {
|
|
1379
|
+
if (isCliEntrypoint(import.meta.url)) {
|
|
1192
1380
|
await runCli();
|
|
1193
1381
|
}
|
|
1194
1382
|
export {
|
|
1195
1383
|
createProgram,
|
|
1384
|
+
isCliEntrypoint,
|
|
1196
1385
|
runCli
|
|
1197
1386
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudburn",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.4",
|
|
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.13.
|
|
14
|
+
"@cloudburn/sdk": "0.13.2"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
17
|
"@biomejs/biome": "^2.4.6",
|