@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
package/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // index.js - Reshot CLI entry point
4
- // Consolidated command structure for ease of use while maintaining power features
5
4
 
6
5
  require("dotenv").config({
7
6
  path: require("path").join(__dirname, "..", ".env"),
@@ -15,7 +14,7 @@ const program = new Command();
15
14
 
16
15
  program
17
16
  .name("reshot")
18
- .description("Visual documentation that stays in sync")
17
+ .description("Visual capture, publishing, and governance for your UI")
19
18
  .version(pkg.version);
20
19
 
21
20
  // ============================================================================
@@ -25,8 +24,9 @@ program
25
24
  // Setup: Interactive wizard for initial configuration
26
25
  program
27
26
  .command("setup")
28
- .description("Interactive setup wizard (auth, config, features)")
29
- .option("--offline", "Skip authentication (local-only mode)")
27
+ .description("Set up the local or hosted workflow from scratch")
28
+ .option("--offline", "Stay local-only and skip hosted authentication")
29
+ .option("--no-studio", "Skip offering to launch Studio after setup")
30
30
  .option("--force", "Force re-initialization even if already set up")
31
31
  .action(async (options) => {
32
32
  try {
@@ -38,13 +38,23 @@ program
38
38
  }
39
39
  });
40
40
 
41
- // Sync: Upload traces and documentation to platform
41
+ program.addHelpText(
42
+ "after",
43
+ `
44
+ Primary workflow:
45
+ reshot setup Configure the project and choose local-only or hosted mode
46
+ reshot run Run configured scenarios with fail-fast preflight checks
47
+ reshot publish Upload captured assets to the Reshot platform
48
+ reshot pull Pull approved CDN-backed asset references into your repo
49
+ reshot doctor target Audit certified-target readiness before capture
50
+ `,
51
+ );
52
+
53
+ // Sync: Upload Playwright traces to platform
42
54
  program
43
55
  .command("sync")
44
- .description("Upload Playwright traces and documentation to Reshot")
56
+ .description("Upload Playwright traces to Reshot")
45
57
  .option("--trace-dir <path>", "Path to test-results directory")
46
- .option("--traces", "Sync traces only")
47
- .option("--docs", "Sync documentation only")
48
58
  .option("--dry-run", "Preview what would be synced")
49
59
  .option("-v, --verbose", "Show detailed output")
50
60
  .action(async (options) => {
@@ -52,8 +62,6 @@ program
52
62
  const syncCommand = require("./commands/sync");
53
63
  await syncCommand({
54
64
  traceDir: options.traceDir,
55
- traces: options.traces,
56
- docs: options.docs,
57
65
  dryRun: options.dryRun,
58
66
  verbose: options.verbose,
59
67
  });
@@ -63,7 +71,7 @@ program
63
71
  }
64
72
  });
65
73
 
66
- // Status: View project status, sync jobs, and drift summary
74
+ // Status: View project status and drift summary
67
75
  program
68
76
  .command("status")
69
77
  .description("View project status, sync history, and drift summary")
@@ -99,27 +107,6 @@ program
99
107
  }
100
108
  });
101
109
 
102
- // Validate: Check configuration and documentation bindings
103
- program
104
- .command("validate")
105
- .description("Validate configuration and documentation bindings")
106
- .option("--strict", "Fail on warnings (zombie links, unbound files)")
107
- .option("--fix", "Attempt to auto-fix issues where possible")
108
- .option("-v, --verbose", "Show detailed validation output")
109
- .action(async (options) => {
110
- try {
111
- const validateCommand = require("./commands/validate-docs");
112
- if (typeof validateCommand.validateDocSync === "function") {
113
- await validateCommand.validateDocSync(options);
114
- } else {
115
- await validateCommand.validateDocs(options);
116
- }
117
- } catch (error) {
118
- console.error(chalk.red("Error:"), error.message);
119
- process.exit(1);
120
- }
121
- });
122
-
123
110
  // ============================================================================
124
111
  // VISUAL CAPTURE COMMANDS
125
112
  // ============================================================================
@@ -179,19 +166,96 @@ program
179
166
  }
180
167
  });
181
168
 
169
+ // Capture: Compatibility alias for older docs and snippets
170
+ program
171
+ .command("capture")
172
+ .description("Compatibility alias for `reshot run` (prefer `reshot run` and `reshot publish`)")
173
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
174
+ .option("--mode <mode>", "Compatibility mode: preview | publish", "preview")
175
+ .option("--config <path>", "Compatibility config path (defaults to reshot.config.json)")
176
+ .option("--base-url <url>", "Temporary base URL override for compatibility flows")
177
+ .option("--tag <tag>", "Version tag when mode=publish")
178
+ .option("-m, --message <message>", "Publish message when mode=publish")
179
+ .option("--no-headless", "Run browser in visible mode")
180
+ .option("-c, --concurrency <n>", "Number of parallel browser workers", parseInt)
181
+ .option("--debug", "Enable verbose debug logging")
182
+ .action(async (options) => {
183
+ if (options.debug) {
184
+ process.env.RESHOT_DEBUG = "1";
185
+ }
186
+ if (options.config && options.config !== "reshot.config.json") {
187
+ console.log(
188
+ chalk.yellow(
189
+ "Compatibility mode currently uses reshot.config.json from the working directory.",
190
+ ),
191
+ );
192
+ }
193
+ if (options.baseUrl) {
194
+ process.env.RESHOT_BASE_URL = options.baseUrl;
195
+ console.log(
196
+ chalk.gray(
197
+ `Using temporary base URL override for this run: ${options.baseUrl}`,
198
+ ),
199
+ );
200
+ }
201
+ try {
202
+ const runCommand = require("./commands/run");
203
+ const scenarioKeys = options.scenarios
204
+ ? options.scenarios.split(",").map((s) => s.trim())
205
+ : null;
206
+ await runCommand({
207
+ scenarioKeys,
208
+ headless: options.headless,
209
+ concurrency: options.concurrency,
210
+ });
211
+
212
+ if (String(options.mode || "preview").toLowerCase() === "publish") {
213
+ const publishCommand = require("./commands/publish");
214
+ await publishCommand({
215
+ tag: options.tag,
216
+ message: options.message,
217
+ });
218
+ }
219
+ } catch (error) {
220
+ console.error(chalk.red("Error:"), error.message);
221
+ if (options.debug && error.stack) {
222
+ console.error(chalk.gray(error.stack));
223
+ }
224
+ process.exit(1);
225
+ }
226
+ });
227
+
182
228
  // Record: Interactive visual recording from live browser
183
229
  program
184
230
  .command("record [title]")
185
231
  .description("Interactively record visuals from a live browser session")
232
+ .option("--name <title>", "Compatibility alias for the scenario title")
186
233
  .option("--browser", "Launch Chrome with remote debugging before recording")
187
234
  .option("-p, --port <port>", "Chrome debugging port (default: 9222)")
188
235
  .option("--url <url>", "URL to open when launching browser")
236
+ .option("--refresh-session", "Only refresh the auth session (no recording prompts)")
189
237
  .option("--debug", "Enable verbose debug logging")
190
238
  .action(async (title, options) => {
191
239
  if (options.debug) {
192
240
  process.env.RESHOT_DEBUG = "1";
193
241
  }
242
+ const resolvedTitle = title || options.name;
194
243
  try {
244
+ // If --refresh-session, just sync the session and exit
245
+ if (options.refreshSession) {
246
+ const { autoSyncSessionFromCDP, getDefaultSessionPath } = require("./lib/record-cdp");
247
+ const sessionPath = getDefaultSessionPath();
248
+ console.log(chalk.gray(" Syncing session from active browser..."));
249
+ const result = await autoSyncSessionFromCDP(sessionPath);
250
+ if (result.synced) {
251
+ console.log(chalk.green(" ✔ Session refreshed at " + sessionPath));
252
+ } else {
253
+ console.log(chalk.yellow(" ⚠ No active CDP browser found. Launch Chrome with remote debugging first:"));
254
+ console.log(chalk.gray(" reshot record --browser --refresh-session"));
255
+ }
256
+ return;
257
+ }
258
+
195
259
  // If --browser flag, launch Chrome first
196
260
  if (options.browser) {
197
261
  const chromeCommand = require("./commands/chrome");
@@ -204,7 +268,7 @@ program
204
268
  }
205
269
 
206
270
  const recordCommand = require("./commands/record");
207
- await recordCommand(title);
271
+ await recordCommand(resolvedTitle);
208
272
  } catch (error) {
209
273
  console.error(chalk.red("Error:"), error.message);
210
274
  if (options.debug && error.stack) {
@@ -246,13 +310,18 @@ program
246
310
  .option("--video <path>", "Explicit video file to upload with this publish")
247
311
  .option("--dry-run", "Preview without uploading")
248
312
  .option("-f, --force", "Skip confirmation prompts")
313
+ .option("--all-output", "Publish from the full .reshot/output tree instead of the latest successful run manifest")
249
314
  .option("--output-json", "Write structured result to .reshot/output/publish-result.json")
315
+ .option("--auto-approve", "Automatically approve published visuals (skip review queue)")
316
+ .option("--skip-release-doctor", "Skip the composed release gate precheck")
250
317
  .action(async (options) => {
251
318
  try {
252
319
  const publishCommand = require("./commands/publish");
253
320
  await publishCommand({
254
321
  ...options,
255
322
  outputJson: options.outputJson,
323
+ autoApprove: options.autoApprove,
324
+ skipReleaseDoctor: options.skipReleaseDoctor,
256
325
  });
257
326
  } catch (error) {
258
327
  console.error(chalk.red("Error:"), error.message);
@@ -283,6 +352,78 @@ program
283
352
  }
284
353
  });
285
354
 
355
+ // Doctor: Validate target contract and readiness
356
+ const doctor = program.command("doctor").description("Validate target configuration and readiness");
357
+
358
+ doctor
359
+ .command("target")
360
+ .description("Audit the certified target contract before capture")
361
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
362
+ .option("--json", "Output JSON report")
363
+ .action(async (options) => {
364
+ try {
365
+ const doctorTargetCommand = require("./commands/doctor-target");
366
+ await doctorTargetCommand(options);
367
+ } catch (error) {
368
+ console.error(chalk.red("Error:"), error.message);
369
+ process.exit(1);
370
+ }
371
+ });
372
+
373
+ doctor
374
+ .command("release")
375
+ .description("Run the combined release gate: preflight, target doctor, and docs assets")
376
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
377
+ .option("--json", "Output JSON report")
378
+ .action(async (options) => {
379
+ try {
380
+ const doctorReleaseCommand = require("./commands/doctor-release");
381
+ await doctorReleaseCommand(options);
382
+ } catch (error) {
383
+ console.error(chalk.red("Error:"), error.message);
384
+ process.exit(1);
385
+ }
386
+ });
387
+
388
+ // Verify: Publish/pull/hosted delivery verification
389
+ const verify = program.command("verify").description("Verify publish and delivery flows");
390
+
391
+ verify
392
+ .command("publish")
393
+ .description("Verify publish, pull, and hosted asset delivery")
394
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
395
+ .option("--tag <tag>", "Version tag for verification publish")
396
+ .option("-m, --message <message>", "Publish message override")
397
+ .option("--json", "Output JSON report")
398
+ .action(async (options) => {
399
+ try {
400
+ const verifyPublishCommand = require("./commands/verify-publish");
401
+ await verifyPublishCommand(options);
402
+ } catch (error) {
403
+ console.error(chalk.red("Error:"), error.message);
404
+ process.exit(1);
405
+ }
406
+ });
407
+
408
+ // Certify: full certified-target release gate
409
+ program
410
+ .command("certify")
411
+ .description("Run the full certified target pipeline")
412
+ .option("-s, --scenarios <keys>", "Comma-separated list of scenario keys")
413
+ .option("--tag <tag>", "Version tag for certification publish")
414
+ .option("-m, --message <message>", "Publish message override")
415
+ .option("--skip-release-doctor", "Skip the composed release gate precheck")
416
+ .option("--json", "Output JSON report")
417
+ .action(async (options) => {
418
+ try {
419
+ const certifyCommand = require("./commands/certify");
420
+ await certifyCommand(options);
421
+ } catch (error) {
422
+ console.error(chalk.red("Error:"), error.message);
423
+ process.exit(1);
424
+ }
425
+ });
426
+
286
427
  // CI: CI/CD integration commands
287
428
  const ciCommand = program
288
429
  .command("ci")
@@ -304,11 +445,12 @@ ciCommand
304
445
  ciCommand
305
446
  .command("run")
306
447
  .description("Run capture + publish in one step (CI-optimized)")
307
- .option("-c, --config <path>", "Path to docsync.config.json")
448
+ .option("-c, --config <path>", "Path to reshot.config.json")
308
449
  .option("--tag <tag>", "Version tag for publish")
309
450
  .option("-m, --message <message>", "Commit message for publish")
310
451
  .option("--dry-run", "Preview without uploading")
311
452
  .option("--no-publish", "Run capture only, skip publish")
453
+ .option("--skip-release-doctor", "Skip the composed release gate precheck")
312
454
  .action(async (options) => {
313
455
  try {
314
456
  const ciRun = require("./commands/ci-run");
@@ -323,10 +465,10 @@ ciCommand
323
465
  // DRIFT MANAGEMENT COMMANDS
324
466
  // ============================================================================
325
467
 
326
- // Drifts: View and manage documentation drifts
468
+ // Drifts: View and manage visual drifts
327
469
  program
328
470
  .command("drifts [action] [id]")
329
- .description("View and manage documentation drifts")
471
+ .description("View and manage visual drifts")
330
472
  .addHelpText(
331
473
  "after",
332
474
  `
@@ -339,7 +481,6 @@ Actions:
339
481
  sync <id> Mark as manually synced (external_host)
340
482
  approve-all Approve all pending drifts
341
483
  reject-all Reject all pending drifts
342
- validate Validate journey bindings
343
484
  `,
344
485
  )
345
486
  .option("--status <status>", "Filter: PENDING, APPROVED, REJECTED, IGNORED")
@@ -348,7 +489,6 @@ Actions:
348
489
  .action(async (action, id, options) => {
349
490
  try {
350
491
  const driftsCommand = require("./commands/drifts");
351
- // Map action/id to the expected subcommand/args format
352
492
  const subcommand = action || "list";
353
493
  const args = id ? [id] : [];
354
494
  await driftsCommand(subcommand, args, options);
@@ -363,9 +503,22 @@ Actions:
363
503
  // ============================================================================
364
504
 
365
505
  // Auth: Standalone authentication (for re-auth scenarios)
366
- program
367
- .command("auth", { hidden: true })
368
- .description("Authenticate with Reshot Cloud")
506
+ const auth = program
507
+ .command("auth")
508
+ .description("Authenticate with Reshot Cloud (or set RESHOT_API_KEY + RESHOT_PROJECT_ID env vars)")
509
+ .action(async () => {
510
+ try {
511
+ const authCommand = require("./commands/auth");
512
+ await authCommand();
513
+ } catch (error) {
514
+ console.error(chalk.red("Error:"), error.message);
515
+ process.exit(1);
516
+ }
517
+ });
518
+
519
+ auth
520
+ .command("login")
521
+ .description("Compatibility alias for `reshot auth`")
369
522
  .action(async () => {
370
523
  try {
371
524
  const authCommand = require("./commands/auth");
@@ -406,57 +559,4 @@ program
406
559
  }
407
560
  });
408
561
 
409
- // Ingest: Alias for sync (backward compatibility)
410
- program
411
- .command("ingest", { hidden: true })
412
- .description("Upload traces and docs (alias for sync)")
413
- .option("--trace-dir <path>", "Path to test-results directory")
414
- .option("--dry-run", "Preview what would be synced")
415
- .action(async (options) => {
416
- console.log(
417
- chalk.yellow("Note: 'ingest' is now 'sync'. Running sync...\n"),
418
- );
419
- try {
420
- const syncCommand = require("./commands/sync");
421
- await syncCommand({
422
- traceDir: options.traceDir,
423
- dryRun: options.dryRun,
424
- });
425
- } catch (error) {
426
- console.error(chalk.red("Error:"), error.message);
427
- process.exit(1);
428
- }
429
- });
430
-
431
- // UI: Alias for studio (backward compatibility)
432
- program
433
- .command("ui", { hidden: true })
434
- .description("Launch Reshot Studio (alias for studio)")
435
- .option("--port <port>", "Port for web UI", "4300")
436
- .option("--host <host>", "Host for web UI", "127.0.0.1")
437
- .option("--no-open", "Do not automatically open browser")
438
- .action(async (options) => {
439
- try {
440
- const uiCommand = require("./commands/ui");
441
- await uiCommand(options);
442
- } catch (error) {
443
- console.error(chalk.red("Error:"), error.message);
444
- process.exit(1);
445
- }
446
- });
447
-
448
- // Init-docs: Alias hidden (merged into setup wizard)
449
- program
450
- .command("init-docs", { hidden: true })
451
- .description("Initialize documentation sync (use setup instead)")
452
- .action(async () => {
453
- try {
454
- const initDocsCommand = require("./commands/init-docs");
455
- await initDocsCommand();
456
- } catch (error) {
457
- console.error(chalk.red("Error:"), error.message);
458
- process.exit(1);
459
- }
460
- });
461
-
462
562
  program.parse(process.argv);
@@ -3,15 +3,80 @@ const axios = require("axios");
3
3
  const FormData = require("form-data");
4
4
  const fs = require("fs");
5
5
 
6
- const baseUrl =
7
- process.env.RESHOT_API_BASE_URL ||
8
- process.env.DOCSYNC_API_BASE_URL ||
9
- "http://localhost:3000/api";
6
+ const PRODUCTION_API_URL = "https://reshot.dev/api";
7
+
8
+ function summarizeApiBody(body) {
9
+ if (body == null) return "";
10
+ if (typeof body === "string") return body.trim();
11
+ if (typeof body !== "object") return String(body);
12
+
13
+ const candidates = [
14
+ body.message,
15
+ body.error?.message,
16
+ body.error,
17
+ body.reason,
18
+ body.details,
19
+ ].filter(Boolean);
20
+
21
+ for (const candidate of candidates) {
22
+ if (typeof candidate === "string" && candidate.trim()) {
23
+ return candidate.trim();
24
+ }
25
+ }
26
+
27
+ try {
28
+ return JSON.stringify(body);
29
+ } catch {
30
+ return String(body);
31
+ }
32
+ }
33
+
34
+ function createApiError(kind, endpoint, error) {
35
+ const status = error.response?.status || null;
36
+ const statusText = error.response?.statusText || "";
37
+ const bodySummary = summarizeApiBody(error.response?.data);
38
+ const baseMessage = bodySummary || error.message || "Unknown API error";
39
+ const statusLabel = status ? `${status}${statusText ? ` ${statusText}` : ""}` : "request failed";
40
+ const wrapped = new Error(`${kind} (${statusLabel}) at ${endpoint}: ${baseMessage}`);
41
+ wrapped.response = error.response;
42
+ wrapped.code = error.code;
43
+ wrapped.reshot = {
44
+ kind,
45
+ endpoint,
46
+ status,
47
+ statusText,
48
+ bodySummary,
49
+ };
50
+ return wrapped;
51
+ }
10
52
 
11
53
  function getApiBaseUrl() {
12
- return baseUrl;
54
+ // 1. Explicit env var override (for CI or local dev)
55
+ if (process.env.RESHOT_API_BASE_URL) {
56
+ return process.env.RESHOT_API_BASE_URL;
57
+ }
58
+
59
+ // 2. Read from settings.json (set during auth/setup)
60
+ try {
61
+ const path = require("path");
62
+ const settingsPath = path.join(process.cwd(), ".reshot", "settings.json");
63
+ if (fs.existsSync(settingsPath)) {
64
+ const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
65
+ if (settings.platformUrl) {
66
+ return settings.platformUrl.replace(/\/+$/, "") + "/api";
67
+ }
68
+ }
69
+ } catch {
70
+ // Settings don't exist yet (first auth) — fall through to default
71
+ }
72
+
73
+ // 3. Default to production
74
+ return PRODUCTION_API_URL;
13
75
  }
14
76
 
77
+ // Resolved once at module load — all API calls use this
78
+ const baseUrl = getApiBaseUrl();
79
+
15
80
  /**
16
81
  * Sleep helper for retry delays
17
82
  */
@@ -190,20 +255,7 @@ async function publishAssetsV1(apiKey, metadata, assets) {
190
255
  });
191
256
  return response.data;
192
257
  } catch (error) {
193
- if (error.response) {
194
- const status = error.response.status;
195
- const errorMsg = error.response.data?.error || error.message;
196
-
197
- // Create an error that preserves the response for auth detection
198
- const err = new Error(
199
- status === 401 || status === 403
200
- ? `Authentication failed: ${errorMsg}`
201
- : `Failed to publish assets: ${errorMsg}`,
202
- );
203
- err.response = error.response;
204
- throw err;
205
- }
206
- throw new Error(`Failed to publish assets: ${error.message}`);
258
+ throw createApiError("publish_ingest_failure", `${baseUrl}/v1/publish`, error);
207
259
  }
208
260
  }
209
261
 
@@ -586,6 +638,30 @@ async function publishTransactional(apiKey, payload) {
586
638
  return response.data;
587
639
  }
588
640
 
641
+ /**
642
+ * Batch publish metadata for multiple scenarios in one request
643
+ * @param {string} apiKey - API key for authentication
644
+ * @param {Object} payload - { commits: Array<{ metadata, assets }> }
645
+ * @returns {Promise<Object>}
646
+ */
647
+ async function publishBatch(apiKey, payload) {
648
+ if (!apiKey) {
649
+ throw new Error("API key is required to publish");
650
+ }
651
+
652
+ const response = await axios.post(`${baseUrl}/v1/publish/batch`, payload, {
653
+ headers: {
654
+ "Content-Type": "application/json",
655
+ Authorization: `Bearer ${apiKey}`,
656
+ },
657
+ timeout: 60000,
658
+ maxBodyLength: Infinity,
659
+ maxContentLength: Infinity,
660
+ });
661
+
662
+ return response.data;
663
+ }
664
+
589
665
  /**
590
666
  * Check which hashes already exist in storage (for deduplication)
591
667
  * @param {string} apiKey - API key for authentication
@@ -648,7 +724,7 @@ async function getBaselines(projectId, apiKey) {
648
724
  */
649
725
  async function exportVisuals(projectId, options = {}) {
650
726
  const { format = "json", status = "approved" } = options;
651
- const settings = require("./config").loadSettings();
727
+ const settings = require("./config").readSettings();
652
728
  const apiKey = settings?.apiKey;
653
729
 
654
730
  if (!apiKey) {
@@ -656,15 +732,23 @@ async function exportVisuals(projectId, options = {}) {
656
732
  }
657
733
 
658
734
  return withRetry(async () => {
659
- const response = await axios.get(
660
- `${baseUrl}/projects/${projectId}/visuals/export`,
661
- {
662
- params: { format, status },
663
- headers: { Authorization: `Bearer ${apiKey}` },
664
- timeout: 60000,
665
- },
666
- );
667
- return response.data;
735
+ try {
736
+ const response = await axios.get(
737
+ `${baseUrl}/projects/${projectId}/visuals/export`,
738
+ {
739
+ params: { format, status },
740
+ headers: { Authorization: `Bearer ${apiKey}` },
741
+ timeout: 60000,
742
+ },
743
+ );
744
+ return response.data;
745
+ } catch (error) {
746
+ throw createApiError(
747
+ "export_visuals_failure",
748
+ `${baseUrl}/projects/${projectId}/visuals/export`,
749
+ error,
750
+ );
751
+ }
668
752
  });
669
753
  }
670
754
 
@@ -688,7 +772,7 @@ async function post(endpoint, data, options = {}) {
688
772
  }
689
773
 
690
774
  /**
691
- * DocSync: Initialize ingestion job with manifest
775
+ * Initialize ingestion job with manifest
692
776
  */
693
777
  async function initIngest(apiKey, projectId, manifest) {
694
778
  return withRetry(async () => {
@@ -705,7 +789,7 @@ async function initIngest(apiKey, projectId, manifest) {
705
789
  }
706
790
 
707
791
  /**
708
- * DocSync: Commit ingestion job after uploads complete
792
+ * Commit ingestion job after uploads complete
709
793
  */
710
794
  async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
711
795
  return withRetry(async () => {
@@ -722,7 +806,7 @@ async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
722
806
  }
723
807
 
724
808
  /**
725
- * DocSync: Get drift records for a project
809
+ * Get drift records for a project
726
810
  */
727
811
  async function getDrifts(apiKey, projectId, options = {}) {
728
812
  return withRetry(async () => {
@@ -742,7 +826,7 @@ async function getDrifts(apiKey, projectId, options = {}) {
742
826
  }
743
827
 
744
828
  /**
745
- * DocSync: Get sync jobs for a project
829
+ * Get sync jobs for a project
746
830
  */
747
831
  async function getSyncJobs(apiKey, projectId, options = {}) {
748
832
  return withRetry(async () => {
@@ -762,7 +846,7 @@ async function getSyncJobs(apiKey, projectId, options = {}) {
762
846
  }
763
847
 
764
848
  /**
765
- * DocSync: Perform action on a drift record
849
+ * Perform action on a drift record
766
850
  */
767
851
  async function driftAction(apiKey, projectId, driftId, action, options = {}) {
768
852
  return withRetry(async () => {
@@ -794,18 +878,20 @@ module.exports = {
794
878
  getProjectConfig,
795
879
  postChangelogDrafts,
796
880
  getApiBaseUrl,
881
+ createApiError,
797
882
  syncPushAssets,
798
883
  getSyncStatus,
799
884
  // New transactional flow
800
885
  signAssets,
801
886
  uploadToPresignedUrl,
802
887
  publishTransactional,
888
+ publishBatch,
803
889
  checkExistingHashes,
804
890
  // Diffing support
805
891
  getBaselines,
806
892
  // Export support
807
893
  exportVisuals,
808
- // DocSync
894
+ // Reshot
809
895
  post,
810
896
  initIngest,
811
897
  commitIngest,