cloudburn 0.4.1 → 0.5.1

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 +2 -1
  2. package/dist/cli.js +177 -56
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -21,7 +21,8 @@ directory path you pass to `cloudburn scan`.
21
21
  cloudburn scan ./main.tf
22
22
  cloudburn scan ./template.yaml
23
23
  cloudburn scan ./iac
24
- cloudburn scan --live
24
+ cloudburn discover
25
+ cloudburn discover --region all
25
26
  ```
26
27
 
27
28
  `cloudburn scan --format json` emits the lean canonical grouped result:
package/dist/cli.js CHANGED
@@ -4,55 +4,11 @@
4
4
  import { pathToFileURL } from "url";
5
5
  import { Command } from "commander";
6
6
 
7
- // src/commands/estimate.ts
8
- var registerEstimateCommand = (program) => {
9
- program.command("estimate").description("Request optional pricing estimates from a self-hosted dashboard").option("--server <url>", "Dashboard API base URL").action((options) => {
10
- if (!options.server) {
11
- process.stdout.write("No server configured. This command is optional and requires a dashboard URL.\n");
12
- return;
13
- }
14
- process.stdout.write(`Estimate request scaffold ready for server: ${options.server}
15
- `);
16
- });
17
- };
18
-
19
- // src/commands/init.ts
20
- var starterConfig = `version: 1
21
- profile: dev
22
-
23
- profiles:
24
- dev:
25
- ec2-allowed-instance-types:
26
- allow: [t3.micro, t3.small, t3.medium]
27
-
28
- rules:
29
- ec2-allowed-instance-types:
30
- severity: error
31
-
32
- live:
33
- tags:
34
- Project: myapp
35
- regions: [us-east-1]
36
- `;
37
- var registerInitCommand = (program) => {
38
- program.command("init").description("Print a starter .cloudburn.yml configuration").action(() => {
39
- process.stdout.write(`${starterConfig}
40
- `);
41
- });
42
- };
43
-
44
- // src/commands/rules-list.ts
45
- import { awsCorePreset } from "@cloudburn/sdk";
46
- var registerRulesListCommand = (program) => {
47
- const rulesCommand = program.command("rules").description("Inspect built-in and custom rules");
48
- rulesCommand.command("list").description("List built-in CloudBurn rule IDs").action(() => {
49
- process.stdout.write(`${awsCorePreset.ruleIds.join("\n")}
50
- `);
51
- });
52
- };
53
-
54
- // src/commands/scan.ts
55
- import { CloudBurnScanner } from "@cloudburn/sdk";
7
+ // src/commands/discover.ts
8
+ import {
9
+ assertValidAwsRegion,
10
+ CloudBurnClient
11
+ } from "@cloudburn/sdk";
56
12
  import { InvalidArgumentError } from "commander";
57
13
 
58
14
  // src/exit-codes.ts
@@ -61,6 +17,11 @@ var EXIT_CODE_POLICY_VIOLATION = 1;
61
17
  var EXIT_CODE_RUNTIME_ERROR = 2;
62
18
 
63
19
  // src/formatters/error.ts
20
+ import { isAwsDiscoveryErrorCode } from "@cloudburn/sdk";
21
+ var sanitizeRuntimeErrorMessage = (message) => message.replace(/169\.254\.169\.254/g, "[redacted-host]").replace(/fd00:ec2::254/gi, "[redacted-host]").replace(/(https?:\/\/)([^/\s:@]+):([^/\s@]+)@/gi, "$1[redacted-auth]@").replace(
22
+ /([?&](?:access_token|authorization|token|x-amz-security-token|x-amz-signature|signature)=)[^&\s]+/gi,
23
+ "$1[redacted]"
24
+ ).replace(/(Bearer\s+)[A-Za-z0-9._~+/-]+/gi, "$1[redacted]");
64
25
  var formatError = (err) => {
65
26
  const envelope = { error: categorize(err) };
66
27
  return JSON.stringify(envelope, null, 2);
@@ -85,7 +46,17 @@ var categorize = (err) => {
85
46
  const path = err.path ?? "unknown";
86
47
  return { code: "PATH_NOT_FOUND", message: `Path not found: ${path}` };
87
48
  }
88
- return { code: "RUNTIME_ERROR", message: err.message || "An unexpected error occurred." };
49
+ if ("code" in err && typeof err.code === "string" && isAwsDiscoveryErrorCode(err.code)) {
50
+ return {
51
+ code: err.code,
52
+ message: sanitizeRuntimeErrorMessage(err.message).trim() || "AWS Resource Explorer discovery failed."
53
+ };
54
+ }
55
+ const sanitizedMessage = sanitizeRuntimeErrorMessage(err.message).trim();
56
+ return {
57
+ code: "RUNTIME_ERROR",
58
+ message: sanitizedMessage || "An unexpected error occurred."
59
+ };
89
60
  };
90
61
 
91
62
  // src/formatters/json.ts
@@ -163,13 +134,163 @@ var formatTable = (result) => {
163
134
  }).join("\n");
164
135
  };
165
136
 
137
+ // src/commands/discover.ts
138
+ var supportedDiscoverFormats = ["table", "json", "sarif"];
139
+ var parseDiscoverFormat = (value) => {
140
+ if (supportedDiscoverFormats.includes(value)) {
141
+ return value;
142
+ }
143
+ throw new InvalidArgumentError(`Invalid format "${value}". Allowed formats: ${supportedDiscoverFormats.join(", ")}.`);
144
+ };
145
+ var parseDiscoverListFormat = (value) => {
146
+ if (value === "table" || value === "json") {
147
+ return value;
148
+ }
149
+ throw new InvalidArgumentError("Invalid format. Allowed formats: table, json.");
150
+ };
151
+ var parseAwsRegion = (value) => {
152
+ try {
153
+ return assertValidAwsRegion(value);
154
+ } catch (err) {
155
+ throw new InvalidArgumentError(err instanceof Error ? err.message : "Invalid AWS region.");
156
+ }
157
+ };
158
+ var parseDiscoverRegion = (value) => {
159
+ if (value === "all") {
160
+ return value;
161
+ }
162
+ return parseAwsRegion(value);
163
+ };
164
+ var resolveDiscoveryTarget = (region) => region === void 0 ? { mode: "current" } : region === "all" ? { mode: "all" } : { mode: "region", region };
165
+ var scanFormatters = {
166
+ json: formatJson,
167
+ sarif: formatSarif,
168
+ table: formatTable
169
+ };
170
+ var formatValue = (value) => JSON.stringify(value, null, 2);
171
+ var formatEnabledRegions = (regions) => regions.length === 0 ? "No Resource Explorer indexes are enabled." : regions.map(({ region, type }) => `${region} ${type}`).join("\n");
172
+ var formatSupportedResourceTypes = (resourceTypes) => resourceTypes.length === 0 ? "No supported resource types were returned." : resourceTypes.map(({ resourceType, service }) => `${resourceType} ${service ?? "unknown"}`).join("\n");
173
+ var formatInitializationResult = (result) => result.status === "EXISTING" ? `Resource Explorer setup already exists in ${result.aggregatorRegion}.` : `Resource Explorer setup created in ${result.aggregatorRegion}.`;
174
+ var resolveListFormat = (parentCommand, options) => parentCommand.opts().format === "json" ? "json" : options.format ?? "table";
175
+ var runCommand = async (action) => {
176
+ try {
177
+ process.exitCode = await action() ?? EXIT_CODE_OK;
178
+ } catch (err) {
179
+ process.stderr.write(`${formatError(err)}
180
+ `);
181
+ process.exitCode = EXIT_CODE_RUNTIME_ERROR;
182
+ }
183
+ };
184
+ var registerDiscoverCommand = (program) => {
185
+ const discoverCommand = program.command("discover").description("Run a live AWS discovery").enablePositionalOptions().option(
186
+ "--region <region>",
187
+ 'Discovery region to use. Pass "all" to require an aggregator index.',
188
+ parseDiscoverRegion
189
+ ).option("--format <format>", "Output format: table|json|sarif", parseDiscoverFormat, "table").option("--exit-code", "Exit with code 1 when findings exist").addHelpText(
190
+ "after",
191
+ `
192
+ Examples:
193
+ cloudburn discover
194
+ cloudburn discover --region eu-central-1
195
+ cloudburn discover --region all
196
+ cloudburn discover list-enabled-regions
197
+ cloudburn discover init
198
+ `
199
+ ).action(async (options) => {
200
+ await runCommand(async () => {
201
+ const scanner = new CloudBurnClient();
202
+ const result = await scanner.discover({ target: resolveDiscoveryTarget(options.region) });
203
+ const output = scanFormatters[options.format ?? "table"](result);
204
+ process.stdout.write(`${output}
205
+ `);
206
+ if (options.exitCode && countScanResultFindings(result) > 0) {
207
+ return EXIT_CODE_POLICY_VIOLATION;
208
+ }
209
+ return EXIT_CODE_OK;
210
+ });
211
+ });
212
+ discoverCommand.command("list-enabled-regions").description("List AWS regions with a local or aggregator Resource Explorer index").option("--format <format>", "Output format: table|json", parseDiscoverListFormat, "table").action(async (options) => {
213
+ await runCommand(async () => {
214
+ const scanner = new CloudBurnClient();
215
+ const regions = await scanner.listEnabledDiscoveryRegions();
216
+ const format = resolveListFormat(discoverCommand, options);
217
+ const output = format === "json" ? formatValue(regions) : formatEnabledRegions(regions);
218
+ process.stdout.write(`${output}
219
+ `);
220
+ return EXIT_CODE_OK;
221
+ });
222
+ });
223
+ 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) => {
224
+ await runCommand(async () => {
225
+ const scanner = new CloudBurnClient();
226
+ const parentRegion = discoverCommand.opts().region;
227
+ const region = options.region ?? (parentRegion === "all" ? void 0 : parentRegion);
228
+ const result = await scanner.initializeDiscovery({ region });
229
+ process.stdout.write(`${formatInitializationResult(result)}
230
+ `);
231
+ return EXIT_CODE_OK;
232
+ });
233
+ });
234
+ discoverCommand.command("supported-resource-types").description("List Resource Explorer supported AWS resource types").option("--format <format>", "Output format: table|json", parseDiscoverListFormat, "table").action(async (options) => {
235
+ await runCommand(async () => {
236
+ const scanner = new CloudBurnClient();
237
+ const resourceTypes = await scanner.listSupportedDiscoveryResourceTypes();
238
+ const format = resolveListFormat(discoverCommand, options);
239
+ const output = format === "json" ? formatValue(resourceTypes) : formatSupportedResourceTypes(resourceTypes);
240
+ process.stdout.write(`${output}
241
+ `);
242
+ return EXIT_CODE_OK;
243
+ });
244
+ });
245
+ };
246
+
247
+ // src/commands/estimate.ts
248
+ var registerEstimateCommand = (program) => {
249
+ program.command("estimate").description("Request optional pricing estimates from a self-hosted dashboard").option("--server <url>", "Dashboard API base URL").action((options) => {
250
+ if (!options.server) {
251
+ process.stdout.write("No server configured. This command is optional and requires a dashboard URL.\n");
252
+ return;
253
+ }
254
+ process.stdout.write(`Estimate request scaffold ready for server: ${options.server}
255
+ `);
256
+ });
257
+ };
258
+
259
+ // src/commands/init.ts
260
+ var starterConfig = `version: 1
261
+ profile: dev
262
+
263
+ # Profiles are parsed but not applied yet, so configure the active rules block directly for now.
264
+ rules:
265
+ ec2-instance-type-preferred:
266
+ severity: error
267
+ `;
268
+ var registerInitCommand = (program) => {
269
+ program.command("init").description("Print a starter .cloudburn.yml configuration").action(() => {
270
+ process.stdout.write(`${starterConfig}
271
+ `);
272
+ });
273
+ };
274
+
275
+ // src/commands/rules-list.ts
276
+ import { awsCorePreset } from "@cloudburn/sdk";
277
+ var registerRulesListCommand = (program) => {
278
+ const rulesCommand = program.command("rules").description("Inspect built-in and custom rules");
279
+ rulesCommand.command("list").description("List built-in CloudBurn rule IDs").action(() => {
280
+ process.stdout.write(`${awsCorePreset.ruleIds.join("\n")}
281
+ `);
282
+ });
283
+ };
284
+
166
285
  // src/commands/scan.ts
286
+ import { CloudBurnClient as CloudBurnClient2 } from "@cloudburn/sdk";
287
+ import { InvalidArgumentError as InvalidArgumentError2 } from "commander";
167
288
  var supportedScanFormats = ["table", "json", "sarif"];
168
289
  var parseScanFormat = (value) => {
169
290
  if (supportedScanFormats.includes(value)) {
170
291
  return value;
171
292
  }
172
- throw new InvalidArgumentError(`Invalid format "${value}". Allowed formats: ${supportedScanFormats.join(", ")}.`);
293
+ throw new InvalidArgumentError2(`Invalid format "${value}". Allowed formats: ${supportedScanFormats.join(", ")}.`);
173
294
  };
174
295
  var formatters = {
175
296
  json: formatJson,
@@ -177,19 +298,18 @@ var formatters = {
177
298
  table: formatTable
178
299
  };
179
300
  var registerScanCommand = (program) => {
180
- program.command("scan").description("Run an autodetected static IaC scan, or a live AWS scan with --live").argument("[path]", "Terraform file, CloudFormation template, or directory to scan").option("--live", "Run live AWS scan").option("--format <format>", "Output format: table|json|sarif", parseScanFormat, "table").option("--exit-code", "Exit with code 1 when findings exist").addHelpText(
301
+ program.command("scan").description("Run an autodetected static IaC scan").argument("[path]", "Terraform file, CloudFormation template, or directory to scan").option("--format <format>", "Output format: table|json|sarif", parseScanFormat, "table").option("--exit-code", "Exit with code 1 when findings exist").addHelpText(
181
302
  "after",
182
303
  `
183
304
  Examples:
184
305
  cloudburn scan ./main.tf
185
306
  cloudburn scan ./template.yaml
186
307
  cloudburn scan ./iac
187
- cloudburn scan --live
188
308
  `
189
309
  ).action(async (path, options) => {
190
310
  try {
191
- const scanner = new CloudBurnScanner();
192
- const result = options.live ? await scanner.scanLive() : await scanner.scanStatic(path ?? process.cwd());
311
+ const scanner = new CloudBurnClient2();
312
+ const result = await scanner.scanStatic(path ?? process.cwd());
193
313
  const format = formatters[options.format ?? "table"];
194
314
  const output = format(result);
195
315
  process.stdout.write(`${output}
@@ -210,7 +330,8 @@ Examples:
210
330
  // src/cli.ts
211
331
  var createProgram = () => {
212
332
  const program = new Command();
213
- program.name("cloudburn").description("Know what you spend. Fix what you waste.").version("0.4.1");
333
+ program.name("cloudburn").description("Know what you spend. Fix what you waste.").version("0.5.1");
334
+ registerDiscoverCommand(program);
214
335
  registerScanCommand(program);
215
336
  registerInitCommand(program);
216
337
  registerRulesListCommand(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cloudburn",
3
- "version": "0.4.1",
3
+ "version": "0.5.1",
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.7.0"
14
+ "@cloudburn/sdk": "0.8.1"
15
15
  },
16
16
  "devDependencies": {
17
17
  "@biomejs/biome": "^2.4.6",