@cleartrip/frontguard 0.2.7 → 0.2.8
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 +141 -41
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
- package/templates/bitbucket-pipelines.yml +4 -2
package/dist/cli.js
CHANGED
|
@@ -5,7 +5,7 @@ import f from 'readline';
|
|
|
5
5
|
import * as tty from 'tty';
|
|
6
6
|
import { WriteStream } from 'tty';
|
|
7
7
|
import path5, { sep, normalize, delimiter, resolve, dirname } from 'path';
|
|
8
|
-
import fs, { writeFile } from 'fs/promises';
|
|
8
|
+
import fs, { writeFile, readFile } from 'fs/promises';
|
|
9
9
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
10
10
|
import fs2, { existsSync, statSync, readFileSync } from 'fs';
|
|
11
11
|
import { execFileSync, spawn } from 'child_process';
|
|
@@ -5579,10 +5579,18 @@ var TABLE_X = 20;
|
|
|
5579
5579
|
var TABLE_W = 880;
|
|
5580
5580
|
var HEADER_H = 34;
|
|
5581
5581
|
var ROW_H = 34;
|
|
5582
|
-
var COL_CHECK =
|
|
5583
|
-
var
|
|
5584
|
-
var
|
|
5585
|
-
var
|
|
5582
|
+
var COL_CHECK = 52;
|
|
5583
|
+
var ICON_X = 268;
|
|
5584
|
+
var COL_STATUS = 400;
|
|
5585
|
+
var COL_NUM = 700;
|
|
5586
|
+
var COL_TIME = 820;
|
|
5587
|
+
var OVER_LABEL_W = 132;
|
|
5588
|
+
var OVER_ROW_H = 36;
|
|
5589
|
+
var clipSeq = 0;
|
|
5590
|
+
function nextClipId() {
|
|
5591
|
+
clipSeq += 1;
|
|
5592
|
+
return `fgc${clipSeq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
5593
|
+
}
|
|
5586
5594
|
function escapeXml(s3) {
|
|
5587
5595
|
return s3.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5588
5596
|
}
|
|
@@ -5600,7 +5608,7 @@ function formatDuration(ms) {
|
|
|
5600
5608
|
return r4 ? `${m3}m ${r4}s` : `${m3}m`;
|
|
5601
5609
|
}
|
|
5602
5610
|
function statusText(r4) {
|
|
5603
|
-
if (r4.skipped) return `Skipped \u2014 ${truncate5(r4.skipped,
|
|
5611
|
+
if (r4.skipped) return `Skipped \u2014 ${truncate5(r4.skipped, 42)}`;
|
|
5604
5612
|
if (r4.findings.length === 0) return "Pass";
|
|
5605
5613
|
return `${r4.findings.length} issue(s)`;
|
|
5606
5614
|
}
|
|
@@ -5615,16 +5623,38 @@ function riskFill(risk) {
|
|
|
5615
5623
|
if (risk === "MEDIUM") return "#d97706";
|
|
5616
5624
|
return "#dc2626";
|
|
5617
5625
|
}
|
|
5626
|
+
function resvgFontOptions() {
|
|
5627
|
+
const base = { loadSystemFonts: true };
|
|
5628
|
+
if (process.platform === "linux") {
|
|
5629
|
+
return {
|
|
5630
|
+
...base,
|
|
5631
|
+
fontDirs: [
|
|
5632
|
+
"/usr/share/fonts/truetype/dejavu",
|
|
5633
|
+
"/usr/share/fonts/truetype/liberation",
|
|
5634
|
+
"/usr/share/fonts/opentype/noto",
|
|
5635
|
+
"/usr/share/fonts"
|
|
5636
|
+
]
|
|
5637
|
+
};
|
|
5638
|
+
}
|
|
5639
|
+
return base;
|
|
5640
|
+
}
|
|
5618
5641
|
function buildChecksSnapshotSvg(p2) {
|
|
5619
|
-
const { riskScore, mode,
|
|
5642
|
+
const { riskScore, mode, stack, pr, results, lines } = p2;
|
|
5620
5643
|
const modeLabel = mode === "enforce" ? "Enforce" : "Warn only";
|
|
5621
|
-
const
|
|
5622
|
-
const
|
|
5623
|
-
|
|
5624
|
-
|
|
5644
|
+
const stackLine = truncate5(formatStackOneLiner(stack), 96);
|
|
5645
|
+
const prLine = pr && lines != null ? truncate5(
|
|
5646
|
+
`${lines} lines changed (+${pr.additions} / \u2212${pr.deletions}) \xB7 ${pr.changedFiles} files`,
|
|
5647
|
+
96
|
|
5648
|
+
) : null;
|
|
5649
|
+
const nOverview = 3 + (prLine ? 1 : 0);
|
|
5650
|
+
const overviewH = nOverview * OVER_ROW_H;
|
|
5651
|
+
const overviewY0 = 44;
|
|
5652
|
+
const overviewClip = nextClipId();
|
|
5653
|
+
const checksTitleY = overviewY0 + overviewH + 22;
|
|
5654
|
+
const tableTop = checksTitleY + 22;
|
|
5625
5655
|
const bodyRows = results.length;
|
|
5626
5656
|
const cardH = HEADER_H + bodyRows * ROW_H;
|
|
5627
|
-
const totalH = tableTop + cardH +
|
|
5657
|
+
const totalH = tableTop + cardH + 36;
|
|
5628
5658
|
const headerMid = tableTop + HEADER_H / 2 + 5;
|
|
5629
5659
|
const rowTextY = (i3) => tableTop + HEADER_H + i3 * ROW_H + ROW_H / 2 + 5;
|
|
5630
5660
|
const headerCells = [
|
|
@@ -5633,6 +5663,33 @@ function buildChecksSnapshotSvg(p2) {
|
|
|
5633
5663
|
{ x: COL_NUM, label: "#", anchor: "end" },
|
|
5634
5664
|
{ x: COL_TIME, label: "TIME", anchor: "end" }
|
|
5635
5665
|
];
|
|
5666
|
+
const overviewRows = [];
|
|
5667
|
+
const ov = [
|
|
5668
|
+
{ label: "Risk score", kind: "risk" },
|
|
5669
|
+
{ label: "Mode", kind: "text", text: modeLabel },
|
|
5670
|
+
{ label: "Stack", kind: "text", text: stackLine }
|
|
5671
|
+
];
|
|
5672
|
+
if (prLine) ov.push({ label: "Pull request", kind: "text", text: prLine });
|
|
5673
|
+
for (let i3 = 0; i3 < nOverview; i3++) {
|
|
5674
|
+
const row = ov[i3];
|
|
5675
|
+
const y4 = overviewY0 + i3 * OVER_ROW_H;
|
|
5676
|
+
const mid = y4 + OVER_ROW_H / 2 + 4;
|
|
5677
|
+
overviewRows.push(
|
|
5678
|
+
`<rect x="${TABLE_X}" y="${y4}" width="${OVER_LABEL_W}" height="${OVER_ROW_H}" fill="#f1f5f9" stroke="none"/>`,
|
|
5679
|
+
`<line x1="${TABLE_X}" y1="${y4 + OVER_ROW_H}" x2="${TABLE_X + TABLE_W}" y2="${y4 + OVER_ROW_H}" stroke="#e2e8f0" stroke-width="1"/>`,
|
|
5680
|
+
`<text x="${TABLE_X + 10}" y="${mid}" font-size="12" fill="#64748b" font-weight="500" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">${escapeXml(row.label)}</text>`
|
|
5681
|
+
);
|
|
5682
|
+
if (row.kind === "risk") {
|
|
5683
|
+
const rs = riskScore;
|
|
5684
|
+
overviewRows.push(
|
|
5685
|
+
`<text x="${TABLE_X + OVER_LABEL_W + 10}" y="${mid}" font-size="13" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif"><tspan fill="${riskFill(riskScore)}" font-weight="600">${escapeXml(rs)}</tspan><tspan fill="#64748b"> \u2014 heuristic</tspan></text>`
|
|
5686
|
+
);
|
|
5687
|
+
} else {
|
|
5688
|
+
overviewRows.push(
|
|
5689
|
+
`<text x="${TABLE_X + OVER_LABEL_W + 10}" y="${mid}" font-size="13" fill="#0f172a" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">${escapeXml(row.text)}</text>`
|
|
5690
|
+
);
|
|
5691
|
+
}
|
|
5692
|
+
}
|
|
5636
5693
|
const rowLines = [];
|
|
5637
5694
|
for (let i3 = 0; i3 < bodyRows; i3++) {
|
|
5638
5695
|
const y4 = tableTop + HEADER_H + i3 * ROW_H;
|
|
@@ -5645,13 +5702,14 @@ function buildChecksSnapshotSvg(p2) {
|
|
|
5645
5702
|
for (let i3 = 0; i3 < bodyRows; i3++) {
|
|
5646
5703
|
const r4 = results[i3];
|
|
5647
5704
|
const cy = rowTextY(i3);
|
|
5648
|
-
const cx = 36;
|
|
5649
5705
|
bodyCells.push(
|
|
5650
|
-
`<circle cx="
|
|
5651
|
-
`<text x="${COL_CHECK}" y="${cy}" font-size="13" font-weight="600" fill="#0f172a" font-family="
|
|
5652
|
-
`<
|
|
5653
|
-
`<text x="${
|
|
5654
|
-
`<text x="${
|
|
5706
|
+
`<circle cx="34" cy="${cy - 4}" r="4.5" fill="${dotFill(r4)}"/>`,
|
|
5707
|
+
`<text x="${COL_CHECK}" y="${cy}" font-size="13" font-weight="600" fill="#0f172a" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">${escapeXml(truncate5(r4.checkId, 22))}</text>`,
|
|
5708
|
+
`<circle cx="${ICON_X}" cy="${cy - 3}" r="8" fill="#f1f5f9" stroke="#e2e8f0" stroke-width="1"/>`,
|
|
5709
|
+
`<text x="${ICON_X}" y="${cy}" font-size="9" font-weight="700" fill="#64748b" text-anchor="middle" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">i</text>`,
|
|
5710
|
+
`<text x="${COL_STATUS}" y="${cy}" font-size="13" fill="#0f172a" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">${escapeXml(statusText(r4))}</text>`,
|
|
5711
|
+
`<text x="${COL_NUM}" y="${cy}" font-size="13" fill="#64748b" font-family="Liberation Mono, DejaVu Sans Mono, monospace" text-anchor="end">${escapeXml(r4.skipped ? "\u2014" : String(r4.findings.length))}</text>`,
|
|
5712
|
+
`<text x="${COL_TIME}" y="${cy}" font-size="13" fill="#64748b" font-family="Liberation Mono, DejaVu Sans Mono, monospace" text-anchor="end">${escapeXml(formatDuration(r4.durationMs))}</text>`
|
|
5655
5713
|
);
|
|
5656
5714
|
}
|
|
5657
5715
|
const gridLines = [];
|
|
@@ -5661,27 +5719,30 @@ function buildChecksSnapshotSvg(p2) {
|
|
|
5661
5719
|
`<line x1="${TABLE_X}" y1="${y4}" x2="${TABLE_X + TABLE_W}" y2="${y4}" stroke="#e2e8f0" stroke-width="1"/>`
|
|
5662
5720
|
);
|
|
5663
5721
|
}
|
|
5722
|
+
const checksClip = nextClipId();
|
|
5664
5723
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
5665
5724
|
<svg xmlns="http://www.w3.org/2000/svg" width="${W3}" height="${totalH}" viewBox="0 0 ${W3} ${totalH}">
|
|
5666
5725
|
<rect width="${W3}" height="${totalH}" fill="#f8fafc"/>
|
|
5667
|
-
<text x="${PAD_X}" y="
|
|
5668
|
-
<text x="${PAD_X}" y="
|
|
5669
|
-
<text x="${PAD_X}" y="${metaY}" font-size="13" fill="#64748b" font-family="system-ui, -apple-system, Segoe UI, sans-serif">
|
|
5670
|
-
<tspan>Risk </tspan><tspan fill="${riskFill(riskScore)}" font-weight="600">${escapeXml(riskScore)}</tspan>
|
|
5671
|
-
<tspan> \xB7 ${escapeXml(modeLabel)} \xB7 Blocking </tspan><tspan fill="#0f172a" font-weight="600">${blocks}</tspan>
|
|
5672
|
-
<tspan> \xB7 Warnings </tspan><tspan fill="#0f172a" font-weight="600">${warns}</tspan>
|
|
5673
|
-
<tspan> \xB7 Info </tspan><tspan fill="#0f172a" font-weight="600">${infos}</tspan>
|
|
5674
|
-
</text>
|
|
5726
|
+
<text x="${PAD_X}" y="18" font-size="11" font-weight="600" fill="#64748b" letter-spacing="0.12em" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">FRONTGUARD</text>
|
|
5727
|
+
<text x="${PAD_X}" y="36" font-size="16" font-weight="600" fill="#0f172a" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">Overview</text>
|
|
5675
5728
|
<defs>
|
|
5676
|
-
<clipPath id="
|
|
5729
|
+
<clipPath id="${overviewClip}">
|
|
5730
|
+
<rect x="${TABLE_X}" y="${overviewY0}" width="${TABLE_W}" height="${overviewH}" rx="10" ry="10"/>
|
|
5731
|
+
</clipPath>
|
|
5732
|
+
<clipPath id="${checksClip}">
|
|
5677
5733
|
<rect x="${TABLE_X}" y="${tableTop}" width="${TABLE_W}" height="${cardH}" rx="10" ry="10"/>
|
|
5678
5734
|
</clipPath>
|
|
5679
5735
|
</defs>
|
|
5736
|
+
<rect x="${TABLE_X}" y="${overviewY0}" width="${TABLE_W}" height="${overviewH}" rx="10" ry="10" fill="#ffffff" stroke="#e2e8f0" stroke-width="1"/>
|
|
5737
|
+
<g clip-path="url(#${overviewClip})">
|
|
5738
|
+
${overviewRows.join("\n ")}
|
|
5739
|
+
</g>
|
|
5740
|
+
<text x="${PAD_X}" y="${checksTitleY}" font-size="16" font-weight="600" fill="#0f172a" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif">Checks</text>
|
|
5680
5741
|
<rect x="${TABLE_X}" y="${tableTop}" width="${TABLE_W}" height="${cardH}" rx="10" ry="10" fill="#ffffff" stroke="#e2e8f0" stroke-width="1"/>
|
|
5681
|
-
<g clip-path="url(
|
|
5742
|
+
<g clip-path="url(#${checksClip})">
|
|
5682
5743
|
<rect x="${TABLE_X}" y="${tableTop}" width="${TABLE_W}" height="${HEADER_H}" fill="#f1f5f9"/>
|
|
5683
5744
|
${headerCells.map(
|
|
5684
|
-
(c4) => `<text x="${c4.x}" y="${headerMid}" font-size="11" font-weight="600" fill="#64748b" letter-spacing="0.04em" font-family="
|
|
5745
|
+
(c4) => `<text x="${c4.x}" y="${headerMid}" font-size="11" font-weight="600" fill="#64748b" letter-spacing="0.04em" font-family="DejaVu Sans, Liberation Sans, system-ui, sans-serif" text-anchor="${c4.anchor}">${c4.label}</text>`
|
|
5685
5746
|
).join("\n ")}
|
|
5686
5747
|
${rowLines.join("\n ")}
|
|
5687
5748
|
${gridLines.join("\n ")}
|
|
@@ -5694,10 +5755,21 @@ async function renderChecksSnapshotPng(pngPath, input) {
|
|
|
5694
5755
|
const resvg = new Resvg(svg, {
|
|
5695
5756
|
background: "#f8fafc",
|
|
5696
5757
|
fitTo: { mode: "width", value: W3 },
|
|
5697
|
-
font:
|
|
5758
|
+
font: resvgFontOptions()
|
|
5698
5759
|
});
|
|
5699
5760
|
const img = resvg.render();
|
|
5700
|
-
|
|
5761
|
+
const png = img.asPng();
|
|
5762
|
+
await writeFile(pngPath, png);
|
|
5763
|
+
const written = await readFile(pngPath);
|
|
5764
|
+
if (!isPngBuffer(written) || written.length < 200) {
|
|
5765
|
+
throw new Error(
|
|
5766
|
+
"FrontGuard: checks PNG is missing or invalid after render. Install fonts on the runner (e.g. apt install fonts-dejavu-core fonts-liberation) and ensure @resvg/resvg-js native binary loads."
|
|
5767
|
+
);
|
|
5768
|
+
}
|
|
5769
|
+
const { width, height } = img;
|
|
5770
|
+
if (width < 16 || height < 16) {
|
|
5771
|
+
throw new Error(`FrontGuard: checks PNG has invalid dimensions ${width}x${height}.`);
|
|
5772
|
+
}
|
|
5701
5773
|
}
|
|
5702
5774
|
|
|
5703
5775
|
// src/report/builder.ts
|
|
@@ -5881,6 +5953,32 @@ var CHECKS_TABLE_STYLES = `
|
|
|
5881
5953
|
.dot-block { background: var(--block); }
|
|
5882
5954
|
.dot-skip { background: #cbd5e1; }
|
|
5883
5955
|
`;
|
|
5956
|
+
var SNAPSHOT_OVERVIEW_STYLES = `
|
|
5957
|
+
.snapshot {
|
|
5958
|
+
width: 100%;
|
|
5959
|
+
border-collapse: collapse;
|
|
5960
|
+
font-size: 0.9rem;
|
|
5961
|
+
background: var(--surface);
|
|
5962
|
+
border-radius: var(--radius);
|
|
5963
|
+
overflow: hidden;
|
|
5964
|
+
border: 1px solid var(--border);
|
|
5965
|
+
box-shadow: var(--shadow);
|
|
5966
|
+
margin: 0 0 1.25rem;
|
|
5967
|
+
}
|
|
5968
|
+
.snapshot th, .snapshot td {
|
|
5969
|
+
padding: 0.65rem 1rem;
|
|
5970
|
+
text-align: left;
|
|
5971
|
+
border-bottom: 1px solid var(--border);
|
|
5972
|
+
}
|
|
5973
|
+
.snapshot tr:last-child th, .snapshot tr:last-child td { border-bottom: none; }
|
|
5974
|
+
.snapshot th {
|
|
5975
|
+
width: 9rem;
|
|
5976
|
+
color: var(--muted);
|
|
5977
|
+
font-weight: 500;
|
|
5978
|
+
background: #f1f5f9;
|
|
5979
|
+
}
|
|
5980
|
+
.muted { color: var(--muted); }
|
|
5981
|
+
`;
|
|
5884
5982
|
function renderCheckTableRows(results) {
|
|
5885
5983
|
return results.map((r4) => {
|
|
5886
5984
|
const status = r4.skipped ? `Skipped \u2014 ${escapeHtml(r4.skipped)}` : r4.findings.length === 0 ? "Pass" : `${r4.findings.length} issue(s)`;
|
|
@@ -5891,16 +5989,17 @@ function renderCheckTableRows(results) {
|
|
|
5891
5989
|
}).join("\n");
|
|
5892
5990
|
}
|
|
5893
5991
|
function buildChecksSnapshotHtml(p2) {
|
|
5894
|
-
const { riskScore, mode,
|
|
5992
|
+
const { riskScore, mode, stack, pr, results, lines } = p2;
|
|
5895
5993
|
const modeLabel = mode === "enforce" ? "Enforce" : "Warn only";
|
|
5896
5994
|
const riskClass = riskScore === "LOW" ? "risk-low" : riskScore === "MEDIUM" ? "risk-med" : "risk-high";
|
|
5897
5995
|
const checkRows = renderCheckTableRows(results);
|
|
5996
|
+
const prRow = pr && lines != null ? `<tr><th>Pull request</th><td>${lines} lines changed (+${pr.additions} / \u2212${pr.deletions}) \xB7 ${pr.changedFiles} files</td></tr>` : "";
|
|
5898
5997
|
return `<!DOCTYPE html>
|
|
5899
5998
|
<html lang="en">
|
|
5900
5999
|
<head>
|
|
5901
6000
|
<meta charset="utf-8" />
|
|
5902
6001
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
5903
|
-
<title>FrontGuard \u2014
|
|
6002
|
+
<title>FrontGuard \u2014 Snapshot</title>
|
|
5904
6003
|
<style>
|
|
5905
6004
|
:root {
|
|
5906
6005
|
--bg: #f8fafc;
|
|
@@ -5939,26 +6038,27 @@ function buildChecksSnapshotHtml(p2) {
|
|
|
5939
6038
|
.h2 {
|
|
5940
6039
|
font-size: 1rem;
|
|
5941
6040
|
font-weight: 600;
|
|
5942
|
-
margin: 0 0 0.
|
|
6041
|
+
margin: 0 0 0.85rem;
|
|
5943
6042
|
color: var(--text);
|
|
5944
6043
|
letter-spacing: -0.02em;
|
|
5945
6044
|
}
|
|
5946
|
-
.snap-meta {
|
|
5947
|
-
font-size: 0.8rem;
|
|
5948
|
-
color: var(--muted);
|
|
5949
|
-
margin: 0 0 0.85rem;
|
|
5950
|
-
}
|
|
5951
|
-
.snap-meta strong { color: var(--text); font-weight: 600; }
|
|
5952
6045
|
.risk-low { color: var(--ok); }
|
|
5953
6046
|
.risk-med { color: var(--warn); }
|
|
5954
6047
|
.risk-high { color: var(--block); }
|
|
6048
|
+
${SNAPSHOT_OVERVIEW_STYLES}
|
|
5955
6049
|
${CHECKS_TABLE_STYLES}
|
|
5956
6050
|
</style>
|
|
5957
6051
|
</head>
|
|
5958
6052
|
<body>
|
|
5959
6053
|
<div class="brand">FrontGuard</div>
|
|
6054
|
+
<h2 class="h2">Overview</h2>
|
|
6055
|
+
<table class="snapshot">
|
|
6056
|
+
<tr><th>Risk score</th><td><strong class="${riskClass}">${riskScore}</strong> <span class="muted">\u2014 heuristic</span></td></tr>
|
|
6057
|
+
<tr><th>Mode</th><td>${escapeHtml(modeLabel)}</td></tr>
|
|
6058
|
+
<tr><th>Stack</th><td>${escapeHtml(formatStackOneLiner(stack))}</td></tr>
|
|
6059
|
+
${prRow}
|
|
6060
|
+
</table>
|
|
5960
6061
|
<h2 class="h2">Checks</h2>
|
|
5961
|
-
<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>
|
|
5962
6062
|
<table class="results">
|
|
5963
6063
|
<thead><tr><th></th><th>Check</th><th>Status</th><th>#</th><th>Time</th></tr></thead>
|
|
5964
6064
|
<tbody>${checkRows}</tbody>
|