cloudburn 0.9.4 → 0.9.5
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 +8 -4
- package/dist/cli.js +200 -124
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -28,16 +28,20 @@ Config is optional. By default, CloudBurn runs all checks for the mode you use.
|
|
|
28
28
|
Create a starter config with:
|
|
29
29
|
|
|
30
30
|
```bash
|
|
31
|
-
cloudburn init
|
|
31
|
+
cloudburn config --init
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
If you want to
|
|
34
|
+
If you want to print the current discovered config file:
|
|
35
35
|
|
|
36
36
|
```bash
|
|
37
|
-
cloudburn
|
|
37
|
+
cloudburn config --print
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
If you want to inspect the starter template without writing a file:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
cloudburn config --print-template
|
|
44
|
+
```
|
|
41
45
|
|
|
42
46
|
### Scan
|
|
43
47
|
|
package/dist/cli.js
CHANGED
|
@@ -467,9 +467,9 @@ var registerCompletionCommand = (program) => {
|
|
|
467
467
|
});
|
|
468
468
|
};
|
|
469
469
|
|
|
470
|
-
// src/commands/
|
|
471
|
-
import {
|
|
472
|
-
import {
|
|
470
|
+
// src/commands/config.ts
|
|
471
|
+
import { access, readFile, writeFile } from "fs/promises";
|
|
472
|
+
import { dirname, join, resolve } from "path";
|
|
473
473
|
|
|
474
474
|
// src/exit-codes.ts
|
|
475
475
|
var EXIT_CODE_OK = 0;
|
|
@@ -831,6 +831,201 @@ var renderAsciiTable = (rows, columns) => {
|
|
|
831
831
|
return [border, ...header, border, ...body, border].join("\n");
|
|
832
832
|
};
|
|
833
833
|
|
|
834
|
+
// src/commands/config.ts
|
|
835
|
+
var CONFIG_FILENAMES = [".cloudburn.yml", ".cloudburn.yaml"];
|
|
836
|
+
var starterConfig = `# Static IaC scan configuration.
|
|
837
|
+
# enabled-rules restricts scans to only the listed rule IDs.
|
|
838
|
+
# disabled-rules removes specific rule IDs from the active set.
|
|
839
|
+
# services restricts scans to rules for the listed services.
|
|
840
|
+
# format sets the default output format when --format is not passed.
|
|
841
|
+
iac:
|
|
842
|
+
enabled-rules:
|
|
843
|
+
- CLDBRN-AWS-EBS-1
|
|
844
|
+
disabled-rules:
|
|
845
|
+
- CLDBRN-AWS-EC2-2
|
|
846
|
+
services:
|
|
847
|
+
- ebs
|
|
848
|
+
- ec2
|
|
849
|
+
format: table
|
|
850
|
+
|
|
851
|
+
# Live AWS discovery configuration.
|
|
852
|
+
# Use the same rule controls here to tune discover runs separately from IaC scans.
|
|
853
|
+
discovery:
|
|
854
|
+
enabled-rules:
|
|
855
|
+
- CLDBRN-AWS-EBS-1
|
|
856
|
+
disabled-rules:
|
|
857
|
+
- CLDBRN-AWS-S3-1
|
|
858
|
+
services:
|
|
859
|
+
- ebs
|
|
860
|
+
- s3
|
|
861
|
+
format: json
|
|
862
|
+
`;
|
|
863
|
+
var resolveExplicitOutputFormat = (command) => {
|
|
864
|
+
const options = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : command.opts();
|
|
865
|
+
return options.format;
|
|
866
|
+
};
|
|
867
|
+
var renderConfigDocument = (command, content) => {
|
|
868
|
+
const explicitFormat = resolveExplicitOutputFormat(command);
|
|
869
|
+
if (explicitFormat === void 0) {
|
|
870
|
+
return content;
|
|
871
|
+
}
|
|
872
|
+
return renderResponse(
|
|
873
|
+
{
|
|
874
|
+
kind: "document",
|
|
875
|
+
content,
|
|
876
|
+
contentType: "application/yaml"
|
|
877
|
+
},
|
|
878
|
+
explicitFormat
|
|
879
|
+
);
|
|
880
|
+
};
|
|
881
|
+
var fileExists = async (path) => {
|
|
882
|
+
try {
|
|
883
|
+
await access(path);
|
|
884
|
+
return true;
|
|
885
|
+
} catch {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
};
|
|
889
|
+
var ensureSingleConfigFile = async (directory) => {
|
|
890
|
+
const configPaths = await Promise.all(
|
|
891
|
+
CONFIG_FILENAMES.map(async (filename) => {
|
|
892
|
+
const path = join(directory, filename);
|
|
893
|
+
return await fileExists(path) ? path : void 0;
|
|
894
|
+
})
|
|
895
|
+
);
|
|
896
|
+
const existingPaths = configPaths.filter((path) => path !== void 0);
|
|
897
|
+
if (existingPaths.length > 1) {
|
|
898
|
+
throw new Error("Found both .cloudburn.yml and .cloudburn.yaml in the same directory.");
|
|
899
|
+
}
|
|
900
|
+
return existingPaths[0];
|
|
901
|
+
};
|
|
902
|
+
var isGitRoot = async (directory) => fileExists(join(directory, ".git"));
|
|
903
|
+
var findProjectRoot = async (startDirectory) => {
|
|
904
|
+
let currentDirectory = resolve(startDirectory);
|
|
905
|
+
while (true) {
|
|
906
|
+
if (await isGitRoot(currentDirectory)) {
|
|
907
|
+
return currentDirectory;
|
|
908
|
+
}
|
|
909
|
+
const parentDirectory = dirname(currentDirectory);
|
|
910
|
+
if (parentDirectory === currentDirectory) {
|
|
911
|
+
return resolve(startDirectory);
|
|
912
|
+
}
|
|
913
|
+
currentDirectory = parentDirectory;
|
|
914
|
+
}
|
|
915
|
+
};
|
|
916
|
+
var findConfigPath = async (startDirectory) => {
|
|
917
|
+
let currentDirectory = resolve(startDirectory);
|
|
918
|
+
while (true) {
|
|
919
|
+
const configPath = await ensureSingleConfigFile(currentDirectory);
|
|
920
|
+
if (configPath) {
|
|
921
|
+
return configPath;
|
|
922
|
+
}
|
|
923
|
+
const parentDirectory = dirname(currentDirectory);
|
|
924
|
+
if (await isGitRoot(currentDirectory) || parentDirectory === currentDirectory) {
|
|
925
|
+
return void 0;
|
|
926
|
+
}
|
|
927
|
+
currentDirectory = parentDirectory;
|
|
928
|
+
}
|
|
929
|
+
};
|
|
930
|
+
var validateRequestedAction = (options) => {
|
|
931
|
+
const actions = [options.init, options.print, options.printTemplate].filter(Boolean);
|
|
932
|
+
if (actions.length !== 1) {
|
|
933
|
+
throw new Error("Choose exactly one action: --init, --print, or --print-template.");
|
|
934
|
+
}
|
|
935
|
+
if (options.path && options.printTemplate) {
|
|
936
|
+
throw new Error("--path can only be used with --init or --print.");
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
var resolvePrintPath = async (pathOption) => {
|
|
940
|
+
if (pathOption !== void 0) {
|
|
941
|
+
const explicitPath = resolve(pathOption);
|
|
942
|
+
if (!await fileExists(explicitPath)) {
|
|
943
|
+
throw new Error(`CloudBurn config file not found: ${explicitPath}`);
|
|
944
|
+
}
|
|
945
|
+
return explicitPath;
|
|
946
|
+
}
|
|
947
|
+
const discoveredPath = await findConfigPath(process.cwd());
|
|
948
|
+
if (discoveredPath === void 0) {
|
|
949
|
+
throw new Error(
|
|
950
|
+
'No CloudBurn config file found. Run "cloudburn config --init" to create one or "cloudburn config --print-template" to inspect the starter template.'
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
return discoveredPath;
|
|
954
|
+
};
|
|
955
|
+
var resolveInitPath = async (pathOption) => {
|
|
956
|
+
if (pathOption !== void 0) {
|
|
957
|
+
return resolve(pathOption);
|
|
958
|
+
}
|
|
959
|
+
return join(await findProjectRoot(process.cwd()), ".cloudburn.yml");
|
|
960
|
+
};
|
|
961
|
+
var registerConfigCommand = (program) => {
|
|
962
|
+
setCommandExamples(
|
|
963
|
+
program.command("config").description("Inspect or create CloudBurn configuration").option("--init", "Create a starter CloudBurn config file").option("--print", "Print the current CloudBurn config file").option("--print-template", "Print the starter CloudBurn config template").option("--path <path>", "Use an explicit config file path with --init or --print").action(async function(options) {
|
|
964
|
+
try {
|
|
965
|
+
validateRequestedAction(options);
|
|
966
|
+
if (options.printTemplate) {
|
|
967
|
+
process.stdout.write(`${renderConfigDocument(this, starterConfig)}
|
|
968
|
+
`);
|
|
969
|
+
process.exitCode = EXIT_CODE_OK;
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (options.print) {
|
|
973
|
+
const configPath2 = await resolvePrintPath(options.path);
|
|
974
|
+
const content = await readFile(configPath2, "utf8");
|
|
975
|
+
process.stdout.write(`${renderConfigDocument(this, content)}
|
|
976
|
+
`);
|
|
977
|
+
process.exitCode = EXIT_CODE_OK;
|
|
978
|
+
return;
|
|
979
|
+
}
|
|
980
|
+
const configPath = await resolveInitPath(options.path);
|
|
981
|
+
if (options.path === void 0) {
|
|
982
|
+
const existingConfigPath = await ensureSingleConfigFile(dirname(configPath));
|
|
983
|
+
if (existingConfigPath) {
|
|
984
|
+
throw new Error(
|
|
985
|
+
`CloudBurn config already exists at ${existingConfigPath}. Use --print to inspect the current config.`
|
|
986
|
+
);
|
|
987
|
+
}
|
|
988
|
+
} else {
|
|
989
|
+
const existingConfigPath = await ensureSingleConfigFile(dirname(configPath));
|
|
990
|
+
if (existingConfigPath) {
|
|
991
|
+
throw new Error(
|
|
992
|
+
`CloudBurn config already exists at ${existingConfigPath}. Use --print to inspect the current config.`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
if (await fileExists(configPath)) {
|
|
996
|
+
throw new Error(
|
|
997
|
+
`CloudBurn config already exists at ${configPath}. Use --print to inspect the current config.`
|
|
998
|
+
);
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
await writeFile(configPath, starterConfig, { encoding: "utf8", flag: "wx" });
|
|
1002
|
+
const output = renderResponse(
|
|
1003
|
+
{
|
|
1004
|
+
kind: "status",
|
|
1005
|
+
data: {
|
|
1006
|
+
message: "Created CloudBurn config.",
|
|
1007
|
+
path: configPath
|
|
1008
|
+
}
|
|
1009
|
+
},
|
|
1010
|
+
resolveExplicitOutputFormat(this) ?? "table"
|
|
1011
|
+
);
|
|
1012
|
+
process.stdout.write(`${output}
|
|
1013
|
+
`);
|
|
1014
|
+
process.exitCode = EXIT_CODE_OK;
|
|
1015
|
+
} catch (err) {
|
|
1016
|
+
process.stderr.write(`${formatError(err)}
|
|
1017
|
+
`);
|
|
1018
|
+
process.exitCode = EXIT_CODE_RUNTIME_ERROR;
|
|
1019
|
+
}
|
|
1020
|
+
}),
|
|
1021
|
+
["cloudburn config --init", "cloudburn config --print", "cloudburn config --print-template"]
|
|
1022
|
+
);
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// src/commands/discover.ts
|
|
1026
|
+
import { assertValidAwsRegion, CloudBurnClient } from "@cloudburn/sdk";
|
|
1027
|
+
import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
|
|
1028
|
+
|
|
834
1029
|
// src/commands/config-options.ts
|
|
835
1030
|
import { builtInRuleMetadata } from "@cloudburn/sdk";
|
|
836
1031
|
import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
|
|
@@ -1170,125 +1365,6 @@ var registerEstimateCommand = (program) => {
|
|
|
1170
1365
|
});
|
|
1171
1366
|
};
|
|
1172
1367
|
|
|
1173
|
-
// src/commands/init.ts
|
|
1174
|
-
import { access, writeFile } from "fs/promises";
|
|
1175
|
-
import { dirname, join, resolve } from "path";
|
|
1176
|
-
var CONFIG_FILENAMES = [".cloudburn.yml", ".cloudburn.yaml"];
|
|
1177
|
-
var starterConfig = `# Static IaC scan configuration.
|
|
1178
|
-
# enabled-rules restricts scans to only the listed rule IDs.
|
|
1179
|
-
# disabled-rules removes specific rule IDs from the active set.
|
|
1180
|
-
# services restricts scans to rules for the listed services.
|
|
1181
|
-
# format sets the default output format when --format is not passed.
|
|
1182
|
-
iac:
|
|
1183
|
-
enabled-rules:
|
|
1184
|
-
- CLDBRN-AWS-EBS-1
|
|
1185
|
-
disabled-rules:
|
|
1186
|
-
- CLDBRN-AWS-EC2-2
|
|
1187
|
-
services:
|
|
1188
|
-
- ebs
|
|
1189
|
-
- ec2
|
|
1190
|
-
format: table
|
|
1191
|
-
|
|
1192
|
-
# Live AWS discovery configuration.
|
|
1193
|
-
# Use the same rule controls here to tune discover runs separately from IaC scans.
|
|
1194
|
-
discovery:
|
|
1195
|
-
enabled-rules:
|
|
1196
|
-
- CLDBRN-AWS-EBS-1
|
|
1197
|
-
disabled-rules:
|
|
1198
|
-
- CLDBRN-AWS-S3-1
|
|
1199
|
-
services:
|
|
1200
|
-
- ebs
|
|
1201
|
-
- s3
|
|
1202
|
-
format: json
|
|
1203
|
-
`;
|
|
1204
|
-
var resolveExplicitOutputFormat = (command) => {
|
|
1205
|
-
const options = typeof command.optsWithGlobals === "function" ? command.optsWithGlobals() : command.opts();
|
|
1206
|
-
return options.format;
|
|
1207
|
-
};
|
|
1208
|
-
var renderStarterConfig = (command) => {
|
|
1209
|
-
const explicitFormat = resolveExplicitOutputFormat(command);
|
|
1210
|
-
if (explicitFormat === void 0) {
|
|
1211
|
-
return starterConfig;
|
|
1212
|
-
}
|
|
1213
|
-
return renderResponse(
|
|
1214
|
-
{
|
|
1215
|
-
kind: "document",
|
|
1216
|
-
content: starterConfig,
|
|
1217
|
-
contentType: "application/yaml"
|
|
1218
|
-
},
|
|
1219
|
-
explicitFormat
|
|
1220
|
-
);
|
|
1221
|
-
};
|
|
1222
|
-
var fileExists = async (path) => {
|
|
1223
|
-
try {
|
|
1224
|
-
await access(path);
|
|
1225
|
-
return true;
|
|
1226
|
-
} catch {
|
|
1227
|
-
return false;
|
|
1228
|
-
}
|
|
1229
|
-
};
|
|
1230
|
-
var findProjectRoot = async (startDirectory) => {
|
|
1231
|
-
let currentDirectory = resolve(startDirectory);
|
|
1232
|
-
while (true) {
|
|
1233
|
-
if (await fileExists(join(currentDirectory, ".git"))) {
|
|
1234
|
-
return currentDirectory;
|
|
1235
|
-
}
|
|
1236
|
-
const parentDirectory = dirname(currentDirectory);
|
|
1237
|
-
if (parentDirectory === currentDirectory) {
|
|
1238
|
-
return resolve(startDirectory);
|
|
1239
|
-
}
|
|
1240
|
-
currentDirectory = parentDirectory;
|
|
1241
|
-
}
|
|
1242
|
-
};
|
|
1243
|
-
var registerInitCommand = (program) => {
|
|
1244
|
-
const initCommand = program.command("init").description("Initialize CloudBurn scaffolding").usage("[command]").action(function() {
|
|
1245
|
-
process.stdout.write(`${renderStarterConfig(this)}
|
|
1246
|
-
`);
|
|
1247
|
-
process.exitCode = EXIT_CODE_OK;
|
|
1248
|
-
});
|
|
1249
|
-
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) {
|
|
1250
|
-
try {
|
|
1251
|
-
if (options.print) {
|
|
1252
|
-
process.stdout.write(`${renderStarterConfig(this)}
|
|
1253
|
-
`);
|
|
1254
|
-
process.exitCode = EXIT_CODE_OK;
|
|
1255
|
-
return;
|
|
1256
|
-
}
|
|
1257
|
-
const rootDirectory = await findProjectRoot(process.cwd());
|
|
1258
|
-
const existingConfigPath = (await Promise.all(
|
|
1259
|
-
CONFIG_FILENAMES.map(async (filename) => {
|
|
1260
|
-
const path = join(rootDirectory, filename);
|
|
1261
|
-
return await fileExists(path) ? path : void 0;
|
|
1262
|
-
})
|
|
1263
|
-
)).find((path) => path !== void 0);
|
|
1264
|
-
if (existingConfigPath) {
|
|
1265
|
-
throw new Error(
|
|
1266
|
-
`CloudBurn config already exists at ${existingConfigPath}. Use --print to inspect the template.`
|
|
1267
|
-
);
|
|
1268
|
-
}
|
|
1269
|
-
const configPath = join(rootDirectory, ".cloudburn.yml");
|
|
1270
|
-
await writeFile(configPath, starterConfig, { encoding: "utf8", flag: "wx" });
|
|
1271
|
-
const output = renderResponse(
|
|
1272
|
-
{
|
|
1273
|
-
kind: "status",
|
|
1274
|
-
data: {
|
|
1275
|
-
message: "Created CloudBurn config.",
|
|
1276
|
-
path: configPath
|
|
1277
|
-
}
|
|
1278
|
-
},
|
|
1279
|
-
resolveOutputFormat(this)
|
|
1280
|
-
);
|
|
1281
|
-
process.stdout.write(`${output}
|
|
1282
|
-
`);
|
|
1283
|
-
process.exitCode = EXIT_CODE_OK;
|
|
1284
|
-
} catch (err) {
|
|
1285
|
-
process.stderr.write(`${formatError(err)}
|
|
1286
|
-
`);
|
|
1287
|
-
process.exitCode = EXIT_CODE_RUNTIME_ERROR;
|
|
1288
|
-
}
|
|
1289
|
-
});
|
|
1290
|
-
};
|
|
1291
|
-
|
|
1292
1368
|
// src/commands/rules-list.ts
|
|
1293
1369
|
import { builtInRuleMetadata as builtInRuleMetadata2 } from "@cloudburn/sdk";
|
|
1294
1370
|
import { InvalidArgumentError as InvalidArgumentError4 } from "commander";
|
|
@@ -1398,12 +1474,12 @@ var isCliEntrypoint = (moduleUrl, argvEntry = process.argv[1]) => {
|
|
|
1398
1474
|
};
|
|
1399
1475
|
var createProgram = () => {
|
|
1400
1476
|
const program = createCliCommand();
|
|
1401
|
-
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.9.
|
|
1477
|
+
program.name("cloudburn").usage("[command]").description("Know what you spend. Fix what you waste.").version("0.9.5").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
|
|
1402
1478
|
configureCliHelp(program);
|
|
1403
1479
|
registerCompletionCommand(program);
|
|
1480
|
+
registerConfigCommand(program);
|
|
1404
1481
|
registerDiscoverCommand(program);
|
|
1405
1482
|
registerScanCommand(program);
|
|
1406
|
-
registerInitCommand(program);
|
|
1407
1483
|
registerRulesListCommand(program);
|
|
1408
1484
|
registerEstimateCommand(program);
|
|
1409
1485
|
return program;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cloudburn",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.5",
|
|
4
4
|
"description": "Cloudburn CLI for cloud cost optimization",
|
|
5
5
|
"homepage": "https://cloudburn.io/docs",
|
|
6
6
|
"bugs": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
],
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"commander": "^13.1.0",
|
|
23
|
-
"@cloudburn/sdk": "0.
|
|
23
|
+
"@cloudburn/sdk": "0.18.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
26
|
"@biomejs/biome": "^2.4.6",
|