@qwertybit/pr-preview 0.1.5 → 0.1.7
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 +21 -1
- package/README.md +3 -2
- package/dist/cli/index.js +44 -14
- package/dist/cli/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,25 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.1.7] — 2026-07-08
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **PR Preview for Teams early-access nudge** — when a recording finishes, a short
|
|
14
|
+
promo points to the early-access list (`pr-preview.com/#teams`). In the manual CLI
|
|
15
|
+
(`pr-preview run`) it prints as the final block with a clickable terminal link
|
|
16
|
+
(OSC 8, degrading to a plain URL where unsupported); in the `/record` skill flow it
|
|
17
|
+
rides along with the `finish_recording` output as a markdown link.
|
|
18
|
+
|
|
19
|
+
## [0.1.6] — 2026-07-07
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **Demo shown inline** — the README now embeds the demo directly at the top
|
|
24
|
+
(an inline `<video>` player on GitHub; the autoplaying GIF on npm, which can't
|
|
25
|
+
render video) instead of a separate "Watch in higher quality" link. Demo URLs
|
|
26
|
+
are version-pinned so the CDN/GitHub image proxy never serve a stale clip.
|
|
27
|
+
|
|
9
28
|
## [0.1.5] — 2026-07-07
|
|
10
29
|
|
|
11
30
|
### Changed
|
|
@@ -108,7 +127,8 @@ Initial release.
|
|
|
108
127
|
CSP `frame-ancestors`) are stripped.
|
|
109
128
|
- CLI: `pr-preview init`, `pr-preview record`, `pr-preview run`.
|
|
110
129
|
|
|
111
|
-
[Unreleased]: https://github.com/QwertyBit-Ventures/pr-preview/compare/v0.1.
|
|
130
|
+
[Unreleased]: https://github.com/QwertyBit-Ventures/pr-preview/compare/v0.1.6...HEAD
|
|
131
|
+
[0.1.6]: https://github.com/QwertyBit-Ventures/pr-preview/compare/v0.1.5...v0.1.6
|
|
112
132
|
[0.1.5]: https://github.com/QwertyBit-Ventures/pr-preview/compare/v0.1.4...v0.1.5
|
|
113
133
|
[0.1.4]: https://github.com/QwertyBit-Ventures/pr-preview/compare/v0.1.0...v0.1.4
|
|
114
134
|
[0.1.0]: https://github.com/QwertyBit-Ventures/pr-preview/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -15,12 +15,13 @@
|
|
|
15
15
|
<a href="https://pr-preview.com"><img src="https://img.shields.io/badge/website-pr--preview.com-635bff" alt="pr-preview.com" /></a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
+
<!-- GitHub renders this <video> as an inline player; npm strips it and falls back to the GIF below. -->
|
|
18
19
|
<p align="center">
|
|
19
|
-
<
|
|
20
|
+
<video src="https://cdn.jsdelivr.net/npm/@qwertybit/pr-preview@0.1.6/assets/demo.mp4" width="720" controls autoplay muted loop playsinline></video>
|
|
20
21
|
</p>
|
|
21
22
|
|
|
22
23
|
<p align="center">
|
|
23
|
-
<
|
|
24
|
+
<img src="https://cdn.jsdelivr.net/npm/@qwertybit/pr-preview@0.1.6/assets/demo.gif" alt="PR Preview demo — a Claude Code prompt drives the app in Chrome and produces before/after videos" width="720" />
|
|
24
25
|
</p>
|
|
25
26
|
|
|
26
27
|
**PR Preview** opens your app in a controlled Chrome window and records the journey you perform —
|
package/dist/cli/index.js
CHANGED
|
@@ -2560,6 +2560,31 @@ async function detectLocalApps(preferred) {
|
|
|
2560
2560
|
return results.filter((r) => r !== null);
|
|
2561
2561
|
}
|
|
2562
2562
|
|
|
2563
|
+
// src/branding.ts
|
|
2564
|
+
import pc2 from "picocolors";
|
|
2565
|
+
var TEAMS_URL = "https://pr-preview.com/#teams";
|
|
2566
|
+
function osc8(url, label) {
|
|
2567
|
+
return `\x1B]8;;${url}\x1B\\${label}\x1B]8;;\x1B\\`;
|
|
2568
|
+
}
|
|
2569
|
+
function isInteractive() {
|
|
2570
|
+
return Boolean(process.stdout.isTTY) && process.env.TERM !== "dumb";
|
|
2571
|
+
}
|
|
2572
|
+
function teamsPromoTerminal() {
|
|
2573
|
+
const link = isInteractive() ? osc8(TEAMS_URL, TEAMS_URL) : TEAMS_URL;
|
|
2574
|
+
return [
|
|
2575
|
+
`${pc2.magenta("\u2605")} ${pc2.bold("Love it? PR Preview for Teams is coming")} \u2014`,
|
|
2576
|
+
pc2.dim(" hosted clips, team reviews & sharing."),
|
|
2577
|
+
` Join the early-access list \u2192 ${pc2.cyan(link)}`
|
|
2578
|
+
].join("\n");
|
|
2579
|
+
}
|
|
2580
|
+
function teamsPromoMarkdown() {
|
|
2581
|
+
return `\u{1F49C} Enjoying this? **PR Preview for Teams** \u2014 hosted clips, team reviews & sharing \u2014 is coming soon. [Join the early-access list \u2192](${TEAMS_URL})`;
|
|
2582
|
+
}
|
|
2583
|
+
function printTeamsPromo() {
|
|
2584
|
+
console.log();
|
|
2585
|
+
console.log(teamsPromoTerminal());
|
|
2586
|
+
}
|
|
2587
|
+
|
|
2563
2588
|
// src/mcp/server.ts
|
|
2564
2589
|
function urlAsk(found, hasConfig) {
|
|
2565
2590
|
const lines = ["No URL was specified \u2014 I won't guess which app to record."];
|
|
@@ -2711,7 +2736,9 @@ ${snapshot}${SNAPSHOT_HINT}`);
|
|
|
2711
2736
|
`Recording complete. Output:
|
|
2712
2737
|
${rel.map((r) => ` - ${r}`).join("\n")}${note}
|
|
2713
2738
|
|
|
2714
|
-
Drag the file into your PR description to embed it, or commit it and open the PR
|
|
2739
|
+
Drag the file into your PR description to embed it, or commit it and open the PR.
|
|
2740
|
+
|
|
2741
|
+
` + teamsPromoMarkdown()
|
|
2715
2742
|
);
|
|
2716
2743
|
} catch (e) {
|
|
2717
2744
|
return fail(e);
|
|
@@ -2777,7 +2804,7 @@ async function mcpCommand(repoRoot) {
|
|
|
2777
2804
|
|
|
2778
2805
|
// src/cli/commands/record.ts
|
|
2779
2806
|
import path16 from "path";
|
|
2780
|
-
import
|
|
2807
|
+
import pc3 from "picocolors";
|
|
2781
2808
|
|
|
2782
2809
|
// src/recorder/journey.ts
|
|
2783
2810
|
import { readFile as readFile5, writeFile as writeFile4, mkdir as mkdir5 } from "fs/promises";
|
|
@@ -2831,7 +2858,7 @@ async function recordCommand(repoRoot, opts) {
|
|
|
2831
2858
|
const boot = await bootstrap(repoRoot, config, "record");
|
|
2832
2859
|
await boot.startApp("after", repoRoot);
|
|
2833
2860
|
await boot.openBrowser("after");
|
|
2834
|
-
log.info(`Harness open at ${
|
|
2861
|
+
log.info(`Harness open at ${pc3.bold(boot.harness.url)}`);
|
|
2835
2862
|
log.step("Click Record in the sidebar, perform the journey, then Confirm.");
|
|
2836
2863
|
const steps = await boot.session.recordUntilConfirmed();
|
|
2837
2864
|
if (steps.length === 0) {
|
|
@@ -2854,7 +2881,7 @@ async function recordCommand(repoRoot, opts) {
|
|
|
2854
2881
|
// src/cli/commands/run.ts
|
|
2855
2882
|
import { existsSync as existsSync10 } from "fs";
|
|
2856
2883
|
import path17 from "path";
|
|
2857
|
-
import
|
|
2884
|
+
import pc4 from "picocolors";
|
|
2858
2885
|
|
|
2859
2886
|
// src/cli/util/openPath.ts
|
|
2860
2887
|
import { spawn as spawn4 } from "child_process";
|
|
@@ -2937,7 +2964,7 @@ async function runCommand(repoRoot, opts) {
|
|
|
2937
2964
|
);
|
|
2938
2965
|
}
|
|
2939
2966
|
const base = await detectBase(repoRoot, opts.base ?? config.baseBranch);
|
|
2940
|
-
log.info(`PR base: ${
|
|
2967
|
+
log.info(`PR base: ${pc4.bold(base.ref)} (${base.sha.slice(0, 10)}, via ${base.source})`);
|
|
2941
2968
|
if (await tryGit(repoRoot, ["status", "--porcelain"])) {
|
|
2942
2969
|
log.info("Heads up: the AFTER clip captures your working tree, including uncommitted changes.");
|
|
2943
2970
|
}
|
|
@@ -2964,7 +2991,7 @@ async function runCommand(repoRoot, opts) {
|
|
|
2964
2991
|
boot.session.setBranches(base.ref, currentBranch);
|
|
2965
2992
|
const beforeServer = await boot.startApp("before", worktree.dir);
|
|
2966
2993
|
await boot.openBrowser("before");
|
|
2967
|
-
log.info(`Harness open at ${
|
|
2994
|
+
log.info(`Harness open at ${pc4.bold(boot.harness.url)}`);
|
|
2968
2995
|
log.step(`Record the journey on BEFORE (${base.ref}), then Confirm in the sidebar.`);
|
|
2969
2996
|
await boot.session.promptResetChoice();
|
|
2970
2997
|
const beforeSteps = await boot.session.recordUntilConfirmed();
|
|
@@ -3011,11 +3038,12 @@ async function runCommand(repoRoot, opts) {
|
|
|
3011
3038
|
outputs: { before: before.paths[0], after: after.paths[0] }
|
|
3012
3039
|
});
|
|
3013
3040
|
console.log();
|
|
3014
|
-
log.success(
|
|
3041
|
+
log.success(pc4.bold("Done \u2014 drag these into your PR description:"));
|
|
3015
3042
|
for (const p of [...before.paths, ...after.paths]) {
|
|
3016
3043
|
log.step(path17.relative(repoRoot, p));
|
|
3017
3044
|
}
|
|
3018
3045
|
revealFiles([...before.paths, ...after.paths], outDir);
|
|
3046
|
+
printTeamsPromo();
|
|
3019
3047
|
} finally {
|
|
3020
3048
|
await runCleanups();
|
|
3021
3049
|
}
|
|
@@ -3027,12 +3055,12 @@ async function runSingleClip(repoRoot, config, outDir, opts) {
|
|
|
3027
3055
|
const branch = await branchName(repoRoot, "app");
|
|
3028
3056
|
let server;
|
|
3029
3057
|
if (opts.url) {
|
|
3030
|
-
log.info(`Using your running app at ${
|
|
3058
|
+
log.info(`Using your running app at ${pc4.bold(opts.url)} (single clip).`);
|
|
3031
3059
|
} else {
|
|
3032
3060
|
server = await boot.startApp("before", repoRoot);
|
|
3033
3061
|
}
|
|
3034
3062
|
await boot.openBrowser("before");
|
|
3035
|
-
log.info(`Harness open at ${
|
|
3063
|
+
log.info(`Harness open at ${pc4.bold(boot.harness.url)}`);
|
|
3036
3064
|
log.step(`Record the journey, then Confirm to save the clip.`);
|
|
3037
3065
|
await boot.session.promptResetChoice();
|
|
3038
3066
|
const steps = await boot.session.recordUntilConfirmed();
|
|
@@ -3053,22 +3081,23 @@ async function runSingleClip(repoRoot, config, outDir, opts) {
|
|
|
3053
3081
|
boot.session.setPhase("done");
|
|
3054
3082
|
boot.harness.bus.send({ type: "DONE", outputs: { before: clip.paths[0] } });
|
|
3055
3083
|
console.log();
|
|
3056
|
-
log.success(
|
|
3084
|
+
log.success(pc4.bold("Done \u2014 your clip:"));
|
|
3057
3085
|
for (const p of clip.paths) log.step(path17.relative(repoRoot, p));
|
|
3058
3086
|
revealFiles(clip.paths, outDir);
|
|
3087
|
+
printTeamsPromo();
|
|
3059
3088
|
} finally {
|
|
3060
3089
|
await runCleanups();
|
|
3061
3090
|
}
|
|
3062
3091
|
}
|
|
3063
3092
|
async function runWithExternalApp(repoRoot, config, outDir, opts) {
|
|
3064
|
-
log.info(`Using your running app at ${
|
|
3093
|
+
log.info(`Using your running app at ${pc4.bold(opts.url)} (no dev server managed).`);
|
|
3065
3094
|
try {
|
|
3066
3095
|
const boot = await bootstrap(repoRoot, config, "run", { fixedUrl: opts.url });
|
|
3067
3096
|
boot.session.setPasses(2);
|
|
3068
3097
|
const beforeBranch = await branchName(repoRoot, "before");
|
|
3069
3098
|
boot.session.setBranches(beforeBranch, "your PR branch");
|
|
3070
3099
|
await boot.openBrowser("before");
|
|
3071
|
-
log.info(`Harness open at ${
|
|
3100
|
+
log.info(`Harness open at ${pc4.bold(boot.harness.url)}`);
|
|
3072
3101
|
log.step(`Record the journey on your app (currently ${beforeBranch}), then Confirm.`);
|
|
3073
3102
|
await boot.session.promptResetChoice();
|
|
3074
3103
|
const beforeSteps = await boot.session.recordUntilConfirmed();
|
|
@@ -3084,7 +3113,7 @@ async function runWithExternalApp(repoRoot, config, outDir, opts) {
|
|
|
3084
3113
|
{ branch: beforeBranch, baseBranch: beforeBranch, timestamp }
|
|
3085
3114
|
);
|
|
3086
3115
|
log.success(`${before.paths.map((p) => path17.basename(p)).join(" + ")} (${before.frameCount} frames)`);
|
|
3087
|
-
log.info(
|
|
3116
|
+
log.info(pc4.bold("\u2192 Switch your app to the PR branch and restart it on the same URL, then Continue in the harness."));
|
|
3088
3117
|
boot.harness.bus.send({
|
|
3089
3118
|
type: "MANUAL_PAUSE",
|
|
3090
3119
|
stepId: null,
|
|
@@ -3113,9 +3142,10 @@ async function runWithExternalApp(repoRoot, config, outDir, opts) {
|
|
|
3113
3142
|
boot.session.setPhase("done");
|
|
3114
3143
|
boot.harness.bus.send({ type: "DONE", outputs: { before: before.paths[0], after: after.paths[0] } });
|
|
3115
3144
|
console.log();
|
|
3116
|
-
log.success(
|
|
3145
|
+
log.success(pc4.bold("Done \u2014 drag these into your PR description:"));
|
|
3117
3146
|
for (const p of [...before.paths, ...after.paths]) log.step(path17.relative(repoRoot, p));
|
|
3118
3147
|
revealFiles([...before.paths, ...after.paths], outDir);
|
|
3148
|
+
printTeamsPromo();
|
|
3119
3149
|
} finally {
|
|
3120
3150
|
await runCleanups();
|
|
3121
3151
|
}
|