relight-cli 0.1.0 → 0.2.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.
Files changed (42) hide show
  1. package/README.md +77 -34
  2. package/package.json +12 -4
  3. package/src/cli.js +305 -1
  4. package/src/commands/apps.js +128 -0
  5. package/src/commands/auth.js +75 -4
  6. package/src/commands/config.js +282 -0
  7. package/src/commands/cost.js +593 -0
  8. package/src/commands/db.js +531 -0
  9. package/src/commands/deploy.js +298 -0
  10. package/src/commands/doctor.js +41 -9
  11. package/src/commands/domains.js +223 -0
  12. package/src/commands/logs.js +111 -0
  13. package/src/commands/open.js +42 -0
  14. package/src/commands/ps.js +121 -0
  15. package/src/commands/scale.js +132 -0
  16. package/src/lib/clouds/aws.js +309 -35
  17. package/src/lib/clouds/cf.js +401 -2
  18. package/src/lib/clouds/gcp.js +234 -3
  19. package/src/lib/clouds/slicervm.js +139 -0
  20. package/src/lib/config.js +40 -0
  21. package/src/lib/docker.js +34 -0
  22. package/src/lib/link.js +20 -5
  23. package/src/lib/providers/aws/app.js +481 -0
  24. package/src/lib/providers/aws/db.js +513 -0
  25. package/src/lib/providers/aws/dns.js +232 -0
  26. package/src/lib/providers/aws/registry.js +59 -0
  27. package/src/lib/providers/cf/app.js +596 -0
  28. package/src/lib/providers/cf/bundle.js +70 -0
  29. package/src/lib/providers/cf/db.js +279 -0
  30. package/src/lib/providers/cf/dns.js +148 -0
  31. package/src/lib/providers/cf/registry.js +17 -0
  32. package/src/lib/providers/gcp/app.js +429 -0
  33. package/src/lib/providers/gcp/db.js +457 -0
  34. package/src/lib/providers/gcp/dns.js +166 -0
  35. package/src/lib/providers/gcp/registry.js +30 -0
  36. package/src/lib/providers/resolve.js +49 -0
  37. package/src/lib/providers/slicervm/app.js +396 -0
  38. package/src/lib/providers/slicervm/db.js +33 -0
  39. package/src/lib/providers/slicervm/dns.js +58 -0
  40. package/src/lib/providers/slicervm/registry.js +7 -0
  41. package/worker-template/package.json +10 -0
  42. package/worker-template/src/index.js +260 -0
@@ -0,0 +1,593 @@
1
+ import { phase, status, fatal, fmt, table } from "../lib/output.js";
2
+ import { resolveAppName } from "../lib/link.js";
3
+ import { resolveCloudId, getCloudCfg, getProvider } from "../lib/providers/resolve.js";
4
+
5
+ // --- Pricing (Workers Paid plan, $5/mo) ---
6
+
7
+ var CF_PRICING = {
8
+ workerRequests: { included: 10_000_000, rate: 0.30 / 1_000_000 },
9
+ workerCpuMs: { included: 30_000_000, rate: 0.02 / 1_000_000 },
10
+ doRequests: { included: 1_000_000, rate: 0.15 / 1_000_000 },
11
+ doGbSeconds: { included: 400_000, rate: 12.50 / 1_000_000 },
12
+ containerVcpuSec: { included: 375 * 60, rate: 0.000020 },
13
+ containerMemGibSec: { included: 25 * 3600, rate: 0.0000025 },
14
+ containerDiskGbSec: { included: 200 * 3600, rate: 0.00000007 },
15
+ containerEgressGb: { included: 0, rate: 0.025 },
16
+ platform: 5.0,
17
+ };
18
+
19
+ // --- GCP Cloud Run pricing ---
20
+
21
+ var GCP_PRICING = {
22
+ vcpuSecond: 0.00002400,
23
+ memGibSecond: 0.00000250,
24
+ requestsFree: 2_000_000,
25
+ requestRate: 0.40 / 1_000_000,
26
+ };
27
+
28
+ // --- AWS App Runner pricing ---
29
+
30
+ var AWS_PRICING = {
31
+ activeVcpuHr: 0.064,
32
+ provisionedVcpuHr: 0.007,
33
+ memGbHr: 0.007,
34
+ };
35
+
36
+ // --- Date range parsing ---
37
+
38
+ function parseDateRange(since) {
39
+ var now = new Date();
40
+ var until = now;
41
+ var start;
42
+
43
+ if (!since) {
44
+ start = new Date(now.getFullYear(), now.getMonth(), 1);
45
+ } else if (/^\d+d$/.test(since)) {
46
+ var days = parseInt(since);
47
+ start = new Date(now.getTime() - days * 86400_000);
48
+ } else if (/^\d{4}-\d{2}-\d{2}$/.test(since)) {
49
+ start = new Date(since + "T00:00:00Z");
50
+ if (isNaN(start.getTime())) {
51
+ fatal(`Invalid date: ${since}`, "Use YYYY-MM-DD or Nd (e.g. 7d)");
52
+ }
53
+ } else {
54
+ fatal(
55
+ `Invalid --since value: ${since}`,
56
+ "Use YYYY-MM-DD or Nd (e.g. 7d, 30d)"
57
+ );
58
+ }
59
+
60
+ var sinceISO = start.toISOString().slice(0, 19) + "Z";
61
+ var untilISO = until.toISOString().slice(0, 19) + "Z";
62
+
63
+ var label = formatDateRange(start, until);
64
+ return { sinceISO, untilISO, sinceDate: start, untilDate: until, label };
65
+ }
66
+
67
+ function formatDateRange(start, end) {
68
+ var opts = { month: "short", day: "numeric" };
69
+ var s = start.toLocaleDateString("en-US", opts);
70
+ var e = end.toLocaleDateString("en-US", opts);
71
+ return `${s} - ${e}`;
72
+ }
73
+
74
+ // --- CF cost calculation ---
75
+
76
+ function calculateAppCosts(usage) {
77
+ return {
78
+ workerRequests: usage.workerRequests * CF_PRICING.workerRequests.rate,
79
+ workerCpuMs: usage.workerCpuMs * CF_PRICING.workerCpuMs.rate,
80
+ doRequests: usage.doRequests * CF_PRICING.doRequests.rate,
81
+ doGbSeconds: usage.doGbSeconds * CF_PRICING.doGbSeconds.rate,
82
+ containerVcpuSec: usage.containerVcpuSec * CF_PRICING.containerVcpuSec.rate,
83
+ containerMemGibSec: usage.containerMemGibSec * CF_PRICING.containerMemGibSec.rate,
84
+ containerDiskGbSec: usage.containerDiskGbSec * CF_PRICING.containerDiskGbSec.rate,
85
+ containerEgressGb: usage.containerEgressGb * CF_PRICING.containerEgressGb.rate,
86
+ };
87
+ }
88
+
89
+ function applyFreeTier(appResults) {
90
+ var totals = {
91
+ workerRequests: 0,
92
+ workerCpuMs: 0,
93
+ doRequests: 0,
94
+ doGbSeconds: 0,
95
+ containerVcpuSec: 0,
96
+ containerMemGibSec: 0,
97
+ containerDiskGbSec: 0,
98
+ containerEgressGb: 0,
99
+ };
100
+ for (var app of appResults) {
101
+ for (var key of Object.keys(totals)) {
102
+ totals[key] += app.usage[key];
103
+ }
104
+ }
105
+
106
+ var fleetOverage = {};
107
+ for (var key of Object.keys(totals)) {
108
+ var included = CF_PRICING[key].included;
109
+ var overageUsage = Math.max(0, totals[key] - included);
110
+ fleetOverage[key] = overageUsage * CF_PRICING[key].rate;
111
+ }
112
+
113
+ var grossFleetTotal = 0;
114
+ for (var app of appResults) {
115
+ var costs = calculateAppCosts(app.usage);
116
+ app.grossCosts = costs;
117
+ var appGross = Object.values(costs).reduce((a, b) => a + b, 0);
118
+ app.grossTotal = appGross;
119
+ grossFleetTotal += appGross;
120
+ }
121
+
122
+ var netFleetTotal = Object.values(fleetOverage).reduce((a, b) => a + b, 0);
123
+ var freeTierDiscount = grossFleetTotal - netFleetTotal;
124
+
125
+ for (var app of appResults) {
126
+ if (grossFleetTotal > 0) {
127
+ var share = app.grossTotal / grossFleetTotal;
128
+ app.freeTierDiscount = freeTierDiscount * share;
129
+ } else {
130
+ app.freeTierDiscount = 0;
131
+ }
132
+ app.netTotal = Math.max(0, app.grossTotal - app.freeTierDiscount);
133
+
134
+ app.workersCost = app.grossCosts.workerRequests + app.grossCosts.workerCpuMs;
135
+ app.doCost = app.grossCosts.doRequests + app.grossCosts.doGbSeconds;
136
+ app.containerCost =
137
+ app.grossCosts.containerVcpuSec +
138
+ app.grossCosts.containerMemGibSec +
139
+ app.grossCosts.containerDiskGbSec +
140
+ app.grossCosts.containerEgressGb;
141
+ }
142
+
143
+ return { appResults, freeTierDiscount, netFleetTotal, grossFleetTotal };
144
+ }
145
+
146
+ // --- GCP cost calculation ---
147
+
148
+ function calculateGcpAppCosts(usage) {
149
+ var cpuCost = usage.cpuSeconds * GCP_PRICING.vcpuSecond;
150
+ var memCost = usage.memGibSeconds * GCP_PRICING.memGibSecond;
151
+ var billableRequests = Math.max(0, usage.requests - GCP_PRICING.requestsFree);
152
+ var requestCost = billableRequests * GCP_PRICING.requestRate;
153
+ return { cpuCost, memCost, requestCost, total: cpuCost + memCost + requestCost };
154
+ }
155
+
156
+ // --- Formatting helpers ---
157
+
158
+ function fmtCost(n) {
159
+ return "$" + n.toFixed(2);
160
+ }
161
+
162
+ function fmtUsage(n, unit) {
163
+ if (n >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M " + unit;
164
+ if (n >= 1_000) return (n / 1_000).toFixed(1) + "K " + unit;
165
+ return n.toLocaleString("en-US") + " " + unit;
166
+ }
167
+
168
+ function fmtDuration(seconds) {
169
+ var hrs = seconds / 3600;
170
+ if (hrs >= 1) return hrs.toFixed(1) + " vCPU-hrs";
171
+ var mins = seconds / 60;
172
+ return mins.toFixed(1) + " vCPU-min";
173
+ }
174
+
175
+ function fmtGibHours(gibSec) {
176
+ var hrs = gibSec / 3600;
177
+ if (hrs >= 1) return hrs.toFixed(1) + " GiB-hrs";
178
+ var mins = gibSec / 60;
179
+ return mins.toFixed(1) + " GiB-min";
180
+ }
181
+
182
+ function fmtGbHours(gbSec) {
183
+ var hrs = gbSec / 3600;
184
+ if (hrs >= 1) return hrs.toFixed(1) + " GB-hrs";
185
+ var mins = gbSec / 60;
186
+ return mins.toFixed(1) + " GB-min";
187
+ }
188
+
189
+ // --- Render CF single app ---
190
+
191
+ function renderCfSingleApp(app, range) {
192
+ console.log("");
193
+ console.log(
194
+ ` ${fmt.bold("Estimated cost for")} ${fmt.app(app.name)} ${fmt.dim(`(${range.label})`)}`
195
+ );
196
+ console.log("");
197
+
198
+ var header = " COMPONENT USAGE ESTIMATED COST";
199
+ var sep = " " + "-".repeat(50);
200
+
201
+ console.log(fmt.bold(header));
202
+ console.log(fmt.dim(sep));
203
+
204
+ var rows = [
205
+ ["Workers", fmtUsage(app.usage.workerRequests, "requests"), fmtCost(app.grossCosts.workerRequests)],
206
+ ["", fmtUsage(app.usage.workerCpuMs, "CPU-ms"), fmtCost(app.grossCosts.workerCpuMs)],
207
+ ["Durable Obj", fmtUsage(app.usage.doRequests, "requests"), fmtCost(app.grossCosts.doRequests)],
208
+ app.usage.doWsMsgs > 0
209
+ ? ["", fmtUsage(app.usage.doWsMsgs, "WS msgs") + fmt.dim(" (20:1)"), ""]
210
+ : null,
211
+ ["", fmtUsage(app.usage.doGbSeconds, "GB-s"), fmtCost(app.grossCosts.doGbSeconds)],
212
+ ["Containers", fmtDuration(app.usage.containerVcpuSec), fmtCost(app.grossCosts.containerVcpuSec)],
213
+ ["", fmtGibHours(app.usage.containerMemGibSec) + " mem", fmtCost(app.grossCosts.containerMemGibSec)],
214
+ ["", fmtGbHours(app.usage.containerDiskGbSec) + " disk", fmtCost(app.grossCosts.containerDiskGbSec)],
215
+ ["", fmtUsage(app.usage.containerEgressGb, "GB egress"), fmtCost(app.grossCosts.containerEgressGb)],
216
+ ];
217
+
218
+ for (var row of rows.filter(Boolean)) {
219
+ var comp = row[0] ? fmt.bold(row[0].padEnd(14)) : " ".repeat(14);
220
+ var usage = row[1].padEnd(22);
221
+ console.log(` ${comp} ${usage} ${row[2]}`);
222
+ }
223
+
224
+ console.log(fmt.dim(sep));
225
+ console.log(
226
+ ` ${" ".repeat(14)} ${"".padEnd(22)} ${fmt.bold(fmtCost(app.grossTotal))}`
227
+ );
228
+
229
+ if (app.freeTierDiscount > 0) {
230
+ console.log(
231
+ ` ${" ".repeat(14)} ${fmt.dim("Free tier".padEnd(22))} ${fmt.dim("-" + fmtCost(app.freeTierDiscount))}`
232
+ );
233
+ console.log(
234
+ ` ${" ".repeat(14)} ${fmt.bold("Net".padEnd(22))} ${fmt.bold(fmtCost(app.netTotal))}`
235
+ );
236
+ }
237
+
238
+ console.log("");
239
+ console.log(fmt.dim(" Estimates based on Cloudflare Workers Paid plan pricing."));
240
+ console.log("");
241
+ }
242
+
243
+ // --- Render CF fleet ---
244
+
245
+ function renderCfFleet(fleet, range) {
246
+ console.log("");
247
+ console.log(
248
+ ` ${fmt.bold("Estimated costs")} ${fmt.dim(`(${range.label})`)}`
249
+ );
250
+ console.log("");
251
+
252
+ var headers = ["NAME", "WORKERS", "DO", "CONTAINERS", "TOTAL"];
253
+ var rows = fleet.appResults.map((a) => [
254
+ fmt.app(a.name),
255
+ fmtCost(a.workersCost),
256
+ fmtCost(a.doCost),
257
+ fmtCost(a.containerCost),
258
+ fmtCost(a.grossTotal),
259
+ ]);
260
+
261
+ console.log(table(headers, rows));
262
+ console.log("");
263
+
264
+ var labelW = 44;
265
+ console.log(
266
+ " " + fmt.dim("Subtotal".padEnd(labelW)) + fmtCost(fleet.grossFleetTotal)
267
+ );
268
+ if (fleet.freeTierDiscount > 0) {
269
+ console.log(
270
+ " " +
271
+ fmt.dim("Free tier".padEnd(labelW)) +
272
+ fmt.dim("-" + fmtCost(fleet.freeTierDiscount))
273
+ );
274
+ }
275
+ console.log(
276
+ " " + fmt.dim("Platform".padEnd(labelW)) + fmtCost(CF_PRICING.platform)
277
+ );
278
+ console.log(" " + fmt.dim("-".repeat(labelW + 8)));
279
+ console.log(
280
+ " " +
281
+ fmt.bold("TOTAL".padEnd(labelW)) +
282
+ fmt.bold(fmtCost(fleet.netFleetTotal + CF_PRICING.platform))
283
+ );
284
+ console.log("");
285
+ console.log(fmt.dim(" Estimates based on Cloudflare Workers Paid plan pricing."));
286
+ console.log("");
287
+ }
288
+
289
+ // --- Render GCP single app ---
290
+
291
+ function renderGcpSingleApp(app, range) {
292
+ var costs = calculateGcpAppCosts(app.usage);
293
+
294
+ console.log("");
295
+ console.log(
296
+ ` ${fmt.bold("Estimated cost for")} ${fmt.app(app.name)} ${fmt.dim(`(${range.label})`)}`
297
+ );
298
+ console.log("");
299
+
300
+ var header = " COMPONENT USAGE ESTIMATED COST";
301
+ var sep = " " + "-".repeat(50);
302
+
303
+ console.log(fmt.bold(header));
304
+ console.log(fmt.dim(sep));
305
+
306
+ var rows = [
307
+ ["CPU", fmtDuration(app.usage.cpuSeconds), fmtCost(costs.cpuCost)],
308
+ ["Memory", fmtGibHours(app.usage.memGibSeconds), fmtCost(costs.memCost)],
309
+ ["Requests", fmtUsage(app.usage.requests, "requests"), fmtCost(costs.requestCost)],
310
+ ];
311
+
312
+ for (var row of rows) {
313
+ var comp = row[0] ? fmt.bold(row[0].padEnd(14)) : " ".repeat(14);
314
+ var usage = row[1].padEnd(22);
315
+ console.log(` ${comp} ${usage} ${row[2]}`);
316
+ }
317
+
318
+ console.log(fmt.dim(sep));
319
+ console.log(
320
+ ` ${" ".repeat(14)} ${"".padEnd(22)} ${fmt.bold(fmtCost(costs.total))}`
321
+ );
322
+
323
+ if (app.usage.requests <= GCP_PRICING.requestsFree) {
324
+ console.log(
325
+ ` ${" ".repeat(14)} ${fmt.dim("2M free requests/mo".padEnd(22))}`
326
+ );
327
+ }
328
+
329
+ console.log("");
330
+ console.log(fmt.dim(" Estimates based on GCP Cloud Run pricing."));
331
+ console.log("");
332
+ }
333
+
334
+ // --- Render GCP fleet ---
335
+
336
+ function renderGcpFleet(appResults, range) {
337
+ console.log("");
338
+ console.log(
339
+ ` ${fmt.bold("Estimated costs")} ${fmt.dim(`(${range.label})`)}`
340
+ );
341
+ console.log("");
342
+
343
+ var headers = ["NAME", "CPU", "MEMORY", "REQUESTS", "TOTAL"];
344
+ var rows = appResults.map((a) => {
345
+ var costs = calculateGcpAppCosts(a.usage);
346
+ return [
347
+ fmt.app(a.name),
348
+ fmtCost(costs.cpuCost),
349
+ fmtCost(costs.memCost),
350
+ fmtCost(costs.requestCost),
351
+ fmtCost(costs.total),
352
+ ];
353
+ });
354
+
355
+ console.log(table(headers, rows));
356
+ console.log("");
357
+
358
+ var grandTotal = appResults.reduce((sum, a) => sum + calculateGcpAppCosts(a.usage).total, 0);
359
+ var labelW = 44;
360
+ console.log(" " + fmt.dim("-".repeat(labelW + 8)));
361
+ console.log(
362
+ " " +
363
+ fmt.bold("TOTAL".padEnd(labelW)) +
364
+ fmt.bold(fmtCost(grandTotal))
365
+ );
366
+ console.log("");
367
+ console.log(fmt.dim(" Estimates based on GCP Cloud Run pricing."));
368
+ console.log("");
369
+ }
370
+
371
+ // --- AWS cost calculation ---
372
+
373
+ function calculateAwsAppCosts(usage) {
374
+ var activeCost = usage.activeVcpuHrs * AWS_PRICING.activeVcpuHr;
375
+ var provisionedCost = usage.provisionedVcpuHrs * AWS_PRICING.provisionedVcpuHr;
376
+ var memCost = usage.memGbHrs * AWS_PRICING.memGbHr;
377
+ return { activeCost, provisionedCost, memCost, total: activeCost + provisionedCost + memCost };
378
+ }
379
+
380
+ // --- Render AWS single app ---
381
+
382
+ function renderAwsSingleApp(app, range) {
383
+ var costs = calculateAwsAppCosts(app.usage);
384
+
385
+ console.log("");
386
+ console.log(
387
+ ` ${fmt.bold("Estimated cost for")} ${fmt.app(app.name)} ${fmt.dim(`(${range.label})`)}`
388
+ );
389
+ console.log("");
390
+
391
+ var header = " COMPONENT USAGE ESTIMATED COST";
392
+ var sep = " " + "-".repeat(50);
393
+
394
+ console.log(fmt.bold(header));
395
+ console.log(fmt.dim(sep));
396
+
397
+ var rows = [
398
+ ["Active vCPU", app.usage.activeVcpuHrs.toFixed(1) + " vCPU-hrs", fmtCost(costs.activeCost)],
399
+ ["Provisioned", app.usage.provisionedVcpuHrs.toFixed(1) + " vCPU-hrs", fmtCost(costs.provisionedCost)],
400
+ ["Memory", app.usage.memGbHrs.toFixed(1) + " GB-hrs", fmtCost(costs.memCost)],
401
+ ];
402
+
403
+ for (var row of rows) {
404
+ var comp = row[0] ? fmt.bold(row[0].padEnd(14)) : " ".repeat(14);
405
+ var usage = row[1].padEnd(22);
406
+ console.log(` ${comp} ${usage} ${row[2]}`);
407
+ }
408
+
409
+ console.log(fmt.dim(sep));
410
+ console.log(
411
+ ` ${" ".repeat(14)} ${"".padEnd(22)} ${fmt.bold(fmtCost(costs.total))}`
412
+ );
413
+
414
+ console.log("");
415
+ console.log(fmt.dim(" Estimates based on AWS App Runner pricing (min 1 provisioned instance)."));
416
+ console.log("");
417
+ }
418
+
419
+ // --- Render AWS fleet ---
420
+
421
+ function renderAwsFleet(appResults, range) {
422
+ console.log("");
423
+ console.log(
424
+ ` ${fmt.bold("Estimated costs")} ${fmt.dim(`(${range.label})`)}`
425
+ );
426
+ console.log("");
427
+
428
+ var headers = ["NAME", "ACTIVE", "PROVISIONED", "MEMORY", "TOTAL"];
429
+ var rows = appResults.map((a) => {
430
+ var costs = calculateAwsAppCosts(a.usage);
431
+ return [
432
+ fmt.app(a.name),
433
+ fmtCost(costs.activeCost),
434
+ fmtCost(costs.provisionedCost),
435
+ fmtCost(costs.memCost),
436
+ fmtCost(costs.total),
437
+ ];
438
+ });
439
+
440
+ console.log(table(headers, rows));
441
+ console.log("");
442
+
443
+ var grandTotal = appResults.reduce((sum, a) => sum + calculateAwsAppCosts(a.usage).total, 0);
444
+ var labelW = 44;
445
+ console.log(" " + fmt.dim("-".repeat(labelW + 8)));
446
+ console.log(
447
+ " " +
448
+ fmt.bold("TOTAL".padEnd(labelW)) +
449
+ fmt.bold(fmtCost(grandTotal))
450
+ );
451
+ console.log("");
452
+ console.log(fmt.dim(" Estimates based on AWS App Runner pricing (min 1 provisioned instance)."));
453
+ console.log("");
454
+ }
455
+
456
+ // --- Main command ---
457
+
458
+ export async function cost(name, options) {
459
+ var cloud = resolveCloudId(options.cloud);
460
+ var cfg = getCloudCfg(cloud);
461
+ var appProvider = await getProvider(cloud, "app");
462
+
463
+ var range = parseDateRange(options.since);
464
+
465
+ var singleApp = name ? resolveAppName(name) : null;
466
+
467
+ phase("Fetching analytics");
468
+ status("Querying...");
469
+
470
+ var appNames = singleApp ? [singleApp] : null;
471
+ var appResults = await appProvider.getCosts(cfg, appNames, range);
472
+
473
+ if (appResults.length === 0) {
474
+ fatal(
475
+ "No apps deployed.",
476
+ `Run ${fmt.cmd("relight deploy")} to deploy your first app.`
477
+ );
478
+ }
479
+
480
+ if (cloud === "aws") {
481
+ // AWS rendering
482
+ if (options.json) {
483
+ var jsonOut = singleApp
484
+ ? {
485
+ app: appResults[0].name,
486
+ period: range.label,
487
+ since: range.sinceISO,
488
+ until: range.untilISO,
489
+ usage: appResults[0].usage,
490
+ costs: calculateAwsAppCosts(appResults[0].usage),
491
+ }
492
+ : {
493
+ period: range.label,
494
+ since: range.sinceISO,
495
+ until: range.untilISO,
496
+ apps: appResults.map((a) => ({
497
+ name: a.name,
498
+ usage: a.usage,
499
+ costs: calculateAwsAppCosts(a.usage),
500
+ })),
501
+ total: appResults.reduce((s, a) => s + calculateAwsAppCosts(a.usage).total, 0),
502
+ };
503
+ console.log(JSON.stringify(jsonOut, null, 2));
504
+ return;
505
+ }
506
+
507
+ if (singleApp) {
508
+ renderAwsSingleApp(appResults[0], range);
509
+ } else {
510
+ renderAwsFleet(appResults, range);
511
+ }
512
+ return;
513
+ }
514
+
515
+ if (cloud === "gcp") {
516
+ // GCP rendering
517
+ if (options.json) {
518
+ var jsonOut = singleApp
519
+ ? {
520
+ app: appResults[0].name,
521
+ period: range.label,
522
+ since: range.sinceISO,
523
+ until: range.untilISO,
524
+ usage: appResults[0].usage,
525
+ costs: calculateGcpAppCosts(appResults[0].usage),
526
+ }
527
+ : {
528
+ period: range.label,
529
+ since: range.sinceISO,
530
+ until: range.untilISO,
531
+ apps: appResults.map((a) => ({
532
+ name: a.name,
533
+ usage: a.usage,
534
+ costs: calculateGcpAppCosts(a.usage),
535
+ })),
536
+ total: appResults.reduce((s, a) => s + calculateGcpAppCosts(a.usage).total, 0),
537
+ };
538
+ console.log(JSON.stringify(jsonOut, null, 2));
539
+ return;
540
+ }
541
+
542
+ if (singleApp) {
543
+ renderGcpSingleApp(appResults[0], range);
544
+ } else {
545
+ renderGcpFleet(appResults, range);
546
+ }
547
+ return;
548
+ }
549
+
550
+ // CF rendering
551
+ var fleet = applyFreeTier(appResults);
552
+
553
+ if (options.json) {
554
+ var jsonOut = singleApp
555
+ ? {
556
+ app: fleet.appResults[0].name,
557
+ period: range.label,
558
+ since: range.sinceISO,
559
+ until: range.untilISO,
560
+ usage: fleet.appResults[0].usage,
561
+ costs: fleet.appResults[0].grossCosts,
562
+ grossTotal: fleet.appResults[0].grossTotal,
563
+ freeTierDiscount: fleet.appResults[0].freeTierDiscount,
564
+ netTotal: fleet.appResults[0].netTotal,
565
+ }
566
+ : {
567
+ period: range.label,
568
+ since: range.sinceISO,
569
+ until: range.untilISO,
570
+ apps: fleet.appResults.map((a) => ({
571
+ name: a.name,
572
+ usage: a.usage,
573
+ costs: a.grossCosts,
574
+ grossTotal: a.grossTotal,
575
+ freeTierDiscount: a.freeTierDiscount,
576
+ netTotal: a.netTotal,
577
+ })),
578
+ grossFleetTotal: fleet.grossFleetTotal,
579
+ freeTierDiscount: fleet.freeTierDiscount,
580
+ netFleetTotal: fleet.netFleetTotal,
581
+ platform: CF_PRICING.platform,
582
+ total: fleet.netFleetTotal + CF_PRICING.platform,
583
+ };
584
+ console.log(JSON.stringify(jsonOut, null, 2));
585
+ return;
586
+ }
587
+
588
+ if (singleApp) {
589
+ renderCfSingleApp(fleet.appResults[0], range);
590
+ } else {
591
+ renderCfFleet(fleet, range);
592
+ }
593
+ }