afterbefore 0.1.3 → 0.1.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 +37 -132
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +0 -2
- package/dist/index.js +36 -131
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -100,11 +100,9 @@ var Logger = class {
|
|
|
100
100
|
this.pipelineActive = false;
|
|
101
101
|
if (this.spinner) {
|
|
102
102
|
if (finished) {
|
|
103
|
+
this.spinner.stop();
|
|
103
104
|
const bar = "\u2588".repeat(BAR_WIDTH);
|
|
104
|
-
|
|
105
|
-
symbol: chalk.green("\u2714"),
|
|
106
|
-
text: bar
|
|
107
|
-
});
|
|
105
|
+
console.log(`${chalk.green("\u2714")} ${bar}`);
|
|
108
106
|
} else {
|
|
109
107
|
this.spinner.stop();
|
|
110
108
|
}
|
|
@@ -223,8 +221,8 @@ function getCurrentBranch(cwd) {
|
|
|
223
221
|
|
|
224
222
|
// src/pipeline.ts
|
|
225
223
|
import { resolve as resolve4 } from "path";
|
|
224
|
+
import { unlinkSync } from "fs";
|
|
226
225
|
import { exec } from "child_process";
|
|
227
|
-
import chalk2 from "chalk";
|
|
228
226
|
|
|
229
227
|
// src/config.ts
|
|
230
228
|
import { resolve } from "path";
|
|
@@ -1480,7 +1478,6 @@ async function captureRoutes(tasks, beforeUrl, afterUrl, outputDir, options) {
|
|
|
1480
1478
|
|
|
1481
1479
|
// src/stages/compare.ts
|
|
1482
1480
|
import { readFileSync as readFileSync5 } from "fs";
|
|
1483
|
-
import { writeFileSync } from "fs";
|
|
1484
1481
|
import { join as join7, dirname as dirname2 } from "path";
|
|
1485
1482
|
import { PNG } from "pngjs";
|
|
1486
1483
|
import pixelmatch from "pixelmatch";
|
|
@@ -1528,9 +1525,7 @@ async function generateComposite(beforePath, afterPath, outputPath, browser, bgC
|
|
|
1528
1525
|
}
|
|
1529
1526
|
async function compareOne(capture, outputDir, threshold, options) {
|
|
1530
1527
|
const dir = dirname2(capture.beforePath);
|
|
1531
|
-
const diffPath = join7(dir, `${capture.prefix}-diff.png`);
|
|
1532
1528
|
const comparePath = join7(dir, `${capture.prefix}-compare.png`);
|
|
1533
|
-
const sliderPath = join7(dir, `${capture.prefix}-slider.html`);
|
|
1534
1529
|
const beforeBuffer = readFileSync5(capture.beforePath);
|
|
1535
1530
|
const afterBuffer = readFileSync5(capture.afterPath);
|
|
1536
1531
|
const beforeImg = PNG.sync.read(beforeBuffer);
|
|
@@ -1538,35 +1533,29 @@ async function compareOne(capture, outputDir, threshold, options) {
|
|
|
1538
1533
|
const [normBefore, normAfter] = normalizeDimensions(beforeImg, afterImg);
|
|
1539
1534
|
const { width, height } = normBefore;
|
|
1540
1535
|
const totalPixels = width * height;
|
|
1541
|
-
const diffImg = new PNG({ width, height });
|
|
1542
1536
|
const diffPixels = pixelmatch(
|
|
1543
1537
|
normBefore.data,
|
|
1544
1538
|
normAfter.data,
|
|
1545
|
-
|
|
1539
|
+
new Uint8Array(width * height * 4),
|
|
1546
1540
|
width,
|
|
1547
1541
|
height,
|
|
1548
1542
|
{ threshold: 0.1 }
|
|
1549
1543
|
);
|
|
1550
1544
|
const diffPercentage = diffPixels / totalPixels * 100;
|
|
1551
1545
|
const changed = diffPercentage > threshold;
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
options.bgColor
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1546
|
+
await generateComposite(
|
|
1547
|
+
capture.beforePath,
|
|
1548
|
+
capture.afterPath,
|
|
1549
|
+
comparePath,
|
|
1550
|
+
options.browser,
|
|
1551
|
+
options.bgColor
|
|
1552
|
+
);
|
|
1562
1553
|
return {
|
|
1563
1554
|
route: capture.route,
|
|
1564
1555
|
prefix: capture.prefix,
|
|
1565
1556
|
beforePath: capture.beforePath,
|
|
1566
1557
|
afterPath: capture.afterPath,
|
|
1567
|
-
diffPath,
|
|
1568
1558
|
comparePath,
|
|
1569
|
-
sliderPath,
|
|
1570
1559
|
diffPixels,
|
|
1571
1560
|
totalPixels,
|
|
1572
1561
|
diffPercentage,
|
|
@@ -1582,13 +1571,12 @@ async function compareScreenshots(captures, outputDir, threshold = 0.1, options)
|
|
|
1582
1571
|
}
|
|
1583
1572
|
|
|
1584
1573
|
// src/stages/report.ts
|
|
1585
|
-
import { writeFileSync
|
|
1574
|
+
import { writeFileSync } from "fs";
|
|
1586
1575
|
import { join as join8 } from "path";
|
|
1587
1576
|
import { execSync as execSync4 } from "child_process";
|
|
1588
1577
|
|
|
1589
1578
|
// src/templates/report.html.ts
|
|
1590
1579
|
import { readFileSync as readFileSync6 } from "fs";
|
|
1591
|
-
import { relative as relative2 } from "path";
|
|
1592
1580
|
function toBase64(filePath) {
|
|
1593
1581
|
return readFileSync6(filePath).toString("base64");
|
|
1594
1582
|
}
|
|
@@ -1599,7 +1587,6 @@ function generateReportHtml(results, outputDir) {
|
|
|
1599
1587
|
const changed = results.filter((r) => r.changed);
|
|
1600
1588
|
const unchanged = results.filter((r) => !r.changed);
|
|
1601
1589
|
const card = (r) => {
|
|
1602
|
-
const sliderHref = outputDir ? relative2(outputDir, r.sliderPath) : r.sliderPath;
|
|
1603
1590
|
return `
|
|
1604
1591
|
<div class="card ${r.changed ? "changed" : "unchanged"}">
|
|
1605
1592
|
<div class="card-header">
|
|
@@ -1617,12 +1604,7 @@ function generateReportHtml(results, outputDir) {
|
|
|
1617
1604
|
<div class="label">After</div>
|
|
1618
1605
|
<img src="${imgSrc(r.afterPath)}" alt="After" />
|
|
1619
1606
|
</div>
|
|
1620
|
-
<div class="img-col">
|
|
1621
|
-
<div class="label">Diff</div>
|
|
1622
|
-
<img src="${imgSrc(r.diffPath)}" alt="Diff" />
|
|
1623
|
-
</div>
|
|
1624
1607
|
</div>
|
|
1625
|
-
${r.changed ? `<a class="slider-link" href="${sliderHref}">Open slider view</a>` : ""}
|
|
1626
1608
|
</div>`;
|
|
1627
1609
|
};
|
|
1628
1610
|
return `<!DOCTYPE html>
|
|
@@ -1644,12 +1626,10 @@ function generateReportHtml(results, outputDir) {
|
|
|
1644
1626
|
.badge { font-size: 12px; padding: 2px 8px; border-radius: 9999px; font-weight: 500; }
|
|
1645
1627
|
.badge-changed { background: #fef3c7; color: #92400e; }
|
|
1646
1628
|
.badge-unchanged { background: #d1fae5; color: #065f46; }
|
|
1647
|
-
.images { display: grid; grid-template-columns: 1fr 1fr
|
|
1629
|
+
.images { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: #e5e7eb; }
|
|
1648
1630
|
.img-col { background: #fff; padding: 8px; }
|
|
1649
1631
|
.img-col img { width: 100%; height: auto; display: block; border-radius: 4px; }
|
|
1650
1632
|
.label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; margin-bottom: 4px; font-weight: 600; }
|
|
1651
|
-
.slider-link { display: block; padding: 8px 16px; text-align: center; font-size: 13px; color: #2563eb; text-decoration: none; border-top: 1px solid #e5e7eb; }
|
|
1652
|
-
.slider-link:hover { background: #eff6ff; }
|
|
1653
1633
|
.section-title { font-size: 18px; font-weight: 600; margin: 24px 0 12px; }
|
|
1654
1634
|
</style>
|
|
1655
1635
|
</head>
|
|
@@ -1664,83 +1644,6 @@ ${unchanged.length > 0 ? `<h2 class="section-title">Unchanged (${unchanged.lengt
|
|
|
1664
1644
|
</html>`;
|
|
1665
1645
|
}
|
|
1666
1646
|
|
|
1667
|
-
// src/templates/slider.html.ts
|
|
1668
|
-
import { readFileSync as readFileSync7 } from "fs";
|
|
1669
|
-
function toBase642(filePath) {
|
|
1670
|
-
return readFileSync7(filePath).toString("base64");
|
|
1671
|
-
}
|
|
1672
|
-
function imgSrc2(filePath) {
|
|
1673
|
-
return `data:image/png;base64,${toBase642(filePath)}`;
|
|
1674
|
-
}
|
|
1675
|
-
function generateSliderHtml(result) {
|
|
1676
|
-
return `<!DOCTYPE html>
|
|
1677
|
-
<html lang="en">
|
|
1678
|
-
<head>
|
|
1679
|
-
<meta charset="utf-8" />
|
|
1680
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1681
|
-
<title>afterbefore Slider - ${result.route}</title>
|
|
1682
|
-
<style>
|
|
1683
|
-
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1684
|
-
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f9fafb; color: #111827; padding: 24px; }
|
|
1685
|
-
h1 { font-size: 20px; margin-bottom: 4px; }
|
|
1686
|
-
.meta { color: #6b7280; font-size: 13px; margin-bottom: 16px; }
|
|
1687
|
-
.container { position: relative; overflow: hidden; border-radius: 8px; border: 1px solid #e5e7eb; user-select: none; cursor: ew-resize; }
|
|
1688
|
-
.container img { display: block; width: 100%; height: auto; }
|
|
1689
|
-
.before-wrap { position: absolute; top: 0; left: 0; height: 100%; overflow: hidden; }
|
|
1690
|
-
.before-wrap img { display: block; height: 100%; width: auto; min-width: 100%; object-fit: cover; }
|
|
1691
|
-
.slider-line { position: absolute; top: 0; width: 3px; height: 100%; background: #2563eb; cursor: ew-resize; z-index: 10; }
|
|
1692
|
-
.slider-handle { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); width: 36px; height: 36px; background: #2563eb; border-radius: 50%; border: 3px solid #fff; box-shadow: 0 2px 8px rgba(0,0,0,0.3); display: flex; align-items: center; justify-content: center; }
|
|
1693
|
-
.slider-handle::before { content: '\\2194'; color: #fff; font-size: 18px; }
|
|
1694
|
-
.labels { display: flex; justify-content: space-between; margin-top: 8px; font-size: 12px; color: #6b7280; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
|
|
1695
|
-
</style>
|
|
1696
|
-
</head>
|
|
1697
|
-
<body>
|
|
1698
|
-
<h1>${result.route}</h1>
|
|
1699
|
-
<p class="meta">${result.diffPercentage.toFixed(2)}% changed (${result.diffPixels.toLocaleString()} pixels)</p>
|
|
1700
|
-
|
|
1701
|
-
<div class="container" id="slider">
|
|
1702
|
-
<img src="${imgSrc2(result.afterPath)}" alt="After" draggable="false" />
|
|
1703
|
-
<div class="before-wrap" id="beforeWrap">
|
|
1704
|
-
<img src="${imgSrc2(result.beforePath)}" alt="Before" draggable="false" />
|
|
1705
|
-
</div>
|
|
1706
|
-
<div class="slider-line" id="sliderLine">
|
|
1707
|
-
<div class="slider-handle"></div>
|
|
1708
|
-
</div>
|
|
1709
|
-
</div>
|
|
1710
|
-
<div class="labels"><span>Before</span><span>After</span></div>
|
|
1711
|
-
|
|
1712
|
-
<script>
|
|
1713
|
-
(function() {
|
|
1714
|
-
const container = document.getElementById('slider');
|
|
1715
|
-
const beforeWrap = document.getElementById('beforeWrap');
|
|
1716
|
-
const sliderLine = document.getElementById('sliderLine');
|
|
1717
|
-
let dragging = false;
|
|
1718
|
-
|
|
1719
|
-
function setPosition(x) {
|
|
1720
|
-
const rect = container.getBoundingClientRect();
|
|
1721
|
-
let pct = ((x - rect.left) / rect.width) * 100;
|
|
1722
|
-
pct = Math.max(0, Math.min(100, pct));
|
|
1723
|
-
beforeWrap.style.width = pct + '%';
|
|
1724
|
-
sliderLine.style.left = pct + '%';
|
|
1725
|
-
}
|
|
1726
|
-
|
|
1727
|
-
// Start at 50%
|
|
1728
|
-
beforeWrap.style.width = '50%';
|
|
1729
|
-
sliderLine.style.left = '50%';
|
|
1730
|
-
|
|
1731
|
-
container.addEventListener('mousedown', function(e) { dragging = true; setPosition(e.clientX); });
|
|
1732
|
-
window.addEventListener('mousemove', function(e) { if (dragging) setPosition(e.clientX); });
|
|
1733
|
-
window.addEventListener('mouseup', function() { dragging = false; });
|
|
1734
|
-
|
|
1735
|
-
container.addEventListener('touchstart', function(e) { dragging = true; setPosition(e.touches[0].clientX); }, { passive: true });
|
|
1736
|
-
window.addEventListener('touchmove', function(e) { if (dragging) setPosition(e.touches[0].clientX); }, { passive: true });
|
|
1737
|
-
window.addEventListener('touchend', function() { dragging = false; });
|
|
1738
|
-
})();
|
|
1739
|
-
</script>
|
|
1740
|
-
</body>
|
|
1741
|
-
</html>`;
|
|
1742
|
-
}
|
|
1743
|
-
|
|
1744
1647
|
// src/templates/summary.md.ts
|
|
1745
1648
|
function generateSummaryMd(results, gitDiff, options) {
|
|
1746
1649
|
const includeFilePaths = options?.includeFilePaths ?? true;
|
|
@@ -1841,18 +1744,12 @@ async function generateReport(results, outputDir, options) {
|
|
|
1841
1744
|
await ensureDir(outputDir);
|
|
1842
1745
|
const summaryMd = generateSummaryMd(results);
|
|
1843
1746
|
const summaryPath = join8(outputDir, "summary.md");
|
|
1844
|
-
|
|
1747
|
+
writeFileSync(summaryPath, summaryMd, "utf-8");
|
|
1845
1748
|
logger.success(`Written summary to ${summaryPath}`);
|
|
1846
1749
|
const reportHtml = generateReportHtml(results, outputDir);
|
|
1847
1750
|
const indexPath = join8(outputDir, "index.html");
|
|
1848
|
-
|
|
1751
|
+
writeFileSync(indexPath, reportHtml, "utf-8");
|
|
1849
1752
|
logger.success(`Written report to ${indexPath}`);
|
|
1850
|
-
for (const result of results) {
|
|
1851
|
-
if (!result.changed) continue;
|
|
1852
|
-
const sliderHtml = generateSliderHtml(result);
|
|
1853
|
-
writeFileSync2(result.sliderPath, sliderHtml, "utf-8");
|
|
1854
|
-
logger.dim(`Written slider for ${result.route}`);
|
|
1855
|
-
}
|
|
1856
1753
|
if (options.post) {
|
|
1857
1754
|
const prNumber = findPrNumber();
|
|
1858
1755
|
if (!prNumber) {
|
|
@@ -1868,10 +1765,6 @@ async function generateReport(results, outputDir, options) {
|
|
|
1868
1765
|
}
|
|
1869
1766
|
|
|
1870
1767
|
// src/pipeline.ts
|
|
1871
|
-
function formatTriggerChain(chain) {
|
|
1872
|
-
if (chain.length <= 1) return chain[0] ?? "";
|
|
1873
|
-
return chain.join(" \u2192 ");
|
|
1874
|
-
}
|
|
1875
1768
|
function generateSessionName(cwd) {
|
|
1876
1769
|
const branch = getCurrentBranch(cwd);
|
|
1877
1770
|
const name = branch.replace(/^(feat|fix|perf|chore|refactor|docs|style|test|ci|build)\//, "");
|
|
@@ -1958,7 +1851,7 @@ async function runPipeline(options) {
|
|
|
1958
1851
|
const outputDir = resolve4(cwd, output, sessionName);
|
|
1959
1852
|
const startTime = Date.now();
|
|
1960
1853
|
try {
|
|
1961
|
-
const version = true ? "0.1.
|
|
1854
|
+
const version = true ? "0.1.4" : "dev";
|
|
1962
1855
|
console.log(`
|
|
1963
1856
|
afterbefore v${version} \xB7 Comparing against ${base}
|
|
1964
1857
|
`);
|
|
@@ -2025,13 +1918,6 @@ afterbefore v${version} \xB7 Comparing against ${base}
|
|
|
2025
1918
|
);
|
|
2026
1919
|
return;
|
|
2027
1920
|
}
|
|
2028
|
-
console.log();
|
|
2029
|
-
for (const r of affectedRoutes) {
|
|
2030
|
-
const chain = formatTriggerChain(r.triggerChain);
|
|
2031
|
-
const label = r.reason === "direct" ? "(direct)" : `\u2190 ${chain}`;
|
|
2032
|
-
console.log(chalk2.dim(` ${r.route} ${label}`));
|
|
2033
|
-
}
|
|
2034
|
-
console.log();
|
|
2035
1921
|
logger.pipeline(4, "Setting up worktree...");
|
|
2036
1922
|
const worktree = await worktreePromise;
|
|
2037
1923
|
logger.pipeline(5, "Starting servers...");
|
|
@@ -2068,7 +1954,26 @@ afterbefore v${version} \xB7 Comparing against ${base}
|
|
|
2068
1954
|
);
|
|
2069
1955
|
logger.pipeline(7, "Comparing screenshots...");
|
|
2070
1956
|
const bgColor = detectBgColor(cwd);
|
|
2071
|
-
const
|
|
1957
|
+
const allResults = await compareScreenshots(captures, outputDir, options.threshold, { browser, bgColor });
|
|
1958
|
+
const results = allResults.filter((r) => {
|
|
1959
|
+
const isSubCapture = r.prefix.includes("~");
|
|
1960
|
+
if (isSubCapture && !r.changed) {
|
|
1961
|
+
try {
|
|
1962
|
+
unlinkSync(r.beforePath);
|
|
1963
|
+
} catch {
|
|
1964
|
+
}
|
|
1965
|
+
try {
|
|
1966
|
+
unlinkSync(r.afterPath);
|
|
1967
|
+
} catch {
|
|
1968
|
+
}
|
|
1969
|
+
try {
|
|
1970
|
+
unlinkSync(r.comparePath);
|
|
1971
|
+
} catch {
|
|
1972
|
+
}
|
|
1973
|
+
return false;
|
|
1974
|
+
}
|
|
1975
|
+
return true;
|
|
1976
|
+
});
|
|
2072
1977
|
logger.pipeline(8, "Generating report...");
|
|
2073
1978
|
await generateReport(results, outputDir, { post });
|
|
2074
1979
|
const summary = generateSummaryMd(results, gitDiff);
|
|
@@ -2092,7 +1997,7 @@ afterbefore v${version} \xB7 Comparing against ${base}
|
|
|
2092
1997
|
var program = new Command();
|
|
2093
1998
|
program.name("afterbefore").description(
|
|
2094
1999
|
"Automatic before/after screenshot capture for PRs. Git diff is the config."
|
|
2095
|
-
).version("0.1.
|
|
2000
|
+
).version("0.1.4").option("--base <ref>", "Base branch or ref to compare against", "main").option("--output <dir>", "Output directory for screenshots", ".afterbefore").option("--post", "Post results as a PR comment via gh CLI", false).option(
|
|
2096
2001
|
"--threshold <percent>",
|
|
2097
2002
|
"Diff threshold percentage (changes below this are ignored)",
|
|
2098
2003
|
"0.1"
|