@juangadm/pre-post 1.0.0 → 1.1.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.
- package/README.md +99 -20
- package/dist/baseline.d.ts +119 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +316 -0
- package/dist/baseline.js.map +1 -0
- package/dist/bin/cli.js +80 -41
- package/dist/bin/cli.js.map +1 -1
- package/dist/browser.d.ts +31 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +239 -97
- package/dist/browser.js.map +1 -1
- package/dist/commands/compare.d.ts.map +1 -1
- package/dist/commands/compare.js +21 -3
- package/dist/commands/compare.js.map +1 -1
- package/dist/commands/detect.d.ts +5 -0
- package/dist/commands/detect.d.ts.map +1 -1
- package/dist/commands/detect.js +4 -1
- package/dist/commands/detect.js.map +1 -1
- package/dist/commands/doctor.d.ts +32 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +90 -9
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/login.js +1 -1
- package/dist/commands/pr.d.ts +4 -0
- package/dist/commands/pr.d.ts.map +1 -1
- package/dist/commands/pr.js +150 -50
- package/dist/commands/pr.js.map +1 -1
- package/dist/commands/prune.js +1 -1
- package/dist/commands/prune.js.map +1 -1
- package/dist/comparison.d.ts +104 -0
- package/dist/comparison.d.ts.map +1 -0
- package/dist/comparison.js +236 -0
- package/dist/comparison.js.map +1 -0
- package/dist/config.d.ts +12 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -3
- package/dist/config.js.map +1 -1
- package/dist/deployments.d.ts +71 -0
- package/dist/deployments.d.ts.map +1 -0
- package/dist/deployments.js +204 -0
- package/dist/deployments.js.map +1 -0
- package/dist/diff-pool.d.ts.map +1 -1
- package/dist/diff-pool.js +5 -2
- package/dist/diff-pool.js.map +1 -1
- package/dist/diff-worker.d.ts +3 -0
- package/dist/diff-worker.d.ts.map +1 -1
- package/dist/diff-worker.js +1 -0
- package/dist/diff-worker.js.map +1 -1
- package/dist/diff.d.ts +10 -0
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +117 -7
- package/dist/diff.js.map +1 -1
- package/dist/doctor.d.ts +42 -1
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +73 -7
- package/dist/doctor.js.map +1 -1
- package/dist/git.d.ts +41 -3
- package/dist/git.d.ts.map +1 -1
- package/dist/git.js +103 -7
- package/dist/git.js.map +1 -1
- package/dist/github.d.ts +92 -2
- package/dist/github.d.ts.map +1 -1
- package/dist/github.js +191 -20
- package/dist/github.js.map +1 -1
- package/dist/landing.d.ts +48 -0
- package/dist/landing.d.ts.map +1 -0
- package/dist/landing.js +93 -0
- package/dist/landing.js.map +1 -0
- package/dist/pkg.d.ts +9 -0
- package/dist/pkg.d.ts.map +1 -1
- package/dist/pkg.js +14 -0
- package/dist/pkg.js.map +1 -1
- package/dist/report.d.ts.map +1 -1
- package/dist/report.js +49 -26
- package/dist/report.js.map +1 -1
- package/dist/routes.d.ts +27 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +98 -23
- package/dist/routes.js.map +1 -1
- package/dist/run.d.ts +88 -8
- package/dist/run.d.ts.map +1 -1
- package/dist/run.js +214 -26
- package/dist/run.js.map +1 -1
- package/dist/sameness.d.ts +91 -0
- package/dist/sameness.d.ts.map +1 -0
- package/dist/sameness.js +157 -0
- package/dist/sameness.js.map +1 -0
- package/dist/sessions.d.ts.map +1 -1
- package/dist/sessions.js +7 -1
- package/dist/sessions.js.map +1 -1
- package/dist/shift.d.ts +67 -0
- package/dist/shift.d.ts.map +1 -0
- package/dist/shift.js +184 -0
- package/dist/shift.js.map +1 -0
- package/dist/types.d.ts +67 -5
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/url.d.ts +12 -0
- package/dist/url.d.ts.map +1 -0
- package/dist/url.js +30 -0
- package/dist/url.js.map +1 -0
- package/package.json +1 -1
- package/skill/SKILL.md +40 -7
package/dist/sessions.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"sessions.js","sourceRoot":"","sources":["../src/sessions.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,IAAI,MAAM,MAAM,CAAC;AAWxB,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe;WACnC,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;WAChE,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;AAC3E,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,KAAmB;IAC3D,EAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAiB,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,IAAc;IAC3C,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,KAAK,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QACjC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1B,CAAC;IACD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;QAChC,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,OAAO,GAAG,CAAC,IAAI,CAAC,CAAC,OAAO,GAAG,GAAG;gBAAE,SAAS;YAC5D,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAcD;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAuB;IACjD,MAAM,OAAO,GAA2B,EAAE,GAAG,CAAC,KAAK,CAAC,aAAa,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,CAAC;IACrG,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,+BAA+B,CAAC;IAC3D,2EAA2E;IAC3E,4EAA4E;IAC5E,wEAAwE;IACxE,4EAA4E;IAC5E,0EAA0E;IAC1E,2EAA2E;IAC3E,gEAAgE;IAChE,IAAI,MAAM,IAAI,CAAC,OAAO,CAAC,4BAA4B,CAAC,EAAE,CAAC;QACrD,OAAO,CAAC,4BAA4B,CAAC,GAAG,MAAM,CAAC;IACjD,CAAC;IACD,MAAM,OAAO,GAAwC;QACnD,GAAG,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC;QAC7B,GAAG,CAAC,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;KACrF,CAAC;IACF,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;AAC1F,CAAC"}
|
package/dist/shift.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertical displacement between two captures.
|
|
3
|
+
*
|
|
4
|
+
* A padding change near the top of a page moves everything below it. Pixel
|
|
5
|
+
* diffing counts every moved pixel as changed, so a change a designer would
|
|
6
|
+
* call "slightly roomier" reads as 60-90% of the page repainted — and above
|
|
7
|
+
* CROP_MAX_RATIO no crop is produced at all, so the most dramatic-looking
|
|
8
|
+
* result gives a reviewer the least to look at.
|
|
9
|
+
*
|
|
10
|
+
* A layout shift always has a start: content above the changed element stays
|
|
11
|
+
* put and content below it moves by a constant amount. Recovering that pair
|
|
12
|
+
* (how far, from where) turns the repaint back into a sentence — "content
|
|
13
|
+
* shifted down 48px" — and lets the diff run on aligned images, which is the
|
|
14
|
+
* change the branch actually made.
|
|
15
|
+
*
|
|
16
|
+
* Rows are matched by hash rather than correlated numerically: a shifted page
|
|
17
|
+
* re-renders its rows identically, so exact matches carry the signal and cost
|
|
18
|
+
* one pass over the pixels.
|
|
19
|
+
*/
|
|
20
|
+
import { PNG } from 'pngjs';
|
|
21
|
+
export interface ShiftCandidate {
|
|
22
|
+
/** Device pixels the Post content moved down by; negative means up. */
|
|
23
|
+
dy: number;
|
|
24
|
+
/** First row (in Pre coordinates) that moved. Rows above it stayed put. */
|
|
25
|
+
from: number;
|
|
26
|
+
/** Rows the offset explains, as a fraction of all distinctive rows. */
|
|
27
|
+
coverage: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* FNV-1a over each row's bytes. Distinct content gives distinct hashes; the
|
|
31
|
+
* collision rate over a few thousand rows is negligible, and a collision can
|
|
32
|
+
* only add a stray vote to an offset that other rows have to agree on anyway.
|
|
33
|
+
*/
|
|
34
|
+
export declare function rowHashes(png: PNG): Uint32Array;
|
|
35
|
+
/**
|
|
36
|
+
* Find a single vertical displacement that explains the difference between two
|
|
37
|
+
* equally sized images, or null when none does.
|
|
38
|
+
*
|
|
39
|
+
* Rows that repeat (blank space, flat backgrounds, rules) are dropped first:
|
|
40
|
+
* they match every offset equally and would otherwise outvote the content.
|
|
41
|
+
* What remains votes for the offset that maps it from Pre to Post, and the
|
|
42
|
+
* winner has to be supported by enough rows to mean something.
|
|
43
|
+
*/
|
|
44
|
+
export declare function detectShift(before: PNG, after: PNG): ShiftCandidate | null;
|
|
45
|
+
/**
|
|
46
|
+
* Rows Post has that Pre never did. A downward shift means something new
|
|
47
|
+
* pushed the page down, and those rows are that something.
|
|
48
|
+
*/
|
|
49
|
+
export declare function insertedBand(shift: ShiftCandidate): {
|
|
50
|
+
y: number;
|
|
51
|
+
height: number;
|
|
52
|
+
} | null;
|
|
53
|
+
/**
|
|
54
|
+
* Rebuild Pre in Post's coordinates: content above the shift stays where it
|
|
55
|
+
* is, content below it moves down by `dy`, and an insertion leaves a gap.
|
|
56
|
+
*
|
|
57
|
+
* Aligning this way round rather than pulling Post back is what makes an
|
|
58
|
+
* insertion visible. Mapping every Pre row onto a Post row steps straight over
|
|
59
|
+
* the rows Post gained, so a banner nobody had seen before would contribute
|
|
60
|
+
* nothing and the report would call it "nothing else changed". Left as
|
|
61
|
+
* background, whatever Post put there reads as what it is: new content.
|
|
62
|
+
*
|
|
63
|
+
* It also means the Post image published beside it is the real capture rather
|
|
64
|
+
* than a synthesis, and the re-spaced side is the one being compared against.
|
|
65
|
+
*/
|
|
66
|
+
export declare function alignBefore(before: PNG, shift: ShiftCandidate, pad: [number, number, number]): PNG;
|
|
67
|
+
//# sourceMappingURL=shift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.d.ts","sourceRoot":"","sources":["../src/shift.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAS5B,MAAM,WAAW,cAAc;IAC7B,uEAAuE;IACvE,EAAE,EAAE,MAAM,CAAC;IACX,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,GAAG,WAAW,CAc/C;AAQD;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,GAAG,cAAc,GAAG,IAAI,CAiE1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,cAAc,GAAG;IAAE,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAExF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,GAAG,CAoBlG"}
|
package/dist/shift.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vertical displacement between two captures.
|
|
3
|
+
*
|
|
4
|
+
* A padding change near the top of a page moves everything below it. Pixel
|
|
5
|
+
* diffing counts every moved pixel as changed, so a change a designer would
|
|
6
|
+
* call "slightly roomier" reads as 60-90% of the page repainted — and above
|
|
7
|
+
* CROP_MAX_RATIO no crop is produced at all, so the most dramatic-looking
|
|
8
|
+
* result gives a reviewer the least to look at.
|
|
9
|
+
*
|
|
10
|
+
* A layout shift always has a start: content above the changed element stays
|
|
11
|
+
* put and content below it moves by a constant amount. Recovering that pair
|
|
12
|
+
* (how far, from where) turns the repaint back into a sentence — "content
|
|
13
|
+
* shifted down 48px" — and lets the diff run on aligned images, which is the
|
|
14
|
+
* change the branch actually made.
|
|
15
|
+
*
|
|
16
|
+
* Rows are matched by hash rather than correlated numerically: a shifted page
|
|
17
|
+
* re-renders its rows identically, so exact matches carry the signal and cost
|
|
18
|
+
* one pass over the pixels.
|
|
19
|
+
*/
|
|
20
|
+
import { PNG } from 'pngjs';
|
|
21
|
+
/** A row hash seen more often than this in either image carries no position information. */
|
|
22
|
+
const MAX_ROW_REPEATS = 4;
|
|
23
|
+
/** Below this many distinctive rows there is not enough signal to trust an offset. */
|
|
24
|
+
const MIN_DISTINCTIVE_ROWS = 16;
|
|
25
|
+
/** An offset supported by fewer rows than this is noise. */
|
|
26
|
+
const MIN_MOVED_ROWS = 8;
|
|
27
|
+
/**
|
|
28
|
+
* FNV-1a over each row's bytes. Distinct content gives distinct hashes; the
|
|
29
|
+
* collision rate over a few thousand rows is negligible, and a collision can
|
|
30
|
+
* only add a stray vote to an offset that other rows have to agree on anyway.
|
|
31
|
+
*/
|
|
32
|
+
export function rowHashes(png) {
|
|
33
|
+
const { width, height, data } = png;
|
|
34
|
+
const stride = width * 4;
|
|
35
|
+
const out = new Uint32Array(height);
|
|
36
|
+
for (let y = 0; y < height; y++) {
|
|
37
|
+
let h = 0x811c9dc5;
|
|
38
|
+
const start = y * stride;
|
|
39
|
+
for (let i = start; i < start + stride; i++) {
|
|
40
|
+
h ^= data[i];
|
|
41
|
+
h = Math.imul(h, 0x01000193);
|
|
42
|
+
}
|
|
43
|
+
out[y] = h >>> 0;
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
function countHashes(hashes) {
|
|
48
|
+
const counts = new Map();
|
|
49
|
+
for (const h of hashes)
|
|
50
|
+
counts.set(h, (counts.get(h) ?? 0) + 1);
|
|
51
|
+
return counts;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Find a single vertical displacement that explains the difference between two
|
|
55
|
+
* equally sized images, or null when none does.
|
|
56
|
+
*
|
|
57
|
+
* Rows that repeat (blank space, flat backgrounds, rules) are dropped first:
|
|
58
|
+
* they match every offset equally and would otherwise outvote the content.
|
|
59
|
+
* What remains votes for the offset that maps it from Pre to Post, and the
|
|
60
|
+
* winner has to be supported by enough rows to mean something.
|
|
61
|
+
*/
|
|
62
|
+
export function detectShift(before, after) {
|
|
63
|
+
if (before.width !== after.width || before.height !== after.height)
|
|
64
|
+
return null;
|
|
65
|
+
const height = before.height;
|
|
66
|
+
const beforeHashes = rowHashes(before);
|
|
67
|
+
const afterHashes = rowHashes(after);
|
|
68
|
+
const beforeCounts = countHashes(beforeHashes);
|
|
69
|
+
const afterCounts = countHashes(afterHashes);
|
|
70
|
+
const distinctive = (h) => (beforeCounts.get(h) ?? 0) > 0 &&
|
|
71
|
+
(beforeCounts.get(h) ?? 0) <= MAX_ROW_REPEATS &&
|
|
72
|
+
(afterCounts.get(h) ?? 0) <= MAX_ROW_REPEATS;
|
|
73
|
+
// Where each distinctive row sits in Pre, so Post rows can look it up.
|
|
74
|
+
const beforeRows = new Map();
|
|
75
|
+
for (let y = 0; y < height; y++) {
|
|
76
|
+
const h = beforeHashes[y];
|
|
77
|
+
if (!distinctive(h))
|
|
78
|
+
continue;
|
|
79
|
+
const list = beforeRows.get(h);
|
|
80
|
+
if (list)
|
|
81
|
+
list.push(y);
|
|
82
|
+
else
|
|
83
|
+
beforeRows.set(h, [y]);
|
|
84
|
+
}
|
|
85
|
+
const votes = new Map();
|
|
86
|
+
let distinctiveRows = 0;
|
|
87
|
+
for (let y = 0; y < height; y++) {
|
|
88
|
+
const h = afterHashes[y];
|
|
89
|
+
if (!distinctive(h))
|
|
90
|
+
continue;
|
|
91
|
+
distinctiveRows++;
|
|
92
|
+
const origins = beforeRows.get(h);
|
|
93
|
+
if (!origins)
|
|
94
|
+
continue;
|
|
95
|
+
for (const origin of origins) {
|
|
96
|
+
const d = y - origin;
|
|
97
|
+
votes.set(d, (votes.get(d) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (distinctiveRows < MIN_DISTINCTIVE_ROWS)
|
|
101
|
+
return null;
|
|
102
|
+
let dy = 0;
|
|
103
|
+
let moved = 0;
|
|
104
|
+
for (const [d, count] of votes) {
|
|
105
|
+
if (d === 0)
|
|
106
|
+
continue;
|
|
107
|
+
if (count > moved) {
|
|
108
|
+
dy = d;
|
|
109
|
+
moved = count;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (dy === 0 || moved < MIN_MOVED_ROWS)
|
|
113
|
+
return null;
|
|
114
|
+
// Where the movement starts: the first Pre row that maps to Post at the
|
|
115
|
+
// winning offset and never goes back to sitting still. Everything above it
|
|
116
|
+
// is content the change did not move.
|
|
117
|
+
const stayed = new Set();
|
|
118
|
+
const shifted = new Set();
|
|
119
|
+
for (let y = 0; y < height; y++) {
|
|
120
|
+
const h = beforeHashes[y];
|
|
121
|
+
if (!distinctive(h))
|
|
122
|
+
continue;
|
|
123
|
+
if (afterHashes[y] === h)
|
|
124
|
+
stayed.add(y);
|
|
125
|
+
if (y + dy >= 0 && y + dy < height && afterHashes[y + dy] === h)
|
|
126
|
+
shifted.add(y);
|
|
127
|
+
}
|
|
128
|
+
let from = height;
|
|
129
|
+
for (let y = height - 1; y >= 0; y--) {
|
|
130
|
+
if (shifted.has(y))
|
|
131
|
+
from = y;
|
|
132
|
+
else if (stayed.has(y))
|
|
133
|
+
break;
|
|
134
|
+
}
|
|
135
|
+
if (from >= height)
|
|
136
|
+
return null;
|
|
137
|
+
return { dy, from, coverage: moved / distinctiveRows };
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Rows Post has that Pre never did. A downward shift means something new
|
|
141
|
+
* pushed the page down, and those rows are that something.
|
|
142
|
+
*/
|
|
143
|
+
export function insertedBand(shift) {
|
|
144
|
+
return shift.dy > 0 ? { y: shift.from, height: shift.dy } : null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Rebuild Pre in Post's coordinates: content above the shift stays where it
|
|
148
|
+
* is, content below it moves down by `dy`, and an insertion leaves a gap.
|
|
149
|
+
*
|
|
150
|
+
* Aligning this way round rather than pulling Post back is what makes an
|
|
151
|
+
* insertion visible. Mapping every Pre row onto a Post row steps straight over
|
|
152
|
+
* the rows Post gained, so a banner nobody had seen before would contribute
|
|
153
|
+
* nothing and the report would call it "nothing else changed". Left as
|
|
154
|
+
* background, whatever Post put there reads as what it is: new content.
|
|
155
|
+
*
|
|
156
|
+
* It also means the Post image published beside it is the real capture rather
|
|
157
|
+
* than a synthesis, and the re-spaced side is the one being compared against.
|
|
158
|
+
*/
|
|
159
|
+
export function alignBefore(before, shift, pad) {
|
|
160
|
+
const { width, height } = before;
|
|
161
|
+
const out = new PNG({ width, height });
|
|
162
|
+
const stride = width * 4;
|
|
163
|
+
const gap = insertedBand(shift);
|
|
164
|
+
for (let y = 0; y < height; y++) {
|
|
165
|
+
// Above the shift the two sides agree on position; below it Pre sits `dy`
|
|
166
|
+
// higher than Post does.
|
|
167
|
+
const source = y < shift.from ? y : y - shift.dy;
|
|
168
|
+
const inGap = gap !== null && y >= gap.y && y < gap.y + gap.height;
|
|
169
|
+
const target = y * stride;
|
|
170
|
+
if (!inGap && source >= 0 && source < height) {
|
|
171
|
+
before.data.copy(out.data, target, source * stride, source * stride + stride);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
for (let i = target; i < target + stride; i += 4) {
|
|
175
|
+
out.data[i] = pad[0];
|
|
176
|
+
out.data[i + 1] = pad[1];
|
|
177
|
+
out.data[i + 2] = pad[2];
|
|
178
|
+
out.data[i + 3] = 255;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
return out;
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=shift.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shift.js","sourceRoot":"","sources":["../src/shift.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -48,6 +48,14 @@ export interface CaptureResult {
|
|
|
48
48
|
selector?: string;
|
|
49
49
|
/** HTTP status of the main document */
|
|
50
50
|
status?: number;
|
|
51
|
+
/** URL the browser ended on, which is not the requested one after a redirect */
|
|
52
|
+
finalUrl?: string;
|
|
53
|
+
/** Document title, used to recognise a sign-in wall that answered with 200 */
|
|
54
|
+
title?: string;
|
|
55
|
+
/** Visible text, used to tell "the same site, changed" from "a different site" */
|
|
56
|
+
text?: string;
|
|
57
|
+
/** Response looked like it came from Vercel (deployment protection) */
|
|
58
|
+
vercel?: boolean;
|
|
51
59
|
/** Wall-clock milliseconds for navigation + settle + screenshot */
|
|
52
60
|
durationMs: number;
|
|
53
61
|
}
|
|
@@ -87,6 +95,23 @@ export interface DiffResult {
|
|
|
87
95
|
after: Buffer;
|
|
88
96
|
region: DiffRegion;
|
|
89
97
|
};
|
|
98
|
+
/** Vertical displacement explaining most of the difference, when one does */
|
|
99
|
+
shift?: ShiftSummary;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* A layout shift: Post content moved down (or up) by a constant amount from a
|
|
103
|
+
* given row, and what remains different once the two sides are put back in
|
|
104
|
+
* register. Plain numbers, so it survives the worker-thread boundary.
|
|
105
|
+
*/
|
|
106
|
+
export interface ShiftSummary {
|
|
107
|
+
/** Device pixels Post content moved down by; negative means up. */
|
|
108
|
+
dy: number;
|
|
109
|
+
/** First row, in Pre coordinates, that moved. Rows above it stayed put. */
|
|
110
|
+
from: number;
|
|
111
|
+
/** Changed pixels once Post is aligned with Pre. */
|
|
112
|
+
alignedChangedPixels: number;
|
|
113
|
+
/** Aligned changed pixels as a fraction of the canvas. */
|
|
114
|
+
alignedChangedRatio: number;
|
|
90
115
|
}
|
|
91
116
|
export type Framework = 'nextjs-app' | 'nextjs-pages' | 'vite' | 'generic';
|
|
92
117
|
export interface DetectedRoute {
|
|
@@ -104,6 +129,8 @@ export interface RouteDetectionOptions {
|
|
|
104
129
|
framework?: Framework;
|
|
105
130
|
/** Maximum number of routes to return (default: 6) */
|
|
106
131
|
maxRoutes?: number;
|
|
132
|
+
/** Where warnings go. Left unset, they are silent. */
|
|
133
|
+
log?: (msg: string) => void;
|
|
107
134
|
}
|
|
108
135
|
export interface PrePostConfig {
|
|
109
136
|
/** Production base URL — the "before" state */
|
|
@@ -114,13 +141,16 @@ export interface PrePostConfig {
|
|
|
114
141
|
routes?: string[];
|
|
115
142
|
/** Sample URLs for dynamic routes, e.g. { "/blog/[slug]": "/blog/hello" } */
|
|
116
143
|
samples?: Record<string, string>;
|
|
117
|
-
/** Viewports to capture. Default: ["desktop"
|
|
144
|
+
/** Viewports to capture. Default: ["desktop"] */
|
|
118
145
|
viewports?: Array<ViewportPreset | string>;
|
|
119
146
|
/** Extra headers for every request */
|
|
120
147
|
headers?: Record<string, string>;
|
|
121
148
|
/** Change ratio (fraction 0..1) at or above which a route counts as changed. Default 0.001 */
|
|
122
149
|
threshold?: number;
|
|
123
150
|
/** Absolute changed-pixel floor at or above which a route counts as changed, so small edits on tall pages register. Default 40 */
|
|
151
|
+
/** Smallest painted change that counts, in CSS pixels². */
|
|
152
|
+
minChangedArea?: number;
|
|
153
|
+
/** @deprecated device pixels; use minChangedArea. Converted at the capture scale. */
|
|
124
154
|
minChangedPixels?: number;
|
|
125
155
|
/** Max detected routes. Default 6 */
|
|
126
156
|
maxRoutes?: number;
|
|
@@ -137,8 +167,10 @@ export interface PrePostConfig {
|
|
|
137
167
|
}
|
|
138
168
|
/** Pre, post, and (when there was a change) diff and crop images, as local paths or URLs. */
|
|
139
169
|
export interface ArtifactSet {
|
|
140
|
-
|
|
141
|
-
|
|
170
|
+
/** Absent when the route has no baseline (a page this branch adds). */
|
|
171
|
+
before?: string;
|
|
172
|
+
/** Absent when the branch removed the route. */
|
|
173
|
+
after?: string;
|
|
142
174
|
diff?: string;
|
|
143
175
|
cropBefore?: string;
|
|
144
176
|
cropAfter?: string;
|
|
@@ -149,15 +181,45 @@ export interface RouteCaptureOutcome {
|
|
|
149
181
|
/** Route actually requested (after sample substitution) */
|
|
150
182
|
resolvedRoute: string;
|
|
151
183
|
viewport: string;
|
|
152
|
-
status: 'changed' | 'unchanged' | 'error';
|
|
184
|
+
status: 'changed' | 'unchanged' | 'added' | 'removed' | 'error';
|
|
153
185
|
changedRatio?: number;
|
|
154
186
|
sizeChanged?: boolean;
|
|
155
187
|
error?: string;
|
|
156
188
|
files?: ArtifactSet;
|
|
157
189
|
urls?: ArtifactSet;
|
|
158
190
|
durationMs?: number;
|
|
159
|
-
/**
|
|
191
|
+
/** sha1 of the baseline capture, for spotting a host that serves one page for every route */
|
|
192
|
+
baselineHash?: string;
|
|
193
|
+
/** Extra context, e.g. "new page — no baseline (example.com returned 404)" */
|
|
160
194
|
note?: string;
|
|
195
|
+
/** Vertical displacement of Post against Pre, when one explains the change */
|
|
196
|
+
shift?: RouteShift;
|
|
197
|
+
/** A side answered with a sign-in wall, so nothing was really compared */
|
|
198
|
+
blocked?: BlockedSide;
|
|
199
|
+
/**
|
|
200
|
+
* How much of the two sides' wording is shared, or null when neither page
|
|
201
|
+
* had enough text to judge. Near zero on every route means the two sides are
|
|
202
|
+
* different sites, which a pixel ratio cannot tell you — see `sameness.ts`.
|
|
203
|
+
*/
|
|
204
|
+
textOverlap?: number | null;
|
|
205
|
+
/**
|
|
206
|
+
* The same measure over the two titles. A title is where a site names
|
|
207
|
+
* itself, so it corroborates a run that only has one route to judge.
|
|
208
|
+
*/
|
|
209
|
+
titleOverlap?: number | null;
|
|
210
|
+
}
|
|
211
|
+
export interface BlockedSide {
|
|
212
|
+
side: 'before' | 'after';
|
|
213
|
+
/** The sign-in page the capture landed on */
|
|
214
|
+
finalUrl: string;
|
|
215
|
+
/** Deployment protection, so the bypass secret is the fix */
|
|
216
|
+
vercel: boolean;
|
|
217
|
+
}
|
|
218
|
+
export interface RouteShift {
|
|
219
|
+
/** CSS pixels the content moved down by; negative means up. */
|
|
220
|
+
px: number;
|
|
221
|
+
/** Whether anything changed beyond the move, judged by the same rule as any other capture. */
|
|
222
|
+
otherChange: boolean;
|
|
161
223
|
}
|
|
162
224
|
export interface PrRunResult {
|
|
163
225
|
repo: string;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,MAAM,cAAc,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAE7D,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,YAAY,CAAC;AAE3D,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,cAAc,EAAE,YAAY,CAIjE,CAAC;AAMF,MAAM,WAAW,WAAW;IAC1B,0EAA0E;IAC1E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,wCAAwC;IACxC,OAAO,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChG;AAED,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mCAAmC;IACnC,QAAQ,CAAC,EAAE,cAAc,CAAC;IAC1B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uFAAuF;IACvF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6BAA6B;IAC7B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,gCAAgC;IAChC,QAAQ,EAAE,YAAY,CAAC;IACvB,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;IACZ,gCAAgC;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,cAAc,GAAG,MAAM,CAAC;IAChC,KAAK,EAAE,cAAc,GAAG,MAAM,CAAC;IAC/B,uEAAuE;IACvE,QAAQ,CAAC,EAAE,cAAc,CAAC;CAC3B;AAED,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,aAAa,CAAC;IACtB,KAAK,EAAE,aAAa,CAAC;CACtB;AAMD,MAAM,WAAW,UAAU;IACzB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,mEAAmE;IACnE,YAAY,EAAE,MAAM,CAAC;IACrB,iCAAiC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IAC1B,sDAAsD;IACtD,WAAW,EAAE,OAAO,CAAC;IACrB,+FAA+F;IAC/F,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oFAAoF;IACpF,IAAI,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,UAAU,CAAA;KAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,WAAW;AACX,+DAA+D;AAW/D,MAAM,CAAC,MAAM,gBAAgB,GAAyC;IACpE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IACrC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IACpC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;CACpC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,WAAW;AACX,+DAA+D;AAW/D,MAAM,CAAC,MAAM,gBAAgB,GAAyC;IACpE,OAAO,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE;IACrC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE;IACpC,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE;CACpC,CAAC;AAmMF,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,WAAW,CAAU,CAAC"}
|
package/dist/url.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL helpers shared by the capture layer and the command pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of run.ts so browser.ts can ask "is this loopback?" without
|
|
5
|
+
* importing the module that imports it.
|
|
6
|
+
*/
|
|
7
|
+
/** Is this URL served from the machine running the command? */
|
|
8
|
+
export declare function isLocalUrl(url: string): boolean;
|
|
9
|
+
export declare function normalizeUrl(url: string): string;
|
|
10
|
+
export declare function hostOf(url: string): string;
|
|
11
|
+
export declare function joinUrl(base: string, route: string): string;
|
|
12
|
+
//# sourceMappingURL=url.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.d.ts","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,+DAA+D;AAC/D,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE/C;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKhD;AAED,wBAAgB,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAM1C;AAED,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAE3D"}
|
package/dist/url.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL helpers shared by the capture layer and the command pipeline.
|
|
3
|
+
*
|
|
4
|
+
* Kept out of run.ts so browser.ts can ask "is this loopback?" without
|
|
5
|
+
* importing the module that imports it.
|
|
6
|
+
*/
|
|
7
|
+
/** Add a scheme to a bare URL (http for loopback hosts, https otherwise) and drop trailing slashes. */
|
|
8
|
+
const LOOPBACK = /^(?:(?:https?|file):\/\/)?(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(:|\/|$)/i;
|
|
9
|
+
/** Is this URL served from the machine running the command? */
|
|
10
|
+
export function isLocalUrl(url) {
|
|
11
|
+
return LOOPBACK.test(url);
|
|
12
|
+
}
|
|
13
|
+
export function normalizeUrl(url) {
|
|
14
|
+
const withScheme = /^(https?|file):\/\//i.test(url)
|
|
15
|
+
? url
|
|
16
|
+
: isLocalUrl(url) ? `http://${url}` : `https://${url}`;
|
|
17
|
+
return withScheme.replace(/\/+$/, '');
|
|
18
|
+
}
|
|
19
|
+
export function hostOf(url) {
|
|
20
|
+
try {
|
|
21
|
+
return new URL(url).host;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return url;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function joinUrl(base, route) {
|
|
28
|
+
return base.replace(/\/+$/, '') + (route.startsWith('/') ? route : `/${route}`);
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=url.js.map
|
package/dist/url.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"url.js","sourceRoot":"","sources":["../src/url.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,uGAAuG;AACvG,MAAM,QAAQ,GAAG,gFAAgF,CAAC;AAElG,+DAA+D;AAC/D,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,QAAQ,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5B,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAW;IACtC,MAAM,UAAU,GAAG,sBAAsB,CAAC,IAAI,CAAC,GAAG,CAAC;QACjD,CAAC,CAAC,GAAG;QACL,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC,WAAW,GAAG,EAAE,CAAC;IACzD,OAAO,UAAU,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,MAAM,CAAC,GAAW;IAChC,IAAI,CAAC;QACH,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,GAAG,CAAC;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,IAAY,EAAE,KAAa;IACjD,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC;AAClF,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@juangadm/pre-post",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Before/after screenshots for pull requests: detect changed routes, capture, pixel-diff, and post a sticky PR comment",
|
|
5
5
|
"author": "Juan Gabriel Delgado (forked from James Clements)",
|
|
6
6
|
"license": "MIT",
|
package/skill/SKILL.md
CHANGED
|
@@ -10,9 +10,31 @@ allowed-tools:
|
|
|
10
10
|
# pre-post
|
|
11
11
|
|
|
12
12
|
One command does everything: detects the routes this branch changed, screenshots them on
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
desktop, pixel-diffs them, uploads the images to a `pre-post-assets` branch, and
|
|
14
|
+
posts or updates a single comment on the open PR. The human reviews on GitHub.
|
|
15
|
+
|
|
16
|
+
It picks both sides itself:
|
|
17
|
+
|
|
18
|
+
- **Post** (this branch) — the PR's preview deployment when one exists, read from the GitHub
|
|
19
|
+
Deployments API or, for providers that only comment (Vercel's GitHub app does), from the
|
|
20
|
+
deployment bot's PR comment. Otherwise a dev server: one already running, or one it starts
|
|
21
|
+
and stops itself.
|
|
22
|
+
- **Pre** (the baseline) — `before` from `.pre-post.json`, otherwise the production
|
|
23
|
+
deployment for the commit this branch forked from, otherwise that base commit itself,
|
|
24
|
+
checked out into a throwaway worktree and served locally.
|
|
25
|
+
|
|
26
|
+
Both sides always come from the same kind of environment. Pairing a deployment against a
|
|
27
|
+
dev server would compare different builds — a dev server renders Next.js's dev badge that
|
|
28
|
+
production never shows — so a diff would carry changes the branch never made.
|
|
29
|
+
|
|
30
|
+
So on a PR with a preview deployment, no dev server and no checkout are needed — anyone on
|
|
31
|
+
the team can run it against anyone's PR. With nothing deployed and nothing running, it
|
|
32
|
+
serves both sides itself.
|
|
33
|
+
|
|
34
|
+
The last baseline needs no network at all, so the tool still works inside a sandbox, a CI
|
|
35
|
+
container, or behind an egress allowlist where no deployment URL is reachable. It also
|
|
36
|
+
compares against exactly what the branch forked from, rendered in the same environment as
|
|
37
|
+
the Post side. Pass `--no-local-baseline` to turn it off.
|
|
16
38
|
|
|
17
39
|
## Run
|
|
18
40
|
|
|
@@ -20,14 +42,25 @@ comment on the open PR. The human reviews on GitHub.
|
|
|
20
42
|
npx -y @juangadm/pre-post@latest pr
|
|
21
43
|
```
|
|
22
44
|
|
|
23
|
-
Add `--
|
|
24
|
-
|
|
45
|
+
Add `--routes /a,/b` when the user names pages explicitly. Add
|
|
46
|
+
`--before https://production-url` only if the run reports it cannot work out the baseline
|
|
47
|
+
(it is then saved to `.pre-post.json` for next time).
|
|
48
|
+
|
|
49
|
+
## What it posts
|
|
50
|
+
|
|
51
|
+
The visual changes go at the **top of the PR description**, in a delimited block that
|
|
52
|
+
re-runs replace in place — the author's own text is never touched. If the PR cannot be
|
|
53
|
+
edited (a fork, a read-only token) it falls back to a single sticky comment.
|
|
54
|
+
|
|
55
|
+
Each changed route shows **Pre beside Post**, per viewport, full pages in a `<details>`. No
|
|
56
|
+
overlay, no percentage. A page that moved says `Content shifted down 48px`; relay as written.
|
|
25
57
|
|
|
26
58
|
## Rules
|
|
27
59
|
|
|
28
60
|
- Run the command once. Do not open, read, or describe the screenshot files; the PR
|
|
29
61
|
comment is the deliverable. Report the summary the command prints, plus the comment link.
|
|
30
|
-
- Do not start dev servers,
|
|
62
|
+
- Do not switch branches, start dev servers, or use a browser tool yourself. The command
|
|
63
|
+
handles all three.
|
|
31
64
|
- Exit code 3 means a human must do one thing (log in, start the dev server, pass
|
|
32
65
|
`--before`). Relay that one sentence verbatim and stop.
|
|
33
66
|
- Do not use `--dry-run` unless the user asks to preview without posting.
|
|
@@ -39,7 +72,7 @@ Add `--before https://production-url` the first time in a repo (it is saved to
|
|
|
39
72
|
| Flag | Use when |
|
|
40
73
|
|------|----------|
|
|
41
74
|
| `--routes /a,/b` | The user names the pages |
|
|
42
|
-
| `--
|
|
75
|
+
| `-r`, `--responsive` | Desktop and mobile (default is desktop only) |
|
|
43
76
|
| `--viewport-only` | First screen only instead of full page |
|
|
44
77
|
| `--pr <n>` | The branch has several PRs or the lookup fails |
|
|
45
78
|
| `--dry-run` | Preview locally, post nothing |
|