automata-cli 0.3.0-develop.112 → 0.3.0-develop.115
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/index.js +391 -18
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -510,6 +510,8 @@ function getCurrentBranch() {
|
|
|
510
510
|
}
|
|
511
511
|
return stdout.trim();
|
|
512
512
|
}
|
|
513
|
+
var SONAR_FETCH_TIMEOUT_MS = 5e3;
|
|
514
|
+
var SONAR_FAIL_CONCLUSIONS = /* @__PURE__ */ new Set(["FAILURE", "TIMED_OUT", "ACTION_REQUIRED", "CANCELLED"]);
|
|
513
515
|
function parseOwnerRepo() {
|
|
514
516
|
const { stdout, status } = run2("git", ["remote", "get-url", "origin"]);
|
|
515
517
|
if (status !== 0) return null;
|
|
@@ -555,21 +557,285 @@ function extractSonarProjectKey(url) {
|
|
|
555
557
|
return null;
|
|
556
558
|
}
|
|
557
559
|
}
|
|
558
|
-
|
|
559
|
-
|
|
560
|
+
function isSonarUrl(url) {
|
|
561
|
+
try {
|
|
562
|
+
const hostname = new URL(url).hostname;
|
|
563
|
+
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
564
|
+
} catch {
|
|
565
|
+
return false;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
function stripHtml(text) {
|
|
569
|
+
let stripped = "";
|
|
570
|
+
let inTag = false;
|
|
571
|
+
for (const char of text) {
|
|
572
|
+
if (char === "<") {
|
|
573
|
+
inTag = true;
|
|
574
|
+
stripped += " ";
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
if (char === ">") {
|
|
578
|
+
inTag = false;
|
|
579
|
+
continue;
|
|
580
|
+
}
|
|
581
|
+
if (!inTag) {
|
|
582
|
+
stripped += char;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
return stripped;
|
|
586
|
+
}
|
|
587
|
+
function normalizeText(text) {
|
|
588
|
+
if (!text) return void 0;
|
|
589
|
+
const normalized = stripHtml(text).replaceAll(/\s+/g, " ").trim();
|
|
590
|
+
return normalized || void 0;
|
|
591
|
+
}
|
|
592
|
+
function resolveSonarPath(componentKey, components) {
|
|
593
|
+
if (!componentKey) return void 0;
|
|
594
|
+
const mapped = components.get(componentKey);
|
|
595
|
+
if (mapped) return mapped;
|
|
596
|
+
const separatorIndex = componentKey.indexOf(":");
|
|
597
|
+
if (separatorIndex === -1) return void 0;
|
|
598
|
+
const path = componentKey.slice(separatorIndex + 1).trim();
|
|
599
|
+
return path || void 0;
|
|
600
|
+
}
|
|
601
|
+
function mapSonarIssuesPage(data, targetIssues, components, rules) {
|
|
602
|
+
for (const component of data.components ?? []) {
|
|
603
|
+
if (component.key && component.path) {
|
|
604
|
+
components.set(component.key, component.path);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
for (const rule of data.rules ?? []) {
|
|
608
|
+
if (!rule.key) continue;
|
|
609
|
+
const explanation = normalizeText(rule.htmlDesc) ?? normalizeText(rule.htmlNote) ?? normalizeText(rule.name);
|
|
610
|
+
if (explanation) {
|
|
611
|
+
rules.set(rule.key, explanation);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
for (const issue of data.issues ?? []) {
|
|
615
|
+
if (!issue.key || !issue.message) continue;
|
|
616
|
+
const ruleKey = issue.rule;
|
|
617
|
+
const mappedIssue = {
|
|
618
|
+
key: issue.key,
|
|
619
|
+
severity: issue.severity,
|
|
620
|
+
type: issue.type,
|
|
621
|
+
message: issue.message,
|
|
622
|
+
path: resolveSonarPath(issue.component, components),
|
|
623
|
+
line: issue.line ?? issue.textRange?.startLine ?? null,
|
|
624
|
+
...ruleKey ? { rule: ruleKey } : {},
|
|
625
|
+
...ruleKey && rules.has(ruleKey) ? { explanation: rules.get(ruleKey) } : {}
|
|
626
|
+
};
|
|
627
|
+
targetIssues.push(mappedIssue);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function mapSonarHotspotsPage(data, targetHotspots, components) {
|
|
631
|
+
for (const component of data.components ?? []) {
|
|
632
|
+
if (component.key && component.path) {
|
|
633
|
+
components.set(component.key, component.path);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
targetHotspots.push(...data.hotspots ?? []);
|
|
637
|
+
}
|
|
638
|
+
function mapSonarHotspot(hotspot, components, detail) {
|
|
639
|
+
const rule = detail?.rule;
|
|
640
|
+
return {
|
|
641
|
+
key: detail?.key ?? hotspot.key ?? "",
|
|
642
|
+
rule: hotspot.ruleKey ?? rule?.key ?? "",
|
|
643
|
+
ruleName: normalizeText(rule?.name),
|
|
644
|
+
status: detail?.status ?? hotspot.status ?? "UNKNOWN",
|
|
645
|
+
message: detail?.message ?? hotspot.message ?? "",
|
|
646
|
+
path: detail?.component?.path ?? resolveSonarPath(hotspot.component, components),
|
|
647
|
+
line: detail?.line ?? detail?.textRange?.startLine ?? hotspot.line ?? hotspot.textRange?.startLine ?? null,
|
|
648
|
+
securityCategory: rule?.securityCategory ?? hotspot.securityCategory,
|
|
649
|
+
vulnerabilityProbability: rule?.vulnerabilityProbability ?? hotspot.vulnerabilityProbability,
|
|
650
|
+
riskDescription: normalizeText(rule?.riskDescription),
|
|
651
|
+
vulnerabilityDescription: normalizeText(rule?.vulnerabilityDescription),
|
|
652
|
+
fixRecommendations: normalizeText(rule?.fixRecommendations)
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async function fetchSonarJson(apiUrl) {
|
|
560
656
|
const controller = new AbortController();
|
|
561
|
-
const timeoutId = setTimeout(() => controller.abort(),
|
|
657
|
+
const timeoutId = setTimeout(() => controller.abort(), SONAR_FETCH_TIMEOUT_MS);
|
|
562
658
|
try {
|
|
563
659
|
const response = await fetch(apiUrl, { signal: controller.signal });
|
|
564
|
-
if (!response.ok)
|
|
565
|
-
|
|
566
|
-
|
|
660
|
+
if (!response.ok) {
|
|
661
|
+
return { ok: false, status: response.status };
|
|
662
|
+
}
|
|
663
|
+
return {
|
|
664
|
+
ok: true,
|
|
665
|
+
status: response.status,
|
|
666
|
+
data: await response.json()
|
|
667
|
+
};
|
|
567
668
|
} catch {
|
|
568
|
-
return null;
|
|
669
|
+
return { ok: false, status: null };
|
|
569
670
|
} finally {
|
|
570
671
|
clearTimeout(timeoutId);
|
|
571
672
|
}
|
|
572
673
|
}
|
|
674
|
+
async function fetchSonarNewIssues(projectKey, prNumber) {
|
|
675
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=1`;
|
|
676
|
+
const response = await fetchSonarJson(apiUrl);
|
|
677
|
+
if (!response.ok) {
|
|
678
|
+
return null;
|
|
679
|
+
}
|
|
680
|
+
return response.data.paging?.total ?? null;
|
|
681
|
+
}
|
|
682
|
+
async function fetchSonarGateViolations(projectKey, prNumber) {
|
|
683
|
+
const apiUrl = `https://sonarcloud.io/api/qualitygates/project_status?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}`;
|
|
684
|
+
const response = await fetchSonarJson(apiUrl);
|
|
685
|
+
if (!response.ok) {
|
|
686
|
+
return response;
|
|
687
|
+
}
|
|
688
|
+
const projectStatus = response.data.projectStatus;
|
|
689
|
+
const gateViolations = (projectStatus?.conditions ?? []).filter((condition) => condition.status !== void 0 && condition.status !== "OK").map((condition) => ({
|
|
690
|
+
metricKey: condition.metricKey ?? "unknown",
|
|
691
|
+
status: condition.status ?? "ERROR",
|
|
692
|
+
comparator: condition.comparator,
|
|
693
|
+
actualValue: condition.actualValue,
|
|
694
|
+
errorThreshold: condition.errorThreshold
|
|
695
|
+
}));
|
|
696
|
+
return {
|
|
697
|
+
ok: true,
|
|
698
|
+
status: response.status,
|
|
699
|
+
data: {
|
|
700
|
+
status: "available",
|
|
701
|
+
qualityGateStatus: projectStatus?.status,
|
|
702
|
+
gateViolations,
|
|
703
|
+
issues: [],
|
|
704
|
+
securityHotspots: []
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
async function fetchSonarIssues(projectKey, prNumber) {
|
|
709
|
+
const pageSize = 100;
|
|
710
|
+
const issues = [];
|
|
711
|
+
const components = /* @__PURE__ */ new Map();
|
|
712
|
+
const rules = /* @__PURE__ */ new Map();
|
|
713
|
+
let page = 1;
|
|
714
|
+
let total = null;
|
|
715
|
+
while (true) {
|
|
716
|
+
const apiUrl = `https://sonarcloud.io/api/issues/search?componentKeys=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&resolved=false&ps=${String(pageSize)}&p=${String(page)}&additionalFields=_all`;
|
|
717
|
+
const response = await fetchSonarJson(apiUrl);
|
|
718
|
+
if (!response.ok) {
|
|
719
|
+
return response;
|
|
720
|
+
}
|
|
721
|
+
const paging = response.data.paging;
|
|
722
|
+
total ??= paging?.total ?? null;
|
|
723
|
+
mapSonarIssuesPage(response.data, issues, components, rules);
|
|
724
|
+
const fetchedCount = page * (paging?.pageSize ?? pageSize);
|
|
725
|
+
if (paging?.total === void 0 || fetchedCount >= paging.total) {
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
page += 1;
|
|
729
|
+
}
|
|
730
|
+
return {
|
|
731
|
+
ok: true,
|
|
732
|
+
status: 200,
|
|
733
|
+
data: {
|
|
734
|
+
total,
|
|
735
|
+
issues
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
async function fetchSonarHotspotDetail(hotspotKey) {
|
|
740
|
+
const apiUrl = `https://sonarcloud.io/api/hotspots/show?hotspot=${encodeURIComponent(hotspotKey)}`;
|
|
741
|
+
return fetchSonarJson(apiUrl);
|
|
742
|
+
}
|
|
743
|
+
async function fetchSonarHotspots(projectKey, prNumber) {
|
|
744
|
+
const pageSize = 100;
|
|
745
|
+
const rawHotspots = [];
|
|
746
|
+
const components = /* @__PURE__ */ new Map();
|
|
747
|
+
let page = 1;
|
|
748
|
+
while (true) {
|
|
749
|
+
const apiUrl = `https://sonarcloud.io/api/hotspots/search?projectKey=${encodeURIComponent(projectKey)}&pullRequest=${String(prNumber)}&onlyMine=false&sinceLeakPeriod=true&ps=${String(pageSize)}&p=${String(page)}`;
|
|
750
|
+
const response = await fetchSonarJson(apiUrl);
|
|
751
|
+
if (!response.ok) {
|
|
752
|
+
return response;
|
|
753
|
+
}
|
|
754
|
+
const paging = response.data.paging;
|
|
755
|
+
mapSonarHotspotsPage(response.data, rawHotspots, components);
|
|
756
|
+
const fetchedCount = page * (paging?.pageSize ?? pageSize);
|
|
757
|
+
if (paging?.total === void 0 || fetchedCount >= paging.total) {
|
|
758
|
+
break;
|
|
759
|
+
}
|
|
760
|
+
page += 1;
|
|
761
|
+
}
|
|
762
|
+
const detailResults = await Promise.all(rawHotspots.map((hotspot) => fetchSonarHotspotDetail(hotspot.key ?? "")));
|
|
763
|
+
const securityHotspots = [];
|
|
764
|
+
for (let index = 0; index < rawHotspots.length; index += 1) {
|
|
765
|
+
const hotspot = rawHotspots[index];
|
|
766
|
+
const detailResult = detailResults[index];
|
|
767
|
+
if (detailResult && !detailResult.ok && detailResult.status === 401) {
|
|
768
|
+
return detailResult;
|
|
769
|
+
}
|
|
770
|
+
securityHotspots.push(mapSonarHotspot(hotspot, components, detailResult?.ok ? detailResult.data : void 0));
|
|
771
|
+
}
|
|
772
|
+
return {
|
|
773
|
+
ok: true,
|
|
774
|
+
status: 200,
|
|
775
|
+
data: securityHotspots
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
function sonarPrivateFailureSummary(sonarcloudUrl) {
|
|
779
|
+
return {
|
|
780
|
+
status: "private",
|
|
781
|
+
gateViolations: [],
|
|
782
|
+
issues: [],
|
|
783
|
+
securityHotspots: [],
|
|
784
|
+
privateMessage: `SonarCloud project is private. Open the Sonar URL in an authenticated browser: ${sonarcloudUrl}`
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
async function fetchSonarFailureSummary(projectKey, prNumber, sonarcloudUrl) {
|
|
788
|
+
const [gateResult, issuesResult, hotspotsResult] = await Promise.all([
|
|
789
|
+
fetchSonarGateViolations(projectKey, prNumber),
|
|
790
|
+
fetchSonarIssues(projectKey, prNumber),
|
|
791
|
+
fetchSonarHotspots(projectKey, prNumber)
|
|
792
|
+
]);
|
|
793
|
+
if (!gateResult.ok && gateResult.status === 401) {
|
|
794
|
+
return {
|
|
795
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
796
|
+
issueTotal: null
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
if (!issuesResult.ok && issuesResult.status === 401) {
|
|
800
|
+
return {
|
|
801
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
802
|
+
issueTotal: null
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
if (!hotspotsResult.ok && hotspotsResult.status === 401) {
|
|
806
|
+
return {
|
|
807
|
+
summary: sonarPrivateFailureSummary(sonarcloudUrl),
|
|
808
|
+
issueTotal: null
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
const gateViolations = gateResult.ok ? gateResult.data.gateViolations : [];
|
|
812
|
+
const issues = issuesResult.ok ? issuesResult.data.issues : [];
|
|
813
|
+
const securityHotspots = hotspotsResult.ok ? hotspotsResult.data : [];
|
|
814
|
+
const qualityGateStatus = gateResult.ok ? gateResult.data.qualityGateStatus : void 0;
|
|
815
|
+
const issueTotal = issuesResult.ok ? issuesResult.data.total : null;
|
|
816
|
+
if (gateViolations.length === 0 && issues.length === 0 && securityHotspots.length === 0 && !qualityGateStatus) {
|
|
817
|
+
return {
|
|
818
|
+
summary: {
|
|
819
|
+
status: "unavailable",
|
|
820
|
+
gateViolations: [],
|
|
821
|
+
issues: [],
|
|
822
|
+
securityHotspots: [],
|
|
823
|
+
unavailableMessage: "SonarCloud failure details are unavailable right now."
|
|
824
|
+
},
|
|
825
|
+
issueTotal
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
return {
|
|
829
|
+
summary: {
|
|
830
|
+
status: "available",
|
|
831
|
+
qualityGateStatus,
|
|
832
|
+
gateViolations,
|
|
833
|
+
issues,
|
|
834
|
+
securityHotspots
|
|
835
|
+
},
|
|
836
|
+
issueTotal
|
|
837
|
+
};
|
|
838
|
+
}
|
|
573
839
|
async function getPrInfoGh(branch) {
|
|
574
840
|
const { stdout, stderr, status } = run2("gh", [
|
|
575
841
|
"pr",
|
|
@@ -600,21 +866,21 @@ async function getPrInfoGh(branch) {
|
|
|
600
866
|
detailsUrl: enriched?.detailsUrl || c.detailsUrl || ""
|
|
601
867
|
};
|
|
602
868
|
});
|
|
603
|
-
const sonarCheck = checks.find((c) =>
|
|
604
|
-
try {
|
|
605
|
-
const hostname = new URL(c.detailsUrl).hostname;
|
|
606
|
-
return hostname === "sonarcloud.io" || hostname.endsWith(".sonarcloud.io");
|
|
607
|
-
} catch {
|
|
608
|
-
return false;
|
|
609
|
-
}
|
|
610
|
-
});
|
|
869
|
+
const sonarCheck = checks.find((c) => isSonarUrl(c.detailsUrl));
|
|
611
870
|
let sonarcloudUrl;
|
|
612
871
|
let sonarNewIssues;
|
|
872
|
+
let sonarFailures;
|
|
613
873
|
if (sonarCheck) {
|
|
614
874
|
sonarcloudUrl = sonarCheck.detailsUrl;
|
|
615
875
|
const projectKey = extractSonarProjectKey(sonarCheck.detailsUrl);
|
|
616
876
|
if (projectKey) {
|
|
617
|
-
|
|
877
|
+
if (sonarCheck.conclusion !== null && SONAR_FAIL_CONCLUSIONS.has(sonarCheck.conclusion)) {
|
|
878
|
+
const sonarSummary = await fetchSonarFailureSummary(projectKey, raw.number, sonarcloudUrl);
|
|
879
|
+
sonarFailures = sonarSummary.summary;
|
|
880
|
+
sonarNewIssues = sonarSummary.issueTotal;
|
|
881
|
+
} else {
|
|
882
|
+
sonarNewIssues = await fetchSonarNewIssues(projectKey, raw.number);
|
|
883
|
+
}
|
|
618
884
|
} else {
|
|
619
885
|
sonarNewIssues = null;
|
|
620
886
|
}
|
|
@@ -625,7 +891,8 @@ async function getPrInfoGh(branch) {
|
|
|
625
891
|
state: raw.state,
|
|
626
892
|
url: raw.url,
|
|
627
893
|
checks,
|
|
628
|
-
...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues }
|
|
894
|
+
...sonarcloudUrl === void 0 ? {} : { sonarcloudUrl, sonarNewIssues },
|
|
895
|
+
...sonarFailures === void 0 ? {} : { sonarFailures }
|
|
629
896
|
};
|
|
630
897
|
}
|
|
631
898
|
async function getPrInfo2(branch) {
|
|
@@ -857,6 +1124,103 @@ function formatFailedChecks(failed) {
|
|
|
857
1124
|
}
|
|
858
1125
|
return lines.join("\n") + "\n";
|
|
859
1126
|
}
|
|
1127
|
+
function formatGateViolation(violation) {
|
|
1128
|
+
const parts = [sanitizeText(violation.metricKey)];
|
|
1129
|
+
if (violation.actualValue) parts.push(`actual ${sanitizeText(violation.actualValue)}`);
|
|
1130
|
+
if (violation.comparator && violation.errorThreshold) {
|
|
1131
|
+
parts.push(`${sanitizeText(violation.comparator)} ${sanitizeText(violation.errorThreshold)}`);
|
|
1132
|
+
} else if (violation.errorThreshold) {
|
|
1133
|
+
parts.push(`threshold ${sanitizeText(violation.errorThreshold)}`);
|
|
1134
|
+
}
|
|
1135
|
+
return parts.join(" | ");
|
|
1136
|
+
}
|
|
1137
|
+
function formatLocation(path, line) {
|
|
1138
|
+
if (!path) return void 0;
|
|
1139
|
+
const safePath = sanitizeText(path);
|
|
1140
|
+
if (!line) return safePath;
|
|
1141
|
+
return `${safePath}:${String(line)}`;
|
|
1142
|
+
}
|
|
1143
|
+
function formatRuleWithName(rule, ruleName) {
|
|
1144
|
+
const safeRule = sanitizeText(rule);
|
|
1145
|
+
if (!ruleName) return safeRule;
|
|
1146
|
+
return `${safeRule} (${sanitizeText(ruleName)})`;
|
|
1147
|
+
}
|
|
1148
|
+
function formatSonarIssue(issue) {
|
|
1149
|
+
const lines = [` - ${sanitizeText(issue.message)}`];
|
|
1150
|
+
const location = formatLocation(issue.path, issue.line);
|
|
1151
|
+
if (location) lines.push(` Location: ${location}`);
|
|
1152
|
+
if (issue.severity || issue.type) {
|
|
1153
|
+
const labels = [issue.severity, issue.type].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
|
|
1154
|
+
lines.push(` Classification: ${labels}`);
|
|
1155
|
+
}
|
|
1156
|
+
if (issue.rule) lines.push(` Rule: ${sanitizeText(issue.rule)}`);
|
|
1157
|
+
if (issue.explanation) lines.push(` Explanation: ${sanitizeText(issue.explanation)}`);
|
|
1158
|
+
return lines;
|
|
1159
|
+
}
|
|
1160
|
+
function formatSonarHotspot(hotspot) {
|
|
1161
|
+
const lines = [` - ${sanitizeText(hotspot.message)}`];
|
|
1162
|
+
const location = formatLocation(hotspot.path, hotspot.line);
|
|
1163
|
+
if (location) lines.push(` Location: ${location}`);
|
|
1164
|
+
if (hotspot.status) lines.push(` Status: ${sanitizeText(hotspot.status)}`);
|
|
1165
|
+
if (hotspot.vulnerabilityProbability || hotspot.securityCategory) {
|
|
1166
|
+
const labels = [hotspot.vulnerabilityProbability, hotspot.securityCategory].filter((label) => Boolean(label)).map(sanitizeText).join(" / ");
|
|
1167
|
+
lines.push(` Classification: ${labels}`);
|
|
1168
|
+
}
|
|
1169
|
+
if (hotspot.rule) {
|
|
1170
|
+
lines.push(` Rule: ${formatRuleWithName(hotspot.rule, hotspot.ruleName)}`);
|
|
1171
|
+
}
|
|
1172
|
+
if (hotspot.riskDescription) lines.push(` Risk: ${sanitizeText(hotspot.riskDescription)}`);
|
|
1173
|
+
if (hotspot.vulnerabilityDescription) lines.push(` Review: ${sanitizeText(hotspot.vulnerabilityDescription)}`);
|
|
1174
|
+
if (hotspot.fixRecommendations) lines.push(` Fix: ${sanitizeText(hotspot.fixRecommendations)}`);
|
|
1175
|
+
return lines;
|
|
1176
|
+
}
|
|
1177
|
+
function appendGateViolations(lines, gateViolations) {
|
|
1178
|
+
if (gateViolations.length === 0) return;
|
|
1179
|
+
lines.push(" Gate Violations:");
|
|
1180
|
+
for (const violation of gateViolations) {
|
|
1181
|
+
lines.push(` - ${formatGateViolation(violation)}`);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
function appendSonarIssues(lines, issues) {
|
|
1185
|
+
if (issues.length === 0) return;
|
|
1186
|
+
lines.push(" Issues:");
|
|
1187
|
+
for (const issue of issues) {
|
|
1188
|
+
lines.push(...formatSonarIssue(issue));
|
|
1189
|
+
}
|
|
1190
|
+
}
|
|
1191
|
+
function appendSecurityHotspots(lines, securityHotspots) {
|
|
1192
|
+
if (securityHotspots.length === 0) return;
|
|
1193
|
+
lines.push(" Security Hotspots:");
|
|
1194
|
+
for (const hotspot of securityHotspots) {
|
|
1195
|
+
lines.push(...formatSonarHotspot(hotspot));
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
function hasSonarFailureDetails(sonarFailures) {
|
|
1199
|
+
return Boolean(sonarFailures.qualityGateStatus) || sonarFailures.gateViolations.length > 0 || sonarFailures.issues.length > 0 || sonarFailures.securityHotspots.length > 0;
|
|
1200
|
+
}
|
|
1201
|
+
function formatSonarFailures(sonarFailures, sonarcloudUrl) {
|
|
1202
|
+
const lines = ["Sonar Failures:"];
|
|
1203
|
+
if (sonarFailures.status === "private") {
|
|
1204
|
+
lines.push(` Note: ${sanitizeText(sonarFailures.privateMessage ?? "SonarCloud project is private.")}`);
|
|
1205
|
+
return lines.join("\n") + "\n";
|
|
1206
|
+
}
|
|
1207
|
+
if (sonarFailures.status === "unavailable") {
|
|
1208
|
+
lines.push(` Note: ${sanitizeText(sonarFailures.unavailableMessage ?? "SonarCloud failure details are unavailable.")}`);
|
|
1209
|
+
if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
|
|
1210
|
+
return lines.join("\n") + "\n";
|
|
1211
|
+
}
|
|
1212
|
+
if (sonarFailures.qualityGateStatus) {
|
|
1213
|
+
lines.push(` Quality Gate: ${sanitizeText(sonarFailures.qualityGateStatus)}`);
|
|
1214
|
+
}
|
|
1215
|
+
appendGateViolations(lines, sonarFailures.gateViolations);
|
|
1216
|
+
appendSonarIssues(lines, sonarFailures.issues);
|
|
1217
|
+
appendSecurityHotspots(lines, sonarFailures.securityHotspots);
|
|
1218
|
+
if (!hasSonarFailureDetails(sonarFailures)) {
|
|
1219
|
+
lines.push(" Note: SonarCloud reported a failure but returned no violation, issue, or hotspot details.");
|
|
1220
|
+
if (sonarcloudUrl) lines.push(` URL: ${sonarcloudUrl}`);
|
|
1221
|
+
}
|
|
1222
|
+
return lines.join("\n") + "\n";
|
|
1223
|
+
}
|
|
860
1224
|
var POLL_INTERVAL_MS = 1e4;
|
|
861
1225
|
var getPrInfoCmd = new Command2("get-pr-info").description("Show pull request info for the current branch").option("--json", "Output as JSON").option("--wait-finish-checks", "Poll until all checks complete, then print the normal get-pr-info output").addHelpText(
|
|
862
1226
|
"after",
|
|
@@ -932,6 +1296,9 @@ URL: ${pr.url}
|
|
|
932
1296
|
if (failed.length > 0) {
|
|
933
1297
|
process.stdout.write(formatFailedChecks(failed));
|
|
934
1298
|
}
|
|
1299
|
+
if (pr.sonarFailures !== void 0) {
|
|
1300
|
+
process.stdout.write(formatSonarFailures(pr.sonarFailures, pr.sonarcloudUrl));
|
|
1301
|
+
}
|
|
935
1302
|
}
|
|
936
1303
|
});
|
|
937
1304
|
var ANSI_ESCAPE_RE = new RegExp("\x1B(?:[@-Z\\\\-_]|\\[[0-?]*[ -/]*[@-~])", "g");
|
|
@@ -1471,6 +1838,9 @@ function withPush(prompt, push) {
|
|
|
1471
1838
|
|
|
1472
1839
|
${PUSH_INSTRUCTION}` : prompt;
|
|
1473
1840
|
}
|
|
1841
|
+
function formatPrInfoContext(pr) {
|
|
1842
|
+
return JSON.stringify(pr, null, 2);
|
|
1843
|
+
}
|
|
1474
1844
|
function addAiOptions(cmd) {
|
|
1475
1845
|
return cmd.option("--codex", "Use Codex CLI instead of Claude Code").option("--verbose", "Show step-by-step progress (Claude only; ignored for Codex)").option("--push", "Append instruction to commit and push changes after the AI finishes").option("--opus", "Use claude-opus-4-6 (Claude only)").option("--sonnet", "Use claude-sonnet-4-6 (Claude only)").option("--haiku", "Use claude-haiku-4-5-20251001 (Claude only)");
|
|
1476
1846
|
}
|
|
@@ -1512,7 +1882,10 @@ var executeSonarCmd = addAiOptions(
|
|
|
1512
1882
|
const fullPrompt = withPush(
|
|
1513
1883
|
`${sonarPromptText}
|
|
1514
1884
|
|
|
1515
|
-
SonarCloud analysis URL: ${pr.sonarcloudUrl}
|
|
1885
|
+
SonarCloud analysis URL: ${pr.sonarcloudUrl}
|
|
1886
|
+
|
|
1887
|
+
Current PR context from automata git get-pr-info --json:
|
|
1888
|
+
${formatPrInfoContext(pr)}`,
|
|
1516
1889
|
options.push
|
|
1517
1890
|
);
|
|
1518
1891
|
if (options.codex) {
|