@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.11
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 +65 -7
- package/package.json +9 -2
- package/src/commands/auth.js +108 -26
- package/src/commands/certify.js +62 -0
- package/src/commands/ci-run.js +57 -2
- package/src/commands/ci-setup.js +5 -5
- package/src/commands/doctor-release.js +67 -0
- package/src/commands/doctor-target.js +49 -0
- package/src/commands/drifts.js +5 -70
- package/src/commands/import-tests.js +13 -13
- package/src/commands/ingest.js +10 -10
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +204 -237
- package/src/commands/pull.js +253 -23
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +277 -499
- package/src/commands/setup.js +41 -13
- package/src/commands/status.js +313 -125
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +194 -94
- package/src/lib/api-client.js +121 -35
- package/src/lib/capture-engine.js +103 -7
- package/src/lib/capture-script-runner.js +359 -58
- package/src/lib/certification.js +865 -0
- package/src/lib/config.js +181 -76
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-config.js +1 -1
- package/src/lib/release-doctor.js +313 -0
- package/src/lib/run-manifest.js +103 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +4 -4
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api.js +6 -7
- package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
- package/src/commands/validate-docs.js +0 -529
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
scaleRegionByDPR,
|
|
21
21
|
isSharpAvailable,
|
|
22
22
|
} = require("./image-crop");
|
|
23
|
-
const { sanitizeStorageState } = require("./record-cdp");
|
|
23
|
+
const { sanitizeStorageState, assessSessionHealth } = require("./record-cdp");
|
|
24
24
|
const {
|
|
25
25
|
injectPrivacyMasking,
|
|
26
26
|
removePrivacyMasking,
|
|
@@ -98,6 +98,9 @@ class CaptureEngine {
|
|
|
98
98
|
this.capturedAssets = [];
|
|
99
99
|
this.logger = options.logger || console.log;
|
|
100
100
|
this.headless = options.headless !== false; // Default to headless
|
|
101
|
+
this.injectWorkspaceStore = options.injectWorkspaceStore !== false;
|
|
102
|
+
this.diagnostics = [];
|
|
103
|
+
this.sessionHealth = null;
|
|
101
104
|
|
|
102
105
|
// Storage state path for authenticated sessions
|
|
103
106
|
// If provided, loads cookies/localStorage from file to preserve auth state
|
|
@@ -226,6 +229,36 @@ class CaptureEngine {
|
|
|
226
229
|
}
|
|
227
230
|
this.logger(chalk.gray(` → Using pre-loaded auth session`));
|
|
228
231
|
} else if (this.storageStatePath && fs.existsSync(this.storageStatePath)) {
|
|
232
|
+
this.sessionHealth = assessSessionHealth(
|
|
233
|
+
this.storageStatePath,
|
|
234
|
+
this.baseUrl,
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
if (!this.sessionHealth.compatible) {
|
|
238
|
+
this.logger(
|
|
239
|
+
chalk.yellow(
|
|
240
|
+
` ⚠ Skipping cached auth session because it does not match ${this.baseUrl || "the current target"}`,
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
for (const issue of this.sessionHealth.issues) {
|
|
244
|
+
this.logger(chalk.gray(` ${issue}`));
|
|
245
|
+
}
|
|
246
|
+
this.diagnostics.push({
|
|
247
|
+
type: "auth-session-mismatch",
|
|
248
|
+
sessionPath: this.storageStatePath,
|
|
249
|
+
issues: [...this.sessionHealth.issues],
|
|
250
|
+
});
|
|
251
|
+
return contextOptions;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (this.sessionHealth.stale) {
|
|
255
|
+
this.logger(
|
|
256
|
+
chalk.yellow(
|
|
257
|
+
` ⚠ Cached auth session is ${this.sessionHealth.ageMinutes}m old; validating it during startup.`,
|
|
258
|
+
),
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
|
|
229
262
|
// Read and sanitize instead of passing raw file path (Playwright would read it unsanitized)
|
|
230
263
|
try {
|
|
231
264
|
const rawState = JSON.parse(fs.readFileSync(this.storageStatePath, "utf-8"));
|
|
@@ -245,6 +278,10 @@ class CaptureEngine {
|
|
|
245
278
|
)}`
|
|
246
279
|
)
|
|
247
280
|
);
|
|
281
|
+
|
|
282
|
+
for (const warning of this.sessionHealth.warnings) {
|
|
283
|
+
this.logger(chalk.gray(` ${warning}`));
|
|
284
|
+
}
|
|
248
285
|
}
|
|
249
286
|
|
|
250
287
|
return contextOptions;
|
|
@@ -289,7 +326,9 @@ class CaptureEngine {
|
|
|
289
326
|
logVariantSummary(this.variantConfig, this.logger);
|
|
290
327
|
}
|
|
291
328
|
|
|
292
|
-
|
|
329
|
+
if (this.injectWorkspaceStore) {
|
|
330
|
+
await this._injectWorkspaceStore();
|
|
331
|
+
}
|
|
293
332
|
|
|
294
333
|
// Inject privacy masking CSS (after variant injection, before captures)
|
|
295
334
|
this._privacyInjectionOk = true;
|
|
@@ -306,24 +345,76 @@ class CaptureEngine {
|
|
|
306
345
|
this._authResponseDetected = false;
|
|
307
346
|
this.page.on("response", (response) => {
|
|
308
347
|
const status = response.status();
|
|
348
|
+
const url = response.url();
|
|
309
349
|
if (
|
|
310
350
|
(status === 401 || status === 403) &&
|
|
311
351
|
response.request().resourceType() === "document"
|
|
312
352
|
) {
|
|
313
353
|
this._authResponseDetected = true;
|
|
314
354
|
}
|
|
355
|
+
|
|
356
|
+
if (status >= 400 && response.request().resourceType() === "document") {
|
|
357
|
+
this._recordDiagnostic("response_error", "error", {
|
|
358
|
+
url,
|
|
359
|
+
status,
|
|
360
|
+
resourceType: response.request().resourceType(),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
315
363
|
});
|
|
316
364
|
|
|
317
365
|
// Set up error handling
|
|
318
366
|
this.page.on("pageerror", (err) => {
|
|
319
367
|
const firstLine = (err.message || '').split('\n')[0].slice(0, 200);
|
|
368
|
+
this._recordDiagnostic("pageerror", "error", {
|
|
369
|
+
message: err.message || String(err),
|
|
370
|
+
});
|
|
320
371
|
this.logger(chalk.yellow(` [Page Error] ${firstLine}`));
|
|
321
372
|
});
|
|
373
|
+
this.page.on("console", (message) => {
|
|
374
|
+
const type = message.type();
|
|
375
|
+
const text = message.text();
|
|
376
|
+
const severity =
|
|
377
|
+
type === "error"
|
|
378
|
+
? "error"
|
|
379
|
+
: type === "warning"
|
|
380
|
+
? "warning"
|
|
381
|
+
: "info";
|
|
382
|
+
|
|
383
|
+
if (severity === "error" || severity === "warning") {
|
|
384
|
+
this._recordDiagnostic("console", severity, {
|
|
385
|
+
message: text,
|
|
386
|
+
consoleType: type,
|
|
387
|
+
cspViolation: /content security policy|csp/i.test(text),
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
this.page.on("requestfailed", (request) => {
|
|
392
|
+
this._recordDiagnostic("requestfailed", "error", {
|
|
393
|
+
url: request.url(),
|
|
394
|
+
method: request.method(),
|
|
395
|
+
resourceType: request.resourceType(),
|
|
396
|
+
message: request.failure()?.errorText || "Request failed",
|
|
397
|
+
});
|
|
398
|
+
});
|
|
322
399
|
|
|
323
400
|
this.logger(chalk.green(" ✔ Browser initialized"));
|
|
324
401
|
return this;
|
|
325
402
|
}
|
|
326
403
|
|
|
404
|
+
_recordDiagnostic(kind, severity, details = {}) {
|
|
405
|
+
this.diagnostics.push({
|
|
406
|
+
id: `${Date.now()}-${this.diagnostics.length + 1}`,
|
|
407
|
+
kind,
|
|
408
|
+
severity,
|
|
409
|
+
...details,
|
|
410
|
+
capturedAt: new Date().toISOString(),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
getDiagnostics() {
|
|
415
|
+
return [...this.diagnostics];
|
|
416
|
+
}
|
|
417
|
+
|
|
327
418
|
/**
|
|
328
419
|
* Hide development overlays (Next.js devtools, Vercel toolbar, etc.)
|
|
329
420
|
* Injects CSS to hide common development UI elements before each navigation
|
|
@@ -412,11 +503,11 @@ class CaptureEngine {
|
|
|
412
503
|
projectId = settings.urlVariables?.PROJECT_ID;
|
|
413
504
|
// 2. Check settings projectId
|
|
414
505
|
if (!projectId) projectId = settings.projectId;
|
|
415
|
-
// 3. Check
|
|
506
|
+
// 3. Check reshot.config.json urlVariables
|
|
416
507
|
if (!projectId) {
|
|
417
508
|
try {
|
|
418
|
-
const
|
|
419
|
-
projectId =
|
|
509
|
+
const reshotConfig = config.readConfig() || {};
|
|
510
|
+
projectId = reshotConfig.urlVariables?.PROJECT_ID;
|
|
420
511
|
} catch (_e) {
|
|
421
512
|
// Config may not exist
|
|
422
513
|
}
|
|
@@ -512,10 +603,15 @@ class CaptureEngine {
|
|
|
512
603
|
}
|
|
513
604
|
|
|
514
605
|
// Check for auth redirect after navigation (URL patterns + HTTP 401/403)
|
|
606
|
+
// Skip the check if the scenario explicitly targeted this URL (e.g. capturing /login)
|
|
515
607
|
const currentUrl = this.page.url();
|
|
608
|
+
const targetPath = url.startsWith("http") ? new URL(url).pathname : url;
|
|
609
|
+
const currentPath = (() => { try { return new URL(currentUrl).pathname; } catch { return currentUrl; } })();
|
|
610
|
+
const isIntentionalTarget = currentPath === targetPath;
|
|
516
611
|
const isAuthRedirect =
|
|
517
|
-
|
|
518
|
-
this.
|
|
612
|
+
!isIntentionalTarget &&
|
|
613
|
+
(isAuthRedirectUrl(currentUrl, this._customAuthPatterns) ||
|
|
614
|
+
this._authResponseDetected);
|
|
519
615
|
if (isAuthRedirect) {
|
|
520
616
|
const errorMsg = `Auth redirect detected: navigated to ${currentUrl}. Session may be expired. Re-run \`reshot record\` to refresh session, or export a fresh Playwright storage state to .reshot/auth-state.json.`;
|
|
521
617
|
this.logger(chalk.red(` ✖ ${errorMsg}`));
|