afterbefore 0.1.2 → 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/index.d.ts CHANGED
@@ -63,9 +63,7 @@ interface CompareResult {
63
63
  prefix: string;
64
64
  beforePath: string;
65
65
  afterPath: string;
66
- diffPath: string;
67
66
  comparePath: string;
68
- sliderPath: string;
69
67
  diffPixels: number;
70
68
  totalPixels: number;
71
69
  diffPercentage: number;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/pipeline.ts
2
2
  import { resolve as resolve4 } from "path";
3
+ import { unlinkSync } from "fs";
3
4
  import { exec } from "child_process";
4
- import chalk2 from "chalk";
5
5
 
6
6
  // src/logger.ts
7
7
  import chalk from "chalk";
@@ -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
- this.spinner.stopAndPersist({
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
  }
@@ -1467,7 +1465,6 @@ async function captureRoutes(tasks, beforeUrl, afterUrl, outputDir, options) {
1467
1465
 
1468
1466
  // src/stages/compare.ts
1469
1467
  import { readFileSync as readFileSync5 } from "fs";
1470
- import { writeFileSync } from "fs";
1471
1468
  import { join as join7, dirname as dirname2 } from "path";
1472
1469
  import { PNG } from "pngjs";
1473
1470
  import pixelmatch from "pixelmatch";
@@ -1515,9 +1512,7 @@ async function generateComposite(beforePath, afterPath, outputPath, browser, bgC
1515
1512
  }
1516
1513
  async function compareOne(capture, outputDir, threshold, options) {
1517
1514
  const dir = dirname2(capture.beforePath);
1518
- const diffPath = join7(dir, `${capture.prefix}-diff.png`);
1519
1515
  const comparePath = join7(dir, `${capture.prefix}-compare.png`);
1520
- const sliderPath = join7(dir, `${capture.prefix}-slider.html`);
1521
1516
  const beforeBuffer = readFileSync5(capture.beforePath);
1522
1517
  const afterBuffer = readFileSync5(capture.afterPath);
1523
1518
  const beforeImg = PNG.sync.read(beforeBuffer);
@@ -1525,35 +1520,29 @@ async function compareOne(capture, outputDir, threshold, options) {
1525
1520
  const [normBefore, normAfter] = normalizeDimensions(beforeImg, afterImg);
1526
1521
  const { width, height } = normBefore;
1527
1522
  const totalPixels = width * height;
1528
- const diffImg = new PNG({ width, height });
1529
1523
  const diffPixels = pixelmatch(
1530
1524
  normBefore.data,
1531
1525
  normAfter.data,
1532
- diffImg.data,
1526
+ new Uint8Array(width * height * 4),
1533
1527
  width,
1534
1528
  height,
1535
1529
  { threshold: 0.1 }
1536
1530
  );
1537
1531
  const diffPercentage = diffPixels / totalPixels * 100;
1538
1532
  const changed = diffPercentage > threshold;
1539
- writeFileSync(diffPath, PNG.sync.write(diffImg));
1540
- if (changed) {
1541
- await generateComposite(
1542
- capture.beforePath,
1543
- capture.afterPath,
1544
- comparePath,
1545
- options.browser,
1546
- options.bgColor
1547
- );
1548
- }
1533
+ await generateComposite(
1534
+ capture.beforePath,
1535
+ capture.afterPath,
1536
+ comparePath,
1537
+ options.browser,
1538
+ options.bgColor
1539
+ );
1549
1540
  return {
1550
1541
  route: capture.route,
1551
1542
  prefix: capture.prefix,
1552
1543
  beforePath: capture.beforePath,
1553
1544
  afterPath: capture.afterPath,
1554
- diffPath,
1555
1545
  comparePath,
1556
- sliderPath,
1557
1546
  diffPixels,
1558
1547
  totalPixels,
1559
1548
  diffPercentage,
@@ -1569,13 +1558,12 @@ async function compareScreenshots(captures, outputDir, threshold = 0.1, options)
1569
1558
  }
1570
1559
 
1571
1560
  // src/stages/report.ts
1572
- import { writeFileSync as writeFileSync2 } from "fs";
1561
+ import { writeFileSync } from "fs";
1573
1562
  import { join as join8 } from "path";
1574
1563
  import { execSync as execSync4 } from "child_process";
1575
1564
 
1576
1565
  // src/templates/report.html.ts
1577
1566
  import { readFileSync as readFileSync6 } from "fs";
1578
- import { relative as relative2 } from "path";
1579
1567
  function toBase64(filePath) {
1580
1568
  return readFileSync6(filePath).toString("base64");
1581
1569
  }
@@ -1586,7 +1574,6 @@ function generateReportHtml(results, outputDir) {
1586
1574
  const changed = results.filter((r) => r.changed);
1587
1575
  const unchanged = results.filter((r) => !r.changed);
1588
1576
  const card = (r) => {
1589
- const sliderHref = outputDir ? relative2(outputDir, r.sliderPath) : r.sliderPath;
1590
1577
  return `
1591
1578
  <div class="card ${r.changed ? "changed" : "unchanged"}">
1592
1579
  <div class="card-header">
@@ -1604,12 +1591,7 @@ function generateReportHtml(results, outputDir) {
1604
1591
  <div class="label">After</div>
1605
1592
  <img src="${imgSrc(r.afterPath)}" alt="After" />
1606
1593
  </div>
1607
- <div class="img-col">
1608
- <div class="label">Diff</div>
1609
- <img src="${imgSrc(r.diffPath)}" alt="Diff" />
1610
- </div>
1611
1594
  </div>
1612
- ${r.changed ? `<a class="slider-link" href="${sliderHref}">Open slider view</a>` : ""}
1613
1595
  </div>`;
1614
1596
  };
1615
1597
  return `<!DOCTYPE html>
@@ -1631,12 +1613,10 @@ function generateReportHtml(results, outputDir) {
1631
1613
  .badge { font-size: 12px; padding: 2px 8px; border-radius: 9999px; font-weight: 500; }
1632
1614
  .badge-changed { background: #fef3c7; color: #92400e; }
1633
1615
  .badge-unchanged { background: #d1fae5; color: #065f46; }
1634
- .images { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 1px; background: #e5e7eb; }
1616
+ .images { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; background: #e5e7eb; }
1635
1617
  .img-col { background: #fff; padding: 8px; }
1636
1618
  .img-col img { width: 100%; height: auto; display: block; border-radius: 4px; }
1637
1619
  .label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em; color: #6b7280; margin-bottom: 4px; font-weight: 600; }
1638
- .slider-link { display: block; padding: 8px 16px; text-align: center; font-size: 13px; color: #2563eb; text-decoration: none; border-top: 1px solid #e5e7eb; }
1639
- .slider-link:hover { background: #eff6ff; }
1640
1620
  .section-title { font-size: 18px; font-weight: 600; margin: 24px 0 12px; }
1641
1621
  </style>
1642
1622
  </head>
@@ -1651,83 +1631,6 @@ ${unchanged.length > 0 ? `<h2 class="section-title">Unchanged (${unchanged.lengt
1651
1631
  </html>`;
1652
1632
  }
1653
1633
 
1654
- // src/templates/slider.html.ts
1655
- import { readFileSync as readFileSync7 } from "fs";
1656
- function toBase642(filePath) {
1657
- return readFileSync7(filePath).toString("base64");
1658
- }
1659
- function imgSrc2(filePath) {
1660
- return `data:image/png;base64,${toBase642(filePath)}`;
1661
- }
1662
- function generateSliderHtml(result) {
1663
- return `<!DOCTYPE html>
1664
- <html lang="en">
1665
- <head>
1666
- <meta charset="utf-8" />
1667
- <meta name="viewport" content="width=device-width, initial-scale=1" />
1668
- <title>afterbefore Slider - ${result.route}</title>
1669
- <style>
1670
- * { box-sizing: border-box; margin: 0; padding: 0; }
1671
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f9fafb; color: #111827; padding: 24px; }
1672
- h1 { font-size: 20px; margin-bottom: 4px; }
1673
- .meta { color: #6b7280; font-size: 13px; margin-bottom: 16px; }
1674
- .container { position: relative; overflow: hidden; border-radius: 8px; border: 1px solid #e5e7eb; user-select: none; cursor: ew-resize; }
1675
- .container img { display: block; width: 100%; height: auto; }
1676
- .before-wrap { position: absolute; top: 0; left: 0; height: 100%; overflow: hidden; }
1677
- .before-wrap img { display: block; height: 100%; width: auto; min-width: 100%; object-fit: cover; }
1678
- .slider-line { position: absolute; top: 0; width: 3px; height: 100%; background: #2563eb; cursor: ew-resize; z-index: 10; }
1679
- .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; }
1680
- .slider-handle::before { content: '\\2194'; color: #fff; font-size: 18px; }
1681
- .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; }
1682
- </style>
1683
- </head>
1684
- <body>
1685
- <h1>${result.route}</h1>
1686
- <p class="meta">${result.diffPercentage.toFixed(2)}% changed (${result.diffPixels.toLocaleString()} pixels)</p>
1687
-
1688
- <div class="container" id="slider">
1689
- <img src="${imgSrc2(result.afterPath)}" alt="After" draggable="false" />
1690
- <div class="before-wrap" id="beforeWrap">
1691
- <img src="${imgSrc2(result.beforePath)}" alt="Before" draggable="false" />
1692
- </div>
1693
- <div class="slider-line" id="sliderLine">
1694
- <div class="slider-handle"></div>
1695
- </div>
1696
- </div>
1697
- <div class="labels"><span>Before</span><span>After</span></div>
1698
-
1699
- <script>
1700
- (function() {
1701
- const container = document.getElementById('slider');
1702
- const beforeWrap = document.getElementById('beforeWrap');
1703
- const sliderLine = document.getElementById('sliderLine');
1704
- let dragging = false;
1705
-
1706
- function setPosition(x) {
1707
- const rect = container.getBoundingClientRect();
1708
- let pct = ((x - rect.left) / rect.width) * 100;
1709
- pct = Math.max(0, Math.min(100, pct));
1710
- beforeWrap.style.width = pct + '%';
1711
- sliderLine.style.left = pct + '%';
1712
- }
1713
-
1714
- // Start at 50%
1715
- beforeWrap.style.width = '50%';
1716
- sliderLine.style.left = '50%';
1717
-
1718
- container.addEventListener('mousedown', function(e) { dragging = true; setPosition(e.clientX); });
1719
- window.addEventListener('mousemove', function(e) { if (dragging) setPosition(e.clientX); });
1720
- window.addEventListener('mouseup', function() { dragging = false; });
1721
-
1722
- container.addEventListener('touchstart', function(e) { dragging = true; setPosition(e.touches[0].clientX); }, { passive: true });
1723
- window.addEventListener('touchmove', function(e) { if (dragging) setPosition(e.touches[0].clientX); }, { passive: true });
1724
- window.addEventListener('touchend', function() { dragging = false; });
1725
- })();
1726
- </script>
1727
- </body>
1728
- </html>`;
1729
- }
1730
-
1731
1634
  // src/templates/summary.md.ts
1732
1635
  function generateSummaryMd(results, gitDiff, options) {
1733
1636
  const includeFilePaths = options?.includeFilePaths ?? true;
@@ -1828,18 +1731,12 @@ async function generateReport(results, outputDir, options) {
1828
1731
  await ensureDir(outputDir);
1829
1732
  const summaryMd = generateSummaryMd(results);
1830
1733
  const summaryPath = join8(outputDir, "summary.md");
1831
- writeFileSync2(summaryPath, summaryMd, "utf-8");
1734
+ writeFileSync(summaryPath, summaryMd, "utf-8");
1832
1735
  logger.success(`Written summary to ${summaryPath}`);
1833
1736
  const reportHtml = generateReportHtml(results, outputDir);
1834
1737
  const indexPath = join8(outputDir, "index.html");
1835
- writeFileSync2(indexPath, reportHtml, "utf-8");
1738
+ writeFileSync(indexPath, reportHtml, "utf-8");
1836
1739
  logger.success(`Written report to ${indexPath}`);
1837
- for (const result of results) {
1838
- if (!result.changed) continue;
1839
- const sliderHtml = generateSliderHtml(result);
1840
- writeFileSync2(result.sliderPath, sliderHtml, "utf-8");
1841
- logger.dim(`Written slider for ${result.route}`);
1842
- }
1843
1740
  if (options.post) {
1844
1741
  const prNumber = findPrNumber();
1845
1742
  if (!prNumber) {
@@ -1855,10 +1752,6 @@ async function generateReport(results, outputDir, options) {
1855
1752
  }
1856
1753
 
1857
1754
  // src/pipeline.ts
1858
- function formatTriggerChain(chain) {
1859
- if (chain.length <= 1) return chain[0] ?? "";
1860
- return chain.join(" \u2192 ");
1861
- }
1862
1755
  function generateSessionName(cwd) {
1863
1756
  const branch = getCurrentBranch(cwd);
1864
1757
  const name = branch.replace(/^(feat|fix|perf|chore|refactor|docs|style|test|ci|build)\//, "");
@@ -1945,7 +1838,7 @@ async function runPipeline(options) {
1945
1838
  const outputDir = resolve4(cwd, output, sessionName);
1946
1839
  const startTime = Date.now();
1947
1840
  try {
1948
- const version = true ? "0.1.2" : "dev";
1841
+ const version = true ? "0.1.4" : "dev";
1949
1842
  console.log(`
1950
1843
  afterbefore v${version} \xB7 Comparing against ${base}
1951
1844
  `);
@@ -2012,13 +1905,6 @@ afterbefore v${version} \xB7 Comparing against ${base}
2012
1905
  );
2013
1906
  return;
2014
1907
  }
2015
- console.log();
2016
- for (const r of affectedRoutes) {
2017
- const chain = formatTriggerChain(r.triggerChain);
2018
- const label = r.reason === "direct" ? "(direct)" : `\u2190 ${chain}`;
2019
- console.log(chalk2.dim(` ${r.route} ${label}`));
2020
- }
2021
- console.log();
2022
1908
  logger.pipeline(4, "Setting up worktree...");
2023
1909
  const worktree = await worktreePromise;
2024
1910
  logger.pipeline(5, "Starting servers...");
@@ -2055,7 +1941,26 @@ afterbefore v${version} \xB7 Comparing against ${base}
2055
1941
  );
2056
1942
  logger.pipeline(7, "Comparing screenshots...");
2057
1943
  const bgColor = detectBgColor(cwd);
2058
- const results = await compareScreenshots(captures, outputDir, options.threshold, { browser, bgColor });
1944
+ const allResults = await compareScreenshots(captures, outputDir, options.threshold, { browser, bgColor });
1945
+ const results = allResults.filter((r) => {
1946
+ const isSubCapture = r.prefix.includes("~");
1947
+ if (isSubCapture && !r.changed) {
1948
+ try {
1949
+ unlinkSync(r.beforePath);
1950
+ } catch {
1951
+ }
1952
+ try {
1953
+ unlinkSync(r.afterPath);
1954
+ } catch {
1955
+ }
1956
+ try {
1957
+ unlinkSync(r.comparePath);
1958
+ } catch {
1959
+ }
1960
+ return false;
1961
+ }
1962
+ return true;
1963
+ });
2059
1964
  logger.pipeline(8, "Generating report...");
2060
1965
  await generateReport(results, outputDir, { post });
2061
1966
  const summary = generateSummaryMd(results, gitDiff);