@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.10

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.
Files changed (38) hide show
  1. package/README.md +65 -7
  2. package/package.json +9 -2
  3. package/src/commands/auth.js +108 -26
  4. package/src/commands/certify.js +62 -0
  5. package/src/commands/ci-run.js +57 -2
  6. package/src/commands/ci-setup.js +5 -5
  7. package/src/commands/doctor-release.js +67 -0
  8. package/src/commands/doctor-target.js +49 -0
  9. package/src/commands/drifts.js +5 -70
  10. package/src/commands/import-tests.js +13 -13
  11. package/src/commands/ingest.js +10 -10
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +204 -237
  14. package/src/commands/pull.js +253 -23
  15. package/src/commands/run.js +292 -12
  16. package/src/commands/setup-wizard.js +277 -499
  17. package/src/commands/setup.js +41 -13
  18. package/src/commands/status.js +313 -125
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/verify-publish.js +46 -0
  22. package/src/index.js +194 -94
  23. package/src/lib/api-client.js +121 -35
  24. package/src/lib/capture-engine.js +103 -7
  25. package/src/lib/capture-script-runner.js +305 -58
  26. package/src/lib/certification.js +865 -0
  27. package/src/lib/config.js +181 -76
  28. package/src/lib/record-cdp.js +288 -16
  29. package/src/lib/record-config.js +1 -1
  30. package/src/lib/release-doctor.js +313 -0
  31. package/src/lib/run-manifest.js +103 -0
  32. package/src/lib/standalone-mode.js +1 -1
  33. package/src/lib/storage-providers.js +4 -4
  34. package/src/lib/target-contract.js +292 -0
  35. package/src/lib/ui-api.js +6 -7
  36. package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
  37. package/web/manager/dist/index.html +1 -1
  38. 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
- await this._injectWorkspaceStore();
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 docsync.config.json urlVariables
506
+ // 3. Check reshot.config.json urlVariables
416
507
  if (!projectId) {
417
508
  try {
418
- const docsyncConfig = config.readConfig() || {};
419
- projectId = docsyncConfig.urlVariables?.PROJECT_ID;
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
- isAuthRedirectUrl(currentUrl, this._customAuthPatterns) ||
518
- this._authResponseDetected;
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}`));