@ganakailabs/cloudeval-cli 0.27.1 → 0.27.2
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/{App-XHSK5SJA.js → App-VSOTHLOD.js} +2 -2
- package/dist/{Banner-GAOI3CZR.js → Banner-7XUOJYL5.js} +2 -2
- package/dist/{chunk-LWHSK5SO.js → chunk-3DBCT463.js} +1 -1
- package/dist/{chunk-FJDUSS2S.js → chunk-SLRYBS3P.js} +1 -1
- package/dist/cli.js +292 -298
- package/package.json +1 -1
- package/sbom.spdx.json +1 -1
|
@@ -38,10 +38,10 @@ import {
|
|
|
38
38
|
} from "./chunk-USSCB2ZU.js";
|
|
39
39
|
import {
|
|
40
40
|
Banner
|
|
41
|
-
} from "./chunk-
|
|
41
|
+
} from "./chunk-3DBCT463.js";
|
|
42
42
|
import {
|
|
43
43
|
CLI_VERSION
|
|
44
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-SLRYBS3P.js";
|
|
45
45
|
import {
|
|
46
46
|
raisedButtonStyle,
|
|
47
47
|
terminalTheme
|
package/dist/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
} from "./chunk-USSCB2ZU.js";
|
|
40
40
|
import {
|
|
41
41
|
CLI_VERSION
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-SLRYBS3P.js";
|
|
43
43
|
|
|
44
44
|
// src/runtime/prepareInk.ts
|
|
45
45
|
import fs from "fs";
|
|
@@ -2506,6 +2506,14 @@ var firstRecord = (...values) => {
|
|
|
2506
2506
|
}
|
|
2507
2507
|
return void 0;
|
|
2508
2508
|
};
|
|
2509
|
+
var firstArray = (...values) => {
|
|
2510
|
+
for (const value of values) {
|
|
2511
|
+
if (Array.isArray(value)) {
|
|
2512
|
+
return value;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
return void 0;
|
|
2516
|
+
};
|
|
2509
2517
|
var entriesAsNamedRecords = (value, amountKey = "amount") => {
|
|
2510
2518
|
const record = firstRecord(value);
|
|
2511
2519
|
if (!record) {
|
|
@@ -2660,21 +2668,6 @@ var statusIcon = (status) => {
|
|
|
2660
2668
|
return "\u26AA";
|
|
2661
2669
|
}
|
|
2662
2670
|
};
|
|
2663
|
-
var formatValidation = (validation) => {
|
|
2664
|
-
const policyFailed = numberFrom(validation?.policyChecks?.failed);
|
|
2665
|
-
const unitFailed = numberFrom(validation?.unitTests?.failed);
|
|
2666
|
-
if (policyFailed === 0 && unitFailed === 0) {
|
|
2667
|
-
return "Validation clean";
|
|
2668
|
-
}
|
|
2669
|
-
const parts = [];
|
|
2670
|
-
if (unitFailed !== void 0) {
|
|
2671
|
-
parts.push(unitFailed === 0 ? "unit tests clean" : `${unitFailed} unit tests failed`);
|
|
2672
|
-
}
|
|
2673
|
-
if (policyFailed !== void 0) {
|
|
2674
|
-
parts.push(policyFailed === 0 ? "policy checks clean" : `${policyFailed} policy checks failed`);
|
|
2675
|
-
}
|
|
2676
|
-
return parts.length ? parts.join(", ") : "Validation not available";
|
|
2677
|
-
};
|
|
2678
2671
|
var validationSummaryLine = (validation) => {
|
|
2679
2672
|
const unitFailed = numberFrom(validation?.unitTests?.failed);
|
|
2680
2673
|
if (unitFailed === void 0) {
|
|
@@ -2716,6 +2709,135 @@ var validationDetailLines = (validation) => {
|
|
|
2716
2709
|
}
|
|
2717
2710
|
return lines.length ? lines : ["- Validation data was not available."];
|
|
2718
2711
|
};
|
|
2712
|
+
var failedValidationRecords = (records) => (Array.isArray(records) ? records : []).map(asRecord).filter((record) => {
|
|
2713
|
+
const status = String(
|
|
2714
|
+
record.status ?? record.outcome ?? record.result ?? record.state ?? ""
|
|
2715
|
+
).toLowerCase();
|
|
2716
|
+
if (record.passed === false || record.success === false) {
|
|
2717
|
+
return true;
|
|
2718
|
+
}
|
|
2719
|
+
return ["fail", "failed", "error", "critical"].includes(status);
|
|
2720
|
+
});
|
|
2721
|
+
var severityIcon = (severity) => {
|
|
2722
|
+
switch (String(severity ?? "").toLowerCase()) {
|
|
2723
|
+
case "critical":
|
|
2724
|
+
case "error":
|
|
2725
|
+
case "high":
|
|
2726
|
+
case "fail":
|
|
2727
|
+
case "failed":
|
|
2728
|
+
return "\u{1F534}";
|
|
2729
|
+
case "warning":
|
|
2730
|
+
case "warn":
|
|
2731
|
+
case "medium":
|
|
2732
|
+
return "\u{1F7E1}";
|
|
2733
|
+
case "info":
|
|
2734
|
+
case "low":
|
|
2735
|
+
return "\u{1F535}";
|
|
2736
|
+
default:
|
|
2737
|
+
return "\u26AA";
|
|
2738
|
+
}
|
|
2739
|
+
};
|
|
2740
|
+
var compactMarkdownCell = (value, fallback = "not available") => {
|
|
2741
|
+
const text = String(value ?? "").replace(/\s+/g, " ").replace(/\|/g, "\\|").trim();
|
|
2742
|
+
return text || fallback;
|
|
2743
|
+
};
|
|
2744
|
+
var failureName = (record, fallback) => compactMarkdownCell(
|
|
2745
|
+
record.test_name ?? record.testName ?? record.rule_name ?? record.ruleName ?? record.name ?? record.title ?? record.id ?? fallback
|
|
2746
|
+
);
|
|
2747
|
+
var failureLocation = (record) => compactMarkdownCell(
|
|
2748
|
+
record.file_path ?? record.filePath ?? record.path ?? record.resource_id ?? record.resourceId ?? record.resource ?? record.target,
|
|
2749
|
+
"-"
|
|
2750
|
+
);
|
|
2751
|
+
var failureWhy = (record) => {
|
|
2752
|
+
const message = compactMarkdownCell(
|
|
2753
|
+
record.message ?? record.reason ?? record.description ?? record.details,
|
|
2754
|
+
""
|
|
2755
|
+
);
|
|
2756
|
+
const recommendation = compactMarkdownCell(
|
|
2757
|
+
record.recommendation ?? record.remediation ?? record.fix ?? record.next_step,
|
|
2758
|
+
""
|
|
2759
|
+
);
|
|
2760
|
+
if (message && recommendation && message !== recommendation) {
|
|
2761
|
+
return `${message} ${recommendation}`;
|
|
2762
|
+
}
|
|
2763
|
+
return message || recommendation || "No failure reason was included in the report payload.";
|
|
2764
|
+
};
|
|
2765
|
+
var validationFailureRows = (validation) => {
|
|
2766
|
+
const rows = [];
|
|
2767
|
+
const unitFailures = (Array.isArray(validation?.unitTests?.failures) ? validation?.unitTests?.failures : []).map(asRecord).slice(0, 5);
|
|
2768
|
+
const policyFailures = (Array.isArray(validation?.policyChecks?.failures) ? validation?.policyChecks?.failures : []).map(asRecord).slice(0, 5);
|
|
2769
|
+
const groupedFailures = [
|
|
2770
|
+
["Unit test", unitFailures],
|
|
2771
|
+
["Policy check", policyFailures]
|
|
2772
|
+
];
|
|
2773
|
+
for (const [kind, failures] of groupedFailures) {
|
|
2774
|
+
failures.forEach((failure, index) => {
|
|
2775
|
+
const severity = compactMarkdownCell(
|
|
2776
|
+
failure.severity ?? failure.status ?? failure.outcome,
|
|
2777
|
+
"failed"
|
|
2778
|
+
);
|
|
2779
|
+
rows.push(
|
|
2780
|
+
`| ${kind} | ${failureName(failure, `${kind} ${index + 1}`)} | \`${failureLocation(failure)}\` | ${severityIcon(severity)} ${severity} | ${failureWhy(failure)} |`
|
|
2781
|
+
);
|
|
2782
|
+
});
|
|
2783
|
+
}
|
|
2784
|
+
return rows;
|
|
2785
|
+
};
|
|
2786
|
+
var architectureSignalLines = ({
|
|
2787
|
+
architecture,
|
|
2788
|
+
costServices,
|
|
2789
|
+
costCurrency,
|
|
2790
|
+
highRiskFindings,
|
|
2791
|
+
pillars
|
|
2792
|
+
}) => {
|
|
2793
|
+
const lines = [];
|
|
2794
|
+
const resourceCount = numberFrom(architecture?.resources);
|
|
2795
|
+
const resourceTypeCount = numberFrom(architecture?.resourceTypes);
|
|
2796
|
+
const relationshipCount = numberFrom(architecture?.relationships);
|
|
2797
|
+
const density = numberFrom(architecture?.relationshipDensity);
|
|
2798
|
+
if (resourceCount !== void 0 && resourceTypeCount !== void 0) {
|
|
2799
|
+
lines.push(
|
|
2800
|
+
`- Scale: **${displayNumber(resourceCount)} resources** across **${displayNumber(resourceTypeCount)} resource types**`
|
|
2801
|
+
);
|
|
2802
|
+
} else if (resourceCount !== void 0) {
|
|
2803
|
+
lines.push(`- Scale: **${displayNumber(resourceCount)} resources**`);
|
|
2804
|
+
}
|
|
2805
|
+
if (relationshipCount !== void 0) {
|
|
2806
|
+
const densityText = density !== void 0 ? ` (**${trimNumber(density, 2)} per resource**)` : "";
|
|
2807
|
+
const shape = density !== void 0 && density < 0.5 ? "; sparse dependency graph, review isolated resources and missing links" : density !== void 0 && density > 2 ? "; dense dependency graph, review blast radius before changes" : "";
|
|
2808
|
+
lines.push(
|
|
2809
|
+
`- Dependency shape: **${displayNumber(relationshipCount)} relationships**${densityText}${shape}`
|
|
2810
|
+
);
|
|
2811
|
+
}
|
|
2812
|
+
const highRisk = numberFrom(highRiskFindings);
|
|
2813
|
+
const weakestPillar = pillars.map((pillar) => ({
|
|
2814
|
+
label: String(pillar.label ?? pillar.id ?? "pillar"),
|
|
2815
|
+
score: numberFrom(pillar.score)
|
|
2816
|
+
})).filter((pillar) => pillar.score !== void 0).sort((left, right) => left.score - right.score)[0];
|
|
2817
|
+
if (highRisk !== void 0 || weakestPillar) {
|
|
2818
|
+
const parts = [];
|
|
2819
|
+
if (highRisk !== void 0) {
|
|
2820
|
+
parts.push(`**${displayNumber(highRisk)} high-risk findings**`);
|
|
2821
|
+
}
|
|
2822
|
+
if (weakestPillar) {
|
|
2823
|
+
const rating = scoreRating(weakestPillar.score);
|
|
2824
|
+
parts.push(
|
|
2825
|
+
`weakest pillar **${weakestPillar.label} ${formatScore(weakestPillar.score)} (${rating ?? "UNKNOWN"})**`
|
|
2826
|
+
);
|
|
2827
|
+
}
|
|
2828
|
+
lines.push(`- Risk concentration: ${parts.join("; ")}`);
|
|
2829
|
+
}
|
|
2830
|
+
const topCostDrivers = costServices.filter((service) => service.amount > 0).sort((left, right) => right.amount - left.amount).slice(0, 2);
|
|
2831
|
+
if (topCostDrivers.length) {
|
|
2832
|
+
const total = topCostDrivers.reduce((sum, service) => sum + service.amount, 0);
|
|
2833
|
+
lines.push(
|
|
2834
|
+
`- Cost drivers: ${joinReadableList(
|
|
2835
|
+
topCostDrivers.map((service) => `**${service.name}**`)
|
|
2836
|
+
)} account for **${formatMonthlyMoney(total, costCurrency ?? topCostDrivers[0]?.currency)}**`
|
|
2837
|
+
);
|
|
2838
|
+
}
|
|
2839
|
+
return lines;
|
|
2840
|
+
};
|
|
2719
2841
|
var namedAmount = (record) => numberFrom(
|
|
2720
2842
|
record.amount,
|
|
2721
2843
|
record.monthly_cost,
|
|
@@ -2725,6 +2847,15 @@ var namedAmount = (record) => numberFrom(
|
|
|
2725
2847
|
);
|
|
2726
2848
|
var namedLabel = (record, fallback) => String(record.name ?? record.service ?? record.label ?? record.category ?? fallback);
|
|
2727
2849
|
var mermaidLabel = (value) => value.replace(/"/g, "'");
|
|
2850
|
+
var joinReadableList = (values) => {
|
|
2851
|
+
if (values.length <= 1) {
|
|
2852
|
+
return values[0] ?? "";
|
|
2853
|
+
}
|
|
2854
|
+
if (values.length === 2) {
|
|
2855
|
+
return `${values[0]} and ${values[1]}`;
|
|
2856
|
+
}
|
|
2857
|
+
return `${values.slice(0, -1).join(", ")}, and ${values[values.length - 1]}`;
|
|
2858
|
+
};
|
|
2728
2859
|
var costServiceRows = (services, currency) => (Array.isArray(services) ? services : []).map((service, index) => {
|
|
2729
2860
|
const record = asRecord(service);
|
|
2730
2861
|
const amount = namedAmount(record);
|
|
@@ -2818,19 +2949,41 @@ var extractValidation = ({
|
|
|
2818
2949
|
preload?.reports?.unit_tests?.metrics,
|
|
2819
2950
|
project?.status?.unit_tests
|
|
2820
2951
|
);
|
|
2952
|
+
const unitFailures = failedValidationRecords(
|
|
2953
|
+
firstArray(
|
|
2954
|
+
preload?.latest_payloads?.unit_tests?.test_results,
|
|
2955
|
+
preload?.latest_payloads?.unit_tests?.results,
|
|
2956
|
+
preload?.reports?.unit_tests?.metrics?.test_results,
|
|
2957
|
+
preload?.reports?.unit_tests?.metrics?.results,
|
|
2958
|
+
project?.status?.unit_tests?.test_results,
|
|
2959
|
+
project?.status?.unit_tests?.results
|
|
2960
|
+
)
|
|
2961
|
+
);
|
|
2962
|
+
const policyFailures = failedValidationRecords(
|
|
2963
|
+
firstArray(
|
|
2964
|
+
policySummary?.results,
|
|
2965
|
+
policySummary?.checks,
|
|
2966
|
+
waf?.parsed?.validation_results,
|
|
2967
|
+
waf?.parsed?.rules,
|
|
2968
|
+
waf?.raw?.rules,
|
|
2969
|
+
failedRules
|
|
2970
|
+
)
|
|
2971
|
+
);
|
|
2821
2972
|
return {
|
|
2822
2973
|
policyChecks: {
|
|
2823
2974
|
total: numberFrom(policySummary?.total_rules, policySummary?.totalRules, rules.length),
|
|
2824
2975
|
passed: numberFrom(policySummary?.passed_rules, policySummary?.passedRules),
|
|
2825
2976
|
failed: numberFrom(policySummary?.failed_rules, policySummary?.failedRules, failedRules.length),
|
|
2826
|
-
errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules)
|
|
2977
|
+
errors: numberFrom(policySummary?.error_rules, policySummary?.errorRules),
|
|
2978
|
+
failures: policyFailures.slice(0, 5)
|
|
2827
2979
|
},
|
|
2828
2980
|
unitTests: {
|
|
2829
2981
|
status: unitSummary?.status,
|
|
2830
2982
|
total: numberFrom(unitSummary?.total_tests, unitSummary?.totalTests),
|
|
2831
2983
|
passed: numberFrom(unitSummary?.passed_tests, unitSummary?.passedTests),
|
|
2832
2984
|
failed: numberFrom(unitSummary?.failed_tests, unitSummary?.failedTests),
|
|
2833
|
-
skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests)
|
|
2985
|
+
skipped: numberFrom(unitSummary?.skipped_tests, unitSummary?.skippedTests),
|
|
2986
|
+
failures: unitFailures.slice(0, 5)
|
|
2834
2987
|
}
|
|
2835
2988
|
};
|
|
2836
2989
|
};
|
|
@@ -3185,81 +3338,8 @@ var waitForReviewReports = async ({
|
|
|
3185
3338
|
}
|
|
3186
3339
|
return latest;
|
|
3187
3340
|
};
|
|
3188
|
-
var buildAiSummaryPrompt = (data) => [
|
|
3189
|
-
"Write a concise CloudEval pull request review summary in Markdown.",
|
|
3190
|
-
"Return exactly two sections: Short Summary and Details.",
|
|
3191
|
-
"Short Summary: one dense paragraph under 45 words with gate status, Well-Architected posture, validation, and cost.",
|
|
3192
|
-
"Details: short bullets with bold labels only when useful, such as **Key risks:**, **Cost posture:**, and **Recommended next step:**.",
|
|
3193
|
-
"Keep the full response under 180 words. Do not invent facts not present below.",
|
|
3194
|
-
"Do not include citations, source markers, hidden tool ids, or HTML comments.",
|
|
3195
|
-
"",
|
|
3196
|
-
`Project: ${data.projectId}`,
|
|
3197
|
-
`Repository: ${data.repo ?? "unknown"}`,
|
|
3198
|
-
`Ref: ${data.ref ?? "unknown"}`,
|
|
3199
|
-
`Commit: ${data.commitSha ?? "unknown"}`,
|
|
3200
|
-
`Gate: ${String(data.gate?.status ?? "unknown").toUpperCase()}`,
|
|
3201
|
-
`Well-Architected score: ${data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore ?? "unknown"}`,
|
|
3202
|
-
`High-risk findings: ${data.gate?.wellArchitected?.risks?.high ?? data.gate?.highRisk ?? "unknown"}`,
|
|
3203
|
-
`Monthly cost: ${formatMoney(data.gate?.cost?.monthly?.amount ?? data.gate?.monthlyCost, data.gate?.cost?.monthly?.currency)}`,
|
|
3204
|
-
`Validation: ${formatValidation(data.gate?.validation)}`,
|
|
3205
|
-
Array.isArray(data.gate?.failures) && data.gate.failures.length ? `Gate failures: ${data.gate.failures.join("; ")}` : "Gate failures: none reported"
|
|
3206
|
-
].join("\n");
|
|
3207
|
-
var isTransientAiSummaryText = (text) => {
|
|
3208
|
-
if (!text?.trim()) {
|
|
3209
|
-
return false;
|
|
3210
|
-
}
|
|
3211
|
-
return /too many requests|rate[- ]?limit|try again in a moment|temporarily unavailable|something went wrong while processing|please try again or ask a different question|did not complete within/i.test(
|
|
3212
|
-
text
|
|
3213
|
-
);
|
|
3214
|
-
};
|
|
3215
|
-
var normalizeAiSummaryMarkdown = (text) => {
|
|
3216
|
-
return normalizeAiSummarySections(text).markdown;
|
|
3217
|
-
};
|
|
3218
|
-
var normalizeAiSummarySections = (text) => {
|
|
3219
|
-
const trimmed = String(text ?? "").trim();
|
|
3220
|
-
if (!trimmed) {
|
|
3221
|
-
return { shortSummary: "", detailsMarkdown: "", markdown: "" };
|
|
3222
|
-
}
|
|
3223
|
-
const withNewlines = sanitizeAiSummaryMarkdown(trimmed.replace(/\\n/g, "\n"));
|
|
3224
|
-
const requestMatch = /\bRequest:\s*/i.exec(withNewlines);
|
|
3225
|
-
const sourceText = requestMatch ? withNewlines.slice(requestMatch.index + requestMatch[0].length).split(/\n\s*\nThe request reached\b/i)[0].trim() || withNewlines : withNewlines;
|
|
3226
|
-
const sections = splitAiSummarySections(sourceText);
|
|
3227
|
-
return {
|
|
3228
|
-
...sections,
|
|
3229
|
-
markdown: renderAiSummarySections(sections.shortSummary, sections.detailsMarkdown)
|
|
3230
|
-
};
|
|
3231
|
-
};
|
|
3232
|
-
var sanitizeAiSummaryMarkdown = (text) => text.replace(/<!--[\s\S]*?-->/g, "").replace(/\s*\[S_[A-Za-z0-9_:-]+\]/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
3233
|
-
var stripAiSectionLabel = (text, label) => text.replace(new RegExp(`^\\s*#{0,6}\\s*\\*\\*?${label}\\*\\*?\\s*:?\\s*`, "i"), "").replace(new RegExp(`^\\s*${label}\\s*:?\\s*`, "i"), "").trim();
|
|
3234
|
-
var normalizeAiDetailHeadings = (text) => text.split("\n").map((line) => {
|
|
3235
|
-
const cleaned = line.replace(/^\s*#{1,6}\s*/, "").replace(/^\s*[-*]\s+/, "").trimEnd();
|
|
3236
|
-
const labelMatch = cleaned.match(/^\*\*(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact):\*\*\s*(.*)$/i) ?? cleaned.match(/^\*\*(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact)\*\*\s*:\s*(.*)$/i) ?? cleaned.match(/^(Key risks|Cost posture|Recommended next step|Recommendation|Validation|Impact)\s*:\s*(.*)$/i);
|
|
3237
|
-
if (!labelMatch) {
|
|
3238
|
-
return cleaned;
|
|
3239
|
-
}
|
|
3240
|
-
return `- **${labelMatch[1]}:** ${labelMatch[2]}`.trimEnd();
|
|
3241
|
-
}).join("\n").trim();
|
|
3242
|
-
var splitAiSummarySections = (text) => {
|
|
3243
|
-
const cleaned = sanitizeAiSummaryMarkdown(text).replace(/^\s*#{1,6}\s*AI summary\s*$/gim, "").trim();
|
|
3244
|
-
const shortMatch = /\bShort Summary\s*:\s*/i.exec(cleaned);
|
|
3245
|
-
const detailsMatch = /\bDetails\s*:\s*/i.exec(cleaned);
|
|
3246
|
-
if (shortMatch && detailsMatch && shortMatch.index < detailsMatch.index) {
|
|
3247
|
-
const shortSummary2 = stripAiSectionLabel(
|
|
3248
|
-
cleaned.slice(shortMatch.index, detailsMatch.index).trim(),
|
|
3249
|
-
"Short Summary"
|
|
3250
|
-
);
|
|
3251
|
-
const detailsMarkdown2 = normalizeAiDetailHeadings(
|
|
3252
|
-
stripAiSectionLabel(cleaned.slice(detailsMatch.index).trim(), "Details")
|
|
3253
|
-
);
|
|
3254
|
-
return { shortSummary: shortSummary2, detailsMarkdown: detailsMarkdown2 };
|
|
3255
|
-
}
|
|
3256
|
-
const paragraphs = cleaned.split(/\n\s*\n/).map((paragraph) => paragraph.trim()).filter(Boolean);
|
|
3257
|
-
const shortSummary = stripAiSectionLabel(paragraphs[0] ?? cleaned, "Short Summary");
|
|
3258
|
-
const detailsMarkdown = normalizeAiDetailHeadings(paragraphs.slice(1).join("\n\n"));
|
|
3259
|
-
return { shortSummary, detailsMarkdown };
|
|
3260
|
-
};
|
|
3261
3341
|
var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
|
|
3262
|
-
const lines = [
|
|
3342
|
+
const lines = [shortSummary.trim()];
|
|
3263
3343
|
if (detailsMarkdown.trim()) {
|
|
3264
3344
|
lines.push(
|
|
3265
3345
|
"",
|
|
@@ -3273,195 +3353,108 @@ var renderAiSummarySections = (shortSummary, detailsMarkdown) => {
|
|
|
3273
3353
|
}
|
|
3274
3354
|
return lines.join("\n");
|
|
3275
3355
|
};
|
|
3276
|
-
var
|
|
3277
|
-
const
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
}
|
|
3283
|
-
}
|
|
3284
|
-
return 18e4;
|
|
3285
|
-
};
|
|
3286
|
-
var aiSummaryRetryDelaysMs = () => {
|
|
3287
|
-
const raw = process.env.CLOUDEVAL_REVIEW_AI_RETRY_DELAYS_MS;
|
|
3288
|
-
if (raw?.trim()) {
|
|
3289
|
-
return raw.split(",").map((value) => Number.parseInt(value.trim(), 10)).filter((value) => Number.isFinite(value) && value >= 0);
|
|
3356
|
+
var githubWorkflowRunUrl = () => {
|
|
3357
|
+
const server = process.env.GITHUB_SERVER_URL;
|
|
3358
|
+
const repo = process.env.GITHUB_REPOSITORY;
|
|
3359
|
+
const runId = process.env.GITHUB_RUN_ID;
|
|
3360
|
+
if (server && repo && runId) {
|
|
3361
|
+
return `${server.replace(/\/$/, "")}/${repo}/actions/runs/${runId}`;
|
|
3290
3362
|
}
|
|
3291
|
-
return
|
|
3363
|
+
return void 0;
|
|
3292
3364
|
};
|
|
3293
|
-
var
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
data
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
|
|
3307
|
-
|
|
3308
|
-
|
|
3309
|
-
|
|
3310
|
-
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
cloud_provider: typeof project.cloud_provider === "string" ? project.cloud_provider : void 0,
|
|
3333
|
-
type: typeof project.type === "string" ? project.type : void 0,
|
|
3334
|
-
connection_ids: Array.isArray(project.connection_ids) ? project.connection_ids : void 0
|
|
3335
|
-
} : {
|
|
3336
|
-
id: String(data.projectId),
|
|
3337
|
-
name: String(data.projectId)
|
|
3338
|
-
},
|
|
3339
|
-
settings: {
|
|
3340
|
-
mode,
|
|
3341
|
-
...model ? { model } : {}
|
|
3342
|
-
},
|
|
3343
|
-
...mode === "agent" ? { agentProfileId: agentProfileId ?? "architecture" } : {},
|
|
3344
|
-
completeAfterResponse: true,
|
|
3345
|
-
responseCompletionGraceMs: 250,
|
|
3346
|
-
streamIdleTimeoutMs: 12e4
|
|
3347
|
-
});
|
|
3348
|
-
try {
|
|
3349
|
-
while (true) {
|
|
3350
|
-
const next = await Promise.race([iterator.next(), timeoutPromise]);
|
|
3351
|
-
if (next.done) {
|
|
3352
|
-
break;
|
|
3353
|
-
}
|
|
3354
|
-
const chunk = next.value;
|
|
3355
|
-
chatState = core.reduceChunk(chatState, chunk);
|
|
3356
|
-
const latestMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
|
|
3357
|
-
const chunkAssistantMessage = Array.isArray(chunk?.messages) ? [...chunk.messages].reverse().find((message) => message?.role === "assistant" && typeof message?.content === "string")?.content : void 0;
|
|
3358
|
-
const content = chunk?.content;
|
|
3359
|
-
if (typeof chunkAssistantMessage === "string" && chunkAssistantMessage.trim()) {
|
|
3360
|
-
markdown = chunkAssistantMessage;
|
|
3361
|
-
}
|
|
3362
|
-
if (chunk.type === "responding" && typeof content === "string") {
|
|
3363
|
-
markdown = latestMessage?.content || `${markdown}${content}`;
|
|
3364
|
-
}
|
|
3365
|
-
}
|
|
3366
|
-
} finally {
|
|
3367
|
-
if (timeoutId) {
|
|
3368
|
-
clearTimeout(timeoutId);
|
|
3369
|
-
}
|
|
3370
|
-
if (timedOut) {
|
|
3371
|
-
await iterator.return?.(void 0).catch(() => void 0);
|
|
3372
|
-
}
|
|
3373
|
-
}
|
|
3374
|
-
const finalMessage = [...chatState.messages ?? []].reverse().find((message) => message.role === "assistant");
|
|
3375
|
-
const rawMarkdown = String(finalMessage?.content || markdown).trim();
|
|
3376
|
-
const normalized = normalizeAiSummarySections(rawMarkdown);
|
|
3365
|
+
var reviewSurface = () => {
|
|
3366
|
+
const event = String(process.env.GITHUB_EVENT_NAME ?? "").toLowerCase();
|
|
3367
|
+
const ref = String(process.env.GITHUB_REF ?? "").toLowerCase();
|
|
3368
|
+
return event.startsWith("pull_request") || ref.startsWith("refs/pull/") ? "pull_request" : "local_review";
|
|
3369
|
+
};
|
|
3370
|
+
var buildReviewSummaryPayload = (data) => ({
|
|
3371
|
+
source: process.env.GITHUB_ACTIONS === "true" ? "github_action" : "cli",
|
|
3372
|
+
surface: reviewSurface(),
|
|
3373
|
+
project: data.project ?? { id: data.projectId },
|
|
3374
|
+
repository: { full_name: data.repo },
|
|
3375
|
+
ref: data.ref,
|
|
3376
|
+
commit_sha: data.commitSha,
|
|
3377
|
+
workflow_run_url: githubWorkflowRunUrl(),
|
|
3378
|
+
gate_result: {
|
|
3379
|
+
status: data.gate?.status,
|
|
3380
|
+
failures: data.gate?.failures ?? [],
|
|
3381
|
+
thresholds: data.gate?.thresholds ?? {},
|
|
3382
|
+
enforcement: data.gate?.enforcement
|
|
3383
|
+
},
|
|
3384
|
+
well_architected: data.gate?.wellArchitected ?? {},
|
|
3385
|
+
cost: data.gate?.cost ?? {},
|
|
3386
|
+
validation: data.gate?.validation ?? {},
|
|
3387
|
+
policy: data.gate?.validation?.policy ?? data.gate?.policy ?? {},
|
|
3388
|
+
architecture_signals: data.gate?.architecture ?? {},
|
|
3389
|
+
changed_files: data.changedFiles ?? []
|
|
3390
|
+
});
|
|
3391
|
+
var deterministicAiSummary = (data, error) => {
|
|
3392
|
+
const score = data.gate?.wellArchitected?.overall?.score ?? data.gate?.overallScore;
|
|
3393
|
+
const rating = scoreRating(score) ?? "UNKNOWN";
|
|
3394
|
+
const validation = data.gate?.validation ?? {};
|
|
3395
|
+
const cost = data.gate?.cost?.monthly ?? {};
|
|
3396
|
+
const failedTests = numberFrom(validation?.unitTests?.failed) ?? numberFrom(validation?.unit_tests?.failed) ?? numberFrom(validation?.failedUnitTests) ?? numberFrom(validation?.failed_tests) ?? 0;
|
|
3397
|
+
const policyFailed = numberFrom(validation?.policyChecks?.failed) ?? numberFrom(validation?.policy_checks?.failed) ?? numberFrom(data.gate?.policy?.failed) ?? 0;
|
|
3398
|
+
const policyStatus = policyFailed > 0 ? "has failed checks" : "GOOD";
|
|
3399
|
+
const summary = [
|
|
3400
|
+
`CloudEval review completed with ${String(data.gate?.status ?? "UNKNOWN").toUpperCase()}.`,
|
|
3401
|
+
`Well-Architected posture is ${formatScore(score)} (${rating}), validation has ${displayNumber(failedTests)} failed unit tests, policy checks are ${policyStatus}, and monthly cost is ${formatMonthlyMoney(cost?.amount, cost?.currency)}.`,
|
|
3402
|
+
"Prioritize failed validation checks and the weakest Well-Architected pillar first."
|
|
3403
|
+
].join(" ");
|
|
3377
3404
|
return {
|
|
3378
3405
|
enabled: true,
|
|
3379
|
-
status: "
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3406
|
+
status: "fallback",
|
|
3407
|
+
fallbackUsed: true,
|
|
3408
|
+
warnings: error ? [`Review summary endpoint failed: ${error}`] : [],
|
|
3409
|
+
shortSummary: summary,
|
|
3410
|
+
detailsMarkdown: [
|
|
3411
|
+
"**Main risk**\nCloudEval could not produce an AI-written review summary, so use the deterministic gate evidence.",
|
|
3412
|
+
"**Why it matters**\nFailed validation and weak architecture pillars are the highest-signal remediation inputs.",
|
|
3413
|
+
"**Recommended actions**\nFix failed validation checks, address the weakest pillar, rerun CloudEval review, and compare the updated gate.",
|
|
3414
|
+
"**Evidence used**\nGate status, Well-Architected score, validation totals, policy totals, and monthly cost."
|
|
3415
|
+
].join("\n\n"),
|
|
3416
|
+
markdown: renderAiSummarySections(
|
|
3417
|
+
summary,
|
|
3418
|
+
[
|
|
3419
|
+
"**Main risk**\nCloudEval could not produce an AI-written review summary, so use the deterministic gate evidence.",
|
|
3420
|
+
"**Why it matters**\nFailed validation and weak architecture pillars are the highest-signal remediation inputs.",
|
|
3421
|
+
"**Recommended actions**\nFix failed validation checks, address the weakest pillar, rerun CloudEval review, and compare the updated gate.",
|
|
3422
|
+
"**Evidence used**\nGate status, Well-Architected score, validation totals, policy totals, and monthly cost."
|
|
3423
|
+
].join("\n\n")
|
|
3424
|
+
)
|
|
3385
3425
|
};
|
|
3386
3426
|
};
|
|
3387
3427
|
var generateAiSummary = async (input) => {
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
enabled: true,
|
|
3403
|
-
status: "transient_error",
|
|
3404
|
-
mode: activeInput.mode,
|
|
3405
|
-
...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
|
|
3406
|
-
...activeInput.model ? { model: activeInput.model } : {},
|
|
3407
|
-
markdown: message,
|
|
3408
|
-
error: message
|
|
3409
|
-
};
|
|
3410
|
-
}
|
|
3411
|
-
result.attempts = attemptIndex + 1;
|
|
3412
|
-
if (fallbackFromMode) {
|
|
3413
|
-
result.fallbackFromMode = fallbackFromMode;
|
|
3414
|
-
}
|
|
3415
|
-
const normalizedSummary = typeof result.shortSummary === "string" || typeof result.detailsMarkdown === "string" ? {
|
|
3416
|
-
shortSummary: String(result.shortSummary ?? "").trim(),
|
|
3417
|
-
detailsMarkdown: String(result.detailsMarkdown ?? "").trim(),
|
|
3418
|
-
markdown: renderAiSummarySections(
|
|
3419
|
-
String(result.shortSummary ?? "").trim(),
|
|
3420
|
-
String(result.detailsMarkdown ?? "").trim()
|
|
3421
|
-
)
|
|
3422
|
-
} : normalizeAiSummarySections(result.markdown);
|
|
3423
|
-
result = { ...result, ...normalizedSummary };
|
|
3424
|
-
if (isTransientAiSummaryText(result.markdown)) {
|
|
3425
|
-
const normalizedError = normalizeAiSummaryMarkdown(result.error);
|
|
3426
|
-
if (normalizedError && !isTransientAiSummaryText(normalizedError)) {
|
|
3427
|
-
result = {
|
|
3428
|
-
...result,
|
|
3429
|
-
...normalizeAiSummarySections(normalizedError)
|
|
3430
|
-
};
|
|
3431
|
-
result.status = "ok";
|
|
3432
|
-
delete result.error;
|
|
3433
|
-
}
|
|
3434
|
-
}
|
|
3435
|
-
lastResult = result;
|
|
3436
|
-
if (!isTransientAiSummaryText(result.markdown)) {
|
|
3437
|
-
return result;
|
|
3438
|
-
}
|
|
3439
|
-
const retryDelay = retryDelays[attemptIndex];
|
|
3440
|
-
if (retryDelay === void 0) {
|
|
3441
|
-
break;
|
|
3442
|
-
}
|
|
3443
|
-
await sleep2(retryDelay);
|
|
3444
|
-
if (activeInput.mode === "agent") {
|
|
3445
|
-
fallbackFromMode = "agent";
|
|
3446
|
-
activeInput = {
|
|
3447
|
-
...input,
|
|
3448
|
-
mode: "ask",
|
|
3449
|
-
agentProfileId: void 0
|
|
3450
|
-
};
|
|
3428
|
+
try {
|
|
3429
|
+
const payload = buildReviewSummaryPayload(input.data);
|
|
3430
|
+
const response = await fetchCloudEvalJson({
|
|
3431
|
+
baseUrl: input.baseUrl,
|
|
3432
|
+
authToken: input.token,
|
|
3433
|
+
path: `/projects/${encodeURIComponent(String(input.data.projectId))}/review/summary`,
|
|
3434
|
+
method: "POST",
|
|
3435
|
+
body: payload,
|
|
3436
|
+
idempotencyKey: `cloudeval-review-summary-${input.data.projectId}-${input.data.commitSha ?? "head"}`
|
|
3437
|
+
});
|
|
3438
|
+
const shortSummary = String(response.summary ?? "").trim();
|
|
3439
|
+
const detailsMarkdown = String(response.details ?? "").trim();
|
|
3440
|
+
if (!shortSummary) {
|
|
3441
|
+
return deterministicAiSummary(input.data, "Review summary endpoint returned no summary.");
|
|
3451
3442
|
}
|
|
3443
|
+
return {
|
|
3444
|
+
enabled: true,
|
|
3445
|
+
status: response.fallback_used ? "fallback" : "ok",
|
|
3446
|
+
fallbackUsed: Boolean(response.fallback_used),
|
|
3447
|
+
warnings: Array.isArray(response.warnings) ? response.warnings : [],
|
|
3448
|
+
riskHighlights: Array.isArray(response.risk_highlights) ? response.risk_highlights : [],
|
|
3449
|
+
recommendedActions: Array.isArray(response.recommended_actions) ? response.recommended_actions : [],
|
|
3450
|
+
evidenceUsed: Array.isArray(response.evidence_used) ? response.evidence_used : [],
|
|
3451
|
+
shortSummary,
|
|
3452
|
+
detailsMarkdown,
|
|
3453
|
+
markdown: renderAiSummarySections(shortSummary, detailsMarkdown)
|
|
3454
|
+
};
|
|
3455
|
+
} catch (error) {
|
|
3456
|
+
return deterministicAiSummary(input.data, error?.message ?? "request failed");
|
|
3452
3457
|
}
|
|
3453
|
-
return {
|
|
3454
|
-
...lastResult ?? {},
|
|
3455
|
-
enabled: true,
|
|
3456
|
-
status: "unavailable",
|
|
3457
|
-
mode: activeInput.mode,
|
|
3458
|
-
...activeInput.mode === "agent" ? { agentProfileId: activeInput.agentProfileId ?? "architecture" } : {},
|
|
3459
|
-
...input.model ? { model: input.model } : {},
|
|
3460
|
-
...fallbackFromMode ? { fallbackFromMode } : {},
|
|
3461
|
-
attempts: lastResult?.attempts ?? retryDelays.length + 1,
|
|
3462
|
-
error: lastResult?.markdown || "AI summary unavailable",
|
|
3463
|
-
markdown: "AI summary unavailable: CloudEval AI was rate-limited. Retry the workflow or rerun `cloudeval review`."
|
|
3464
|
-
};
|
|
3465
3458
|
};
|
|
3466
3459
|
var buildMarkdownSummary = (data) => {
|
|
3467
3460
|
const gateStatus = String(data.gate?.status ?? "unknown").toUpperCase();
|
|
@@ -3471,7 +3464,8 @@ var buildMarkdownSummary = (data) => {
|
|
|
3471
3464
|
const architecture = data.gate?.architecture;
|
|
3472
3465
|
const projectLabel = String(data.project?.name ?? data.projectName ?? data.projectId);
|
|
3473
3466
|
const projectDisplay = markdownLink(projectLabel, data.project?.url ?? data.projectUrl);
|
|
3474
|
-
const
|
|
3467
|
+
const repository = String(data.repo ?? "unknown repository");
|
|
3468
|
+
const ref = String(data.ref ?? "unknown ref");
|
|
3475
3469
|
const commit = String(data.commitSha ?? "unknown").slice(0, 12);
|
|
3476
3470
|
const pillarLines = Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars.map((pillar) => {
|
|
3477
3471
|
const rating = scoreRating(pillar.score);
|
|
@@ -3488,24 +3482,14 @@ var buildMarkdownSummary = (data) => {
|
|
|
3488
3482
|
cost?.currency
|
|
3489
3483
|
);
|
|
3490
3484
|
const positiveCostServices = costServices.filter((service) => service.amount > 0);
|
|
3491
|
-
const architectureLines =
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
`- Graph connectivity: **${trimNumber(density, 2)} relationships per resource**`
|
|
3500
|
-
);
|
|
3501
|
-
}
|
|
3502
|
-
const resourceCount = numberFrom(architecture?.resources);
|
|
3503
|
-
const resourceTypeCount = numberFrom(architecture?.resourceTypes);
|
|
3504
|
-
if (resourceCount !== void 0 && resourceTypeCount !== void 0) {
|
|
3505
|
-
architectureLines.push(
|
|
3506
|
-
`- Resource diversity: **${displayNumber(resourceTypeCount)} types across ${displayNumber(resourceCount)} resources**`
|
|
3507
|
-
);
|
|
3508
|
-
}
|
|
3485
|
+
const architectureLines = architectureSignalLines({
|
|
3486
|
+
architecture,
|
|
3487
|
+
costServices,
|
|
3488
|
+
costCurrency: cost?.currency,
|
|
3489
|
+
highRiskFindings: data.gate?.wellArchitected?.risks?.high,
|
|
3490
|
+
pillars: Array.isArray(data.gate?.wellArchitected?.pillars) ? data.gate.wellArchitected.pillars : []
|
|
3491
|
+
});
|
|
3492
|
+
const validationRows = validationFailureRows(validation);
|
|
3509
3493
|
const overallRating = scoreRating(score);
|
|
3510
3494
|
const lines = [
|
|
3511
3495
|
`${statusIcon(data.gate?.status)} **Overall** : ${gateStatus}`,
|
|
@@ -3513,9 +3497,13 @@ var buildMarkdownSummary = (data) => {
|
|
|
3513
3497
|
validationSummaryLine(validation),
|
|
3514
3498
|
policySummaryLine(validation),
|
|
3515
3499
|
costSummaryLine(cost),
|
|
3516
|
-
`**Cloudeval Project**: ${projectDisplay}`,
|
|
3517
3500
|
"",
|
|
3518
|
-
|
|
3501
|
+
"#### Source",
|
|
3502
|
+
"",
|
|
3503
|
+
`- **CloudEval project**: ${projectDisplay}`,
|
|
3504
|
+
`- **Repository**: \`${repository}\``,
|
|
3505
|
+
`- **Ref**: \`${ref}\``,
|
|
3506
|
+
`- **Commit**: \`${commit}\``
|
|
3519
3507
|
];
|
|
3520
3508
|
if (data.aiSummary?.markdown) {
|
|
3521
3509
|
lines.push("", "#### AI summary", "", data.aiSummary.markdown);
|
|
@@ -3582,9 +3570,15 @@ var buildMarkdownSummary = (data) => {
|
|
|
3582
3570
|
lines.push(
|
|
3583
3571
|
"",
|
|
3584
3572
|
"<details>",
|
|
3585
|
-
"
|
|
3573
|
+
`<summary>${validationRows.length ? "Validation failures" : "Validation details"}</summary>`,
|
|
3586
3574
|
"",
|
|
3587
3575
|
...validationDetailLines(validation),
|
|
3576
|
+
...validationRows.length ? [
|
|
3577
|
+
"",
|
|
3578
|
+
"| Type | Name | Location | Severity | Why / next step |",
|
|
3579
|
+
"| --- | --- | --- | --- | --- |",
|
|
3580
|
+
...validationRows
|
|
3581
|
+
] : [],
|
|
3588
3582
|
"",
|
|
3589
3583
|
"</details>"
|
|
3590
3584
|
);
|
|
@@ -3593,7 +3587,7 @@ var buildMarkdownSummary = (data) => {
|
|
|
3593
3587
|
lines.push(
|
|
3594
3588
|
"",
|
|
3595
3589
|
"<details>",
|
|
3596
|
-
"<summary>Architecture
|
|
3590
|
+
"<summary>Architecture signals</summary>",
|
|
3597
3591
|
"",
|
|
3598
3592
|
...architectureLines,
|
|
3599
3593
|
"",
|
|
@@ -15632,7 +15626,7 @@ program.command("tui").description("Open the CloudEval Terminal UI").option(
|
|
|
15632
15626
|
const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
|
|
15633
15627
|
const [{ render }, { App }] = await Promise.all([
|
|
15634
15628
|
import("ink"),
|
|
15635
|
-
import("./App-
|
|
15629
|
+
import("./App-VSOTHLOD.js")
|
|
15636
15630
|
]);
|
|
15637
15631
|
const baseUrl = await resolveBaseUrl(options, command);
|
|
15638
15632
|
assertSecureBaseUrl(baseUrl);
|
|
@@ -15690,7 +15684,7 @@ program.command("chat").description("Start an interactive chat session").option(
|
|
|
15690
15684
|
const { assertSecureBaseUrl } = await import("./dist-PEYJDO7A.js");
|
|
15691
15685
|
const [{ render }, { App }] = await Promise.all([
|
|
15692
15686
|
import("ink"),
|
|
15693
|
-
import("./App-
|
|
15687
|
+
import("./App-VSOTHLOD.js")
|
|
15694
15688
|
]);
|
|
15695
15689
|
const baseUrl = await resolveBaseUrl(options, command);
|
|
15696
15690
|
assertSecureBaseUrl(baseUrl);
|
|
@@ -16444,7 +16438,7 @@ Error: ${errorMsg}
|
|
|
16444
16438
|
program.command("banner").description("Preview the startup banner and terminal capabilities").action(async () => {
|
|
16445
16439
|
const { render } = await import("ink");
|
|
16446
16440
|
const BannerPreview = React.lazy(async () => ({
|
|
16447
|
-
default: (await import("./Banner-
|
|
16441
|
+
default: (await import("./Banner-7XUOJYL5.js")).Banner
|
|
16448
16442
|
}));
|
|
16449
16443
|
render(
|
|
16450
16444
|
/* @__PURE__ */ jsx(React.Suspense, { fallback: null, children: /* @__PURE__ */ jsx(BannerPreview, { disable: false }) })
|
package/package.json
CHANGED
package/sbom.spdx.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
{
|
|
15
15
|
"SPDXID": "SPDXRef-Package-CloudEval-CLI",
|
|
16
16
|
"name": "CloudEval CLI",
|
|
17
|
-
"versionInfo": "0.27.
|
|
17
|
+
"versionInfo": "0.27.2",
|
|
18
18
|
"downloadLocation": "https://github.com/ganakailabs/cloudeval-cli",
|
|
19
19
|
"filesAnalyzed": false,
|
|
20
20
|
"licenseConcluded": "LicenseRef-CloudEval-CLI",
|