@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,241 @@
1
+ /**
2
+ * Choosing what to compare against what.
3
+ *
4
+ * Pre and Post used to be resolved independently, which could pair a production
5
+ * deployment against a local dev server. Different fonts, different data,
6
+ * different analytics — a diff full of changes the PR did not make. Nobody
7
+ * reviews the raw images (the skill forbids it), so a noisy diff is believed.
8
+ *
9
+ * So a *pair* is resolved, not two URLs. Each strategy either produces both
10
+ * sides from the same kind of environment or yields to the next one. Mixing
11
+ * happens only when the caller asked for it by name, and is reported when it
12
+ * does.
13
+ */
14
+ import { deploymentUrlForSha, findPreviewForCommit, latestProductionDeployment } from './deployments.js';
15
+ import { serveBaseCommit, serveWorkingTree } from './baseline.js';
16
+ import { NeedsHumanError } from './doctor.js';
17
+ import { isLocalUrl, normalizeUrl } from './url.js';
18
+ import { mergeBase } from './git.js';
19
+ const noop = async () => undefined;
20
+ const side = (url, detail, probe) => ({ url: normalizeUrl(url), detail, probe });
21
+ /**
22
+ * The one place a Comparison is built, so `mixed` is derived rather than
23
+ * recomputed — a construction site that forgot it would report a production
24
+ * page against a dev server as a clean comparison.
25
+ */
26
+ const pair = (strategy, before, after, stop = noop) => ({ strategy, before, after, mixed: isLocalUrl(before.url) !== isLocalUrl(after.url), stop });
27
+ /** Reachable and not sitting behind a login wall. */
28
+ function isUsable(result) {
29
+ return result.status !== null && result.status !== 401 && result.status !== 403;
30
+ }
31
+ /**
32
+ * The preview deployment for the commit being reviewed, if there is a fresh one.
33
+ *
34
+ * Keyed on the commit rather than on the PR: a host builds a branch as soon as
35
+ * it is pushed, so the preview usually exists before anyone opens the PR. The
36
+ * PR number is passed when there is one only because the bot-comment fallback
37
+ * needs somewhere to look.
38
+ */
39
+ async function previewForHead(ctx) {
40
+ const { gh, ownerRepo, pr } = ctx;
41
+ const sha = pr?.head.sha ?? ctx.headSha;
42
+ if (!gh || !sha)
43
+ return null;
44
+ const found = await findPreviewForCommit(gh, ownerRepo, { sha, prNumber: pr?.number, appPrefix: ctx.appPrefix });
45
+ return found ? side(found.url, `${found.environment} deployment for ${sha.slice(0, 7)}`) : null;
46
+ }
47
+ /**
48
+ * The deployed baseline, most honest answer first.
49
+ *
50
+ * Pinning Pre to the base commit is the right question and it is tried first.
51
+ * The second step exists because production is a branch, not a commit: a
52
+ * repository that does not deploy every push to its default branch has no
53
+ * deployment at the fork point, and requiring one left the deployed comparison
54
+ * with no Pre — a preview would be found, no baseline would be, and the run
55
+ * fell back to needing a dev server nobody in the target audience has.
56
+ *
57
+ * Widening stops at deployments the host actually recorded. Every step names
58
+ * the commit it answered with, so Pre is never mistaken for the base.
59
+ */
60
+ async function deployedBaseline(ctx) {
61
+ if (ctx.before)
62
+ return side(ctx.before, 'passed with --before');
63
+ if (ctx.config.before)
64
+ return side(ctx.config.before, 'from .pre-post.json');
65
+ if (!ctx.gh)
66
+ return null;
67
+ // What this branch forked from, if that exact commit was deployed. Usually it
68
+ // was — a host that deploys every push to the default branch has one — but a
69
+ // repository that deploys on a tag, promotes by hand, or whose base commit is
70
+ // older than its retained deployments will find nothing here.
71
+ const baseSha = ctx.baseSha ?? ctx.pr?.base.sha;
72
+ if (baseSha) {
73
+ const pinned = await deploymentUrlForSha(ctx.gh, ctx.ownerRepo, baseSha, { production: true });
74
+ if (pinned)
75
+ return side(pinned.url, `${pinned.environment} deployment for ${baseSha.slice(0, 7)}`);
76
+ }
77
+ // Widening is for a fork point this tool worked out on its own. A base the
78
+ // caller named is a different thing: production is some other commit, so
79
+ // answering with it would compare against a commit they did not ask for while
80
+ // route detection still describes the ref they did -- the same Pre/route-list
81
+ // disagreement that made `--base` worth fixing in the first place. Yield
82
+ // instead, and let the local strategy build the named commit from source.
83
+ if (ctx.baseExplicit) {
84
+ ctx.log(`No deployment for the base you asked for (${baseSha?.slice(0, 7)}); building it locally rather than comparing against a different commit.`);
85
+ return null;
86
+ }
87
+ // Otherwise what is on production now: the same site, possibly a few commits
88
+ // ahead, so name the commit it was built from instead of implying the base.
89
+ const latest = await latestProductionDeployment(ctx.gh, ctx.ownerRepo);
90
+ if (latest) {
91
+ return side(latest.url, `${latest.environment} deployment${latest.sha ? ` for ${latest.sha.slice(0, 7)}` : ''}`);
92
+ }
93
+ // Anything past here would be a guess at the site's address, and a baseline
94
+ // that is quietly the wrong site reads as 100% changed on every route — a
95
+ // result that looks exactly like a real one. Better to say so and stop.
96
+ return null;
97
+ }
98
+ /** Both sides named by the caller: their call, mixed or not. */
99
+ function explicitPair(ctx) {
100
+ if (!ctx.before || !ctx.after)
101
+ return null;
102
+ return pair('explicit', side(ctx.before, 'passed with --before'), side(ctx.after, 'passed with --after'));
103
+ }
104
+ /**
105
+ * Both sides deployed — same build pipeline, same CDN, same data.
106
+ *
107
+ * The preview is looked up first and alone: it is the cheap question, and when
108
+ * there is no preview there is nothing for a baseline to pair with, so no
109
+ * baseline requests are spent on a run that will compare locally anyway.
110
+ */
111
+ async function deployedPair(ctx) {
112
+ const preview = await previewForHead(ctx);
113
+ if (!preview)
114
+ return { comparison: null, preview: null };
115
+ const [previewProbe, baseline] = await Promise.all([ctx.probe(preview.url), deployedBaseline(ctx)]);
116
+ if (!isUsable(previewProbe)) {
117
+ ctx.log(`Preview deployment ${preview.url} is not reachable (it may be behind Deployment Protection); comparing locally instead.`);
118
+ return { comparison: null, preview: null };
119
+ }
120
+ const baselineProbe = baseline && !isLocalUrl(baseline.url) ? await ctx.probe(baseline.url) : null;
121
+ if (!baseline || !baselineProbe || !isUsable(baselineProbe)) {
122
+ ctx.log('Preview deployment found but no reachable deployed baseline; comparing locally instead.');
123
+ return { comparison: null, preview, rejectedBaseline: baseline ? { side: baseline, probe: baselineProbe } : undefined };
124
+ }
125
+ return { comparison: pair('deployed', { ...baseline, probe: baselineProbe }, { ...preview, probe: previewProbe }), preview };
126
+ }
127
+ /**
128
+ * Both sides local — same browser, same machine, no network.
129
+ *
130
+ * This is the strategy that always has something to offer, so it owns the dev
131
+ * servers it starts: every exit below either hands their teardown to the
132
+ * Comparison or runs it before throwing.
133
+ */
134
+ async function localPair(ctx, deployed) {
135
+ const running = await ctx.devServer;
136
+ let after = ctx.after
137
+ ? side(ctx.after, 'passed with --after')
138
+ : running ? side(running, 'local dev server') : null;
139
+ // Nothing deployed and nothing running is not a reason to stop: this is the
140
+ // command you hit on the way out of a PR, so it starts the dev server itself
141
+ // rather than handing back a chore.
142
+ let postServer = null;
143
+ if (!after && ctx.allowLocalBaseline !== false) {
144
+ postServer = await (ctx.servePost ?? serveWorkingTree)({ repoRoot: ctx.repoRoot, appPrefix: ctx.appPrefix, log: ctx.log });
145
+ if (postServer)
146
+ after = side(postServer.url, 'working tree, served locally');
147
+ }
148
+ if (!after)
149
+ throw deployed.preview ? new NoDeployedBaselineError(deployed.preview.url, deployed.rejectedBaseline) : new NoPostError();
150
+ const stopPost = postServer ? postServer.stop : noop;
151
+ // A baseline the caller named wins even if it is remote; they asked for it.
152
+ if (ctx.before)
153
+ return pair('explicit', side(ctx.before, 'passed with --before'), after, stopPost);
154
+ // git knows what this branch forked from, so a baseline does not depend on a
155
+ // PR existing yet — this often runs before one is opened.
156
+ const baseSha = ctx.baseSha ?? ctx.pr?.base.sha ?? mergeBase(ctx.repoRoot) ?? undefined;
157
+ // Serving the base can now fail loudly rather than yielding — an install
158
+ // that died is not a "try the next option", it is the answer. The Post
159
+ // server is already up by then, so it has to come down before the throw
160
+ // escapes; nothing above this has a handle on it yet.
161
+ const baseline = ctx.allowLocalBaseline === false || !baseSha
162
+ ? null
163
+ : await (ctx.serveBaseline ?? serveBaseCommit)({ repoRoot: ctx.repoRoot, sha: baseSha, appPrefix: ctx.appPrefix, log: ctx.log })
164
+ .catch(async (err) => { await stopPost(); throw err; });
165
+ if (baseline) {
166
+ const before = side(baseline.url, `base commit ${baseSha.slice(0, 7)}, served locally`);
167
+ return pair('local', before, after, async () => { await baseline.stop(); await stopPost(); });
168
+ }
169
+ // Nothing local could be built; a configured URL beats no comparison at all.
170
+ if (ctx.config.before)
171
+ return pair('explicit', side(ctx.config.before, 'from .pre-post.json'), after, stopPost);
172
+ await stopPost();
173
+ throw new NoBaselineError();
174
+ }
175
+ /**
176
+ * Resolve both sides together, cheapest strategy first.
177
+ *
178
+ * `explicit` when the caller named both; then `deployed`, which needs a preview
179
+ * for this commit and a deployed baseline; then `local`, which needs nothing but
180
+ * the repository itself.
181
+ */
182
+ export async function resolveComparison(ctx) {
183
+ const explicit = explicitPair(ctx);
184
+ if (explicit)
185
+ return explicit;
186
+ const deployed = await deployedPair(ctx);
187
+ return deployed.comparison ?? localPair(ctx, deployed);
188
+ }
189
+ export class NoPostError extends NeedsHumanError {
190
+ constructor() {
191
+ super('No preview deployment for this commit and no dev server on the usual ports (3000, 5173, ...). Start the dev server (e.g. npm run dev), then re-run — or pass --after http://localhost:PORT.');
192
+ this.name = 'NoPostError';
193
+ }
194
+ }
195
+ /**
196
+ * A preview exists but nothing could stand beside it.
197
+ *
198
+ * Worth its own error because the generic one says "no preview deployment for
199
+ * this commit", which in this case is the opposite of what happened — and the
200
+ * person hitting it is the one least able to act on advice about dev servers.
201
+ *
202
+ * The instruction depends on *why* there was no baseline. Telling someone to
203
+ * pass `--before` when the baseline was found and turned them away at a login
204
+ * wall sends them back to the same URL for the same failure; the fix there is
205
+ * access, not an argument.
206
+ */
207
+ export class NoDeployedBaselineError extends NeedsHumanError {
208
+ constructor(previewUrl, rejected) {
209
+ super(`Found the preview deployment for this commit (${previewUrl}) but nothing to compare it against: ${reasonFor(rejected)} No dev server is running either.`);
210
+ this.name = 'NoDeployedBaselineError';
211
+ }
212
+ }
213
+ const PIN_HINT = 'Re-run with --before https://your-production-url (it is saved to .pre-post.json for next time).';
214
+ function reasonFor(rejected) {
215
+ if (!rejected)
216
+ return `no production deployment is recorded for this repository. ${PIN_HINT}`;
217
+ const { side: baseline, probe } = rejected;
218
+ if (probe && (probe.status === 401 || probe.status === 403)) {
219
+ return `the baseline (${baseline.url} — ${baseline.detail}) returned ${probe.status}, so it is behind access control.${probe.vercel ? ' Set VERCEL_AUTOMATION_BYPASS_SECRET, or' : ''} run \`pre-post login ${baseline.url}\`, then re-run.`;
220
+ }
221
+ return `the baseline (${baseline.url} — ${baseline.detail}) could not be reached. Check it is up, or ${PIN_HINT[0].toLowerCase()}${PIN_HINT.slice(1)}`;
222
+ }
223
+ export class NoBaselineError extends NeedsHumanError {
224
+ constructor() {
225
+ super('No baseline to compare against: no deployed baseline, and the base commit could not be served locally. Re-run with --before https://your-production-url (it is saved to .pre-post.json for next time).');
226
+ this.name = 'NoBaselineError';
227
+ }
228
+ }
229
+ /** The plan, for the log — an unattended tool has to say what it chose. */
230
+ export function describeComparison(c) {
231
+ const lines = [
232
+ `Comparing (${c.strategy}):`,
233
+ ` Pre ${c.before.url} — ${c.before.detail}`,
234
+ ` Post ${c.after.url} — ${c.after.detail}`,
235
+ ];
236
+ if (c.mixed) {
237
+ lines.push(' Note: the two sides run in different environments, so some differences may not come from this branch.');
238
+ }
239
+ return lines;
240
+ }
241
+ //# sourceMappingURL=comparison.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comparison.js","sourceRoot":"","sources":["../src/comparison.ts"],"names":[],"mappings":"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"}
package/dist/config.d.ts CHANGED
@@ -7,7 +7,17 @@ export declare const CONFIG_FILENAME = ".pre-post.json";
7
7
  export declare const CONFIG_DEFAULTS: {
8
8
  viewports: string[];
9
9
  threshold: number;
10
- minChangedPixels: number;
10
+ /**
11
+ * Smallest painted change that counts, in CSS pixels².
12
+ *
13
+ * Roughly a third of a 16px icon. Against the fixture ladder every real
14
+ * change measures 242 CSS px² or more while every no-op measures exactly 0,
15
+ * so this sits in the middle of a wide empty band — low enough that missing
16
+ * a real change (the worse error by far: the tool would report "no visual
17
+ * changes" on a PR that changed something) stays implausible, high enough to
18
+ * absorb antialiasing differences between two machines.
19
+ */
20
+ minChangedArea: number;
11
21
  maxRoutes: number;
12
22
  fullPage: boolean;
13
23
  maxHeight: number;
@@ -18,7 +28,7 @@ export declare const CONFIG_DEFAULTS: {
18
28
  export interface Settings {
19
29
  viewports: string[];
20
30
  threshold: number;
21
- minChangedPixels: number;
31
+ minChangedArea: number;
22
32
  maxRoutes: number;
23
33
  fullPage: boolean;
24
34
  maxHeight: number;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,eAAO,MAAM,eAAe;eACU,MAAM,EAAE;;;;;;;;;CAS7C,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,MAAM,GAAE,aAAkB,EAAE,SAAS,GAAE,OAAO,CAAC,QAAQ,CAAM,GAAG,QAAQ,CAcvG;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAU1D;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAExE;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAI3F"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,eAAO,MAAM,eAAe,mBAAmB,CAAC;AAEhD,eAAO,MAAM,eAAe;eACA,MAAM,EAAE;;IAElC;;;;;;;;;OASG;;;;;;;;CAQJ,CAAC;AAEF,MAAM,WAAW,QAAQ;IACvB,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,MAAM,GAAE,aAAkB,EAAE,SAAS,GAAE,OAAO,CAAC,QAAQ,CAAM,GAAG,QAAQ,CA0BvG;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,CAU1D;AAED,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,GAAG,IAAI,CAExE;AAED,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,aAAa,CAI3F"}
package/dist/config.js CHANGED
@@ -6,9 +6,19 @@ import fs from 'fs';
6
6
  import path from 'path';
7
7
  export const CONFIG_FILENAME = '.pre-post.json';
8
8
  export const CONFIG_DEFAULTS = {
9
- viewports: ['desktop', 'mobile'],
9
+ viewports: ['desktop'],
10
10
  threshold: 0.001,
11
- minChangedPixels: 40,
11
+ /**
12
+ * Smallest painted change that counts, in CSS pixels².
13
+ *
14
+ * Roughly a third of a 16px icon. Against the fixture ladder every real
15
+ * change measures 242 CSS px² or more while every no-op measures exactly 0,
16
+ * so this sits in the middle of a wide empty band — low enough that missing
17
+ * a real change (the worse error by far: the tool would report "no visual
18
+ * changes" on a PR that changed something) stays implausible, high enough to
19
+ * absorb antialiasing differences between two machines.
20
+ */
21
+ minChangedArea: 100,
12
22
  maxRoutes: 6,
13
23
  fullPage: true,
14
24
  maxHeight: 2400,
@@ -19,10 +29,22 @@ export const CONFIG_DEFAULTS = {
19
29
  /** Defaults < .pre-post.json < explicit overrides (undefined overrides are ignored). */
20
30
  export function resolveSettings(config = {}, overrides = {}) {
21
31
  const pick = (key) => (overrides[key] ?? config[key] ?? CONFIG_DEFAULTS[key]);
32
+ // minChangedPixels was device pixels, and library callers passed it as an
33
+ // override as well as setting it in the config file, so both levels have to
34
+ // be honoured rather than silently falling back to the new default. The new
35
+ // key wins over the legacy one at the same level; overrides win over config.
36
+ if (overrides.minChangedArea === undefined) {
37
+ const legacy = overrides.minChangedPixels
38
+ ?? (config.minChangedArea === undefined ? config.minChangedPixels : undefined);
39
+ if (legacy !== undefined) {
40
+ const scale = pick('scale');
41
+ config = { ...config, minChangedArea: legacy / (scale * scale) };
42
+ }
43
+ }
22
44
  return {
23
45
  viewports: pick('viewports'),
24
46
  threshold: pick('threshold'),
25
- minChangedPixels: pick('minChangedPixels'),
47
+ minChangedArea: pick('minChangedArea'),
26
48
  maxRoutes: pick('maxRoutes'),
27
49
  fullPage: pick('fullPage'),
28
50
  maxHeight: pick('maxHeight'),
@@ -1 +1 @@
1
- {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,CAAC,MAAM,eAAe,GAAG,gBAAgB,CAAC;AAEhD,MAAM,CAAC,MAAM,eAAe,GAAG;IAC7B,SAAS,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAa;IAC5C,SAAS,EAAE,KAAK;IAChB,gBAAgB,EAAE,EAAE;IACpB,SAAS,EAAE,CAAC;IACZ,QAAQ,EAAE,IAAI;IACd,SAAS,EAAE,IAAI;IACf,KAAK,EAAE,CAAC;IACR,YAAY,EAAE,iBAAiB;IAC/B,SAAS,EAAE,EAAE;CACd,CAAC;AAcF,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,SAAwB,EAAE,EAAE,YAA+B,EAAE;IAC3F,MAAM,IAAI,GAAG,CAA2B,GAAM,EAAe,EAAE,CAC7D,CAAC,SAAS,CAAC,GAAG,CAAC,IAAK,MAA4B,CAAC,GAAG,CAAC,IAAI,eAAe,CAAC,GAAG,CAAC,CAAgB,CAAC;IAChG,OAAO;QACL,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;QAC5B,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;QAC5B,gBAAgB,EAAE,IAAI,CAAC,kBAAkB,CAAC;QAC1C,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;QAC5B,QAAQ,EAAE,IAAI,CAAC,UAAU,CAAC;QAC1B,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;QAC5B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC;QACpB,YAAY,EAAE,IAAI,CAAC,cAAc,CAAC;QAClC,SAAS,EAAE,IAAI,CAAC,WAAW,CAAC;KAC7B,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB;IACzC,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ;YAAE,OAAO,MAAuB,CAAC;QACzE,OAAO,EAAE,CAAC;IACZ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,GAAG,eAAe,uBAAwB,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACrF,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAgB,EAAE,MAAqB;IAChE,EAAE,CAAC,aAAa,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;AACjF,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,YAAY,CAAC,QAAgB,EAAE,KAA6B;IAC1E,MAAM,IAAI,GAAG,EAAE,GAAG,UAAU,CAAC,QAAQ,CAAC,EAAE,GAAG,KAAK,EAAE,CAAC;IACnD,UAAU,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IAC3B,OAAO,IAAI,CAAC;AACd,CAAC"}
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Deployment URLs from the GitHub Deployments API.
3
+ *
4
+ * Vercel, Cloudflare Pages, Netlify and Render all report their deploys to
5
+ * GitHub against a commit, so one lookup covers every provider: no per-host
6
+ * API tokens, and nothing to configure per repo.
7
+ */
8
+ import { GitHub } from './github.js';
9
+ export interface DeploymentUrl {
10
+ url: string;
11
+ environment: string;
12
+ /** The commit the deployment was built from, when the API reported one. */
13
+ sha?: string;
14
+ }
15
+ /**
16
+ * The newest successful deployment URL for a commit.
17
+ *
18
+ * `production: true` accepts only production environments (the baseline for
19
+ * "Pre"); `false` accepts only the others, which is where every provider puts
20
+ * per-PR previews. Neither falls back to the other kind — a preview standing in
21
+ * for production would silently compare against the wrong thing.
22
+ *
23
+ * Returns null whenever the answer is not unambiguous, so callers fall through
24
+ * to their next option. Never throws: a repo with no deployments, or a token
25
+ * without `deployments: read`, is a normal outcome, not a failure.
26
+ */
27
+ export declare function deploymentUrlForSha(gh: GitHub, ownerRepo: string, sha: string, opts: {
28
+ production: boolean;
29
+ }): Promise<DeploymentUrl | null>;
30
+ /**
31
+ * What is deployed to production right now, whatever commit it was built from.
32
+ *
33
+ * `deploymentUrlForSha` pins Pre to the commit the branch forked from, which is
34
+ * the most honest baseline there is — but it only exists if production happened
35
+ * to be deployed at exactly that commit. Production is a moving branch, so on
36
+ * most repositories it was not, and pinning alone leaves the deployed
37
+ * comparison with no Pre at all.
38
+ *
39
+ * This asks the same question without the pin. It reports the commit it found,
40
+ * so the caller can name what Pre actually is rather than implying the base.
41
+ */
42
+ export declare function latestProductionDeployment(gh: GitHub, ownerRepo: string): Promise<DeploymentUrl | null>;
43
+ /**
44
+ * The preview for a commit, from whichever mechanism the provider uses.
45
+ *
46
+ * The freshness rule lives here rather than in the caller: a bot comment is
47
+ * edited in place and carries no SHA, so mid-deploy it still advertises the
48
+ * previous commit's preview. Screenshotting that and labelling it as this
49
+ * branch is the worst failure this tool has, because it looks exactly like a
50
+ * correct result. Every caller gets that guarantee by construction.
51
+ */
52
+ export declare function findPreviewForCommit(gh: GitHub, ownerRepo: string, opts: {
53
+ sha: string;
54
+ prNumber?: number;
55
+ appPrefix?: string;
56
+ }): Promise<DeploymentUrl | null>;
57
+ /**
58
+ * The preview URL a deployment bot posted on the PR.
59
+ *
60
+ * Not every provider records a GitHub Deployment — Vercel's GitHub app reports
61
+ * a commit status and a comment, and the commit status only links to its own
62
+ * dashboard. The comment is where the deployed URL actually appears, so this is
63
+ * the fallback when `deploymentUrlForSha` finds nothing.
64
+ *
65
+ * `appPrefix` disambiguates a monorepo comment listing several projects, by
66
+ * matching the project whose root directory is the app being captured.
67
+ */
68
+ export declare function previewUrlFromComments(gh: GitHub, ownerRepo: string, prNumber: number, opts?: {
69
+ appPrefix?: string;
70
+ }): Promise<DeploymentUrl | null>;
71
+ //# sourceMappingURL=deployments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployments.d.ts","sourceRoot":"","sources":["../src/deployments.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AA2BrC,MAAM,WAAW,aAAa;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,2EAA2E;IAC3E,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AA8DD;;;;;;;;;;;GAWG;AACH,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE;IAAE,UAAU,EAAE,OAAO,CAAA;CAAE,GAC5B,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAE/B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,0BAA0B,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAa7G;AAYD;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3D,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAU/B;AAwBD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,MAAM,EACV,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAChC,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAmC/B"}
@@ -0,0 +1,204 @@
1
+ /**
2
+ * Deployment URLs from the GitHub Deployments API.
3
+ *
4
+ * Vercel, Cloudflare Pages, Netlify and Render all report their deploys to
5
+ * GitHub against a commit, so one lookup covers every provider: no per-host
6
+ * API tokens, and nothing to configure per repo.
7
+ */
8
+ import { normalizeUrl } from './url.js';
9
+ /**
10
+ * Bots whose PR comments may be read for a preview URL. A comment is only
11
+ * trusted when GitHub reports its author as one of these apps: anyone can post
12
+ * a comment that looks like Vercel's, and following it would point a capture at
13
+ * a URL a stranger chose.
14
+ */
15
+ const TRUSTED_BOTS = new Set(['vercel[bot]', 'netlify[bot]']);
16
+ /** Commit-status contexts these same providers post under. */
17
+ const PROVIDER_STATUS = /vercel|netlify|cloudflare|render/i;
18
+ const PRODUCTION = /^prod/i;
19
+ /** How many deployments to inspect per page; each costs one status request. */
20
+ const MAX_DEPLOYMENTS = 5;
21
+ /** Deployments per page when scanning for production. */
22
+ const SCAN_PAGE_SIZE = 100;
23
+ /** How far back to scan. A repo can bury production under a wall of previews. */
24
+ const SCAN_PAGES = 3;
25
+ /**
26
+ * The newest successful deployment URL matching a query.
27
+ *
28
+ * Shared by the two questions callers ask — "what was deployed for this
29
+ * commit?" and "what is deployed to production right now?" — so both apply the
30
+ * same production/preview split and the same "success with a URL" rule.
31
+ */
32
+ async function listDeployments(gh, ownerRepo, query) {
33
+ try {
34
+ const deployments = await gh.request('GET', `/repos/${ownerRepo}/deployments?${query}`);
35
+ return Array.isArray(deployments) ? deployments : null;
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ }
41
+ /** The first of these deployments that reported success with a live URL. */
42
+ async function firstReady(gh, ownerRepo, candidates) {
43
+ const ready = await Promise.all(candidates.map(async (deployment) => {
44
+ try {
45
+ const statuses = await gh.request('GET', `/repos/${ownerRepo}/deployments/${deployment.id}/statuses?per_page=20`);
46
+ if (!Array.isArray(statuses))
47
+ return null;
48
+ const ok = statuses.find((s) => s.state === 'success' && !!s.environment_url);
49
+ return ok ? { url: ok.environment_url, environment: deployment.environment, sha: deployment.sha } : null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }));
55
+ // Candidates stay in newest-first order, so the first hit is the newest.
56
+ return ready.find(Boolean) ?? null;
57
+ }
58
+ /** One page of deployments, narrowed to production or preview. */
59
+ async function newestSuccessful(gh, ownerRepo, query, production) {
60
+ const deployments = await listDeployments(gh, ownerRepo, query);
61
+ if (!deployments)
62
+ return null;
63
+ return firstReady(gh, ownerRepo, matching(deployments, production));
64
+ }
65
+ /** Production or preview, newest first, capped at what is worth a status call. */
66
+ function matching(deployments, production) {
67
+ return deployments.filter(d => PRODUCTION.test(d.environment || '') === production).slice(0, MAX_DEPLOYMENTS);
68
+ }
69
+ /**
70
+ * The newest successful deployment URL for a commit.
71
+ *
72
+ * `production: true` accepts only production environments (the baseline for
73
+ * "Pre"); `false` accepts only the others, which is where every provider puts
74
+ * per-PR previews. Neither falls back to the other kind — a preview standing in
75
+ * for production would silently compare against the wrong thing.
76
+ *
77
+ * Returns null whenever the answer is not unambiguous, so callers fall through
78
+ * to their next option. Never throws: a repo with no deployments, or a token
79
+ * without `deployments: read`, is a normal outcome, not a failure.
80
+ */
81
+ export async function deploymentUrlForSha(gh, ownerRepo, sha, opts) {
82
+ return newestSuccessful(gh, ownerRepo, `sha=${encodeURIComponent(sha)}&per_page=20`, opts.production);
83
+ }
84
+ /**
85
+ * What is deployed to production right now, whatever commit it was built from.
86
+ *
87
+ * `deploymentUrlForSha` pins Pre to the commit the branch forked from, which is
88
+ * the most honest baseline there is — but it only exists if production happened
89
+ * to be deployed at exactly that commit. Production is a moving branch, so on
90
+ * most repositories it was not, and pinning alone leaves the deployed
91
+ * comparison with no Pre at all.
92
+ *
93
+ * This asks the same question without the pin. It reports the commit it found,
94
+ * so the caller can name what Pre actually is rather than implying the base.
95
+ */
96
+ export async function latestProductionDeployment(gh, ownerRepo) {
97
+ // Deployments come back newest first, previews and production interleaved.
98
+ // A busy repository can push its live production deployment well past the
99
+ // first page, so scan a few pages rather than reading one and giving up —
100
+ // but stop the moment a page comes back short, which is the end of the list.
101
+ for (let page = 1; page <= SCAN_PAGES; page++) {
102
+ const deployments = await listDeployments(gh, ownerRepo, `per_page=${SCAN_PAGE_SIZE}&page=${page}`);
103
+ if (!deployments)
104
+ return null;
105
+ const found = await firstReady(gh, ownerRepo, matching(deployments, true));
106
+ if (found)
107
+ return { ...found, url: normalizeUrl(found.url) };
108
+ if (deployments.length < SCAN_PAGE_SIZE)
109
+ return null;
110
+ }
111
+ return null;
112
+ }
113
+ /** Has a deployment provider reported success against this exact commit? */
114
+ async function providerSucceededFor(gh, ownerRepo, sha) {
115
+ try {
116
+ const combined = await gh.request('GET', `/repos/${ownerRepo}/commits/${sha}/status`);
117
+ return (combined.statuses ?? []).some(s => s.state === 'success' && PROVIDER_STATUS.test(s.context));
118
+ }
119
+ catch {
120
+ return false;
121
+ }
122
+ }
123
+ /**
124
+ * The preview for a commit, from whichever mechanism the provider uses.
125
+ *
126
+ * The freshness rule lives here rather than in the caller: a bot comment is
127
+ * edited in place and carries no SHA, so mid-deploy it still advertises the
128
+ * previous commit's preview. Screenshotting that and labelling it as this
129
+ * branch is the worst failure this tool has, because it looks exactly like a
130
+ * correct result. Every caller gets that guarantee by construction.
131
+ */
132
+ export async function findPreviewForCommit(gh, ownerRepo, opts) {
133
+ const recorded = await deploymentUrlForSha(gh, ownerRepo, opts.sha, { production: false });
134
+ if (recorded)
135
+ return { ...recorded, url: normalizeUrl(recorded.url) };
136
+ if (opts.prNumber === undefined)
137
+ return null;
138
+ const [fresh, commented] = await Promise.all([
139
+ providerSucceededFor(gh, ownerRepo, opts.sha),
140
+ previewUrlFromComments(gh, ownerRepo, opts.prNumber, { appPrefix: opts.appPrefix }),
141
+ ]);
142
+ return fresh && commented ? { ...commented, url: normalizeUrl(commented.url) } : null;
143
+ }
144
+ function decodeVercelPayload(body) {
145
+ const marker = /^\[vc\]: #[^:\n]+:(\S+)/m.exec(body);
146
+ if (!marker)
147
+ return null;
148
+ try {
149
+ const json = JSON.parse(Buffer.from(marker[1], 'base64').toString('utf8'));
150
+ return json && typeof json === 'object' ? json : null;
151
+ }
152
+ catch {
153
+ return null;
154
+ }
155
+ }
156
+ /**
157
+ * The preview URL a deployment bot posted on the PR.
158
+ *
159
+ * Not every provider records a GitHub Deployment — Vercel's GitHub app reports
160
+ * a commit status and a comment, and the commit status only links to its own
161
+ * dashboard. The comment is where the deployed URL actually appears, so this is
162
+ * the fallback when `deploymentUrlForSha` finds nothing.
163
+ *
164
+ * `appPrefix` disambiguates a monorepo comment listing several projects, by
165
+ * matching the project whose root directory is the app being captured.
166
+ */
167
+ export async function previewUrlFromComments(gh, ownerRepo, prNumber, opts = {}) {
168
+ let comments;
169
+ try {
170
+ comments = await gh.request('GET', `/repos/${ownerRepo}/issues/${prNumber}/comments?per_page=100`);
171
+ }
172
+ catch {
173
+ return null;
174
+ }
175
+ if (!Array.isArray(comments))
176
+ return null;
177
+ // Newest first: a re-run posts an updated URL.
178
+ for (const comment of [...comments].reverse()) {
179
+ const body = comment.body ?? '';
180
+ if (!comment.user || !TRUSTED_BOTS.has(comment.user.login))
181
+ continue;
182
+ const payload = decodeVercelPayload(body);
183
+ const projects = (payload?.projects ?? []).filter(p => p.previewUrl);
184
+ if (projects.length) {
185
+ // Only projects that could be the app being captured. A project that
186
+ // declares a different root directory is a different app, so falling back
187
+ // to it would screenshot the wrong site.
188
+ const norm = (dir) => (dir ?? '').replace(/^\.?\//, '').replace(/\/$/, '');
189
+ const candidates = projects.filter(p => !opts.appPrefix || !p.rootDirectory || norm(p.rootDirectory) === opts.appPrefix);
190
+ const chosen = candidates.length === 1 ? candidates[0] : undefined;
191
+ // A build still running has a URL that does not serve the branch yet.
192
+ if (chosen && (chosen.nextCommitStatus ?? 'DEPLOYED') === 'DEPLOYED') {
193
+ return { url: normalizeUrl(chosen.previewUrl), environment: chosen.name ? `Preview (${chosen.name})` : 'Preview' };
194
+ }
195
+ continue;
196
+ }
197
+ // Providers without a structured payload still link the preview by name.
198
+ const link = /\[(?:Preview|Deploy Preview)\]\((https?:\/\/[^\s)]+)\)/i.exec(body);
199
+ if (link)
200
+ return { url: link[1], environment: 'Preview' };
201
+ }
202
+ return null;
203
+ }
204
+ //# sourceMappingURL=deployments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deployments.js","sourceRoot":"","sources":["../src/deployments.ts"],"names":[],"mappings":"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"}