@qwertybit/pr-preview 0.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/CHANGELOG.md +33 -0
- package/LICENSE +30 -0
- package/README.md +242 -0
- package/assets/fonts/Inter.ttf +0 -0
- package/dist/cli/index.js +2251 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/harness/assets/index-BvUCb8Lb.css +1 -0
- package/dist/harness/assets/index-ZmTksMZ5.js +1 -0
- package/dist/harness/index.html +14 -0
- package/dist/harness/logo-wordmark.svg +22 -0
- package/dist/harness/logo.svg +23 -0
- package/dist/inpage/recorder.global.js +344 -0
- package/package.json +83 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
<svg width="248" height="64" viewBox="0 0 248 64" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="PR Preview">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="prp-grad-w" x1="6" y1="4" x2="58" y2="60" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#635BFF"/>
|
|
5
|
+
<stop offset="1" stop-color="#4B45C6"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="15" fill="url(#prp-grad-w)"/>
|
|
9
|
+
<g transform="rotate(-9 32 19)">
|
|
10
|
+
<rect x="13" y="13.5" width="38" height="9.5" rx="2.4" fill="#FFFFFF"/>
|
|
11
|
+
<path d="M19 13.5 24 23M27 13.5 32 23M35 13.5 40 23M43 13.5 48 23"
|
|
12
|
+
stroke="#4B45C6" stroke-width="2.3" stroke-linecap="round"/>
|
|
13
|
+
</g>
|
|
14
|
+
<rect x="13" y="26" width="38" height="24" rx="4.5" fill="#FFFFFF"/>
|
|
15
|
+
<g stroke="#4B45C6" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
|
16
|
+
<path d="M23 32 17.5 38 23 44"/>
|
|
17
|
+
<path d="M41 32 46.5 38 41 44"/>
|
|
18
|
+
<path d="M35.5 30.5 28.5 45.5" stroke="#635BFF"/>
|
|
19
|
+
</g>
|
|
20
|
+
<text x="78" y="40" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', Inter, sans-serif"
|
|
21
|
+
font-size="26" font-weight="700" fill="#1F2328">PR Preview</text>
|
|
22
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="PR Preview">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="prp-grad" x1="6" y1="4" x2="58" y2="60" gradientUnits="userSpaceOnUse">
|
|
4
|
+
<stop stop-color="#635BFF"/>
|
|
5
|
+
<stop offset="1" stop-color="#4B45C6"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect x="2" y="2" width="60" height="60" rx="15" fill="url(#prp-grad)"/>
|
|
9
|
+
<!-- clapperboard hinge bar (tilted) — the "clip / recording" signal -->
|
|
10
|
+
<g transform="rotate(-9 32 19)">
|
|
11
|
+
<rect x="13" y="13.5" width="38" height="9.5" rx="2.4" fill="#FFFFFF"/>
|
|
12
|
+
<path d="M19 13.5 24 23M27 13.5 32 23M35 13.5 40 23M43 13.5 48 23"
|
|
13
|
+
stroke="#4B45C6" stroke-width="2.3" stroke-linecap="round"/>
|
|
14
|
+
</g>
|
|
15
|
+
<!-- board body -->
|
|
16
|
+
<rect x="13" y="26" width="38" height="24" rx="4.5" fill="#FFFFFF"/>
|
|
17
|
+
<!-- </> — the "code" signal -->
|
|
18
|
+
<g stroke="#4B45C6" stroke-width="2.7" stroke-linecap="round" stroke-linejoin="round" fill="none">
|
|
19
|
+
<path d="M23 32 17.5 38 23 44"/>
|
|
20
|
+
<path d="M41 32 46.5 38 41 44"/>
|
|
21
|
+
<path d="M35.5 30.5 28.5 45.5" stroke="#635BFF"/>
|
|
22
|
+
</g>
|
|
23
|
+
</svg>
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/recorder/inpage/selector.ts
|
|
4
|
+
var TESTID_ATTRS = ["data-testid", "data-test", "data-cy", "data-qa"];
|
|
5
|
+
function looksGenerated(id) {
|
|
6
|
+
if (/^[«:]/.test(id)) return true;
|
|
7
|
+
if (/\d{4,}/.test(id)) return true;
|
|
8
|
+
if (/[0-9a-f]{6,}/i.test(id)) return true;
|
|
9
|
+
if (/^(radix|headlessui|mui|aria)-/i.test(id)) return true;
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
function cssEscape(value) {
|
|
13
|
+
return window.CSS && CSS.escape ? CSS.escape(value) : value.replace(/"/g, '\\"');
|
|
14
|
+
}
|
|
15
|
+
function looksLikeExampleValue(s) {
|
|
16
|
+
const t = s.trim();
|
|
17
|
+
if (!t) return true;
|
|
18
|
+
return /^[\d.,:%$£€/\\\s+-]+$/.test(t);
|
|
19
|
+
}
|
|
20
|
+
function isUnique(selector, el) {
|
|
21
|
+
try {
|
|
22
|
+
const root = el.getRootNode();
|
|
23
|
+
const matches = root.querySelectorAll(selector);
|
|
24
|
+
return matches.length === 1 && matches[0] === el;
|
|
25
|
+
} catch {
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function cssPath(el) {
|
|
30
|
+
const parts = [];
|
|
31
|
+
let node = el;
|
|
32
|
+
while (node && node !== document.documentElement && parts.length < 8) {
|
|
33
|
+
let part = node.tagName.toLowerCase();
|
|
34
|
+
const parent = node.parentElement;
|
|
35
|
+
if (parent) {
|
|
36
|
+
const sameTag = Array.from(parent.children).filter(
|
|
37
|
+
(c) => c.tagName === node.tagName
|
|
38
|
+
);
|
|
39
|
+
if (sameTag.length > 1) {
|
|
40
|
+
part += `:nth-of-type(${sameTag.indexOf(node) + 1})`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
parts.unshift(part);
|
|
44
|
+
if (parent && isUnique(parts.join(" > "), el)) break;
|
|
45
|
+
node = parent;
|
|
46
|
+
}
|
|
47
|
+
return parts.join(" > ");
|
|
48
|
+
}
|
|
49
|
+
function buildSelectors(target) {
|
|
50
|
+
const el = closestInteractive(target) ?? target;
|
|
51
|
+
const out = [];
|
|
52
|
+
const push = (sel) => {
|
|
53
|
+
if (sel && !out.includes(sel) && isUnique(sel, el)) out.push(sel);
|
|
54
|
+
};
|
|
55
|
+
for (const attr of TESTID_ATTRS) {
|
|
56
|
+
const v = el.getAttribute(attr);
|
|
57
|
+
if (v) push(`[${attr}="${cssEscape(v)}"]`);
|
|
58
|
+
}
|
|
59
|
+
if (el.id && !looksGenerated(el.id)) push(`#${cssEscape(el.id)}`);
|
|
60
|
+
const name = el.getAttribute("name");
|
|
61
|
+
if (name) push(`${el.tagName.toLowerCase()}[name="${cssEscape(name)}"]`);
|
|
62
|
+
const ariaLabel = el.getAttribute("aria-label");
|
|
63
|
+
if (ariaLabel) push(`[aria-label="${cssEscape(ariaLabel)}"]`);
|
|
64
|
+
const placeholder = el.getAttribute("placeholder");
|
|
65
|
+
if (placeholder && !looksLikeExampleValue(placeholder)) {
|
|
66
|
+
push(`[placeholder="${cssEscape(placeholder)}"]`);
|
|
67
|
+
}
|
|
68
|
+
const text = (el.textContent ?? "").trim();
|
|
69
|
+
if (text.length > 0 && text.length <= 40 && !text.includes('"') && ["a", "button", "summary", "label"].includes(el.tagName.toLowerCase())) {
|
|
70
|
+
const sel = `${el.tagName.toLowerCase()}:has-text("${text}")`;
|
|
71
|
+
const root = el.getRootNode();
|
|
72
|
+
const sameText = Array.from(root.querySelectorAll(el.tagName)).filter(
|
|
73
|
+
(e) => (e.textContent ?? "").trim() === text
|
|
74
|
+
);
|
|
75
|
+
if (sameText.length === 1 && !out.includes(sel)) out.push(sel);
|
|
76
|
+
}
|
|
77
|
+
const inShadow = el.getRootNode() instanceof ShadowRoot;
|
|
78
|
+
if (!inShadow) {
|
|
79
|
+
const path = cssPath(el);
|
|
80
|
+
if (path && isUnique(path, el)) push(path);
|
|
81
|
+
if (out.length === 0 && path) out.push(path);
|
|
82
|
+
}
|
|
83
|
+
return out;
|
|
84
|
+
}
|
|
85
|
+
function closestInteractive(el) {
|
|
86
|
+
return el.closest(
|
|
87
|
+
'button, a[href], input, select, textarea, summary, [role="button"], [role="link"], [role="tab"], [role="menuitem"], [role="checkbox"], [role="switch"], label, [onclick], [tabindex]'
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/recorder/inpage/recorder.ts
|
|
92
|
+
var SCROLL_IDLE_MS = 250;
|
|
93
|
+
var start = Date.now();
|
|
94
|
+
function emit(event) {
|
|
95
|
+
try {
|
|
96
|
+
void window.__prPreviewEmit?.(JSON.stringify(event));
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
function frameUrl() {
|
|
101
|
+
return location.pathname + location.search + location.hash;
|
|
102
|
+
}
|
|
103
|
+
function norm(x, max) {
|
|
104
|
+
return max > 0 ? Math.min(1, Math.max(0, x / max)) : 0;
|
|
105
|
+
}
|
|
106
|
+
function realTarget(e) {
|
|
107
|
+
const path = e.composedPath?.();
|
|
108
|
+
const first = path && path[0];
|
|
109
|
+
if (first instanceof Element) return first;
|
|
110
|
+
return e.target instanceof Element ? e.target : null;
|
|
111
|
+
}
|
|
112
|
+
function installRecorder() {
|
|
113
|
+
if (window.__prPreviewRecorderInstalled) return;
|
|
114
|
+
window.__prPreviewRecorderInstalled = true;
|
|
115
|
+
document.addEventListener(
|
|
116
|
+
"click",
|
|
117
|
+
(e) => {
|
|
118
|
+
if (!e.isTrusted) return;
|
|
119
|
+
if (e.detail === 0) return;
|
|
120
|
+
const target = realTarget(e);
|
|
121
|
+
if (!target) return;
|
|
122
|
+
if (window.__prPreviewRecording) window.__prPreviewCursor?.clickPulse();
|
|
123
|
+
const el = closestInteractive(target) ?? target;
|
|
124
|
+
emit({
|
|
125
|
+
kind: "click",
|
|
126
|
+
selectors: buildSelectors(target),
|
|
127
|
+
xNorm: norm(e.clientX, window.innerWidth),
|
|
128
|
+
yNorm: norm(e.clientY, window.innerHeight),
|
|
129
|
+
text: (el.textContent ?? "").trim().slice(0, 40) || void 0,
|
|
130
|
+
frameUrl: frameUrl(),
|
|
131
|
+
ts: Date.now() - start
|
|
132
|
+
});
|
|
133
|
+
},
|
|
134
|
+
{ capture: true }
|
|
135
|
+
);
|
|
136
|
+
let mouseRaf = 0;
|
|
137
|
+
let lastX = 0;
|
|
138
|
+
let lastY = 0;
|
|
139
|
+
document.addEventListener(
|
|
140
|
+
"mousemove",
|
|
141
|
+
(e) => {
|
|
142
|
+
if (!e.isTrusted || !window.__prPreviewRecording) return;
|
|
143
|
+
lastX = e.clientX;
|
|
144
|
+
lastY = e.clientY;
|
|
145
|
+
if (mouseRaf) return;
|
|
146
|
+
mouseRaf = requestAnimationFrame(() => {
|
|
147
|
+
mouseRaf = 0;
|
|
148
|
+
window.__prPreviewCursor?.show(lastX, lastY);
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
{ capture: true, passive: true }
|
|
152
|
+
);
|
|
153
|
+
document.addEventListener(
|
|
154
|
+
"input",
|
|
155
|
+
(e) => {
|
|
156
|
+
if (!e.isTrusted) return;
|
|
157
|
+
const el = realTarget(e);
|
|
158
|
+
if (!el || !("value" in el)) return;
|
|
159
|
+
if (el.tagName === "SELECT") return;
|
|
160
|
+
const type = el.type ?? "";
|
|
161
|
+
if (["checkbox", "radio", "file", "range", "color"].includes(type)) return;
|
|
162
|
+
const rect = el.getBoundingClientRect();
|
|
163
|
+
emit({
|
|
164
|
+
kind: "input",
|
|
165
|
+
selectors: buildSelectors(el),
|
|
166
|
+
value: el.value,
|
|
167
|
+
xNorm: norm(rect.x + rect.width / 2, window.innerWidth),
|
|
168
|
+
yNorm: norm(rect.y + rect.height / 2, window.innerHeight),
|
|
169
|
+
frameUrl: frameUrl(),
|
|
170
|
+
ts: Date.now() - start
|
|
171
|
+
});
|
|
172
|
+
},
|
|
173
|
+
{ capture: true }
|
|
174
|
+
);
|
|
175
|
+
document.addEventListener(
|
|
176
|
+
"change",
|
|
177
|
+
(e) => {
|
|
178
|
+
if (!e.isTrusted) return;
|
|
179
|
+
const el = realTarget(e);
|
|
180
|
+
if (!(el instanceof HTMLSelectElement)) return;
|
|
181
|
+
const rect = el.getBoundingClientRect();
|
|
182
|
+
emit({
|
|
183
|
+
kind: "select",
|
|
184
|
+
selectors: buildSelectors(el),
|
|
185
|
+
value: el.value,
|
|
186
|
+
xNorm: norm(rect.x + rect.width / 2, window.innerWidth),
|
|
187
|
+
yNorm: norm(rect.y + rect.height / 2, window.innerHeight),
|
|
188
|
+
frameUrl: frameUrl(),
|
|
189
|
+
ts: Date.now() - start
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
{ capture: true }
|
|
193
|
+
);
|
|
194
|
+
document.addEventListener(
|
|
195
|
+
"keydown",
|
|
196
|
+
(e) => {
|
|
197
|
+
if (!e.isTrusted) return;
|
|
198
|
+
if (!["Enter", "Escape", "Tab", "ArrowDown", "ArrowUp"].includes(e.key)) return;
|
|
199
|
+
const target = realTarget(e);
|
|
200
|
+
emit({
|
|
201
|
+
kind: "key",
|
|
202
|
+
key: e.key,
|
|
203
|
+
selectors: target ? buildSelectors(target) : [],
|
|
204
|
+
frameUrl: frameUrl(),
|
|
205
|
+
ts: Date.now() - start
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
{ capture: true }
|
|
209
|
+
);
|
|
210
|
+
let scrollTimer;
|
|
211
|
+
let lastScrollTarget = "window";
|
|
212
|
+
document.addEventListener(
|
|
213
|
+
"scroll",
|
|
214
|
+
(e) => {
|
|
215
|
+
if (!e.isTrusted) return;
|
|
216
|
+
const t = e.target;
|
|
217
|
+
lastScrollTarget = t === document || t === document.documentElement || t === window ? "window" : t;
|
|
218
|
+
clearTimeout(scrollTimer);
|
|
219
|
+
scrollTimer = setTimeout(() => {
|
|
220
|
+
if (lastScrollTarget === "window") {
|
|
221
|
+
const doc = document.documentElement;
|
|
222
|
+
emit({
|
|
223
|
+
kind: "scroll",
|
|
224
|
+
target: "window",
|
|
225
|
+
xNorm: norm(window.scrollX, doc.scrollWidth - window.innerWidth),
|
|
226
|
+
yNorm: norm(window.scrollY, doc.scrollHeight - window.innerHeight),
|
|
227
|
+
frameUrl: frameUrl(),
|
|
228
|
+
ts: Date.now() - start
|
|
229
|
+
});
|
|
230
|
+
} else {
|
|
231
|
+
const el = lastScrollTarget;
|
|
232
|
+
emit({
|
|
233
|
+
kind: "scroll",
|
|
234
|
+
target: buildSelectors(el)[0] ?? "window",
|
|
235
|
+
xNorm: norm(el.scrollLeft, el.scrollWidth - el.clientWidth),
|
|
236
|
+
yNorm: norm(el.scrollTop, el.scrollHeight - el.clientHeight),
|
|
237
|
+
frameUrl: frameUrl(),
|
|
238
|
+
ts: Date.now() - start
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}, SCROLL_IDLE_MS);
|
|
242
|
+
},
|
|
243
|
+
{ capture: true, passive: true }
|
|
244
|
+
);
|
|
245
|
+
const fireNavigate = () => emit({ kind: "navigate", url: frameUrl(), frameUrl: frameUrl(), ts: Date.now() - start });
|
|
246
|
+
const wrap = (method) => {
|
|
247
|
+
const original = history[method].bind(history);
|
|
248
|
+
history[method] = ((...args) => {
|
|
249
|
+
const result = original(...args);
|
|
250
|
+
fireNavigate();
|
|
251
|
+
return result;
|
|
252
|
+
});
|
|
253
|
+
};
|
|
254
|
+
wrap("pushState");
|
|
255
|
+
wrap("replaceState");
|
|
256
|
+
window.addEventListener("popstate", fireNavigate);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/recorder/inpage/cursor.ts
|
|
260
|
+
var CURSOR_SVG = `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M5.5 3.21V20.8c0 .45.54.67.85.35l4.86-4.86a.5.5 0 0 1 .35-.15h6.87a.5.5 0 0 0 .35-.85L6.35 2.85a.5.5 0 0 0-.85.36Z" fill="#1a1a2e" stroke="#fff" stroke-width="1.5"/></svg>`;
|
|
261
|
+
function ease(t) {
|
|
262
|
+
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
|
263
|
+
}
|
|
264
|
+
function installCursor() {
|
|
265
|
+
if (window.__prPreviewCursor) return;
|
|
266
|
+
let el = null;
|
|
267
|
+
let pos = { x: (window.innerWidth || 1280) / 2, y: (window.innerHeight || 800) / 2 };
|
|
268
|
+
function ensure() {
|
|
269
|
+
if (el && document.body.contains(el)) return el;
|
|
270
|
+
el = document.createElement("div");
|
|
271
|
+
el.id = "__pr-preview-cursor";
|
|
272
|
+
el.style.cssText = "position:fixed;z-index:2147483647;pointer-events:none;width:24px;height:24px;transition:none;will-change:transform;display:none;filter:drop-shadow(0 1px 2px rgba(0,0,0,.4));";
|
|
273
|
+
el.innerHTML = CURSOR_SVG;
|
|
274
|
+
document.body.appendChild(el);
|
|
275
|
+
return el;
|
|
276
|
+
}
|
|
277
|
+
function place(x, y) {
|
|
278
|
+
pos = { x, y };
|
|
279
|
+
const node = ensure();
|
|
280
|
+
node.style.transform = `translate(${x}px, ${y}px)`;
|
|
281
|
+
}
|
|
282
|
+
window.__prPreviewCursor = {
|
|
283
|
+
show(x, y) {
|
|
284
|
+
const node = ensure();
|
|
285
|
+
place(x, y);
|
|
286
|
+
node.style.display = "block";
|
|
287
|
+
},
|
|
288
|
+
moveTo(x, y, durationMs) {
|
|
289
|
+
return new Promise((resolve) => {
|
|
290
|
+
const node = ensure();
|
|
291
|
+
node.style.display = "block";
|
|
292
|
+
const from = { ...pos };
|
|
293
|
+
const dist = Math.hypot(x - from.x, y - from.y);
|
|
294
|
+
const dur = Math.max(140, Math.min(durationMs, dist * 0.7));
|
|
295
|
+
if (dist < 1) {
|
|
296
|
+
resolve();
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
const t0 = performance.now();
|
|
300
|
+
const tick = (now) => {
|
|
301
|
+
const t = Math.min(1, (now - t0) / dur);
|
|
302
|
+
const k = ease(t);
|
|
303
|
+
place(from.x + (x - from.x) * k, from.y + (y - from.y) * k);
|
|
304
|
+
if (t < 1) requestAnimationFrame(tick);
|
|
305
|
+
else resolve();
|
|
306
|
+
};
|
|
307
|
+
requestAnimationFrame(tick);
|
|
308
|
+
});
|
|
309
|
+
},
|
|
310
|
+
clickPulse() {
|
|
311
|
+
return new Promise((resolve) => {
|
|
312
|
+
const ring = document.createElement("div");
|
|
313
|
+
ring.style.cssText = `position:fixed;z-index:2147483646;pointer-events:none;left:${pos.x - 14}px;top:${pos.y - 14}px;width:28px;height:28px;border-radius:50%;border:3px solid #4f8ef7;opacity:.9;transform:scale(.4);transition:transform .3s ease-out,opacity .3s ease-out;`;
|
|
314
|
+
document.body.appendChild(ring);
|
|
315
|
+
requestAnimationFrame(() => {
|
|
316
|
+
ring.style.transform = "scale(1.4)";
|
|
317
|
+
ring.style.opacity = "0";
|
|
318
|
+
});
|
|
319
|
+
setTimeout(() => {
|
|
320
|
+
ring.remove();
|
|
321
|
+
resolve();
|
|
322
|
+
}, 320);
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
hide() {
|
|
326
|
+
if (el) el.style.display = "none";
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// src/recorder/inpage/index.ts
|
|
332
|
+
(() => {
|
|
333
|
+
try {
|
|
334
|
+
if (window.self === window.top) return;
|
|
335
|
+
} catch {
|
|
336
|
+
}
|
|
337
|
+
installRecorder();
|
|
338
|
+
if (document.readyState === "loading") {
|
|
339
|
+
document.addEventListener("DOMContentLoaded", installCursor);
|
|
340
|
+
} else {
|
|
341
|
+
installCursor();
|
|
342
|
+
}
|
|
343
|
+
})();
|
|
344
|
+
})();
|
package/package.json
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@qwertybit/pr-preview",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Record a UI journey once, get polished before/after MP4 clips of every pull request — automatically.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "QwertyBit <vlad@flowpoint.ai>",
|
|
7
|
+
"homepage": "https://pr-preview.com",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/QwertyBit-Ventures/pr-preview.git",
|
|
11
|
+
"directory": "npm_package"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/QwertyBit-Ventures/pr-preview/issues"
|
|
15
|
+
},
|
|
16
|
+
"type": "module",
|
|
17
|
+
"bin": {
|
|
18
|
+
"pr-preview": "dist/cli/index.js"
|
|
19
|
+
},
|
|
20
|
+
"main": "./dist/cli/index.js",
|
|
21
|
+
"files": [
|
|
22
|
+
"dist",
|
|
23
|
+
"assets",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
26
|
+
"CHANGELOG.md"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=20"
|
|
30
|
+
},
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "npm run build:harness && npm run build:lib",
|
|
36
|
+
"build:lib": "tsup",
|
|
37
|
+
"build:harness": "vite build harness",
|
|
38
|
+
"dev:harness": "vite harness",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "vitest run test/unit",
|
|
41
|
+
"test:e2e": "vitest run test/e2e",
|
|
42
|
+
"test:all": "vitest run",
|
|
43
|
+
"test:watch": "vitest test/unit",
|
|
44
|
+
"prepublishOnly": "npm run build"
|
|
45
|
+
},
|
|
46
|
+
"keywords": [
|
|
47
|
+
"pull-request",
|
|
48
|
+
"pr",
|
|
49
|
+
"code-review",
|
|
50
|
+
"demo",
|
|
51
|
+
"mp4",
|
|
52
|
+
"gif",
|
|
53
|
+
"screen-recording",
|
|
54
|
+
"visual-diff",
|
|
55
|
+
"before-after",
|
|
56
|
+
"playwright",
|
|
57
|
+
"git",
|
|
58
|
+
"frontend"
|
|
59
|
+
],
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"commander": "^12.1.0",
|
|
62
|
+
"get-port": "^7.1.0",
|
|
63
|
+
"gifenc": "^1.0.3",
|
|
64
|
+
"jiti": "^2.4.2",
|
|
65
|
+
"ora": "^8.2.0",
|
|
66
|
+
"picocolors": "^1.1.1",
|
|
67
|
+
"playwright": "^1.52.0",
|
|
68
|
+
"sharp": "^0.33.5",
|
|
69
|
+
"tree-kill": "^1.2.2",
|
|
70
|
+
"ws": "^8.18.0",
|
|
71
|
+
"zod": "^3.24.0"
|
|
72
|
+
},
|
|
73
|
+
"devDependencies": {
|
|
74
|
+
"@preact/preset-vite": "^2.10.0",
|
|
75
|
+
"@types/node": "^22.10.0",
|
|
76
|
+
"@types/ws": "^8.5.13",
|
|
77
|
+
"preact": "^10.25.0",
|
|
78
|
+
"tsup": "^8.3.5",
|
|
79
|
+
"typescript": "^5.7.0",
|
|
80
|
+
"vite": "^6.0.0",
|
|
81
|
+
"vitest": "^2.1.8"
|
|
82
|
+
}
|
|
83
|
+
}
|