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.
Files changed (3) hide show
  1. package/README.md +8 -4
  2. package/dist/cli.js +200 -124
  3. 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 config
31
+ cloudburn config --init
32
32
  ```
33
33
 
34
- If you want to inspect the generated YAML first:
34
+ If you want to print the current discovered config file:
35
35
 
36
36
  ```bash
37
- cloudburn init config --print
37
+ cloudburn config --print
38
38
  ```
39
39
 
40
- `cloudburn init` still prints the starter YAML directly if you want a quick redirect-friendly version.
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/discover.ts
471
- import { assertValidAwsRegion, CloudBurnClient } from "@cloudburn/sdk";
472
- import { InvalidArgumentError as InvalidArgumentError3 } from "commander";
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.4").option("--format <format>", OUTPUT_FORMAT_OPTION_DESCRIPTION, parseOutputFormat);
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.4",
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.17.2"
23
+ "@cloudburn/sdk": "0.18.0"
24
24
  },
25
25
  "devDependencies": {
26
26
  "@biomejs/biome": "^2.4.6",