@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
@@ -1,9 +1,15 @@
1
1
  // run.js - Execute all scenarios from config using the robust capture engine
2
+ const axios = require("axios");
2
3
  const chalk = require("chalk");
3
4
  const fs = require("fs-extra");
4
5
  const path = require("path");
5
6
  const config = require("../lib/config");
6
- const { runAllScenarios, generateVersionTimestamp, detectOptimalConcurrency } = require("../lib/capture-script-runner");
7
+ const {
8
+ runAllScenarios,
9
+ generateVersionTimestamp,
10
+ detectOptimalConcurrency,
11
+ resolveAuthPreflightTargets,
12
+ } = require("../lib/capture-script-runner");
7
13
  const { getBaselines } = require("../lib/api-client");
8
14
  const {
9
15
  downloadBaselines,
@@ -14,6 +20,12 @@ const {
14
20
  writeLocalDiffManifest,
15
21
  CACHE_DIR,
16
22
  } = require("../lib/diff-engine");
23
+ const {
24
+ checkCdpEndpoint,
25
+ getDefaultSessionPath,
26
+ assessSessionHealth,
27
+ } = require("../lib/record-cdp");
28
+ const { writeRunManifest, normalizeScenarioResults } = require("../lib/run-manifest");
17
29
 
18
30
  /**
19
31
  * Generate all variant combinations from dimensions config
@@ -68,6 +80,224 @@ function generateVariantCombinations(dimensions, dimensionKeys = null) {
68
80
  });
69
81
  }
70
82
 
83
+ async function probeBaseUrl(baseUrl) {
84
+ if (!baseUrl) {
85
+ return {
86
+ ok: false,
87
+ status: null,
88
+ message: "baseUrl is not configured.",
89
+ };
90
+ }
91
+
92
+ try {
93
+ const response = await axios.get(baseUrl, {
94
+ timeout: 8000,
95
+ maxRedirects: 0,
96
+ validateStatus: () => true,
97
+ });
98
+
99
+ if (response.status >= 500) {
100
+ return {
101
+ ok: false,
102
+ status: response.status,
103
+ message: `baseUrl responded with ${response.status}. Fix the app before running capture.`,
104
+ };
105
+ }
106
+
107
+ return {
108
+ ok: true,
109
+ status: response.status,
110
+ message: `baseUrl responded with ${response.status}`,
111
+ };
112
+ } catch (error) {
113
+ return {
114
+ ok: false,
115
+ status: null,
116
+ message:
117
+ `Could not reach baseUrl ${baseUrl}. ` +
118
+ `Make sure the app is running and reachable before capture. (${error.message})`,
119
+ };
120
+ }
121
+ }
122
+
123
+ function formatSessionAge(minutes) {
124
+ if (minutes < 60) {
125
+ return `${minutes}m`;
126
+ }
127
+
128
+ const hours = Math.round(minutes / 60);
129
+ if (hours < 24) {
130
+ return `${hours}h`;
131
+ }
132
+
133
+ return `${Math.round(hours / 24)}d`;
134
+ }
135
+
136
+ function writeRunPreflightReport(report) {
137
+ const diagnosticsPath = path.join(
138
+ process.cwd(),
139
+ ".reshot",
140
+ "diagnostics",
141
+ "run-preflight.json",
142
+ );
143
+
144
+ try {
145
+ fs.ensureDirSync(path.dirname(diagnosticsPath));
146
+ fs.writeJsonSync(diagnosticsPath, report, { spaces: 2 });
147
+ } catch (error) {
148
+ report.diagnosticsWriteError = error.message;
149
+ }
150
+
151
+ return diagnosticsPath;
152
+ }
153
+
154
+ async function buildRunPreflightReport(docSyncConfig, options = {}) {
155
+ const { scenarioKeys = null } = options;
156
+ const validation = config.validateConfig({
157
+ scenarioKeys,
158
+ requireReadyContract: docSyncConfig.target?.tier === "certified",
159
+ });
160
+ const errors = [...validation.errors];
161
+ const warnings = [...validation.warnings];
162
+ const report = {
163
+ generatedAt: new Date().toISOString(),
164
+ ok: false,
165
+ baseUrl: docSyncConfig.baseUrl || null,
166
+ selectedScenarioKeys: validation.details?.selectedScenarioKeys || [],
167
+ configValidation: validation,
168
+ checks: [],
169
+ auth: {
170
+ required: Boolean(validation.details?.liveAuthScenarioCount),
171
+ sessionPath: null,
172
+ hasCachedSession: false,
173
+ sessionAgeMinutes: null,
174
+ sessionCompatible: null,
175
+ sessionSourceOrigin: null,
176
+ sessionStale: null,
177
+ preflightTargets: [],
178
+ cdpAvailable: null,
179
+ },
180
+ errors,
181
+ warnings,
182
+ };
183
+
184
+ if (validation.details?.baseUrl) {
185
+ const baseUrlCheck = await probeBaseUrl(validation.details.baseUrl);
186
+ report.checks.push({ name: "baseUrl", ...baseUrlCheck });
187
+ if (!baseUrlCheck.ok) {
188
+ errors.push(baseUrlCheck.message);
189
+ }
190
+ }
191
+
192
+ if (report.auth.required) {
193
+ report.auth.preflightTargets = resolveAuthPreflightTargets(docSyncConfig, {
194
+ scenarioKeys,
195
+ }).targets;
196
+ const sessionPath = getDefaultSessionPath();
197
+ report.auth.sessionPath = sessionPath;
198
+ report.auth.hasCachedSession = fs.existsSync(sessionPath);
199
+
200
+ if (report.auth.hasCachedSession) {
201
+ const sessionHealth = assessSessionHealth(
202
+ sessionPath,
203
+ validation.details?.baseUrl || docSyncConfig.baseUrl || null,
204
+ );
205
+ report.auth.sessionAgeMinutes = sessionHealth.ageMinutes;
206
+ report.auth.sessionCompatible = sessionHealth.compatible;
207
+ report.auth.sessionSourceOrigin = sessionHealth.evidence.sourceOrigin;
208
+ report.auth.sessionStale = sessionHealth.stale;
209
+ report.checks.push({
210
+ name: "authSession",
211
+ ok: sessionHealth.compatible,
212
+ status: sessionHealth.stale ? "stale" : "ready",
213
+ message: sessionHealth.compatible
214
+ ? sessionHealth.evidence.sourceOrigin
215
+ ? `Cached auth session matches ${sessionHealth.evidence.sourceOrigin}`
216
+ : "Cached auth session is present"
217
+ : sessionHealth.issues[0],
218
+ });
219
+
220
+ warnings.push(...sessionHealth.warnings);
221
+
222
+ if (!sessionHealth.compatible) {
223
+ const cdpCheck = await checkCdpEndpoint();
224
+ report.auth.cdpAvailable = cdpCheck.available;
225
+ report.checks.push({
226
+ name: "cdp",
227
+ ok: cdpCheck.available,
228
+ status: null,
229
+ message: cdpCheck.available
230
+ ? "Chrome CDP endpoint is available and can refresh the cached session."
231
+ : `Chrome CDP endpoint is unavailable: ${cdpCheck.error}`,
232
+ });
233
+
234
+ if (cdpCheck.available) {
235
+ warnings.push(
236
+ `Cached auth session does not match ${validation.details?.baseUrl || docSyncConfig.baseUrl}. Chrome CDP is available, so Reshot will try to refresh it from the live browser before capture.`,
237
+ );
238
+ } else {
239
+ errors.push(sessionHealth.issues[0]);
240
+ errors.push(
241
+ "Chrome CDP is unavailable, so Reshot cannot automatically replace the mismatched auth session.",
242
+ );
243
+ }
244
+ }
245
+
246
+ if (sessionHealth.stale) {
247
+ warnings.push(
248
+ `Cached auth session is ${formatSessionAge(sessionHealth.ageMinutes)} old. ` +
249
+ `Refresh it with 'reshot record --refresh-session' if captures start redirecting to login.`,
250
+ );
251
+ }
252
+ } else {
253
+ const cdpCheck = await checkCdpEndpoint();
254
+ report.auth.cdpAvailable = cdpCheck.available;
255
+ report.checks.push({
256
+ name: "cdp",
257
+ ok: cdpCheck.available,
258
+ status: null,
259
+ message: cdpCheck.available
260
+ ? "Chrome CDP endpoint is available for live session sync."
261
+ : `Chrome CDP endpoint is unavailable: ${cdpCheck.error}`,
262
+ });
263
+
264
+ if (!cdpCheck.available) {
265
+ errors.push(
266
+ "Authenticated scenarios need either a cached .reshot session or a Chrome browser running with remote debugging enabled.",
267
+ );
268
+ }
269
+ }
270
+ }
271
+
272
+ report.ok = errors.length === 0;
273
+ report.diagnosticsPath = writeRunPreflightReport(report);
274
+ return report;
275
+ }
276
+
277
+ function printRunPreflightReport(report) {
278
+ console.log(chalk.cyan("๐Ÿงช Run Preflight\n"));
279
+
280
+ for (const check of report.checks) {
281
+ const prefix = check.ok ? chalk.green(" โœ”") : chalk.red(" โœ–");
282
+ const statusSuffix = check.status ? ` (${check.status})` : "";
283
+ console.log(`${prefix} ${check.name}${statusSuffix}: ${check.message}`);
284
+ }
285
+
286
+ for (const warning of report.warnings) {
287
+ console.log(chalk.yellow(` โš  ${warning}`));
288
+ }
289
+
290
+ for (const error of report.errors) {
291
+ console.log(chalk.red(` โœ– ${error}`));
292
+ }
293
+
294
+ if (report.diagnosticsPath) {
295
+ console.log(chalk.gray(`\n Diagnostics: ${report.diagnosticsPath}`));
296
+ }
297
+
298
+ console.log();
299
+ }
300
+
71
301
  /**
72
302
  * Run scenarios from config
73
303
  * @param {Object} options - Run options
@@ -111,6 +341,30 @@ async function runCommand(options = {}) {
111
341
  return;
112
342
  }
113
343
 
344
+ const preflightReport = await buildRunPreflightReport(docSyncConfig, {
345
+ scenarioKeys,
346
+ });
347
+ printRunPreflightReport(preflightReport);
348
+
349
+ if (!preflightReport.ok) {
350
+ writeRunManifest({
351
+ success: false,
352
+ selectedScenarioKeys: preflightReport.selectedScenarioKeys || scenarioKeys || [],
353
+ outputBaseDir: path.join(
354
+ process.cwd(),
355
+ docSyncConfig.assetDir || ".reshot/output",
356
+ ),
357
+ scenarios: [],
358
+ preflight: preflightReport,
359
+ diffEnabled: false,
360
+ });
361
+ process.exitCode = 1;
362
+ if (!noExit) {
363
+ setImmediate(() => process.exit(process.exitCode || 1));
364
+ }
365
+ return { success: false, results: [], preflight: preflightReport };
366
+ }
367
+
114
368
  // Determine if diffing is enabled
115
369
  // CLI flag takes precedence, then config, default is TRUE (always diff locally)
116
370
  const shouldDiff =
@@ -164,6 +418,23 @@ async function runCommand(options = {}) {
164
418
  // - If --all-variants or variants are configured: expand all combinations
165
419
  const shouldExpandVariants = !noVariants && !variantOverride && hasVariants;
166
420
 
421
+ const outputBaseDir = path.join(
422
+ process.cwd(),
423
+ docSyncConfig.assetDir || ".reshot/output"
424
+ );
425
+
426
+ function persistRunManifest(success, scenarios) {
427
+ return writeRunManifest({
428
+ success,
429
+ selectedScenarioKeys:
430
+ preflightReport.selectedScenarioKeys || scenarioKeys || [],
431
+ outputBaseDir,
432
+ scenarios: normalizeScenarioResults(scenarios),
433
+ preflight: preflightReport,
434
+ diffEnabled: shouldDiff,
435
+ });
436
+ }
437
+
167
438
  if (shouldExpandVariants) {
168
439
  const combinations = generateVariantCombinations(dimensions);
169
440
 
@@ -238,15 +509,17 @@ async function runCommand(options = {}) {
238
509
  process.exitCode = 1;
239
510
  }
240
511
 
241
- if (!allSuccess) {
242
- process.exitCode = 1;
243
- }
512
+ const flattenedScenarioResults = allResults.flatMap(
513
+ (runResult) => runResult.results || [],
514
+ );
515
+ const persisted = persistRunManifest(allSuccess, flattenedScenarioResults);
516
+ console.log(chalk.gray(`\nRun manifest saved to: ${persisted.manifestPath}`));
244
517
 
245
518
  // Exit after variant expansion completes unless called programmatically
246
519
  if (!noExit) {
247
520
  process.exit(allSuccess ? 0 : 1);
248
521
  }
249
- return { success: allSuccess, results: allResults };
522
+ return { success: allSuccess, results: allResults, runManifest: persisted.manifest };
250
523
  }
251
524
  }
252
525
 
@@ -263,11 +536,6 @@ async function runCommand(options = {}) {
263
536
  // ============================================
264
537
  // POST-PROCESSING: Local Version-to-Version Diffing
265
538
  // ============================================
266
- const outputBaseDir = path.join(
267
- process.cwd(),
268
- docSyncConfig.assetDir || ".reshot/output"
269
- );
270
-
271
539
  // ============================================
272
540
  // CLOUD BASELINE SYNC: Download approved baselines from platform
273
541
  // ============================================
@@ -464,6 +732,8 @@ async function runCommand(options = {}) {
464
732
  }
465
733
 
466
734
  console.log(chalk.gray(`\nOutput saved to: ${outputBaseDir}`));
735
+ const persisted = persistRunManifest(result.success, result.results || []);
736
+ console.log(chalk.gray(`Run manifest saved to: ${persisted.manifestPath}`));
467
737
 
468
738
  // Offer to open Studio for review (interactive TTY only, on success)
469
739
  if (result.success && process.stdin.isTTY && !noExit) {
@@ -481,7 +751,11 @@ async function runCommand(options = {}) {
481
751
  console.log(chalk.cyan("\n๐ŸŽฌ Launching Reshot Studio...\n"));
482
752
  const uiCommand = require("./ui");
483
753
  await uiCommand({ open: true });
484
- return { success: result.success, results: result.results };
754
+ return {
755
+ success: result.success,
756
+ results: result.results,
757
+ runManifest: persisted.manifest,
758
+ };
485
759
  }
486
760
  }
487
761
 
@@ -490,7 +764,13 @@ async function runCommand(options = {}) {
490
764
  setImmediate(() => process.exit(process.exitCode || 0));
491
765
  }
492
766
 
493
- return { success: result.success, results: result.results };
767
+ return {
768
+ success: result.success,
769
+ results: result.results,
770
+ runManifest: persisted.manifest,
771
+ };
494
772
  }
495
773
 
496
774
  module.exports = runCommand;
775
+ module.exports.buildRunPreflightReport = buildRunPreflightReport;
776
+ module.exports.probeBaseUrl = probeBaseUrl;