@juangadm/pre-post 1.0.0 → 1.2.0

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.
Files changed (104) hide show
  1. package/README.md +99 -20
  2. package/dist/baseline.d.ts +208 -0
  3. package/dist/baseline.d.ts.map +1 -0
  4. package/dist/baseline.js +413 -0
  5. package/dist/baseline.js.map +1 -0
  6. package/dist/bin/cli.js +87 -42
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/browser.d.ts +31 -3
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +291 -97
  11. package/dist/browser.js.map +1 -1
  12. package/dist/commands/compare.d.ts.map +1 -1
  13. package/dist/commands/compare.js +21 -3
  14. package/dist/commands/compare.js.map +1 -1
  15. package/dist/commands/detect.d.ts +5 -0
  16. package/dist/commands/detect.d.ts.map +1 -1
  17. package/dist/commands/detect.js +4 -1
  18. package/dist/commands/detect.js.map +1 -1
  19. package/dist/commands/doctor.d.ts +32 -0
  20. package/dist/commands/doctor.d.ts.map +1 -1
  21. package/dist/commands/doctor.js +90 -9
  22. package/dist/commands/doctor.js.map +1 -1
  23. package/dist/commands/login.js +1 -1
  24. package/dist/commands/pr.d.ts +4 -0
  25. package/dist/commands/pr.d.ts.map +1 -1
  26. package/dist/commands/pr.js +157 -51
  27. package/dist/commands/pr.js.map +1 -1
  28. package/dist/commands/prune.js +1 -1
  29. package/dist/commands/prune.js.map +1 -1
  30. package/dist/comparison.d.ts +104 -0
  31. package/dist/comparison.d.ts.map +1 -0
  32. package/dist/comparison.js +241 -0
  33. package/dist/comparison.js.map +1 -0
  34. package/dist/config.d.ts +12 -2
  35. package/dist/config.d.ts.map +1 -1
  36. package/dist/config.js +25 -3
  37. package/dist/config.js.map +1 -1
  38. package/dist/deployments.d.ts +71 -0
  39. package/dist/deployments.d.ts.map +1 -0
  40. package/dist/deployments.js +204 -0
  41. package/dist/deployments.js.map +1 -0
  42. package/dist/diff-pool.d.ts.map +1 -1
  43. package/dist/diff-pool.js +5 -2
  44. package/dist/diff-pool.js.map +1 -1
  45. package/dist/diff-worker.d.ts +3 -0
  46. package/dist/diff-worker.d.ts.map +1 -1
  47. package/dist/diff-worker.js +1 -0
  48. package/dist/diff-worker.js.map +1 -1
  49. package/dist/diff.d.ts +10 -0
  50. package/dist/diff.d.ts.map +1 -1
  51. package/dist/diff.js +117 -7
  52. package/dist/diff.js.map +1 -1
  53. package/dist/doctor.d.ts +42 -1
  54. package/dist/doctor.d.ts.map +1 -1
  55. package/dist/doctor.js +73 -7
  56. package/dist/doctor.js.map +1 -1
  57. package/dist/git.d.ts +41 -3
  58. package/dist/git.d.ts.map +1 -1
  59. package/dist/git.js +103 -7
  60. package/dist/git.js.map +1 -1
  61. package/dist/github.d.ts +92 -2
  62. package/dist/github.d.ts.map +1 -1
  63. package/dist/github.js +191 -20
  64. package/dist/github.js.map +1 -1
  65. package/dist/landing.d.ts +48 -0
  66. package/dist/landing.d.ts.map +1 -0
  67. package/dist/landing.js +93 -0
  68. package/dist/landing.js.map +1 -0
  69. package/dist/pkg.d.ts +9 -0
  70. package/dist/pkg.d.ts.map +1 -1
  71. package/dist/pkg.js +14 -0
  72. package/dist/pkg.js.map +1 -1
  73. package/dist/report.d.ts.map +1 -1
  74. package/dist/report.js +59 -28
  75. package/dist/report.js.map +1 -1
  76. package/dist/routes.d.ts +27 -0
  77. package/dist/routes.d.ts.map +1 -1
  78. package/dist/routes.js +98 -23
  79. package/dist/routes.js.map +1 -1
  80. package/dist/run.d.ts +88 -8
  81. package/dist/run.d.ts.map +1 -1
  82. package/dist/run.js +224 -28
  83. package/dist/run.js.map +1 -1
  84. package/dist/sameness.d.ts +91 -0
  85. package/dist/sameness.d.ts.map +1 -0
  86. package/dist/sameness.js +157 -0
  87. package/dist/sameness.js.map +1 -0
  88. package/dist/sessions.d.ts.map +1 -1
  89. package/dist/sessions.js +7 -1
  90. package/dist/sessions.js.map +1 -1
  91. package/dist/shift.d.ts +67 -0
  92. package/dist/shift.d.ts.map +1 -0
  93. package/dist/shift.js +184 -0
  94. package/dist/shift.js.map +1 -0
  95. package/dist/types.d.ts +83 -5
  96. package/dist/types.d.ts.map +1 -1
  97. package/dist/types.js +17 -0
  98. package/dist/types.js.map +1 -1
  99. package/dist/url.d.ts +12 -0
  100. package/dist/url.d.ts.map +1 -0
  101. package/dist/url.js +30 -0
  102. package/dist/url.js.map +1 -0
  103. package/package.json +1 -1
  104. package/skill/SKILL.md +40 -7
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Did a capture reach the page it asked for, or land somewhere else?
3
+ *
4
+ * Deployment protection, corporate SSO and IP allow-lists answer a request for
5
+ * a page with a sign-in screen, and they do it with HTTP 200 after a redirect —
6
+ * so the 401/403 guard in `browser.ts` never sees them. Screenshotting that and
7
+ * publishing the difference as a visual change is the worst failure this tool
8
+ * has: confident, and wrong. It happened on a real run, twice, before anyone
9
+ * noticed the "Post" screenshot was a login form.
10
+ *
11
+ * The test is deliberately narrow. A redirect on its own proves nothing — a
12
+ * site legitimately sends `example.xyz` to `example.org`, or apex to `www` —
13
+ * so only a redirect that ends somewhere *sign-in shaped* counts. Missing an
14
+ * exotic auth wall is recoverable; refusing to run against a site that redirects
15
+ * for ordinary reasons is not.
16
+ */
17
+ /**
18
+ * The registrable-ish part of a host, so `www.example.com` and `example.com`
19
+ * read as one site. Two labels is a heuristic — it treats `a.co.uk` and
20
+ * `b.co.uk` as the same site — but it is only ever used to make the sign-in
21
+ * test *stricter*, never to reject anything on its own.
22
+ */
23
+ export declare function siteOf(url: string): string;
24
+ /** Did the navigation end up on a different site than the one requested? */
25
+ export declare function leftTheSite(requested: string, final: string): boolean;
26
+ /** Does this URL and title read as a sign-in page rather than a site's own page? */
27
+ export declare function looksLikeSignIn(url: string, title?: string): boolean;
28
+ export interface Landing {
29
+ /** Where the browser actually ended up. */
30
+ finalUrl: string;
31
+ /** True when the capture is of a sign-in wall, not of the requested page. */
32
+ blocked: boolean;
33
+ /** The wall is on another site entirely — usually an identity provider. */
34
+ offSite: boolean;
35
+ }
36
+ /**
37
+ * Judge where a capture landed.
38
+ *
39
+ * Blocked requires *both* that the browser moved and that where it moved to is
40
+ * sign-in shaped: asking for `/login` and getting `/login` is not a wall, and
41
+ * neither is a plain canonical-domain redirect.
42
+ */
43
+ export declare function checkLanding(requested: string, finalUrl: string, title?: string): Landing;
44
+ /**
45
+ * The one sentence a human needs. Names the fix, not the symptom.
46
+ */
47
+ export declare function signInHint(url: string, vercel: boolean): string;
48
+ //# sourceMappingURL=landing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing.d.ts","sourceRoot":"","sources":["../src/landing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQ1C;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIrE;AAMD,oFAAoF;AACpF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAShE;AAED,MAAM,WAAW,OAAO;IACtB,2CAA2C;IAC3C,QAAQ,EAAE,MAAM,CAAC;IACjB,6EAA6E;IAC7E,OAAO,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAOrF;AAcD;;GAEG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM,CAO/D"}
@@ -0,0 +1,93 @@
1
+ /**
2
+ * Did a capture reach the page it asked for, or land somewhere else?
3
+ *
4
+ * Deployment protection, corporate SSO and IP allow-lists answer a request for
5
+ * a page with a sign-in screen, and they do it with HTTP 200 after a redirect —
6
+ * so the 401/403 guard in `browser.ts` never sees them. Screenshotting that and
7
+ * publishing the difference as a visual change is the worst failure this tool
8
+ * has: confident, and wrong. It happened on a real run, twice, before anyone
9
+ * noticed the "Post" screenshot was a login form.
10
+ *
11
+ * The test is deliberately narrow. A redirect on its own proves nothing — a
12
+ * site legitimately sends `example.xyz` to `example.org`, or apex to `www` —
13
+ * so only a redirect that ends somewhere *sign-in shaped* counts. Missing an
14
+ * exotic auth wall is recoverable; refusing to run against a site that redirects
15
+ * for ordinary reasons is not.
16
+ */
17
+ /**
18
+ * The registrable-ish part of a host, so `www.example.com` and `example.com`
19
+ * read as one site. Two labels is a heuristic — it treats `a.co.uk` and
20
+ * `b.co.uk` as the same site — but it is only ever used to make the sign-in
21
+ * test *stricter*, never to reject anything on its own.
22
+ */
23
+ export function siteOf(url) {
24
+ try {
25
+ const { hostname } = new URL(url);
26
+ const labels = hostname.split('.');
27
+ return labels.length <= 2 ? hostname : labels.slice(-2).join('.');
28
+ }
29
+ catch {
30
+ return '';
31
+ }
32
+ }
33
+ /** Did the navigation end up on a different site than the one requested? */
34
+ export function leftTheSite(requested, final) {
35
+ const from = siteOf(requested);
36
+ const to = siteOf(final);
37
+ return Boolean(from && to && from !== to);
38
+ }
39
+ /** Words that appear in the URL or title of a sign-in page and little else. */
40
+ const SIGN_IN_URL = /(login|log-in|signin|sign-in|sso|oauth|authorize|authenticate)/i;
41
+ const SIGN_IN_TITLE = /(sign[ -]?in|log[ -]?in|authentication required|authorization required|access denied)/i;
42
+ /** Does this URL and title read as a sign-in page rather than a site's own page? */
43
+ export function looksLikeSignIn(url, title = '') {
44
+ let path = '';
45
+ try {
46
+ const u = new URL(url);
47
+ path = u.pathname + u.search;
48
+ }
49
+ catch {
50
+ return false;
51
+ }
52
+ return SIGN_IN_URL.test(path) || SIGN_IN_TITLE.test(title);
53
+ }
54
+ /**
55
+ * Judge where a capture landed.
56
+ *
57
+ * Blocked requires *both* that the browser moved and that where it moved to is
58
+ * sign-in shaped: asking for `/login` and getting `/login` is not a wall, and
59
+ * neither is a plain canonical-domain redirect.
60
+ */
61
+ export function checkLanding(requested, finalUrl, title = '') {
62
+ const moved = normalize(finalUrl) !== normalize(requested);
63
+ return {
64
+ finalUrl,
65
+ blocked: moved && looksLikeSignIn(finalUrl, title),
66
+ offSite: leftTheSite(requested, finalUrl),
67
+ };
68
+ }
69
+ /** Ignore the trailing-slash and fragment differences a redirect adds for free. */
70
+ function normalize(url) {
71
+ try {
72
+ const u = new URL(url);
73
+ u.hash = '';
74
+ if (u.pathname.endsWith('/') && u.pathname !== '/')
75
+ u.pathname = u.pathname.slice(0, -1);
76
+ return u.toString();
77
+ }
78
+ catch {
79
+ return url;
80
+ }
81
+ }
82
+ /**
83
+ * The one sentence a human needs. Names the fix, not the symptom.
84
+ */
85
+ export function signInHint(url, vercel) {
86
+ if (vercel) {
87
+ return `${url} answered with a sign-in page, so the site was never captured. ` +
88
+ 'Set VERCEL_AUTOMATION_BYPASS_SECRET (Vercel → Project → Settings → Deployment Protection → Protection Bypass for Automation) and re-run.';
89
+ }
90
+ return `${url} answered with a sign-in page, so the site was never captured. ` +
91
+ `Run: npx pre-post login ${url} (opens a browser once, saves the session locally)`;
92
+ }
93
+ //# sourceMappingURL=landing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"landing.js","sourceRoot":"","sources":["../src/landing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;GAKG;AACH,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACnC,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,KAAa;IAC1D,MAAM,IAAI,GAAG,MAAM,CAAC,SAAS,CAAC,CAAC;IAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACzB,OAAO,OAAO,CAAC,IAAI,IAAI,EAAE,IAAI,IAAI,KAAK,EAAE,CAAC,CAAC;AAC5C,CAAC;AAED,+EAA+E;AAC/E,MAAM,WAAW,GAAG,iEAAiE,CAAC;AACtF,MAAM,aAAa,GAAG,wFAAwF,CAAC;AAE/G,oFAAoF;AACpF,MAAM,UAAU,eAAe,CAAC,GAAW,EAAE,KAAK,GAAG,EAAE;IACrD,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,IAAI,GAAG,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,MAAM,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC7D,CAAC;AAWD;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,QAAgB,EAAE,KAAK,GAAG,EAAE;IAC1E,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,KAAK,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3D,OAAO;QACL,QAAQ;QACR,OAAO,EAAE,KAAK,IAAI,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC;QAClD,OAAO,EAAE,WAAW,CAAC,SAAS,EAAE,QAAQ,CAAC;KAC1C,CAAC;AACJ,CAAC;AAED,mFAAmF;AACnF,SAAS,SAAS,CAAC,GAAW;IAC5B,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QACvB,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC;QACZ,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,KAAK,GAAG;YAAE,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QACzF,OAAO,CAAC,CAAC,QAAQ,EAAE,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW,EAAE,MAAe;IACrD,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,GAAG,GAAG,iEAAiE;YAC5E,0IAA0I,CAAC;IAC/I,CAAC;IACD,OAAO,GAAG,GAAG,iEAAiE;QAC5E,2BAA2B,GAAG,sDAAsD,CAAC;AACzF,CAAC"}
package/dist/pkg.d.ts CHANGED
@@ -5,4 +5,13 @@
5
5
  */
6
6
  export declare function readPackage(root: string): Record<string, any> | null;
7
7
  export declare function hasDependency(root: string, name: string): boolean;
8
+ /**
9
+ * The script that starts a dev server for this package, preferring `dev`.
10
+ *
11
+ * This is the test for "is this directory an app we could actually serve?",
12
+ * used both when picking the app root to detect routes for and when serving a
13
+ * base commit. The two must agree: a directory that cannot be served is not
14
+ * the app a PR changed, however many of its files the diff touched.
15
+ */
16
+ export declare function devScript(dir: string): string | null;
8
17
  //# sourceMappingURL=pkg.d.ts.map
package/dist/pkg.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pkg.d.ts","sourceRoot":"","sources":["../src/pkg.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAkBpE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjE"}
1
+ {"version":3,"file":"pkg.d.ts","sourceRoot":"","sources":["../src/pkg.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAOH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,CAkBpE;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAGjE;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAIpD"}
package/dist/pkg.js CHANGED
@@ -32,4 +32,18 @@ export function hasDependency(root, name) {
32
32
  const pkg = readPackage(root);
33
33
  return Boolean(pkg?.dependencies?.[name] || pkg?.devDependencies?.[name]);
34
34
  }
35
+ /**
36
+ * The script that starts a dev server for this package, preferring `dev`.
37
+ *
38
+ * This is the test for "is this directory an app we could actually serve?",
39
+ * used both when picking the app root to detect routes for and when serving a
40
+ * base commit. The two must agree: a directory that cannot be served is not
41
+ * the app a PR changed, however many of its files the diff touched.
42
+ */
43
+ export function devScript(dir) {
44
+ const scripts = readPackage(dir)?.scripts;
45
+ if (!scripts)
46
+ return null;
47
+ return ['dev', 'start:dev', 'serve', 'start'].find(name => typeof scripts[name] === 'string') ?? null;
48
+ }
35
49
  //# sourceMappingURL=pkg.js.map
package/dist/pkg.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pkg.js","sourceRoot":"","sources":["../src/pkg.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgE,CAAC;AAEtF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC;IACnD,IAAI,GAAG,GAA+B,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,GAAG,IAAI,CAAC;IACb,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5E,CAAC"}
1
+ {"version":3,"file":"pkg.js","sourceRoot":"","sources":["../src/pkg.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,MAAM,KAAK,GAAG,IAAI,GAAG,EAAgE,CAAC;AAEtF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;IAC7C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC;IACtC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC5B,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC,GAAG,CAAC;IACnD,IAAI,GAAG,GAA+B,IAAI,CAAC;IAC3C,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD,CAAC;IAAC,MAAM,CAAC;QACP,GAAG,GAAG,IAAI,CAAC;IACb,CAAC;IACD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,CAAC;IAClC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAY,EAAE,IAAY;IACtD,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC9B,OAAO,OAAO,CAAC,GAAG,EAAE,YAAY,EAAE,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,eAAe,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW;IACnC,MAAM,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,EAAE,OAA6C,CAAC;IAChF,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,CAAC,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,OAAO,CAAC,IAAI,CAAC,KAAK,QAAQ,CAAC,IAAI,IAAI,CAAC;AACxG,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAuB,MAAM,YAAY,CAAC;AAG9D,eAAO,MAAM,aAAa,qCAAqC,CAAC;AAuBhE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CAqDtF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAyBxD"}
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,WAAW,EAAuB,MAAM,YAAY,CAAC;AAI9D,eAAO,MAAM,aAAa,qCAAqC,CAAC;AAgBhE,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CA+EtF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAsCxD"}
package/dist/report.js CHANGED
@@ -2,17 +2,9 @@
2
2
  * Output formatting: the PR comment (for humans) and the terminal summary
3
3
  * (for the developer or the agent that invoked the CLI).
4
4
  */
5
- import { hostOf } from './run.js';
5
+ import { describeShift } from './run.js';
6
+ import { hostOf, isLocalUrl } from './url.js';
6
7
  export const STICKY_MARKER = '<!-- pre-post:visual-changes -->';
7
- function pct(ratio) {
8
- if (ratio === undefined)
9
- return '';
10
- if (ratio === 0)
11
- return '0%';
12
- if (ratio < 0.0001)
13
- return '<0.01%';
14
- return `${(ratio * 100).toFixed(ratio < 0.01 ? 2 : 1)}%`;
15
- }
16
8
  function code(s) {
17
9
  return `\`${s}\``;
18
10
  }
@@ -31,31 +23,61 @@ function groupByRoute(outcomes) {
31
23
  export function buildComment(result, options = {}) {
32
24
  const lines = [STICKY_MARKER, '## Visual changes', ''];
33
25
  const routes = groupByRoute(result.outcomes);
34
- const viewportCount = new Set(result.outcomes.map(o => o.viewport)).size;
35
26
  const changed = result.outcomes.filter(o => o.status === 'changed');
36
27
  const unchanged = result.outcomes.filter(o => o.status === 'unchanged');
37
28
  const errors = result.outcomes.filter(o => o.status === 'error');
38
- const summary = changed.length === 0
39
- ? `No visual changes detected across ${routes.size} route${routes.size === 1 ? '' : 's'} and ${viewportCount} viewport${viewportCount === 1 ? '' : 's'}.`
40
- : `${changed.length} of ${result.outcomes.length} captures changed across ${routes.size} route${routes.size === 1 ? '' : 's'}.`;
41
- lines.push(`**Pre** = ${hostOf(result.beforeBase)} · **Post** = this branch · ${summary}`, '');
29
+ // A page that only one side has is still something a reviewer must see, so it
30
+ // counts as a change for "did anything happen" — but it is shown as one
31
+ // screenshot, never as a Pre/Post pair, because there is no pair.
32
+ const oneSided = result.outcomes.filter(o => (o.status === 'added' || o.status === 'removed') && (o.urls || o.files));
33
+ // Both sides can now be a deployment, so name the actual hosts rather than
34
+ // assuming Post is the reader's own checkout.
35
+ const postLabel = isLocalUrl(result.afterBase) ? 'this branch (local)' : hostOf(result.afterBase);
36
+ const sha = options.headSha ? ` @ ${code(options.headSha.slice(0, 7))}` : '';
37
+ lines.push(`**Pre** = ${hostOf(result.beforeBase)} · **Post** = ${postLabel}${sha} · <a href="https://github.com/juangadm/pre-post">pre-post</a>`, '');
38
+ if (changed.length === 0 && oneSided.length === 0)
39
+ lines.push('No visual changes.', '');
42
40
  for (const [route, outcomes] of routes) {
43
41
  const changedHere = outcomes.filter(o => o.status === 'changed' && (o.urls || o.files));
44
42
  if (changedHere.length === 0)
45
43
  continue;
46
44
  for (const o of changedHere) {
47
45
  const u = o.urls ?? o.files;
48
- const note = [o.sizeChanged ? 'page height changed' : '', o.note || ''].filter(Boolean).map(n => ` · ${n}`).join('');
49
- lines.push(`### ${code(route)} — ${viewportLabel(o.viewport)} · ${pct(o.changedRatio)} of pixels changed${note}`, '');
46
+ lines.push(`### ${code(route)} — ${viewportLabel(o.viewport)}`, '');
47
+ // A move repaints everything below it, so the percentage says nothing a
48
+ // reviewer can use. Say how far it moved instead, and whether the branch
49
+ // changed anything else.
50
+ if (o.shift) {
51
+ lines.push(o.shift.otherChange
52
+ ? `Content ${describeShift(o.shift.px)}. The pair below is aligned on that move, so it shows what changed besides it.`
53
+ : `Content ${describeShift(o.shift.px)}. Nothing else changed.`, '');
54
+ }
55
+ const cropped = Boolean(u.cropBefore && u.cropAfter);
50
56
  const pre = u.cropBefore ?? u.before;
51
57
  const post = u.cropAfter ?? u.after;
52
58
  lines.push('| Pre | Post |', '|:---:|:---:|', `| ![Pre](${pre}) | ![Post](${post}) |`, '');
53
- lines.push('<details>', `<summary>Full page and diff</summary>`, '');
54
- const diffCol = u.diff ? ` ![Diff](${u.diff}) |` : '';
55
- lines.push(`| Pre (full) | Post (full) |${u.diff ? ' Diff |' : ''}`, `|:---:|:---:|${u.diff ? ':---:|' : ''}`, `| ![Pre full](${u.before}) | ![Post full](${u.after}) |${diffCol}`, '');
56
- lines.push('</details>', '');
59
+ // Without a crop the pair above is already the full page; repeating it
60
+ // under a fold gives a reviewer two more identical images to scroll past.
61
+ if (cropped) {
62
+ lines.push('<details>', `<summary>Full page</summary>`, '');
63
+ lines.push('| Pre (full) | Post (full) |', '|:---:|:---:|', `| ![Pre full](${u.before}) | ![Post full](${u.after}) |`, '');
64
+ lines.push('</details>', '');
65
+ }
57
66
  }
58
67
  }
68
+ for (const o of oneSided) {
69
+ const u = o.urls ?? o.files;
70
+ const image = o.status === 'added' ? u.after : u.before;
71
+ if (!image)
72
+ continue;
73
+ const what = o.status === 'added' ? 'New page' : 'Page removed';
74
+ const side = o.status === 'added' ? 'Post' : 'Pre';
75
+ lines.push(`### ${code(o.route)} — ${viewportLabel(o.viewport)} · ${what}`, '');
76
+ lines.push(o.status === 'added'
77
+ ? `This route has no baseline — ${hostOf(result.beforeBase)} returns 404 for it — so there is no "before" to show and no percentage to quote. ${side} only:`
78
+ : `This route is gone on this branch, so there is no "after". ${side} only:`, '');
79
+ lines.push(`![${side}](${image})`, '');
80
+ }
59
81
  if (unchanged.length) {
60
82
  const byRoute = groupByRoute(unchanged);
61
83
  const parts = Array.from(byRoute.entries()).map(([route, os]) => `${code(route)} (${os.map(o => o.viewport).join(', ')})`);
@@ -70,9 +92,6 @@ export function buildComment(result, options = {}) {
70
92
  if (result.skippedDynamic.length) {
71
93
  lines.push(`**Needs a sample URL:** ${result.skippedDynamic.map(code).join(', ')} — add them under ${code('"samples"')} in ${code('.pre-post.json')}.`, '');
72
94
  }
73
- const stamp = (options.now ?? new Date()).toISOString().replace('T', ' ').slice(0, 16) + ' UTC';
74
- const sha = options.headSha ? ` · ${code(options.headSha.slice(0, 7))}` : '';
75
- lines.push('---', `<sub>Captured by <a href="https://github.com/juangadm/pre-post">pre-post</a>${options.version ? ` v${options.version}` : ''}${sha} · ${stamp} · ${(result.durationMs / 1000).toFixed(1)}s</sub>`);
76
95
  return lines.join('\n');
77
96
  }
78
97
  /**
@@ -90,8 +109,12 @@ export function buildSummary(result) {
90
109
  const rows = result.outcomes.map(o => [
91
110
  o.route,
92
111
  o.viewport,
93
- o.status === 'error' ? 'error' : o.status === 'changed' ? `${pct(o.changedRatio)} changed` : 'no change',
94
- o.status === 'error' ? (o.error ?? '') : [o.sizeChanged ? 'height changed' : '', o.note || ''].filter(Boolean).join(', '),
112
+ o.status === 'error' ? 'error'
113
+ : o.status === 'changed' ? 'changed'
114
+ : o.status === 'added' ? 'new page'
115
+ : o.status === 'removed' ? 'removed'
116
+ : 'no change',
117
+ o.status === 'error' ? (o.error ?? '') : (o.note || ''),
95
118
  ]);
96
119
  const widths = [0, 1, 2].map(i => Math.max(...rows.map(r => r[i].length), 0));
97
120
  for (const r of rows) {
@@ -100,8 +123,16 @@ export function buildSummary(result) {
100
123
  if (result.skippedDynamic.length) {
101
124
  lines.push(` needs sample URL: ${result.skippedDynamic.join(', ')} (add to .pre-post.json "samples")`);
102
125
  }
103
- if (result.commentUrl)
104
- lines.push(`Comment: ${result.commentUrl}`);
126
+ // Named for what actually happened. The images normally go in the PR
127
+ // description and only fall back to a comment, so "Comment:" sent a reader
128
+ // looking for a comment that a run with zero comments had never created.
129
+ //
130
+ // Only an explicit 'description' changes the label: `commentKind` is optional
131
+ // and `buildSummary` is exported, so a caller that predates it — or a result
132
+ // persisted before it existed — still means what it always meant.
133
+ if (result.commentUrl) {
134
+ lines.push(`${result.commentKind === 'description' ? 'PR description' : 'Comment'}: ${result.commentUrl}`);
135
+ }
105
136
  return lines.join('\n');
106
137
  }
107
138
  //# sourceMappingURL=report.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
package/dist/routes.d.ts CHANGED
@@ -10,7 +10,30 @@
10
10
  * 5. Dedupe, rank by confidence, cap, and substitute samples for dynamic routes.
11
11
  */
12
12
  import { DetectedRoute, Framework, PrePostConfig, RouteDetectionOptions } from './types.js';
13
+ import { BaseResolution } from './git.js';
13
14
  export type { Framework };
15
+ /**
16
+ * Does this directory hold an app whose routes are worth detecting?
17
+ *
18
+ * A dev script is the test baseline.ts applies before serving a base commit; a
19
+ * recognised framework counts too, since an app can be captured from a
20
+ * deployment without ever being served here. Both are stricter than "owns some
21
+ * changed files", which is the point: counting alone once picked this repo's
22
+ * tests/fixtures over its marketing site by a single file and then reported
23
+ * routes for a directory nothing can serve.
24
+ */
25
+ export declare function isServableApp(root: string): boolean;
26
+ /**
27
+ * Is this directory a package at all, rather than a folder that merely looks
28
+ * like one?
29
+ *
30
+ * findAppRoots accepts any directory holding `app/` or `pages/`, which is how
31
+ * this repo's own tests/fixtures — a pile of static HTML under pages/ with no
32
+ * package.json — became a candidate and, on a diff dominated by fixture files,
33
+ * the winner. Declaring a package (or matching a real framework) is the line
34
+ * between an app and a directory shaped like one.
35
+ */
36
+ export declare function isAppLike(root: string): boolean;
14
37
  export declare function frameworkForRoot(root: string): Framework;
15
38
  /** Directories (depth ≤ 3) that look like an app: package.json, or app/ pages/ route folders. */
16
39
  export declare function findAppRoots(baseDir: string): string[];
@@ -32,6 +55,8 @@ export interface RepoRouteDetection {
32
55
  routes: DetectedRoute[];
33
56
  /** Dynamic routes with no sample URL configured */
34
57
  skippedDynamic: string[];
58
+ /** What the working tree was compared with; null when files were supplied. */
59
+ base: BaseResolution | null;
35
60
  /** Milliseconds spent */
36
61
  durationMs: number;
37
62
  }
@@ -43,6 +68,8 @@ export interface RepoDetectionOptions {
43
68
  diffTarget?: string;
44
69
  /** Override changed files (tests) */
45
70
  changedFiles?: string[];
71
+ /** Where warnings go. Left unset, they are silent — --json must stay parseable. */
72
+ log?: (msg: string) => void;
46
73
  }
47
74
  export declare function detectRoutesForRepo(options?: RepoDetectionOptions): RepoRouteDetection;
48
75
  //# sourceMappingURL=routes.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAS5F,YAAY,EAAE,SAAS,EAAE,CAAC;AAgH1B,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAExD;AAQD,iGAAiG;AACjG,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAG3D;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAS1E;AAaD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAEzF;AAMD,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAgB,GAAG,MAAM,EAAE,CAMlF;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,aAAa,EAAE,CAIzG;AAMD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,yBAAyB;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;CACzB;AASD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,kBAAkB,CA8F1F"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAK5F,OAAO,EAAE,cAAc,EAAyE,MAAM,UAAU,CAAC;AAIjH,YAAY,EAAE,SAAS,EAAE,CAAC;AAgH1B;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEnD;AAED;;;;;;;;;GASG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAExD;AASD,iGAAiG;AACjG,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAsBtD;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAG3D;AAQD,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAS1E;AAaD,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAErD;AAED,uFAAuF;AACvF,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAM,GAAG,MAAM,CAEzF;AAMD,wBAAgB,eAAe,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,GAAG,SAAgB,GAAG,MAAM,EAAE,CAUlF;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,qBAA0B,GAAG,aAAa,EAAE,CAIzG;AAMD,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,SAAS,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,mDAAmD;IACnD,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,8EAA8E;IAC9E,IAAI,EAAE,cAAc,GAAG,IAAI,CAAC;IAC5B,yBAAyB;IACzB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qCAAqC;IACrC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mFAAmF;IACnF,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC7B;AASD,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,kBAAkB,CA6I1F"}
package/dist/routes.js CHANGED
@@ -15,9 +15,9 @@ import { detectAppRouterRoutes, detectPagesRouterRoutes } from './routes/nextjs.
15
15
  import { detectGenericRoutes } from './routes/generic.js';
16
16
  import { buildImportGraph, findAffectedEntries, readAliases, walkSourceFiles, toPosix, SKIP_DIRS } from './routes/imports.js';
17
17
  import { viteRouteEntries, isViteApp } from './routes/vite.js';
18
- import { changedFiles as gitChangedFiles, repoRoot as gitRepoRoot } from './git.js';
19
- import { hasDependency } from './pkg.js';
20
- import { CONFIG_DEFAULTS } from './config.js';
18
+ import { changedFiles as gitChangedFiles, repoRoot as gitRepoRoot, requireBase } from './git.js';
19
+ import { devScript, hasDependency, readPackage } from './pkg.js';
20
+ import { CONFIG_DEFAULTS, CONFIG_FILENAME } from './config.js';
21
21
  const NEXT_APP_PAGE = /^app\/(.+\/)?page\.(tsx|ts|jsx|js|mdx|md)$/;
22
22
  const NEXT_PAGES_HIGH = /^pages\/(?!api\/|_)(.+)\.(tsx|ts|jsx|js|mdx|md)$/;
23
23
  const NEXT_PAGE_FILE = /^(page|layout)\.(tsx|ts|jsx|js|mdx)$/;
@@ -115,6 +115,32 @@ const FRAMEWORKS = [
115
115
  function adapterFor(name) {
116
116
  return FRAMEWORKS.find(f => f.name === name) ?? FRAMEWORKS[FRAMEWORKS.length - 1];
117
117
  }
118
+ /**
119
+ * Does this directory hold an app whose routes are worth detecting?
120
+ *
121
+ * A dev script is the test baseline.ts applies before serving a base commit; a
122
+ * recognised framework counts too, since an app can be captured from a
123
+ * deployment without ever being served here. Both are stricter than "owns some
124
+ * changed files", which is the point: counting alone once picked this repo's
125
+ * tests/fixtures over its marketing site by a single file and then reported
126
+ * routes for a directory nothing can serve.
127
+ */
128
+ export function isServableApp(root) {
129
+ return devScript(root) !== null || frameworkForRoot(root) !== 'generic';
130
+ }
131
+ /**
132
+ * Is this directory a package at all, rather than a folder that merely looks
133
+ * like one?
134
+ *
135
+ * findAppRoots accepts any directory holding `app/` or `pages/`, which is how
136
+ * this repo's own tests/fixtures — a pile of static HTML under pages/ with no
137
+ * package.json — became a candidate and, on a diff dominated by fixture files,
138
+ * the winner. Declaring a package (or matching a real framework) is the line
139
+ * between an app and a directory shaped like one.
140
+ */
141
+ export function isAppLike(root) {
142
+ return readPackage(root) !== null || frameworkForRoot(root) !== 'generic';
143
+ }
118
144
  export function frameworkForRoot(root) {
119
145
  return FRAMEWORKS.find(f => f.matches(root)).name;
120
146
  }
@@ -171,14 +197,13 @@ export function deduplicateRoutes(routes) {
171
197
  }
172
198
  return Array.from(byPath.values());
173
199
  }
174
- function rankAndCap(routes, maxRoutes, warn = true) {
200
+ function rankAndCap(routes, maxRoutes, warn) {
175
201
  let out = deduplicateRoutes(routes);
176
202
  out.sort((a, b) => CONFIDENCE_ORDER[a.confidence] - CONFIDENCE_ORDER[b.confidence] || a.path.localeCompare(b.path));
177
203
  if (out.length > maxRoutes) {
178
204
  const original = out.length;
179
205
  out = out.slice(0, maxRoutes);
180
- if (warn)
181
- console.warn(`Detected ${original} routes, capping at ${maxRoutes}. Use --max-routes to increase.`);
206
+ warn?.(`Detected ${original} routes, capping at ${maxRoutes}. Use --max-routes to increase.`);
182
207
  }
183
208
  return out;
184
209
  }
@@ -193,8 +218,12 @@ export function resolveSample(route, samples = {}) {
193
218
  // File-list API (framework rules only, no import graph)
194
219
  // ============================================================
195
220
  export function getChangedFiles(diffTarget, cwd = process.cwd()) {
221
+ // requireBase throws NeedsHumanError when the base cannot be established;
222
+ // that has to travel, because the alternative is an empty list that reads
223
+ // as "nothing changed".
224
+ const target = diffTarget ?? requireBase(cwd).sha;
196
225
  try {
197
- return gitChangedFiles(cwd, diffTarget);
226
+ return gitChangedFiles(cwd, target);
198
227
  }
199
228
  catch {
200
229
  return [];
@@ -204,7 +233,7 @@ export function detectRoutes(changedFiles, options = {}) {
204
233
  if (changedFiles.length === 0)
205
234
  return [];
206
235
  const adapter = adapterFor(options.framework || detectFramework());
207
- return rankAndCap(adapter.directRoutes(changedFiles.map(adapter.normalize)), options.maxRoutes ?? CONFIG_DEFAULTS.maxRoutes);
236
+ return rankAndCap(adapter.directRoutes(changedFiles.map(adapter.normalize)), options.maxRoutes ?? CONFIG_DEFAULTS.maxRoutes, options.log);
208
237
  }
209
238
  /** For a layout-only route (no page of its own), the closest static page beneath it. */
210
239
  function nearestPageRoute(route, knownRoutes) {
@@ -219,21 +248,66 @@ export function detectRoutesForRepo(options = {}) {
219
248
  const config = options.config || {};
220
249
  const maxRoutes = options.maxRoutes ?? config.maxRoutes ?? CONFIG_DEFAULTS.maxRoutes;
221
250
  const ignore = (config.ignore || []).map(p => p.replace(/^\.?\//, ''));
222
- const allChanged = (options.changedFiles ?? gitChangedFiles(root, options.diffTarget))
251
+ // Establish the base before reading the diff. Getting this wrong is silent:
252
+ // diffing HEAD reports only uncommitted work, so a branch whose changes are
253
+ // committed — every branch in a shallow CI or sandbox checkout — comes back
254
+ // empty and the run reports no visual changes.
255
+ const base = options.changedFiles
256
+ ? null
257
+ : requireBase(root, { explicit: options.diffTarget, log: options.log });
258
+ const allChanged = (options.changedFiles ?? gitChangedFiles(root, base.sha))
259
+ // Our own config file is written mid-run; it never affects which routes changed.
260
+ .filter(f => f !== CONFIG_FILENAME)
223
261
  .filter(f => !ignore.some(prefix => f === prefix || f.startsWith(prefix.replace(/\/?$/, '/'))));
224
- // Choose the app root that owns the most changed files (deepest wins ties).
225
- let appRoot = root;
226
- let best = -1;
227
- for (const candidate of findAppRoots(root)) {
228
- const rel = toPosix(path.relative(root, candidate));
229
- const prefix = rel ? rel + '/' : '';
230
- const count = allChanged.filter(f => f.startsWith(prefix)).length;
231
- if (count > best || (count === best && candidate.length > appRoot.length)) {
232
- best = count;
233
- appRoot = candidate;
234
- }
235
- }
236
- const adapter = adapterFor(options.framework || frameworkForRoot(appRoot));
262
+ // Choose the app root the PR is about. The repo root contains every changed
263
+ // file, so it would always match at least as many as any app nested inside
264
+ // it; score the nested app roots only, and fall back to the repo root when
265
+ // none of them owns a changed file.
266
+ //
267
+ // Servability outranks file count. Counting alone once picked this repo's
268
+ // own tests/fixtures over its marketing site by a single file, and then
269
+ // reported routes for a directory nothing can serve. A directory that cannot
270
+ // be served is not the app a branch changed, however much of the diff lands
271
+ // in it; among candidates that can be served, the diff decides, deepest
272
+ // first on a tie.
273
+ const owners = findAppRoots(root)
274
+ .map(dir => {
275
+ const rel = toPosix(path.relative(root, dir));
276
+ return { dir, count: rel ? allChanged.filter(f => f.startsWith(rel + '/')).length : 0 };
277
+ })
278
+ // Probing the filesystem is the expensive part, so only candidates that own
279
+ // some of the diff pay for it.
280
+ .filter(c => c.count > 0)
281
+ .map(c => {
282
+ const framework = frameworkForRoot(c.dir);
283
+ return {
284
+ ...c,
285
+ framework,
286
+ appLike: readPackage(c.dir) !== null || framework !== 'generic',
287
+ servable: devScript(c.dir) !== null || framework !== 'generic',
288
+ };
289
+ })
290
+ // A folder shaped like an app is not one; see isAppLike.
291
+ .filter(c => c.appLike)
292
+ .sort((a, b) => b.count - a.count || b.dir.length - a.dir.length);
293
+ // Among roots that own a comparable share of the diff, prefer one that can be
294
+ // served; outside that band the diff wins outright.
295
+ //
296
+ // Servability breaks near-ties, which is the case it was added for: this
297
+ // repo's tests/fixtures beat site/ by a single file and produced routes for a
298
+ // directory nothing can serve. It must not override a clear majority, though
299
+ // — a deployed or --before/--after comparison never serves anything locally,
300
+ // so a static app owning most of the diff still has the routes that changed,
301
+ // and handing detection to an incidental package with a start script would
302
+ // discard them.
303
+ const CONTENDER_SHARE = 0.5;
304
+ const leader = owners[0]?.count ?? 0;
305
+ const contenders = owners.filter(c => c.count >= leader * CONTENDER_SHARE);
306
+ const winner = contenders.find(c => c.servable) ?? owners[0];
307
+ const appRoot = winner?.dir ?? root;
308
+ // The winner's framework was already resolved while scoring; frameworkForRoot
309
+ // walks directories, so don't ask twice.
310
+ const adapter = adapterFor(options.framework || winner?.framework || frameworkForRoot(appRoot));
237
311
  const appPrefix = toPosix(path.relative(root, appRoot));
238
312
  const appRel = allChanged
239
313
  .filter(f => !appPrefix || f.startsWith(appPrefix + '/'))
@@ -296,8 +370,9 @@ export function detectRoutesForRepo(options = {}) {
296
370
  framework: adapter.name,
297
371
  appRoot,
298
372
  changedFiles: allChanged,
299
- routes: rankAndCap(resolved, maxRoutes, false),
373
+ routes: rankAndCap(resolved, maxRoutes, options.log),
300
374
  skippedDynamic: Array.from(skippedDynamic),
375
+ base,
301
376
  durationMs: Date.now() - started,
302
377
  };
303
378
  }