@cleartrip/frontguard 0.2.6 → 0.2.7
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 +199 -65
- package/dist/cli.js.map +1 -1
- package/package.json +5 -2
- package/templates/bitbucket-pipelines.yml +17 -6
- package/templates/freekit-ci-setup.md +2 -2
package/dist/cli.js
CHANGED
|
@@ -5,16 +5,16 @@ 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 from 'fs/promises';
|
|
9
|
-
import {
|
|
10
|
-
import { tmpdir } from 'os';
|
|
8
|
+
import fs, { writeFile } from 'fs/promises';
|
|
9
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
10
|
import fs2, { existsSync, statSync, readFileSync } from 'fs';
|
|
12
|
-
import {
|
|
11
|
+
import { execFileSync, spawn } from 'child_process';
|
|
13
12
|
import { createRequire } from 'module';
|
|
14
13
|
import { pipeline } from 'stream/promises';
|
|
15
14
|
import { PassThrough } from 'stream';
|
|
16
15
|
import fg from 'fast-glob';
|
|
17
16
|
import * as ts from 'typescript';
|
|
17
|
+
import { Resvg } from '@resvg/resvg-js';
|
|
18
18
|
|
|
19
19
|
var __create = Object.create;
|
|
20
20
|
var __defProp = Object.defineProperty;
|
|
@@ -2497,26 +2497,52 @@ async function initFrontGuard(cwd) {
|
|
|
2497
2497
|
await fs.writeFile(tplPr, PR_TEMPLATE, "utf8");
|
|
2498
2498
|
}
|
|
2499
2499
|
}
|
|
2500
|
-
var
|
|
2500
|
+
var BITBUCKET_CHECKS_IMAGE_MARKDOWN_ALT = "FrontGuard checks summary";
|
|
2501
|
+
var MAX_BITBUCKET_INLINE_IMAGE_MARKDOWN_LINE_LENGTH = 3e5;
|
|
2502
|
+
var MAX_PNG_BYTES_BITBUCKET_INLINE = 21e4;
|
|
2503
|
+
var MARKDOWN_LINE_PREFIX = ` {
|
|
2505
|
+
return buf.length >= 8 && buf[0] === 137 && buf[1] === 80 && buf[2] === 78 && buf[3] === 71 && buf[4] === 13 && buf[5] === 10 && buf[6] === 26 && buf[7] === 10;
|
|
2506
|
+
}
|
|
2507
|
+
function pngBufferToBitbucketImageMarkdownLine(png) {
|
|
2508
|
+
if (!isPngBuffer(png)) {
|
|
2509
|
+
throw new TypeError("Buffer is not a PNG (missing IHDR signature)");
|
|
2510
|
+
}
|
|
2511
|
+
if (png.length > MAX_PNG_BYTES_BITBUCKET_INLINE) {
|
|
2512
|
+
throw new RangeError(
|
|
2513
|
+
`PNG too large for Bitbucket inline markdown (${png.length} bytes; max ${MAX_PNG_BYTES_BITBUCKET_INLINE}). Host the image and use an HTTPS URL instead.`
|
|
2514
|
+
);
|
|
2515
|
+
}
|
|
2516
|
+
const b64 = png.toString("base64");
|
|
2517
|
+
const line = `${MARKDOWN_LINE_PREFIX}${b64})`;
|
|
2518
|
+
if (line.length > MAX_BITBUCKET_INLINE_IMAGE_MARKDOWN_LINE_LENGTH) {
|
|
2519
|
+
throw new RangeError(
|
|
2520
|
+
`Inline markdown line too long (${line.length} chars; max ${MAX_BITBUCKET_INLINE_IMAGE_MARKDOWN_LINE_LENGTH})`
|
|
2521
|
+
);
|
|
2522
|
+
}
|
|
2523
|
+
return line;
|
|
2524
|
+
}
|
|
2525
|
+
function tryPngFileToBitbucketImageMarkdownLine(filePath) {
|
|
2526
|
+
try {
|
|
2527
|
+
if (!existsSync(filePath)) return null;
|
|
2528
|
+
const st = statSync(filePath);
|
|
2529
|
+
if (!st.isFile() || st.size > MAX_PNG_BYTES_BITBUCKET_INLINE) return null;
|
|
2530
|
+
const buf = readFileSync(filePath);
|
|
2531
|
+
return pngBufferToBitbucketImageMarkdownLine(buf);
|
|
2532
|
+
} catch {
|
|
2533
|
+
return null;
|
|
2534
|
+
}
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
// src/ci/bitbucket-pr-snippet.ts
|
|
2501
2538
|
function checksImageMarkdown() {
|
|
2502
2539
|
const embedPath = process.env.FRONTGUARD_EMBED_CHECKS_PNG_PATH?.trim();
|
|
2503
2540
|
if (embedPath) {
|
|
2504
|
-
|
|
2505
|
-
if (!existsSync(embedPath)) return null;
|
|
2506
|
-
const st = statSync(embedPath);
|
|
2507
|
-
if (!st.isFile() || st.size > MAX_PNG_BYTES_FOR_EMBED) return null;
|
|
2508
|
-
const buf = readFileSync(embedPath);
|
|
2509
|
-
const b64 = buf.toString("base64");
|
|
2510
|
-
const md = ``;
|
|
2511
|
-
if (md.length > 45e4) return null;
|
|
2512
|
-
return md;
|
|
2513
|
-
} catch {
|
|
2514
|
-
return null;
|
|
2515
|
-
}
|
|
2541
|
+
return tryPngFileToBitbucketImageMarkdownLine(embedPath);
|
|
2516
2542
|
}
|
|
2517
2543
|
const u4 = process.env.FRONTGUARD_CHECKS_IMAGE_URL?.trim();
|
|
2518
2544
|
if (!u4) return null;
|
|
2519
|
-
return ``;
|
|
2520
2546
|
}
|
|
2521
2547
|
function bitbucketPipelineResultsUrl() {
|
|
2522
2548
|
const full = process.env.BITBUCKET_REPO_FULL_NAME?.trim();
|
|
@@ -5547,29 +5573,131 @@ function applyAiAssistedEscalation(results, pr, config) {
|
|
|
5547
5573
|
}
|
|
5548
5574
|
}
|
|
5549
5575
|
}
|
|
5550
|
-
var
|
|
5551
|
-
|
|
5552
|
-
|
|
5553
|
-
|
|
5554
|
-
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5576
|
+
var W3 = 920;
|
|
5577
|
+
var PAD_X = 24;
|
|
5578
|
+
var TABLE_X = 20;
|
|
5579
|
+
var TABLE_W = 880;
|
|
5580
|
+
var HEADER_H = 34;
|
|
5581
|
+
var ROW_H = 34;
|
|
5582
|
+
var COL_CHECK = 56;
|
|
5583
|
+
var COL_STATUS = 508;
|
|
5584
|
+
var COL_NUM = 748;
|
|
5585
|
+
var COL_TIME = 888;
|
|
5586
|
+
function escapeXml(s3) {
|
|
5587
|
+
return s3.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
5560
5588
|
}
|
|
5561
|
-
|
|
5562
|
-
const
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5569
|
-
|
|
5570
|
-
|
|
5571
|
-
|
|
5572
|
-
|
|
5589
|
+
function truncate5(s3, max) {
|
|
5590
|
+
const t3 = s3.replace(/\s+/g, " ").trim();
|
|
5591
|
+
if (t3.length <= max) return t3;
|
|
5592
|
+
return `${t3.slice(0, max - 1)}\u2026`;
|
|
5593
|
+
}
|
|
5594
|
+
function formatDuration(ms) {
|
|
5595
|
+
if (ms < 1e3) return `${ms} ms`;
|
|
5596
|
+
const s3 = Math.round(ms / 1e3);
|
|
5597
|
+
if (s3 < 60) return `${s3}s`;
|
|
5598
|
+
const m3 = Math.floor(s3 / 60);
|
|
5599
|
+
const r4 = s3 % 60;
|
|
5600
|
+
return r4 ? `${m3}m ${r4}s` : `${m3}m`;
|
|
5601
|
+
}
|
|
5602
|
+
function statusText(r4) {
|
|
5603
|
+
if (r4.skipped) return `Skipped \u2014 ${truncate5(r4.skipped, 36)}`;
|
|
5604
|
+
if (r4.findings.length === 0) return "Pass";
|
|
5605
|
+
return `${r4.findings.length} issue(s)`;
|
|
5606
|
+
}
|
|
5607
|
+
function dotFill(r4) {
|
|
5608
|
+
if (r4.skipped) return "#cbd5e1";
|
|
5609
|
+
if (r4.findings.length === 0) return "#16a34a";
|
|
5610
|
+
if (r4.findings.some((x3) => x3.severity === "block")) return "#dc2626";
|
|
5611
|
+
return "#d97706";
|
|
5612
|
+
}
|
|
5613
|
+
function riskFill(risk) {
|
|
5614
|
+
if (risk === "LOW") return "#16a34a";
|
|
5615
|
+
if (risk === "MEDIUM") return "#d97706";
|
|
5616
|
+
return "#dc2626";
|
|
5617
|
+
}
|
|
5618
|
+
function buildChecksSnapshotSvg(p2) {
|
|
5619
|
+
const { riskScore, mode, results, warns, infos, blocks } = p2;
|
|
5620
|
+
const modeLabel = mode === "enforce" ? "Enforce" : "Warn only";
|
|
5621
|
+
const brandY = 20;
|
|
5622
|
+
const titleY = 42;
|
|
5623
|
+
const metaY = 66;
|
|
5624
|
+
const tableTop = 88;
|
|
5625
|
+
const bodyRows = results.length;
|
|
5626
|
+
const cardH = HEADER_H + bodyRows * ROW_H;
|
|
5627
|
+
const totalH = tableTop + cardH + 28;
|
|
5628
|
+
const headerMid = tableTop + HEADER_H / 2 + 5;
|
|
5629
|
+
const rowTextY = (i3) => tableTop + HEADER_H + i3 * ROW_H + ROW_H / 2 + 5;
|
|
5630
|
+
const headerCells = [
|
|
5631
|
+
{ x: COL_CHECK, label: "CHECK", anchor: "start" },
|
|
5632
|
+
{ x: COL_STATUS, label: "STATUS", anchor: "start" },
|
|
5633
|
+
{ x: COL_NUM, label: "#", anchor: "end" },
|
|
5634
|
+
{ x: COL_TIME, label: "TIME", anchor: "end" }
|
|
5635
|
+
];
|
|
5636
|
+
const rowLines = [];
|
|
5637
|
+
for (let i3 = 0; i3 < bodyRows; i3++) {
|
|
5638
|
+
const y4 = tableTop + HEADER_H + i3 * ROW_H;
|
|
5639
|
+
const fill = i3 % 2 === 1 ? "#f8fafc" : "#ffffff";
|
|
5640
|
+
rowLines.push(
|
|
5641
|
+
`<rect x="${TABLE_X}" y="${y4}" width="${TABLE_W}" height="${ROW_H}" fill="${fill}" stroke="none"/>`
|
|
5642
|
+
);
|
|
5643
|
+
}
|
|
5644
|
+
const bodyCells = [];
|
|
5645
|
+
for (let i3 = 0; i3 < bodyRows; i3++) {
|
|
5646
|
+
const r4 = results[i3];
|
|
5647
|
+
const cy = rowTextY(i3);
|
|
5648
|
+
const cx = 36;
|
|
5649
|
+
bodyCells.push(
|
|
5650
|
+
`<circle cx="${cx}" cy="${cy - 4}" r="4.5" fill="${dotFill(r4)}"/>`,
|
|
5651
|
+
`<text x="${COL_CHECK}" y="${cy}" font-size="13" font-weight="600" fill="#0f172a" font-family="system-ui, -apple-system, Segoe UI, sans-serif">${escapeXml(truncate5(r4.checkId, 46))}</text>`,
|
|
5652
|
+
`<text x="${COL_STATUS}" y="${cy}" font-size="13" fill="#0f172a" font-family="system-ui, -apple-system, Segoe UI, sans-serif">${escapeXml(statusText(r4))}</text>`,
|
|
5653
|
+
`<text x="${COL_NUM}" y="${cy}" font-size="13" fill="#64748b" font-family="ui-monospace, monospace" text-anchor="end">${escapeXml(r4.skipped ? "\u2014" : String(r4.findings.length))}</text>`,
|
|
5654
|
+
`<text x="${COL_TIME}" y="${cy}" font-size="13" fill="#64748b" font-family="ui-monospace, monospace" text-anchor="end">${escapeXml(formatDuration(r4.durationMs))}</text>`
|
|
5655
|
+
);
|
|
5656
|
+
}
|
|
5657
|
+
const gridLines = [];
|
|
5658
|
+
for (let i3 = 0; i3 <= bodyRows; i3++) {
|
|
5659
|
+
const y4 = tableTop + HEADER_H + i3 * ROW_H;
|
|
5660
|
+
gridLines.push(
|
|
5661
|
+
`<line x1="${TABLE_X}" y1="${y4}" x2="${TABLE_X + TABLE_W}" y2="${y4}" stroke="#e2e8f0" stroke-width="1"/>`
|
|
5662
|
+
);
|
|
5663
|
+
}
|
|
5664
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
5665
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${W3}" height="${totalH}" viewBox="0 0 ${W3} ${totalH}">
|
|
5666
|
+
<rect width="${W3}" height="${totalH}" fill="#f8fafc"/>
|
|
5667
|
+
<text x="${PAD_X}" y="${brandY}" font-size="11" font-weight="600" fill="#64748b" letter-spacing="0.12em" font-family="system-ui, -apple-system, Segoe UI, sans-serif">FRONTGUARD</text>
|
|
5668
|
+
<text x="${PAD_X}" y="${titleY}" font-size="17" font-weight="600" fill="#0f172a" font-family="system-ui, -apple-system, Segoe UI, sans-serif">Checks</text>
|
|
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>
|
|
5675
|
+
<defs>
|
|
5676
|
+
<clipPath id="fg-card">
|
|
5677
|
+
<rect x="${TABLE_X}" y="${tableTop}" width="${TABLE_W}" height="${cardH}" rx="10" ry="10"/>
|
|
5678
|
+
</clipPath>
|
|
5679
|
+
</defs>
|
|
5680
|
+
<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(#fg-card)">
|
|
5682
|
+
<rect x="${TABLE_X}" y="${tableTop}" width="${TABLE_W}" height="${HEADER_H}" fill="#f1f5f9"/>
|
|
5683
|
+
${headerCells.map(
|
|
5684
|
+
(c4) => `<text x="${c4.x}" y="${headerMid}" font-size="11" font-weight="600" fill="#64748b" letter-spacing="0.04em" font-family="system-ui, -apple-system, Segoe UI, sans-serif" text-anchor="${c4.anchor}">${c4.label}</text>`
|
|
5685
|
+
).join("\n ")}
|
|
5686
|
+
${rowLines.join("\n ")}
|
|
5687
|
+
${gridLines.join("\n ")}
|
|
5688
|
+
${bodyCells.join("\n ")}
|
|
5689
|
+
</g>
|
|
5690
|
+
</svg>`;
|
|
5691
|
+
}
|
|
5692
|
+
async function renderChecksSnapshotPng(pngPath, input) {
|
|
5693
|
+
const svg = buildChecksSnapshotSvg(input);
|
|
5694
|
+
const resvg = new Resvg(svg, {
|
|
5695
|
+
background: "#f8fafc",
|
|
5696
|
+
fitTo: { mode: "width", value: W3 },
|
|
5697
|
+
font: { loadSystemFonts: true }
|
|
5698
|
+
});
|
|
5699
|
+
const img = resvg.render();
|
|
5700
|
+
await writeFile(pngPath, img.asPng());
|
|
5573
5701
|
}
|
|
5574
5702
|
|
|
5575
5703
|
// src/report/builder.ts
|
|
@@ -5624,7 +5752,7 @@ function sortFindings(cwd, items) {
|
|
|
5624
5752
|
return a3.f.message.localeCompare(b3.f.message);
|
|
5625
5753
|
});
|
|
5626
5754
|
}
|
|
5627
|
-
function
|
|
5755
|
+
function formatDuration2(ms) {
|
|
5628
5756
|
if (ms < 1e3) return `${ms} ms`;
|
|
5629
5757
|
const s3 = Math.round(ms / 1e3);
|
|
5630
5758
|
if (s3 < 60) return `${s3}s`;
|
|
@@ -5759,7 +5887,7 @@ function renderCheckTableRows(results) {
|
|
|
5759
5887
|
const help = escapeHtml(getCheckDescription(r4.checkId));
|
|
5760
5888
|
const ariaWhat = escapeHtml(`What does the ${r4.checkId} check do?`);
|
|
5761
5889
|
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>`;
|
|
5762
|
-
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">${
|
|
5890
|
+
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">${formatDuration2(r4.durationMs)}</td></tr>`;
|
|
5763
5891
|
}).join("\n");
|
|
5764
5892
|
}
|
|
5765
5893
|
function buildChecksSnapshotHtml(p2) {
|
|
@@ -6185,14 +6313,32 @@ function buildReport(stack, pr, results, options) {
|
|
|
6185
6313
|
lines,
|
|
6186
6314
|
llmAppendix: options?.llmAppendix ?? null
|
|
6187
6315
|
}) : null;
|
|
6188
|
-
const
|
|
6316
|
+
const llmAppendix = options?.llmAppendix ?? null;
|
|
6317
|
+
const checksSnapshotInput = options?.emitChecksSnapshot === true ? {
|
|
6318
|
+
cwd,
|
|
6189
6319
|
riskScore,
|
|
6190
6320
|
mode,
|
|
6321
|
+
stack,
|
|
6322
|
+
pr,
|
|
6191
6323
|
results,
|
|
6192
6324
|
warns,
|
|
6193
6325
|
infos,
|
|
6194
|
-
blocks
|
|
6195
|
-
|
|
6326
|
+
blocks,
|
|
6327
|
+
lines,
|
|
6328
|
+
llmAppendix
|
|
6329
|
+
} : null;
|
|
6330
|
+
const checksSnapshotHtml = checksSnapshotInput != null ? buildChecksSnapshotHtml(checksSnapshotInput) : null;
|
|
6331
|
+
return {
|
|
6332
|
+
riskScore,
|
|
6333
|
+
stack,
|
|
6334
|
+
pr,
|
|
6335
|
+
results,
|
|
6336
|
+
markdown,
|
|
6337
|
+
consoleText,
|
|
6338
|
+
html,
|
|
6339
|
+
checksSnapshotHtml,
|
|
6340
|
+
checksSnapshotInput
|
|
6341
|
+
};
|
|
6196
6342
|
}
|
|
6197
6343
|
function scoreRisk(blocks, warns, lines, files) {
|
|
6198
6344
|
let score = 0;
|
|
@@ -6240,7 +6386,7 @@ function countShieldColor(kind, n3) {
|
|
|
6240
6386
|
if (n3 <= 10) return "yellow";
|
|
6241
6387
|
return "orange";
|
|
6242
6388
|
}
|
|
6243
|
-
function
|
|
6389
|
+
function formatDuration3(ms) {
|
|
6244
6390
|
if (ms < 1e3) return `${ms} ms`;
|
|
6245
6391
|
const s3 = Math.round(ms / 1e3);
|
|
6246
6392
|
if (s3 < 60) return `${s3}s`;
|
|
@@ -6395,7 +6541,7 @@ function formatMarkdown(p2) {
|
|
|
6395
6541
|
}
|
|
6396
6542
|
const nFind = r4.skipped ? "\u2014" : String(r4.findings.length);
|
|
6397
6543
|
sb.push(
|
|
6398
|
-
`| ${he2} | **${r4.checkId}** | ${status} | **${nFind}** | ${
|
|
6544
|
+
`| ${he2} | **${r4.checkId}** | ${status} | **${nFind}** | ${formatDuration3(r4.durationMs)} |`
|
|
6399
6545
|
);
|
|
6400
6546
|
}
|
|
6401
6547
|
sb.push("");
|
|
@@ -6960,36 +7106,24 @@ async function runFrontGuard(opts) {
|
|
|
6960
7106
|
await fs.writeFile(opts.htmlOut, report.html, "utf8");
|
|
6961
7107
|
}
|
|
6962
7108
|
let embedPngPath = null;
|
|
6963
|
-
|
|
6964
|
-
let htmlForPng;
|
|
6965
|
-
if ((opts.checksSnapshotOut || opts.checksPngOut) && report.checksSnapshotHtml) {
|
|
7109
|
+
if ((opts.checksSnapshotOut || opts.checksPngOut) && report.checksSnapshotHtml && report.checksSnapshotInput) {
|
|
6966
7110
|
if (opts.checksSnapshotOut) {
|
|
6967
7111
|
const snapPath = path5.isAbsolute(opts.checksSnapshotOut) ? opts.checksSnapshotOut : path5.join(opts.cwd, opts.checksSnapshotOut);
|
|
6968
7112
|
await fs.writeFile(snapPath, report.checksSnapshotHtml, "utf8");
|
|
6969
|
-
htmlForPng = snapPath;
|
|
6970
7113
|
g.stderr.write(`
|
|
6971
7114
|
FrontGuard: wrote checks snapshot HTML to ${snapPath}
|
|
6972
7115
|
`);
|
|
6973
7116
|
}
|
|
6974
7117
|
if (opts.checksPngOut) {
|
|
6975
|
-
if (!htmlForPng) {
|
|
6976
|
-
tmpDir = await fs.mkdtemp(path5.join(tmpdir(), "fg-checks-"));
|
|
6977
|
-
htmlForPng = path5.join(tmpDir, "checks.html");
|
|
6978
|
-
await fs.writeFile(htmlForPng, report.checksSnapshotHtml, "utf8");
|
|
6979
|
-
}
|
|
6980
7118
|
const pngAbs = path5.isAbsolute(opts.checksPngOut) ? opts.checksPngOut : path5.join(opts.cwd, opts.checksPngOut);
|
|
6981
|
-
await
|
|
7119
|
+
await renderChecksSnapshotPng(pngAbs, report.checksSnapshotInput);
|
|
6982
7120
|
embedPngPath = pngAbs;
|
|
6983
|
-
g.stderr.write(`FrontGuard: wrote checks PNG to ${pngAbs}
|
|
7121
|
+
g.stderr.write(`FrontGuard: wrote checks PNG to ${pngAbs}.
|
|
6984
7122
|
|
|
6985
7123
|
`);
|
|
6986
|
-
|
|
6987
|
-
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
6988
|
-
tmpDir = null;
|
|
6989
|
-
}
|
|
6990
|
-
} else if (opts.checksSnapshotOut && htmlForPng) {
|
|
7124
|
+
} else if (opts.checksSnapshotOut) {
|
|
6991
7125
|
g.stderr.write(
|
|
6992
|
-
` Tip: add --checksPngOut checks.png to render
|
|
7126
|
+
` Tip: add --checksPngOut checks.png to render the checks table to a PNG (no browser).
|
|
6993
7127
|
|
|
6994
7128
|
`
|
|
6995
7129
|
);
|
|
@@ -7067,7 +7201,7 @@ var run = defineCommand({
|
|
|
7067
7201
|
},
|
|
7068
7202
|
checksPngOut: {
|
|
7069
7203
|
type: "string",
|
|
7070
|
-
description: "Write PNG of the checks table
|
|
7204
|
+
description: "Write PNG of the checks table (SVG raster; @resvg/resvg-js, no browser). Pair with --checksSnapshotOut or use alone"
|
|
7071
7205
|
},
|
|
7072
7206
|
prCommentOut: {
|
|
7073
7207
|
type: "string",
|