@reshotdev/screenshot 0.0.1-beta.12 → 0.0.1-beta.14
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 +67 -22
- package/package.json +18 -14
- package/src/commands/auth.js +37 -7
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-release.js +7 -0
- package/src/commands/doctor-target.js +36 -4
- package/src/commands/drifts.js +13 -1
- package/src/commands/publish.js +183 -21
- package/src/commands/pull.js +9 -4
- package/src/commands/refresh.js +166 -0
- package/src/commands/setup-wizard.js +57 -3
- package/src/commands/status.js +22 -2
- package/src/commands/variation.js +194 -0
- package/src/index.js +190 -10
- package/src/lib/api-client.js +61 -35
- package/src/lib/auto-update/refresh.js +598 -0
- package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
- package/src/lib/auto-update/spec.js +89 -0
- package/src/lib/capture-engine.js +76 -2
- package/src/lib/capture-script-runner.js +289 -138
- package/src/lib/certification.js +23 -1
- package/src/lib/compose-context.js +156 -0
- package/src/lib/compose-pack.js +42 -0
- package/src/lib/compose-runtime.js +34 -0
- package/src/lib/compose-upload.js +142 -0
- package/src/lib/config.js +2 -2
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/ensure-browser.js +147 -0
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +0 -4
- package/src/lib/release-doctor.js +11 -3
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +45 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +28 -820
- package/src/lib/ui-asset-cleanup.js +62 -0
- package/src/lib/ui-output-versions.js +165 -0
- package/src/lib/ui-recorder-routes.js +341 -0
- package/src/lib/ui-scenario-metadata.js +161 -0
- package/vendor/compose/dist/auto-update.cjs +5544 -0
- package/vendor/compose/dist/auto-update.mjs +5518 -0
- package/vendor/compose/dist/capture.cjs +1450 -0
- package/vendor/compose/dist/capture.mjs +1416 -0
- package/vendor/compose/dist/eligibility.cjs +5331 -0
- package/vendor/compose/dist/eligibility.mjs +5313 -0
- package/vendor/compose/dist/index.cjs +2046 -0
- package/vendor/compose/dist/index.mjs +1997 -0
- package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
- package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
- package/vendor/compose/dist/jsx-runtime.cjs +58 -0
- package/vendor/compose/dist/jsx-runtime.mjs +31 -0
- package/vendor/compose/dist/render.cjs +558 -0
- package/vendor/compose/dist/render.mjs +515 -0
- package/vendor/compose/dist/verify-cli.cjs +3806 -0
- package/vendor/compose/dist/verify-cli.mjs +3812 -0
- package/vendor/compose/dist/verify.cjs +3880 -0
- package/vendor/compose/dist/verify.mjs +3858 -0
- package/web/manager/dist/assets/{index-CvleJUur.js → index-D0S2otug.js} +56 -56
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -165
- package/src/lib/playwright-runner.js +0 -252
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Phase 5 auto-update — local per-composition state ("the stored spec").
|
|
2
|
+
//
|
|
3
|
+
// A composition's source screen (URL + viewport), the accepted baseline's
|
|
4
|
+
// structure signature + reference frame, and any outstanding flagged candidate
|
|
5
|
+
// live on disk so a CI `reshot refresh` is reproducible and idempotent. The
|
|
6
|
+
// reference frame is the prior ACCEPTED render's recapture; it advances only when
|
|
7
|
+
// a refresh publishes, so a flagged redesign leaves the old baseline (and the old
|
|
8
|
+
// live clip) untouched.
|
|
9
|
+
|
|
10
|
+
const fs = require("fs-extra");
|
|
11
|
+
const path = require("path");
|
|
12
|
+
|
|
13
|
+
function baseDir() {
|
|
14
|
+
return (
|
|
15
|
+
process.env.RESHOT_AUTO_UPDATE_DIR ||
|
|
16
|
+
path.join(process.cwd(), ".reshot", "auto-update")
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function specDir(compositionId) {
|
|
21
|
+
return path.join(baseDir(), compositionId);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function specPath(compositionId) {
|
|
25
|
+
return path.join(specDir(compositionId), "spec.json");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function referencePngPath(compositionId) {
|
|
29
|
+
return path.join(specDir(compositionId), "reference.png");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function writeSpec(spec) {
|
|
33
|
+
if (!spec || !spec.compositionId) {
|
|
34
|
+
throw new Error("writeSpec requires spec.compositionId");
|
|
35
|
+
}
|
|
36
|
+
await fs.ensureDir(specDir(spec.compositionId));
|
|
37
|
+
await fs.writeJson(specPath(spec.compositionId), spec, { spaces: 2 });
|
|
38
|
+
return spec;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function readSpec(compositionId) {
|
|
42
|
+
const file = specPath(compositionId);
|
|
43
|
+
if (!(await fs.pathExists(file))) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`No auto-update spec for composition ${compositionId} at ${file}. ` +
|
|
46
|
+
"Register it with `reshot refresh --register` (or seed it) first.",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return fs.readJson(file);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function listSpecs(projectId) {
|
|
53
|
+
const dir = baseDir();
|
|
54
|
+
if (!(await fs.pathExists(dir))) return [];
|
|
55
|
+
const entries = await fs.readdir(dir);
|
|
56
|
+
const specs = [];
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const file = specPath(entry);
|
|
59
|
+
if (!(await fs.pathExists(file))) continue;
|
|
60
|
+
const spec = await fs.readJson(file);
|
|
61
|
+
if (projectId && spec.projectId !== projectId) continue;
|
|
62
|
+
specs.push(spec);
|
|
63
|
+
}
|
|
64
|
+
// Stable order so CI summaries and idempotence checks are deterministic.
|
|
65
|
+
return specs.sort((a, b) => String(a.slug).localeCompare(String(b.slug)));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function readReferencePng(compositionId) {
|
|
69
|
+
const file = referencePngPath(compositionId);
|
|
70
|
+
if (!(await fs.pathExists(file))) return null;
|
|
71
|
+
return fs.readFile(file);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async function writeReferencePng(compositionId, buffer) {
|
|
75
|
+
await fs.ensureDir(specDir(compositionId));
|
|
76
|
+
await fs.writeFile(referencePngPath(compositionId), buffer);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = {
|
|
80
|
+
baseDir,
|
|
81
|
+
specDir,
|
|
82
|
+
specPath,
|
|
83
|
+
referencePngPath,
|
|
84
|
+
writeSpec,
|
|
85
|
+
readSpec,
|
|
86
|
+
listSpecs,
|
|
87
|
+
readReferencePng,
|
|
88
|
+
writeReferencePng,
|
|
89
|
+
};
|
|
@@ -6,6 +6,7 @@ const path = require("path");
|
|
|
6
6
|
const fs = require("fs-extra");
|
|
7
7
|
const chalk = require("chalk");
|
|
8
8
|
const { buildLaunchOptions } = require("./ci-detect");
|
|
9
|
+
const { launchChromium } = require("./ensure-browser");
|
|
9
10
|
const {
|
|
10
11
|
applyVariantToPage,
|
|
11
12
|
applyStorageAndReload,
|
|
@@ -145,6 +146,13 @@ class CaptureEngine {
|
|
|
145
146
|
// Style configuration (image beautification post-capture)
|
|
146
147
|
this.styleConfig = options.styleConfig || null;
|
|
147
148
|
|
|
149
|
+
// DOM scene (MHTML) capture toggle — emits a self-contained Chromium
|
|
150
|
+
// MHTML bundle alongside each PNG so variations can be rendered from
|
|
151
|
+
// the captured DOM without re-running the live app. Defaults to ON;
|
|
152
|
+
// opt out via reshot.config.json -> { domScene: false } or per-scenario
|
|
153
|
+
// -> { domScene: false }.
|
|
154
|
+
this.domSceneEnabled = options.domScene !== false;
|
|
155
|
+
|
|
148
156
|
// Legacy support for old variant format
|
|
149
157
|
if (!this.variantConfig && options.variant) {
|
|
150
158
|
this.variantConfig = this._convertLegacyVariant(options.variant);
|
|
@@ -292,9 +300,9 @@ class CaptureEngine {
|
|
|
292
300
|
|
|
293
301
|
const contextOptions = this._buildContextOptions();
|
|
294
302
|
|
|
295
|
-
this.browser = await chromium
|
|
303
|
+
this.browser = await launchChromium(chromium, buildLaunchOptions({
|
|
296
304
|
headless: this.headless,
|
|
297
|
-
}));
|
|
305
|
+
}), this.logger);
|
|
298
306
|
this.context = await this.browser.newContext(contextOptions);
|
|
299
307
|
this.page = await this.context.newPage();
|
|
300
308
|
|
|
@@ -623,6 +631,32 @@ class CaptureEngine {
|
|
|
623
631
|
// Wait for network to settle
|
|
624
632
|
await this._waitForStability();
|
|
625
633
|
|
|
634
|
+
// Post-stability auth redirect check: catches SPA redirects that happen
|
|
635
|
+
// after client-side JS has executed (the pre-stability check at domcontentloaded
|
|
636
|
+
// may miss these since JS hasn't finished routing yet)
|
|
637
|
+
if (!isIntentionalTarget) {
|
|
638
|
+
const postStabilityUrl = this.page.url();
|
|
639
|
+
const postStabilityRedirect =
|
|
640
|
+
isAuthRedirectUrl(postStabilityUrl, this._customAuthPatterns) ||
|
|
641
|
+
this._authResponseDetected;
|
|
642
|
+
if (postStabilityRedirect) {
|
|
643
|
+
const errorMsg = `Auth redirect detected after page load: navigated to ${postStabilityUrl}. Session may be expired. Re-run \`reshot record\` to refresh session.`;
|
|
644
|
+
this.logger(chalk.red(` ✖ ${errorMsg}`));
|
|
645
|
+
throw new Error(errorMsg);
|
|
646
|
+
}
|
|
647
|
+
// Also check DOM for login forms (SPA may render login UI without changing URL)
|
|
648
|
+
const hasLoginForm = await this.page.evaluate(() => {
|
|
649
|
+
const h = document.querySelector("h1, h2");
|
|
650
|
+
return h && /sign\s*in|log\s*in/i.test(h.textContent);
|
|
651
|
+
}).catch(() => false);
|
|
652
|
+
if (hasLoginForm) {
|
|
653
|
+
const errorMsg = `Login form detected after page load at ${postStabilityUrl}. Session may be expired. Re-run \`reshot record\` to refresh session.`;
|
|
654
|
+
this.logger(chalk.red(` ✖ ${errorMsg}`));
|
|
655
|
+
throw new Error(errorMsg);
|
|
656
|
+
}
|
|
657
|
+
this._authResponseDetected = false;
|
|
658
|
+
}
|
|
659
|
+
|
|
626
660
|
// Additional wait for theme/variants to fully apply
|
|
627
661
|
// This handles CSS transitions and async re-renders
|
|
628
662
|
if (this.variantConfig && this.variantConfig.injections?.length > 0) {
|
|
@@ -1107,10 +1141,50 @@ class CaptureEngine {
|
|
|
1107
1141
|
// Write the final buffer to file
|
|
1108
1142
|
await fs.writeFile(outputPath, finalBuffer);
|
|
1109
1143
|
|
|
1144
|
+
// ── DOM scene capture (sidecar MHTML) ──────────────────────────────
|
|
1145
|
+
// Capture a self-contained MHTML bundle of the page at the same moment
|
|
1146
|
+
// as the PNG. The bundle re-renders in any Chromium browser without
|
|
1147
|
+
// network access and is the source of truth for variations: marketing
|
|
1148
|
+
// can mutate the captured DOM (swap copy, hide chrome, recrop, rebrand
|
|
1149
|
+
// tenant names) and render new outputs without re-running Playwright
|
|
1150
|
+
// against the live app. Opt out per-scenario or globally via
|
|
1151
|
+
// reshot.config.json -> { domScene: false }.
|
|
1152
|
+
//
|
|
1153
|
+
// The MHTML capture is best-effort — failures are logged but do not
|
|
1154
|
+
// fail the scenario, since the primary PNG already succeeded.
|
|
1155
|
+
let domScenePath = null;
|
|
1156
|
+
let domSceneBytes = null;
|
|
1157
|
+
if (this.domSceneEnabled) {
|
|
1158
|
+
try {
|
|
1159
|
+
const cdp = await this.page.context().newCDPSession(this.page);
|
|
1160
|
+
const { data: mhtml } = await cdp.send("Page.captureSnapshot", {
|
|
1161
|
+
format: "mhtml",
|
|
1162
|
+
});
|
|
1163
|
+
domScenePath = outputPath.replace(/\.png$/i, ".mhtml");
|
|
1164
|
+
await fs.writeFile(domScenePath, mhtml);
|
|
1165
|
+
domSceneBytes = Buffer.byteLength(mhtml, "utf8");
|
|
1166
|
+
this.logger(
|
|
1167
|
+
chalk.gray(
|
|
1168
|
+
` ✓ DOM scene: ${path.basename(domScenePath)} (${(domSceneBytes / 1024).toFixed(0)} KB)`,
|
|
1169
|
+
),
|
|
1170
|
+
);
|
|
1171
|
+
} catch (err) {
|
|
1172
|
+
this.logger(
|
|
1173
|
+
chalk.yellow(
|
|
1174
|
+
` ⚠ DOM scene capture skipped: ${err.message || err}`,
|
|
1175
|
+
),
|
|
1176
|
+
);
|
|
1177
|
+
domScenePath = null;
|
|
1178
|
+
domSceneBytes = null;
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1110
1182
|
// Record asset metadata
|
|
1111
1183
|
this.capturedAssets.push({
|
|
1112
1184
|
name,
|
|
1113
1185
|
path: outputPath,
|
|
1186
|
+
domScenePath,
|
|
1187
|
+
domSceneBytes,
|
|
1114
1188
|
description,
|
|
1115
1189
|
capturedAt: new Date().toISOString(),
|
|
1116
1190
|
viewport: this.viewport,
|