@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.
- package/README.md +174 -233
- 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 +243 -458
- package/dist/bin/cli.js.map +1 -1
- package/dist/browser.d.ts +68 -25
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +500 -105
- package/dist/browser.js.map +1 -1
- package/dist/capture.d.ts +1 -6
- package/dist/capture.d.ts.map +1 -1
- package/dist/capture.js +12 -36
- package/dist/capture.js.map +1 -1
- package/dist/commands/compare.d.ts +18 -0
- package/dist/commands/compare.d.ts.map +1 -0
- package/dist/commands/compare.js +82 -0
- package/dist/commands/compare.js.map +1 -0
- package/dist/commands/detect.d.ts +25 -0
- package/dist/commands/detect.d.ts.map +1 -0
- package/dist/commands/detect.js +22 -0
- package/dist/commands/detect.js.map +1 -0
- package/dist/commands/doctor.d.ts +42 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +119 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +34 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/pr.d.ts +32 -0
- package/dist/commands/pr.d.ts.map +1 -0
- package/dist/commands/pr.js +259 -0
- package/dist/commands/pr.js.map +1 -0
- package/dist/commands/prune.d.ts +10 -0
- package/dist/commands/prune.d.ts.map +1 -0
- package/dist/commands/prune.js +13 -0
- package/dist/commands/prune.js.map +1 -0
- 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 +46 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +82 -0
- package/dist/config.js.map +1 -0
- 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 +17 -0
- package/dist/diff-pool.d.ts.map +1 -0
- package/dist/diff-pool.js +92 -0
- package/dist/diff-pool.js.map +1 -0
- package/dist/diff-worker.d.ts +32 -0
- package/dist/diff-worker.d.ts.map +1 -0
- package/dist/diff-worker.js +39 -0
- package/dist/diff-worker.js.map +1 -0
- package/dist/diff.d.ts +47 -0
- package/dist/diff.d.ts.map +1 -0
- package/dist/diff.js +281 -0
- package/dist/diff.js.map +1 -0
- package/dist/doctor.d.ts +79 -0
- package/dist/doctor.d.ts.map +1 -0
- package/dist/doctor.js +138 -0
- package/dist/doctor.js.map +1 -0
- package/dist/errors.d.ts +38 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +59 -0
- package/dist/errors.js.map +1 -0
- package/dist/git.d.ts +61 -0
- package/dist/git.d.ts.map +1 -0
- package/dist/git.js +182 -0
- package/dist/git.js.map +1 -0
- package/dist/github.d.ts +163 -0
- package/dist/github.d.ts.map +1 -0
- package/dist/github.js +411 -0
- package/dist/github.js.map +1 -0
- package/dist/index.d.ts +22 -23
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -67
- package/dist/index.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 +17 -0
- package/dist/pkg.d.ts.map +1 -0
- package/dist/pkg.js +49 -0
- package/dist/pkg.js.map +1 -0
- package/dist/report.d.ts +20 -0
- package/dist/report.d.ts.map +1 -0
- package/dist/report.js +130 -0
- package/dist/report.js.map +1 -0
- package/dist/routes/imports.d.ts +46 -0
- package/dist/routes/imports.d.ts.map +1 -0
- package/dist/routes/imports.js +208 -0
- package/dist/routes/imports.js.map +1 -0
- package/dist/routes/vite.d.ts +15 -0
- package/dist/routes/vite.d.ts.map +1 -0
- package/dist/routes/vite.js +130 -0
- package/dist/routes/vite.js.map +1 -0
- package/dist/routes.d.ts +65 -11
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +346 -141
- package/dist/routes.js.map +1 -1
- package/dist/run.d.ts +116 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +316 -0
- package/dist/run.js.map +1 -0
- 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 +50 -0
- package/dist/sessions.d.ts.map +1 -0
- package/dist/sessions.js +80 -0
- package/dist/sessions.js.map +1 -0
- 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 +179 -68
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -0
- 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/dist/viewport.d.ts +6 -1
- package/dist/viewport.d.ts.map +1 -1
- package/dist/viewport.js +13 -7
- package/dist/viewport.js.map +1 -1
- package/package.json +21 -14
- package/skill/SKILL.md +56 -221
- package/dist/clipboard.d.ts +0 -9
- package/dist/clipboard.d.ts.map +0 -1
- package/dist/clipboard.js +0 -26
- package/dist/clipboard.js.map +0 -1
- package/dist/upload.d.ts +0 -39
- package/dist/upload.d.ts.map +0 -1
- package/dist/upload.js +0 -159
- package/dist/upload.js.map +0 -1
- package/dist/video.d.ts +0 -19
- package/dist/video.d.ts.map +0 -1
- package/dist/video.js +0 -263
- package/dist/video.js.map +0 -1
- package/skill/scripts/adapters/0x0st.sh +0 -36
- package/skill/scripts/adapters/blob.sh +0 -55
- package/skill/scripts/adapters/git-native.sh +0 -51
- 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"}
|