@cleartrip/frontguard 0.2.3 → 0.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +257 -126
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/bitbucket-pipelines.yml +89 -17
- package/templates/checks-snapshot-bitbucket-snippet.yml +30 -0
- package/templates/freekit-ci-setup.md +25 -2
package/dist/cli.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as tty from 'tty';
|
|
|
6
6
|
import { WriteStream } from 'tty';
|
|
7
7
|
import path5, { sep, normalize, delimiter, resolve, dirname } from 'path';
|
|
8
8
|
import fs from 'fs/promises';
|
|
9
|
-
import {
|
|
9
|
+
import { pathToFileURL, fileURLToPath } from 'url';
|
|
10
10
|
import { execFileSync, spawn } from 'child_process';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
12
|
import fs2 from 'fs';
|
|
@@ -2498,6 +2498,11 @@ async function initFrontGuard(cwd) {
|
|
|
2498
2498
|
}
|
|
2499
2499
|
|
|
2500
2500
|
// src/ci/bitbucket-pr-snippet.ts
|
|
2501
|
+
function checksImageMarkdown() {
|
|
2502
|
+
const u4 = process.env.FRONTGUARD_CHECKS_IMAGE_URL?.trim();
|
|
2503
|
+
if (!u4) return null;
|
|
2504
|
+
return ``;
|
|
2505
|
+
}
|
|
2501
2506
|
function bitbucketPipelineResultsUrl() {
|
|
2502
2507
|
const full = process.env.BITBUCKET_REPO_FULL_NAME?.trim();
|
|
2503
2508
|
const bn = process.env.BITBUCKET_BUILD_NUMBER?.trim();
|
|
@@ -2519,11 +2524,19 @@ function bitbucketDownloadsPageUrl() {
|
|
|
2519
2524
|
if (ws && slug) return `https://bitbucket.org/${ws}/${slug}/downloads/`;
|
|
2520
2525
|
return null;
|
|
2521
2526
|
}
|
|
2527
|
+
var DETAILED_REPORT_LINE = "For detailed check analysis, please open the full interactive report:";
|
|
2522
2528
|
function formatBitbucketPrSnippet(report) {
|
|
2523
2529
|
const publicReport = process.env.FRONTGUARD_PUBLIC_REPORT_URL?.trim();
|
|
2524
2530
|
const linkOnly = process.env.FRONTGUARD_BITBUCKET_COMMENT_LINK_ONLY === "1";
|
|
2531
|
+
const imgMd = checksImageMarkdown();
|
|
2525
2532
|
if (linkOnly && publicReport) {
|
|
2526
|
-
|
|
2533
|
+
const parts = [];
|
|
2534
|
+
if (imgMd) {
|
|
2535
|
+
parts.push(imgMd, "");
|
|
2536
|
+
parts.push(DETAILED_REPORT_LINE);
|
|
2537
|
+
}
|
|
2538
|
+
parts.push(publicReport.endsWith("\n") ? publicReport.slice(0, -1) : publicReport);
|
|
2539
|
+
return `${parts.join("\n")}
|
|
2527
2540
|
`;
|
|
2528
2541
|
}
|
|
2529
2542
|
const downloadsName = process.env.FRONTGUARD_REPORT_DOWNLOAD_NAME?.trim();
|
|
@@ -2538,14 +2551,23 @@ function formatBitbucketPrSnippet(report) {
|
|
|
2538
2551
|
(n3, r4) => n3 + r4.findings.filter((f4) => f4.severity === "warn").length,
|
|
2539
2552
|
0
|
|
2540
2553
|
);
|
|
2541
|
-
const out = [
|
|
2554
|
+
const out = [];
|
|
2555
|
+
if (imgMd) {
|
|
2556
|
+
out.push(imgMd);
|
|
2557
|
+
out.push("");
|
|
2558
|
+
}
|
|
2559
|
+
out.push(
|
|
2542
2560
|
"FrontGuard report (short summary)",
|
|
2543
2561
|
"",
|
|
2544
2562
|
`Risk: ${riskScore} | Blocking: ${blocks} | Warnings: ${warns}`,
|
|
2545
2563
|
""
|
|
2546
|
-
|
|
2564
|
+
);
|
|
2547
2565
|
if (publicReport) {
|
|
2548
|
-
|
|
2566
|
+
if (imgMd) {
|
|
2567
|
+
out.push(DETAILED_REPORT_LINE);
|
|
2568
|
+
} else {
|
|
2569
|
+
out.push("Full interactive report (open in browser):");
|
|
2570
|
+
}
|
|
2549
2571
|
out.push(publicReport);
|
|
2550
2572
|
out.push("");
|
|
2551
2573
|
} else if (downloadsName && downloadsPage) {
|
|
@@ -5578,6 +5600,205 @@ function statusDot(r4) {
|
|
|
5578
5600
|
return '<span class="dot dot-block" title="Blocking"></span>';
|
|
5579
5601
|
return '<span class="dot dot-warn" title="Issues"></span>';
|
|
5580
5602
|
}
|
|
5603
|
+
var CHECKS_TABLE_STYLES = `
|
|
5604
|
+
table.results {
|
|
5605
|
+
width: 100%;
|
|
5606
|
+
border-collapse: collapse;
|
|
5607
|
+
font-size: 0.875rem;
|
|
5608
|
+
background: var(--surface);
|
|
5609
|
+
border-radius: var(--radius);
|
|
5610
|
+
overflow: hidden;
|
|
5611
|
+
border: 1px solid var(--border);
|
|
5612
|
+
box-shadow: var(--shadow);
|
|
5613
|
+
}
|
|
5614
|
+
table.results th, table.results td {
|
|
5615
|
+
padding: 0.55rem 0.85rem;
|
|
5616
|
+
text-align: left;
|
|
5617
|
+
border-bottom: 1px solid var(--border);
|
|
5618
|
+
}
|
|
5619
|
+
table.results tr:last-child td { border-bottom: none; }
|
|
5620
|
+
table.results thead th {
|
|
5621
|
+
background: #f1f5f9;
|
|
5622
|
+
color: var(--muted);
|
|
5623
|
+
font-weight: 600;
|
|
5624
|
+
font-size: 0.72rem;
|
|
5625
|
+
text-transform: uppercase;
|
|
5626
|
+
letter-spacing: 0.04em;
|
|
5627
|
+
}
|
|
5628
|
+
.td-icon { width: 2rem; vertical-align: middle; }
|
|
5629
|
+
.td-check { vertical-align: middle; }
|
|
5630
|
+
.td-num, .td-time { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
5631
|
+
.check-title-cell {
|
|
5632
|
+
display: inline-flex;
|
|
5633
|
+
align-items: center;
|
|
5634
|
+
gap: 0.35rem;
|
|
5635
|
+
flex-wrap: nowrap;
|
|
5636
|
+
}
|
|
5637
|
+
.check-name { font-weight: 600; }
|
|
5638
|
+
.check-info-wrap {
|
|
5639
|
+
position: relative;
|
|
5640
|
+
display: inline-flex;
|
|
5641
|
+
align-items: center;
|
|
5642
|
+
flex-shrink: 0;
|
|
5643
|
+
}
|
|
5644
|
+
.check-info {
|
|
5645
|
+
display: inline-flex;
|
|
5646
|
+
align-items: center;
|
|
5647
|
+
justify-content: center;
|
|
5648
|
+
width: 1.125rem;
|
|
5649
|
+
height: 1.125rem;
|
|
5650
|
+
padding: 0;
|
|
5651
|
+
margin: 0;
|
|
5652
|
+
border: 1px solid var(--border);
|
|
5653
|
+
border-radius: 50%;
|
|
5654
|
+
background: #f1f5f9;
|
|
5655
|
+
color: var(--muted);
|
|
5656
|
+
font-size: 0.62rem;
|
|
5657
|
+
font-weight: 700;
|
|
5658
|
+
font-style: normal;
|
|
5659
|
+
line-height: 1;
|
|
5660
|
+
cursor: help;
|
|
5661
|
+
flex-shrink: 0;
|
|
5662
|
+
}
|
|
5663
|
+
.check-info:hover,
|
|
5664
|
+
.check-info:focus-visible {
|
|
5665
|
+
border-color: var(--accent);
|
|
5666
|
+
color: var(--accent);
|
|
5667
|
+
background: var(--accent-soft);
|
|
5668
|
+
outline: none;
|
|
5669
|
+
}
|
|
5670
|
+
.check-tooltip {
|
|
5671
|
+
position: absolute;
|
|
5672
|
+
left: 50%;
|
|
5673
|
+
bottom: calc(100% + 8px);
|
|
5674
|
+
transform: translateX(-50%);
|
|
5675
|
+
min-width: 12rem;
|
|
5676
|
+
max-width: min(22rem, 86vw);
|
|
5677
|
+
padding: 0.55rem 0.65rem;
|
|
5678
|
+
background: var(--text);
|
|
5679
|
+
color: #f8fafc;
|
|
5680
|
+
font-size: 0.78rem;
|
|
5681
|
+
font-weight: 400;
|
|
5682
|
+
line-height: 1.45;
|
|
5683
|
+
border-radius: 6px;
|
|
5684
|
+
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.18);
|
|
5685
|
+
z-index: 50;
|
|
5686
|
+
opacity: 0;
|
|
5687
|
+
visibility: hidden;
|
|
5688
|
+
pointer-events: none;
|
|
5689
|
+
transition: opacity 0.12s ease, visibility 0.12s ease;
|
|
5690
|
+
text-align: left;
|
|
5691
|
+
}
|
|
5692
|
+
.check-info-wrap:hover .check-tooltip,
|
|
5693
|
+
.check-info-wrap:focus-within .check-tooltip {
|
|
5694
|
+
opacity: 1;
|
|
5695
|
+
visibility: visible;
|
|
5696
|
+
}
|
|
5697
|
+
.check-tooltip::after {
|
|
5698
|
+
content: '';
|
|
5699
|
+
position: absolute;
|
|
5700
|
+
top: 100%;
|
|
5701
|
+
left: 50%;
|
|
5702
|
+
margin-left: -6px;
|
|
5703
|
+
border: 6px solid transparent;
|
|
5704
|
+
border-top-color: var(--text);
|
|
5705
|
+
}
|
|
5706
|
+
.dot {
|
|
5707
|
+
display: inline-block;
|
|
5708
|
+
width: 8px;
|
|
5709
|
+
height: 8px;
|
|
5710
|
+
border-radius: 50%;
|
|
5711
|
+
}
|
|
5712
|
+
.dot-ok { background: var(--ok); }
|
|
5713
|
+
.dot-warn { background: var(--warn); }
|
|
5714
|
+
.dot-block { background: var(--block); }
|
|
5715
|
+
.dot-skip { background: #cbd5e1; }
|
|
5716
|
+
`;
|
|
5717
|
+
function renderCheckTableRows(results) {
|
|
5718
|
+
return results.map((r4) => {
|
|
5719
|
+
const status = r4.skipped ? `Skipped \u2014 ${escapeHtml(r4.skipped)}` : r4.findings.length === 0 ? "Pass" : `${r4.findings.length} issue(s)`;
|
|
5720
|
+
const help = escapeHtml(getCheckDescription(r4.checkId));
|
|
5721
|
+
const ariaWhat = escapeHtml(`What does the ${r4.checkId} check do?`);
|
|
5722
|
+
const checkTitle = `<span class="check-title-cell"><strong class="check-name">${escapeHtml(r4.checkId)}</strong><span class="check-info-wrap"><button type="button" class="check-info" title="${help}" aria-label="${ariaWhat}">i</button><span class="check-tooltip" role="tooltip">${help}</span></span></span>`;
|
|
5723
|
+
return `<tr><td class="td-icon">${statusDot(r4)}</td><td class="td-check">${checkTitle}</td><td class="td-status">${status}</td><td class="td-num">${r4.skipped ? "\u2014" : r4.findings.length}</td><td class="td-time">${formatDuration(r4.durationMs)}</td></tr>`;
|
|
5724
|
+
}).join("\n");
|
|
5725
|
+
}
|
|
5726
|
+
function buildChecksSnapshotHtml(p2) {
|
|
5727
|
+
const { riskScore, mode, results, warns, infos, blocks } = p2;
|
|
5728
|
+
const modeLabel = mode === "enforce" ? "Enforce" : "Warn only";
|
|
5729
|
+
const riskClass = riskScore === "LOW" ? "risk-low" : riskScore === "MEDIUM" ? "risk-med" : "risk-high";
|
|
5730
|
+
const checkRows = renderCheckTableRows(results);
|
|
5731
|
+
return `<!DOCTYPE html>
|
|
5732
|
+
<html lang="en">
|
|
5733
|
+
<head>
|
|
5734
|
+
<meta charset="utf-8" />
|
|
5735
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5736
|
+
<title>FrontGuard \u2014 Checks</title>
|
|
5737
|
+
<style>
|
|
5738
|
+
:root {
|
|
5739
|
+
--bg: #f8fafc;
|
|
5740
|
+
--surface: #ffffff;
|
|
5741
|
+
--text: #0f172a;
|
|
5742
|
+
--muted: #64748b;
|
|
5743
|
+
--border: #e2e8f0;
|
|
5744
|
+
--accent: #4f46e5;
|
|
5745
|
+
--accent-soft: #eef2ff;
|
|
5746
|
+
--block: #dc2626;
|
|
5747
|
+
--warn: #d97706;
|
|
5748
|
+
--info: #0284c7;
|
|
5749
|
+
--ok: #16a34a;
|
|
5750
|
+
--radius: 10px;
|
|
5751
|
+
--shadow: 0 1px 3px rgba(15, 23, 42, 0.06);
|
|
5752
|
+
}
|
|
5753
|
+
* { box-sizing: border-box; }
|
|
5754
|
+
body {
|
|
5755
|
+
margin: 0;
|
|
5756
|
+
padding: 1.25rem 1.5rem 1.5rem;
|
|
5757
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
5758
|
+
background: var(--bg);
|
|
5759
|
+
color: var(--text);
|
|
5760
|
+
line-height: 1.55;
|
|
5761
|
+
font-size: 15px;
|
|
5762
|
+
max-width: 920px;
|
|
5763
|
+
}
|
|
5764
|
+
.brand {
|
|
5765
|
+
font-size: 0.75rem;
|
|
5766
|
+
font-weight: 600;
|
|
5767
|
+
letter-spacing: 0.12em;
|
|
5768
|
+
text-transform: uppercase;
|
|
5769
|
+
color: var(--muted);
|
|
5770
|
+
margin-bottom: 0.35rem;
|
|
5771
|
+
}
|
|
5772
|
+
.h2 {
|
|
5773
|
+
font-size: 1rem;
|
|
5774
|
+
font-weight: 600;
|
|
5775
|
+
margin: 0 0 0.5rem;
|
|
5776
|
+
color: var(--text);
|
|
5777
|
+
letter-spacing: -0.02em;
|
|
5778
|
+
}
|
|
5779
|
+
.snap-meta {
|
|
5780
|
+
font-size: 0.8rem;
|
|
5781
|
+
color: var(--muted);
|
|
5782
|
+
margin: 0 0 0.85rem;
|
|
5783
|
+
}
|
|
5784
|
+
.snap-meta strong { color: var(--text); font-weight: 600; }
|
|
5785
|
+
.risk-low { color: var(--ok); }
|
|
5786
|
+
.risk-med { color: var(--warn); }
|
|
5787
|
+
.risk-high { color: var(--block); }
|
|
5788
|
+
${CHECKS_TABLE_STYLES}
|
|
5789
|
+
</style>
|
|
5790
|
+
</head>
|
|
5791
|
+
<body>
|
|
5792
|
+
<div class="brand">FrontGuard</div>
|
|
5793
|
+
<h2 class="h2">Checks</h2>
|
|
5794
|
+
<p class="snap-meta">Risk <strong class="${riskClass}">${riskScore}</strong> \xB7 ${escapeHtml(modeLabel)} \xB7 Blocking <strong>${blocks}</strong> \xB7 Warnings <strong>${warns}</strong> \xB7 Info <strong>${infos}</strong></p>
|
|
5795
|
+
<table class="results">
|
|
5796
|
+
<thead><tr><th></th><th>Check</th><th>Status</th><th>#</th><th>Time</th></tr></thead>
|
|
5797
|
+
<tbody>${checkRows}</tbody>
|
|
5798
|
+
</table>
|
|
5799
|
+
</body>
|
|
5800
|
+
</html>`;
|
|
5801
|
+
}
|
|
5581
5802
|
function renderFindingCard(cwd, r4, f4) {
|
|
5582
5803
|
const d3 = normalizeFinding(cwd, f4);
|
|
5583
5804
|
const sevClass = f4.severity === "block" ? "sev-block" : f4.severity === "warn" ? "sev-warn" : "sev-info";
|
|
@@ -5602,13 +5823,7 @@ function buildHtmlReport(p2) {
|
|
|
5602
5823
|
} = p2;
|
|
5603
5824
|
const modeLabel = mode === "enforce" ? "Enforce" : "Warn only";
|
|
5604
5825
|
const riskClass = riskScore === "LOW" ? "risk-low" : riskScore === "MEDIUM" ? "risk-med" : "risk-high";
|
|
5605
|
-
const checkRows = results
|
|
5606
|
-
const status = r4.skipped ? `Skipped \u2014 ${escapeHtml(r4.skipped)}` : r4.findings.length === 0 ? "Pass" : `${r4.findings.length} issue(s)`;
|
|
5607
|
-
const help = escapeHtml(getCheckDescription(r4.checkId));
|
|
5608
|
-
const ariaWhat = escapeHtml(`What does the ${r4.checkId} check do?`);
|
|
5609
|
-
const checkTitle = `<span class="check-title-cell"><strong class="check-name">${escapeHtml(r4.checkId)}</strong><span class="check-info-wrap"><button type="button" class="check-info" title="${help}" aria-label="${ariaWhat}">i</button><span class="check-tooltip" role="tooltip">${help}</span></span></span>`;
|
|
5610
|
-
return `<tr><td class="td-icon">${statusDot(r4)}</td><td class="td-check">${checkTitle}</td><td class="td-status">${status}</td><td class="td-num">${r4.skipped ? "\u2014" : r4.findings.length}</td><td class="td-time">${formatDuration(r4.durationMs)}</td></tr>`;
|
|
5611
|
-
}).join("\n");
|
|
5826
|
+
const checkRows = renderCheckTableRows(results);
|
|
5612
5827
|
const blockItems = sortFindings(
|
|
5613
5828
|
cwd,
|
|
5614
5829
|
results.flatMap(
|
|
@@ -5752,118 +5967,7 @@ function buildHtmlReport(p2) {
|
|
|
5752
5967
|
font-weight: 500;
|
|
5753
5968
|
background: #f1f5f9;
|
|
5754
5969
|
}
|
|
5755
|
-
|
|
5756
|
-
width: 100%;
|
|
5757
|
-
border-collapse: collapse;
|
|
5758
|
-
font-size: 0.875rem;
|
|
5759
|
-
background: var(--surface);
|
|
5760
|
-
border-radius: var(--radius);
|
|
5761
|
-
overflow: hidden;
|
|
5762
|
-
border: 1px solid var(--border);
|
|
5763
|
-
box-shadow: var(--shadow);
|
|
5764
|
-
}
|
|
5765
|
-
table.results th, table.results td {
|
|
5766
|
-
padding: 0.55rem 0.85rem;
|
|
5767
|
-
text-align: left;
|
|
5768
|
-
border-bottom: 1px solid var(--border);
|
|
5769
|
-
}
|
|
5770
|
-
table.results tr:last-child td { border-bottom: none; }
|
|
5771
|
-
table.results thead th {
|
|
5772
|
-
background: #f1f5f9;
|
|
5773
|
-
color: var(--muted);
|
|
5774
|
-
font-weight: 600;
|
|
5775
|
-
font-size: 0.72rem;
|
|
5776
|
-
text-transform: uppercase;
|
|
5777
|
-
letter-spacing: 0.04em;
|
|
5778
|
-
}
|
|
5779
|
-
.td-icon { width: 2rem; vertical-align: middle; }
|
|
5780
|
-
.td-check { vertical-align: middle; }
|
|
5781
|
-
.td-num, .td-time { color: var(--muted); font-variant-numeric: tabular-nums; }
|
|
5782
|
-
.check-title-cell {
|
|
5783
|
-
display: inline-flex;
|
|
5784
|
-
align-items: center;
|
|
5785
|
-
gap: 0.35rem;
|
|
5786
|
-
flex-wrap: nowrap;
|
|
5787
|
-
}
|
|
5788
|
-
.check-name { font-weight: 600; }
|
|
5789
|
-
.check-info-wrap {
|
|
5790
|
-
position: relative;
|
|
5791
|
-
display: inline-flex;
|
|
5792
|
-
align-items: center;
|
|
5793
|
-
flex-shrink: 0;
|
|
5794
|
-
}
|
|
5795
|
-
.check-info {
|
|
5796
|
-
display: inline-flex;
|
|
5797
|
-
align-items: center;
|
|
5798
|
-
justify-content: center;
|
|
5799
|
-
width: 1.125rem;
|
|
5800
|
-
height: 1.125rem;
|
|
5801
|
-
padding: 0;
|
|
5802
|
-
margin: 0;
|
|
5803
|
-
border: 1px solid var(--border);
|
|
5804
|
-
border-radius: 50%;
|
|
5805
|
-
background: #f1f5f9;
|
|
5806
|
-
color: var(--muted);
|
|
5807
|
-
font-size: 0.62rem;
|
|
5808
|
-
font-weight: 700;
|
|
5809
|
-
font-style: normal;
|
|
5810
|
-
line-height: 1;
|
|
5811
|
-
cursor: help;
|
|
5812
|
-
flex-shrink: 0;
|
|
5813
|
-
}
|
|
5814
|
-
.check-info:hover,
|
|
5815
|
-
.check-info:focus-visible {
|
|
5816
|
-
border-color: var(--accent);
|
|
5817
|
-
color: var(--accent);
|
|
5818
|
-
background: var(--accent-soft);
|
|
5819
|
-
outline: none;
|
|
5820
|
-
}
|
|
5821
|
-
.check-tooltip {
|
|
5822
|
-
position: absolute;
|
|
5823
|
-
left: 50%;
|
|
5824
|
-
bottom: calc(100% + 8px);
|
|
5825
|
-
transform: translateX(-50%);
|
|
5826
|
-
min-width: 12rem;
|
|
5827
|
-
max-width: min(22rem, 86vw);
|
|
5828
|
-
padding: 0.55rem 0.65rem;
|
|
5829
|
-
background: var(--text);
|
|
5830
|
-
color: #f8fafc;
|
|
5831
|
-
font-size: 0.78rem;
|
|
5832
|
-
font-weight: 400;
|
|
5833
|
-
line-height: 1.45;
|
|
5834
|
-
border-radius: 6px;
|
|
5835
|
-
box-shadow: 0 4px 14px rgba(15, 23, 42, 0.18);
|
|
5836
|
-
z-index: 50;
|
|
5837
|
-
opacity: 0;
|
|
5838
|
-
visibility: hidden;
|
|
5839
|
-
pointer-events: none;
|
|
5840
|
-
transition: opacity 0.12s ease, visibility 0.12s ease;
|
|
5841
|
-
text-align: left;
|
|
5842
|
-
}
|
|
5843
|
-
.check-info-wrap:hover .check-tooltip,
|
|
5844
|
-
.check-info-wrap:focus-within .check-tooltip {
|
|
5845
|
-
opacity: 1;
|
|
5846
|
-
visibility: visible;
|
|
5847
|
-
}
|
|
5848
|
-
.check-tooltip::after {
|
|
5849
|
-
content: '';
|
|
5850
|
-
position: absolute;
|
|
5851
|
-
top: 100%;
|
|
5852
|
-
left: 50%;
|
|
5853
|
-
margin-left: -6px;
|
|
5854
|
-
border: 6px solid transparent;
|
|
5855
|
-
border-top-color: var(--text);
|
|
5856
|
-
}
|
|
5857
|
-
.dot {
|
|
5858
|
-
display: inline-block;
|
|
5859
|
-
width: 8px;
|
|
5860
|
-
height: 8px;
|
|
5861
|
-
border-radius: 50%;
|
|
5862
|
-
}
|
|
5863
|
-
.dot-ok { background: var(--ok); }
|
|
5864
|
-
.dot-warn { background: var(--warn); }
|
|
5865
|
-
.dot-block { background: var(--block); }
|
|
5866
|
-
.dot-skip { background: #cbd5e1; }
|
|
5970
|
+
${CHECKS_TABLE_STYLES}
|
|
5867
5971
|
.panel {
|
|
5868
5972
|
background: var(--surface);
|
|
5869
5973
|
border: 1px solid var(--border);
|
|
@@ -6042,7 +6146,14 @@ function buildReport(stack, pr, results, options) {
|
|
|
6042
6146
|
lines,
|
|
6043
6147
|
llmAppendix: options?.llmAppendix ?? null
|
|
6044
6148
|
}) : null;
|
|
6045
|
-
|
|
6149
|
+
const checksSnapshotHtml = options?.emitChecksSnapshot === true ? buildChecksSnapshotHtml({
|
|
6150
|
+
riskScore,
|
|
6151
|
+
mode,
|
|
6152
|
+
results,
|
|
6153
|
+
warns,
|
|
6154
|
+
infos,
|
|
6155
|
+
blocks}) : null;
|
|
6156
|
+
return { riskScore, stack, pr, results, markdown, consoleText, html, checksSnapshotHtml };
|
|
6046
6157
|
}
|
|
6047
6158
|
function scoreRisk(blocks, warns, lines, files) {
|
|
6048
6159
|
let score = 0;
|
|
@@ -6803,11 +6914,25 @@ async function runFrontGuard(opts) {
|
|
|
6803
6914
|
mode,
|
|
6804
6915
|
llmAppendix,
|
|
6805
6916
|
cwd: opts.cwd,
|
|
6806
|
-
emitHtml: Boolean(opts.htmlOut)
|
|
6917
|
+
emitHtml: Boolean(opts.htmlOut),
|
|
6918
|
+
emitChecksSnapshot: Boolean(opts.checksSnapshotOut)
|
|
6807
6919
|
});
|
|
6808
6920
|
if (opts.htmlOut && report.html) {
|
|
6809
6921
|
await fs.writeFile(opts.htmlOut, report.html, "utf8");
|
|
6810
6922
|
}
|
|
6923
|
+
if (opts.checksSnapshotOut && report.checksSnapshotHtml) {
|
|
6924
|
+
const snapPath = path5.isAbsolute(opts.checksSnapshotOut) ? opts.checksSnapshotOut : path5.join(opts.cwd, opts.checksSnapshotOut);
|
|
6925
|
+
await fs.writeFile(snapPath, report.checksSnapshotHtml, "utf8");
|
|
6926
|
+
const fileUrl = pathToFileURL(snapPath).href;
|
|
6927
|
+
g.stderr.write(
|
|
6928
|
+
`
|
|
6929
|
+
FrontGuard: wrote checks snapshot HTML to ${snapPath} (screenshot this file for PR comments).
|
|
6930
|
+
Example: npx playwright screenshot "${fileUrl}" frontguard-checks.png
|
|
6931
|
+
Host the PNG at an HTTPS URL, then set FRONTGUARD_CHECKS_IMAGE_URL before generating the PR comment.
|
|
6932
|
+
|
|
6933
|
+
`
|
|
6934
|
+
);
|
|
6935
|
+
}
|
|
6811
6936
|
if (opts.prCommentOut) {
|
|
6812
6937
|
const snippet = formatBitbucketPrSnippet(report);
|
|
6813
6938
|
const abs = path5.isAbsolute(opts.prCommentOut) ? opts.prCommentOut : path5.join(opts.cwd, opts.prCommentOut);
|
|
@@ -6817,6 +6942,7 @@ async function runFrontGuard(opts) {
|
|
|
6817
6942
|
FrontGuard: wrote Bitbucket PR comment text to ${abs} (${snippet.length} bytes).
|
|
6818
6943
|
Use ONLY this file in your POST \u2026/pullrequests/{id}/comments payload (content.raw).
|
|
6819
6944
|
Do not post frontguard-report.md or captured stdout \u2014 that is the long markdown log.
|
|
6945
|
+
Optional: set FRONTGUARD_CHECKS_IMAGE_URL so the comment includes a checks summary image.
|
|
6820
6946
|
|
|
6821
6947
|
`
|
|
6822
6948
|
);
|
|
@@ -6869,6 +6995,10 @@ var run = defineCommand({
|
|
|
6869
6995
|
type: "string",
|
|
6870
6996
|
description: "Write interactive HTML report (use with CI artifacts; PR comment links to download)"
|
|
6871
6997
|
},
|
|
6998
|
+
checksSnapshotOut: {
|
|
6999
|
+
type: "string",
|
|
7000
|
+
description: "Write HTML with only the Checks table (screenshot \u2192 PNG \u2192 FRONTGUARD_CHECKS_IMAGE_URL in PR comment)"
|
|
7001
|
+
},
|
|
6872
7002
|
prCommentOut: {
|
|
6873
7003
|
type: "string",
|
|
6874
7004
|
description: "Write short Markdown for Bitbucket PR comment (summary + pipeline link for HTML artifact)"
|
|
@@ -6881,6 +7011,7 @@ var run = defineCommand({
|
|
|
6881
7011
|
enforce: Boolean(args.enforce),
|
|
6882
7012
|
append: typeof args.append === "string" ? args.append : null,
|
|
6883
7013
|
htmlOut: typeof args.htmlOut === "string" ? args.htmlOut : null,
|
|
7014
|
+
checksSnapshotOut: typeof args.checksSnapshotOut === "string" ? args.checksSnapshotOut : null,
|
|
6884
7015
|
prCommentOut: typeof args.prCommentOut === "string" ? args.prCommentOut : null
|
|
6885
7016
|
});
|
|
6886
7017
|
}
|