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