@juangadm/pre-post 0.3.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.
Files changed (153) hide show
  1. package/README.md +174 -233
  2. package/dist/baseline.d.ts +119 -0
  3. package/dist/baseline.d.ts.map +1 -0
  4. package/dist/baseline.js +316 -0
  5. package/dist/baseline.js.map +1 -0
  6. package/dist/bin/cli.js +243 -458
  7. package/dist/bin/cli.js.map +1 -1
  8. package/dist/browser.d.ts +68 -25
  9. package/dist/browser.d.ts.map +1 -1
  10. package/dist/browser.js +500 -105
  11. package/dist/browser.js.map +1 -1
  12. package/dist/capture.d.ts +1 -6
  13. package/dist/capture.d.ts.map +1 -1
  14. package/dist/capture.js +12 -36
  15. package/dist/capture.js.map +1 -1
  16. package/dist/commands/compare.d.ts +18 -0
  17. package/dist/commands/compare.d.ts.map +1 -0
  18. package/dist/commands/compare.js +82 -0
  19. package/dist/commands/compare.js.map +1 -0
  20. package/dist/commands/detect.d.ts +25 -0
  21. package/dist/commands/detect.d.ts.map +1 -0
  22. package/dist/commands/detect.js +22 -0
  23. package/dist/commands/detect.js.map +1 -0
  24. package/dist/commands/doctor.d.ts +42 -0
  25. package/dist/commands/doctor.d.ts.map +1 -0
  26. package/dist/commands/doctor.js +119 -0
  27. package/dist/commands/doctor.js.map +1 -0
  28. package/dist/commands/login.d.ts +6 -0
  29. package/dist/commands/login.d.ts.map +1 -0
  30. package/dist/commands/login.js +34 -0
  31. package/dist/commands/login.js.map +1 -0
  32. package/dist/commands/pr.d.ts +32 -0
  33. package/dist/commands/pr.d.ts.map +1 -0
  34. package/dist/commands/pr.js +259 -0
  35. package/dist/commands/pr.js.map +1 -0
  36. package/dist/commands/prune.d.ts +10 -0
  37. package/dist/commands/prune.d.ts.map +1 -0
  38. package/dist/commands/prune.js +13 -0
  39. package/dist/commands/prune.js.map +1 -0
  40. package/dist/comparison.d.ts +104 -0
  41. package/dist/comparison.d.ts.map +1 -0
  42. package/dist/comparison.js +236 -0
  43. package/dist/comparison.js.map +1 -0
  44. package/dist/config.d.ts +46 -0
  45. package/dist/config.d.ts.map +1 -0
  46. package/dist/config.js +82 -0
  47. package/dist/config.js.map +1 -0
  48. package/dist/deployments.d.ts +71 -0
  49. package/dist/deployments.d.ts.map +1 -0
  50. package/dist/deployments.js +204 -0
  51. package/dist/deployments.js.map +1 -0
  52. package/dist/diff-pool.d.ts +17 -0
  53. package/dist/diff-pool.d.ts.map +1 -0
  54. package/dist/diff-pool.js +92 -0
  55. package/dist/diff-pool.js.map +1 -0
  56. package/dist/diff-worker.d.ts +32 -0
  57. package/dist/diff-worker.d.ts.map +1 -0
  58. package/dist/diff-worker.js +39 -0
  59. package/dist/diff-worker.js.map +1 -0
  60. package/dist/diff.d.ts +47 -0
  61. package/dist/diff.d.ts.map +1 -0
  62. package/dist/diff.js +281 -0
  63. package/dist/diff.js.map +1 -0
  64. package/dist/doctor.d.ts +79 -0
  65. package/dist/doctor.d.ts.map +1 -0
  66. package/dist/doctor.js +138 -0
  67. package/dist/doctor.js.map +1 -0
  68. package/dist/errors.d.ts +38 -0
  69. package/dist/errors.d.ts.map +1 -0
  70. package/dist/errors.js +59 -0
  71. package/dist/errors.js.map +1 -0
  72. package/dist/git.d.ts +61 -0
  73. package/dist/git.d.ts.map +1 -0
  74. package/dist/git.js +182 -0
  75. package/dist/git.js.map +1 -0
  76. package/dist/github.d.ts +163 -0
  77. package/dist/github.d.ts.map +1 -0
  78. package/dist/github.js +411 -0
  79. package/dist/github.js.map +1 -0
  80. package/dist/index.d.ts +22 -23
  81. package/dist/index.d.ts.map +1 -1
  82. package/dist/index.js +17 -67
  83. package/dist/index.js.map +1 -1
  84. package/dist/landing.d.ts +48 -0
  85. package/dist/landing.d.ts.map +1 -0
  86. package/dist/landing.js +93 -0
  87. package/dist/landing.js.map +1 -0
  88. package/dist/pkg.d.ts +17 -0
  89. package/dist/pkg.d.ts.map +1 -0
  90. package/dist/pkg.js +49 -0
  91. package/dist/pkg.js.map +1 -0
  92. package/dist/report.d.ts +20 -0
  93. package/dist/report.d.ts.map +1 -0
  94. package/dist/report.js +130 -0
  95. package/dist/report.js.map +1 -0
  96. package/dist/routes/imports.d.ts +46 -0
  97. package/dist/routes/imports.d.ts.map +1 -0
  98. package/dist/routes/imports.js +208 -0
  99. package/dist/routes/imports.js.map +1 -0
  100. package/dist/routes/vite.d.ts +15 -0
  101. package/dist/routes/vite.d.ts.map +1 -0
  102. package/dist/routes/vite.js +130 -0
  103. package/dist/routes/vite.js.map +1 -0
  104. package/dist/routes.d.ts +65 -11
  105. package/dist/routes.d.ts.map +1 -1
  106. package/dist/routes.js +346 -141
  107. package/dist/routes.js.map +1 -1
  108. package/dist/run.d.ts +116 -0
  109. package/dist/run.d.ts.map +1 -0
  110. package/dist/run.js +316 -0
  111. package/dist/run.js.map +1 -0
  112. package/dist/sameness.d.ts +91 -0
  113. package/dist/sameness.d.ts.map +1 -0
  114. package/dist/sameness.js +157 -0
  115. package/dist/sameness.js.map +1 -0
  116. package/dist/sessions.d.ts +50 -0
  117. package/dist/sessions.d.ts.map +1 -0
  118. package/dist/sessions.js +80 -0
  119. package/dist/sessions.js.map +1 -0
  120. package/dist/shift.d.ts +67 -0
  121. package/dist/shift.d.ts.map +1 -0
  122. package/dist/shift.js +184 -0
  123. package/dist/shift.js.map +1 -0
  124. package/dist/types.d.ts +179 -68
  125. package/dist/types.d.ts.map +1 -1
  126. package/dist/types.js +1 -0
  127. package/dist/types.js.map +1 -1
  128. package/dist/url.d.ts +12 -0
  129. package/dist/url.d.ts.map +1 -0
  130. package/dist/url.js +30 -0
  131. package/dist/url.js.map +1 -0
  132. package/dist/viewport.d.ts +6 -1
  133. package/dist/viewport.d.ts.map +1 -1
  134. package/dist/viewport.js +13 -7
  135. package/dist/viewport.js.map +1 -1
  136. package/package.json +21 -14
  137. package/skill/SKILL.md +56 -221
  138. package/dist/clipboard.d.ts +0 -9
  139. package/dist/clipboard.d.ts.map +0 -1
  140. package/dist/clipboard.js +0 -26
  141. package/dist/clipboard.js.map +0 -1
  142. package/dist/upload.d.ts +0 -39
  143. package/dist/upload.d.ts.map +0 -1
  144. package/dist/upload.js +0 -159
  145. package/dist/upload.js.map +0 -1
  146. package/dist/video.d.ts +0 -19
  147. package/dist/video.d.ts.map +0 -1
  148. package/dist/video.js +0 -263
  149. package/dist/video.js.map +0 -1
  150. package/skill/scripts/adapters/0x0st.sh +0 -36
  151. package/skill/scripts/adapters/blob.sh +0 -55
  152. package/skill/scripts/adapters/git-native.sh +0 -51
  153. package/skill/scripts/upload-and-copy.sh +0 -230
@@ -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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"}
@@ -0,0 +1,17 @@
1
+ /**
2
+ * A small worker pool for pixel diffs. Runs inline when worker threads are
3
+ * unavailable (e.g. executing from TypeScript sources under a test runner).
4
+ */
5
+ import { DiffJob, DiffJobResult } from './diff-worker.js';
6
+ export declare class DiffPool {
7
+ private readonly size;
8
+ private workers;
9
+ private queue;
10
+ private inline;
11
+ constructor(size?: number);
12
+ run(job: DiffJob): Promise<DiffJobResult>;
13
+ private spawn;
14
+ private pump;
15
+ close(): Promise<void>;
16
+ }
17
+ //# sourceMappingURL=diff-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-pool.d.ts","sourceRoot":"","sources":["../src/diff-pool.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAE,OAAO,EAAE,aAAa,EAAkB,MAAM,kBAAkB,CAAC;AAa1E,qBAAa,QAAQ;IAKP,OAAO,CAAC,QAAQ,CAAC,IAAI;IAJjC,OAAO,CAAC,OAAO,CAAc;IAC7B,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,MAAM,CAAS;gBAEM,IAAI,SAAiD;IAElF,GAAG,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAAC,aAAa,CAAC;IAOzC,OAAO,CAAC,KAAK;IA8Bb,OAAO,CAAC,IAAI;IAsBN,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAI7B"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A small worker pool for pixel diffs. Runs inline when worker threads are
3
+ * unavailable (e.g. executing from TypeScript sources under a test runner).
4
+ */
5
+ import os from 'os';
6
+ import { Worker } from 'worker_threads';
7
+ import { executeDiffJob } from './diff-worker.js';
8
+ function runInline(p) {
9
+ try {
10
+ p.resolve(executeDiffJob(p.job));
11
+ }
12
+ catch (e) {
13
+ p.reject(e);
14
+ }
15
+ }
16
+ export class DiffPool {
17
+ size;
18
+ workers = [];
19
+ queue = [];
20
+ inline = false;
21
+ constructor(size = Math.max(1, Math.min(4, os.cpus().length - 1))) {
22
+ this.size = size;
23
+ }
24
+ run(job) {
25
+ return new Promise((resolve, reject) => {
26
+ this.queue.push({ job, resolve, reject });
27
+ this.pump();
28
+ });
29
+ }
30
+ spawn() {
31
+ try {
32
+ const worker = new Worker(new URL('./diff-worker.js', import.meta.url));
33
+ const slot = { worker, busy: null };
34
+ worker.on('message', (msg) => {
35
+ const pending = slot.busy;
36
+ slot.busy = null;
37
+ if (pending) {
38
+ if (msg.error)
39
+ pending.reject(new Error(msg.error));
40
+ else
41
+ pending.resolve(msg);
42
+ }
43
+ this.pump();
44
+ });
45
+ worker.on('error', () => {
46
+ // The worker died: finish its job inline and stop using workers if none are left.
47
+ const pending = slot.busy;
48
+ slot.busy = null;
49
+ this.workers = this.workers.filter(w => w !== slot);
50
+ if (pending)
51
+ runInline(pending);
52
+ if (this.workers.length === 0)
53
+ this.inline = true;
54
+ this.pump();
55
+ });
56
+ this.workers.push(slot);
57
+ return slot;
58
+ }
59
+ catch {
60
+ this.inline = true;
61
+ return null;
62
+ }
63
+ }
64
+ pump() {
65
+ while (this.queue.length) {
66
+ if (this.inline) {
67
+ runInline(this.queue.shift());
68
+ continue;
69
+ }
70
+ let slot = this.workers.find(w => !w.busy);
71
+ if (!slot && this.workers.length < this.size)
72
+ slot = this.spawn() ?? undefined;
73
+ if (!slot) {
74
+ if (this.inline)
75
+ continue; // spawn failed: drain inline
76
+ return; // all workers busy: wait for a message
77
+ }
78
+ const pending = this.queue.shift();
79
+ slot.busy = pending;
80
+ // Copied, not transferred. Transferring detaches the buffers here, which
81
+ // leaves the `error` handler above holding a job it cannot run inline —
82
+ // so the fallback it exists to provide could never work. A structured
83
+ // clone of two PNGs costs far less than the diff that follows it.
84
+ slot.worker.postMessage(pending.job);
85
+ }
86
+ }
87
+ async close() {
88
+ await Promise.all(this.workers.map(w => w.worker.terminate()));
89
+ this.workers = [];
90
+ }
91
+ }
92
+ //# sourceMappingURL=diff-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-pool.js","sourceRoot":"","sources":["../src/diff-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Worker-thread entry: diff two PNGs held in memory and write every output.
3
+ * Keeps pixel work and file I/O off the main thread so captures keep flowing.
4
+ */
5
+ import { DiffOptions } from './diff.js';
6
+ import { ShiftSummary } from './types.js';
7
+ export interface DiffJob {
8
+ /** PNG bytes; transferred to the worker, so the caller must not reuse them. */
9
+ before: Uint8Array;
10
+ after: Uint8Array;
11
+ /** Where to write pre, post, diff, and (when produced) crops. */
12
+ outputs: {
13
+ before: string;
14
+ after: string;
15
+ diff: string;
16
+ cropBefore: string;
17
+ cropAfter: string;
18
+ };
19
+ options?: DiffOptions;
20
+ }
21
+ export interface DiffJobResult {
22
+ changedRatio: number;
23
+ changedPixels: number;
24
+ sizeChanged: boolean;
25
+ hasCrop: boolean;
26
+ hasHighlight: boolean;
27
+ /** Plain numbers, so the summary survives the structured clone back to the pool. */
28
+ shift?: ShiftSummary;
29
+ error?: string;
30
+ }
31
+ export declare function executeDiffJob(job: DiffJob): DiffJobResult;
32
+ //# sourceMappingURL=diff-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-worker.d.ts","sourceRoot":"","sources":["../src/diff-worker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,EAAc,WAAW,EAAE,MAAM,WAAW,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE1C,MAAM,WAAW,OAAO;IACtB,+EAA+E;IAC/E,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,UAAU,CAAC;IAClB,iEAAiE;IACjE,OAAO,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAChG,OAAO,CAAC,EAAE,WAAW,CAAC;CACvB;AAED,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,OAAO,CAAC;IACtB,oFAAoF;IACpF,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,GAAG,aAAa,CAmB1D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Worker-thread entry: diff two PNGs held in memory and write every output.
3
+ * Keeps pixel work and file I/O off the main thread so captures keep flowing.
4
+ */
5
+ import fs from 'fs';
6
+ import { parentPort } from 'worker_threads';
7
+ import { diffImages } from './diff.js';
8
+ export function executeDiffJob(job) {
9
+ const before = Buffer.from(job.before.buffer, job.before.byteOffset, job.before.byteLength);
10
+ const after = Buffer.from(job.after.buffer, job.after.byteOffset, job.after.byteLength);
11
+ fs.writeFileSync(job.outputs.before, before);
12
+ fs.writeFileSync(job.outputs.after, after);
13
+ const result = diffImages(before, after, job.options);
14
+ if (result.highlight)
15
+ fs.writeFileSync(job.outputs.diff, result.highlight);
16
+ if (result.crop) {
17
+ fs.writeFileSync(job.outputs.cropBefore, result.crop.before);
18
+ fs.writeFileSync(job.outputs.cropAfter, result.crop.after);
19
+ }
20
+ return {
21
+ changedRatio: result.changedRatio,
22
+ changedPixels: result.changedPixels,
23
+ sizeChanged: result.sizeChanged,
24
+ hasCrop: Boolean(result.crop),
25
+ hasHighlight: Boolean(result.highlight),
26
+ shift: result.shift,
27
+ };
28
+ }
29
+ if (parentPort) {
30
+ parentPort.on('message', (job) => {
31
+ try {
32
+ parentPort.postMessage(executeDiffJob(job));
33
+ }
34
+ catch (err) {
35
+ parentPort.postMessage({ error: err.message });
36
+ }
37
+ });
38
+ }
39
+ //# sourceMappingURL=diff-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-worker.js","sourceRoot":"","sources":["../src/diff-worker.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,MAAM,IAAI,CAAC;AACpB,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAe,MAAM,WAAW,CAAC;AAuBpD,MAAM,UAAU,cAAc,CAAC,GAAY;IACzC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;IAC5F,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;IACxF,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC7C,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,MAAM,GAAG,UAAU,CAAC,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtD,IAAI,MAAM,CAAC,SAAS;QAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,SAAS,CAAC,CAAC;IAC3E,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;QAChB,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC7D,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC7D,CAAC;IACD,OAAO;QACL,YAAY,EAAE,MAAM,CAAC,YAAY;QACjC,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC;QAC7B,YAAY,EAAE,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC;QACvC,KAAK,EAAE,MAAM,CAAC,KAAK;KACpB,CAAC;AACJ,CAAC;AAED,IAAI,UAAU,EAAE,CAAC;IACf,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAY,EAAE,EAAE;QACxC,IAAI,CAAC;YACH,UAAW,CAAC,WAAW,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,UAAW,CAAC,WAAW,CAAC,EAAE,KAAK,EAAG,GAAa,CAAC,OAAO,EAAmB,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
package/dist/diff.d.ts ADDED
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Pixel diff between two PNG screenshots.
3
+ *
4
+ * Pure JS (pixelmatch + pngjs): no native build step, works everywhere the
5
+ * CLI runs. Produces a change ratio, the bounding box of the change, a
6
+ * highlight image, and tight crops of before/after around the change.
7
+ */
8
+ import { PNG } from 'pngjs';
9
+ import { DiffRegion, DiffResult } from './types.js';
10
+ export interface DiffOptions {
11
+ /** Padding around the change region for crops, in device pixels. Default 80 */
12
+ padding?: number;
13
+ /** Minimum crop size in device pixels. Default 800x400 */
14
+ minCrop?: {
15
+ width: number;
16
+ height: number;
17
+ };
18
+ /** Integer factor to shrink the highlight image by (e.g. 2 for 2x captures). Default 1 */
19
+ highlightDownscale?: number;
20
+ /**
21
+ * Produce the red-highlight overlay. Default true.
22
+ *
23
+ * The pr pipeline uses the bounding box to crop Pre and Post, and ships that
24
+ * pair; it never publishes the overlay, and a wall of red pixels is not
25
+ * something a reviewer reads anyway. Encoding one per capture is a downscale
26
+ * and a deflate of a full-page image for nothing, so that path turns it off.
27
+ * Image mode still writes diff.png.
28
+ */
29
+ highlight?: boolean;
30
+ }
31
+ /** Box-filter downscale by an integer factor. */
32
+ export declare function downscale(src: PNG, factor: number): PNG;
33
+ export declare function decodePng(buffer: Buffer): PNG;
34
+ /** Expand a region by padding and to a minimum size, clamped to the canvas. */
35
+ export declare function expandRegion(region: DiffRegion, canvas: {
36
+ width: number;
37
+ height: number;
38
+ }, padding: number, min: {
39
+ width: number;
40
+ height: number;
41
+ }): DiffRegion;
42
+ /**
43
+ * Compare two PNG buffers. Images of different sizes are padded onto a shared
44
+ * canvas so a height change shows up as a change rather than an error.
45
+ */
46
+ export declare function diffImages(beforePng: Buffer, afterPng: Buffer, options?: DiffOptions): DiffResult;
47
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../src/diff.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAC5B,OAAO,EAAE,UAAU,EAAE,UAAU,EAAgB,MAAM,YAAY,CAAC;AAGlE,MAAM,WAAW,WAAW;IAC1B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0DAA0D;IAC1D,OAAO,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5C,0FAA0F;IAC1F,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA8ED,iDAAiD;AACjD,wBAAgB,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,GAAG,GAAG,CAoBvD;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,GAAG,CAE7C;AAoCD,+EAA+E;AAC/E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACzC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACrC,UAAU,CAqBZ;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,UAAU,CAuGrG"}