@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.20

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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +138 -47
  3. package/package.json +27 -16
  4. package/src/commands/auth.js +159 -30
  5. package/src/commands/capture-dom.js +50 -0
  6. package/src/commands/certify.js +62 -0
  7. package/src/commands/compose.js +220 -0
  8. package/src/commands/doctor-release.js +74 -0
  9. package/src/commands/doctor-target.js +108 -0
  10. package/src/commands/drifts.js +16 -69
  11. package/src/commands/import-tests.js +13 -13
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +484 -257
  14. package/src/commands/pull.js +302 -35
  15. package/src/commands/refresh.js +166 -0
  16. package/src/commands/run.js +292 -12
  17. package/src/commands/setup-wizard.js +348 -496
  18. package/src/commands/status.js +334 -126
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/variation.js +194 -0
  22. package/src/commands/verify-publish.js +46 -0
  23. package/src/index.js +383 -118
  24. package/src/lib/api-client.js +172 -60
  25. package/src/lib/auto-update/refresh.js +598 -0
  26. package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
  27. package/src/lib/auto-update/spec.js +89 -0
  28. package/src/lib/capture-engine.js +179 -9
  29. package/src/lib/capture-script-runner.js +639 -214
  30. package/src/lib/certification.js +887 -0
  31. package/src/lib/compose-context.js +156 -0
  32. package/src/lib/compose-pack.js +42 -0
  33. package/src/lib/compose-runtime.js +34 -0
  34. package/src/lib/compose-upload.js +142 -0
  35. package/src/lib/config.js +186 -81
  36. package/src/lib/dom-capture.js +64 -0
  37. package/src/lib/ensure-browser.js +147 -0
  38. package/src/lib/output-path-template.js +3 -3
  39. package/src/lib/record-cdp.js +288 -16
  40. package/src/lib/record-clip.js +83 -3
  41. package/src/lib/record-config.js +1 -5
  42. package/src/lib/release-doctor.js +321 -0
  43. package/src/lib/resolve-targets.js +60 -0
  44. package/src/lib/run-manifest.js +148 -0
  45. package/src/lib/standalone-mode.js +1 -1
  46. package/src/lib/storage-providers.js +5 -5
  47. package/src/lib/style-engine.js +5 -5
  48. package/src/lib/target-contract.js +292 -0
  49. package/src/lib/ui-api-helpers.js +118 -0
  50. package/src/lib/ui-api.js +31 -824
  51. package/src/lib/ui-asset-cleanup.js +62 -0
  52. package/src/lib/ui-output-versions.js +165 -0
  53. package/src/lib/ui-recorder-routes.js +341 -0
  54. package/src/lib/ui-scenario-metadata.js +161 -0
  55. package/vendor/compose/dist/auto-update.cjs +5544 -0
  56. package/vendor/compose/dist/auto-update.mjs +5518 -0
  57. package/vendor/compose/dist/capture.cjs +1450 -0
  58. package/vendor/compose/dist/capture.mjs +1416 -0
  59. package/vendor/compose/dist/eligibility.cjs +5331 -0
  60. package/vendor/compose/dist/eligibility.mjs +5313 -0
  61. package/vendor/compose/dist/index.cjs +2046 -0
  62. package/vendor/compose/dist/index.mjs +1997 -0
  63. package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
  64. package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
  65. package/vendor/compose/dist/jsx-runtime.cjs +58 -0
  66. package/vendor/compose/dist/jsx-runtime.mjs +31 -0
  67. package/vendor/compose/dist/render.cjs +558 -0
  68. package/vendor/compose/dist/render.mjs +515 -0
  69. package/vendor/compose/dist/verify-cli.cjs +3806 -0
  70. package/vendor/compose/dist/verify-cli.mjs +3812 -0
  71. package/vendor/compose/dist/verify.cjs +3880 -0
  72. package/vendor/compose/dist/verify.mjs +3858 -0
  73. package/web/manager/dist/assets/index-D0S2otug.js +507 -0
  74. package/web/manager/dist/index.html +1 -1
  75. package/src/commands/ci-run.js +0 -123
  76. package/src/commands/ci-setup.js +0 -288
  77. package/src/commands/ingest.js +0 -458
  78. package/src/commands/setup.js +0 -137
  79. package/src/commands/validate-docs.js +0 -529
  80. package/src/lib/playwright-runner.js +0 -252
  81. package/web/manager/dist/assets/index--ZgioErz.js +0 -507
@@ -1,20 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * status - Check DocSync status, sync jobs, and drift queue
3
+ * status - Check project status, sync jobs, and drift queue
4
4
  *
5
5
  * Displays:
6
6
  * - Current project configuration
7
7
  * - Recent sync jobs with status
8
8
  * - Drift queue summary
9
- * - Pending actions
10
9
  *
11
- * Usage: reshot status [--jobs] [--drifts] [--json]
10
+ * Usage: reshot status [--jobs] [--drifts] [--config] [--json]
12
11
  */
13
12
 
14
13
  const chalk = require("chalk");
15
14
  const config = require("../lib/config");
16
15
  const apiClient = require("../lib/api-client");
17
16
 
17
+ function createIssue(scope, level, message, details = {}) {
18
+ return {
19
+ scope,
20
+ level,
21
+ message,
22
+ ...details,
23
+ };
24
+ }
25
+
26
+ function summarizeConfig(reshotConfig) {
27
+ return {
28
+ baseUrl: reshotConfig.baseUrl || null,
29
+ viewport: reshotConfig.viewport || null,
30
+ assetDir: reshotConfig.assetDir || ".reshot/output",
31
+ scenarioCount: reshotConfig.scenarios?.length || 0,
32
+ traceDir: reshotConfig.visuals?.traceDir || null,
33
+ };
34
+ }
35
+
36
+ function readSettingsSafe() {
37
+ try {
38
+ return config.readSettings();
39
+ } catch (error) {
40
+ return null;
41
+ }
42
+ }
43
+
18
44
  /**
19
45
  * Format timestamp for display
20
46
  */
@@ -86,33 +112,275 @@ function formatDriftType(type) {
86
112
  }
87
113
 
88
114
  /**
89
- * Get and display project configuration
115
+ * Display project configuration summary
90
116
  */
91
- async function displayConfig(docSyncConfig) {
117
+ async function displayConfig(configSummary) {
92
118
  console.log(chalk.bold("\n📋 Project Configuration\n"));
93
119
 
94
- const docConfig = docSyncConfig.documentation;
95
- if (!docConfig) {
96
- console.log(chalk.yellow(" No documentation block configured."));
97
- console.log(chalk.gray(" Add a 'documentation' block to docsync.config.json"));
120
+ console.log(chalk.gray(" Base URL: ") + chalk.white(configSummary.baseUrl || "(not set)"));
121
+ console.log(chalk.gray(" Viewport: ") + chalk.white(
122
+ configSummary.viewport
123
+ ? `${configSummary.viewport.width}×${configSummary.viewport.height}`
124
+ : "(default)"
125
+ ));
126
+ console.log(chalk.gray(" Asset Dir: ") + chalk.white(configSummary.assetDir || ".reshot/output"));
127
+
128
+ console.log(chalk.gray(" Scenarios: ") + chalk.white(`${configSummary.scenarioCount || 0} defined`));
129
+
130
+ if (configSummary.traceDir) {
131
+ console.log(chalk.gray(" Trace Dir: ") + chalk.white(configSummary.traceDir));
132
+ }
133
+ }
134
+
135
+ function renderJobs(jobs) {
136
+ if (jobs.length === 0) {
137
+ console.log(chalk.gray(" No sync jobs found."));
138
+ console.log(chalk.gray(" Run `reshot sync` to upload traces."));
98
139
  return;
99
140
  }
100
141
 
101
- console.log(chalk.gray(" Root: ") + chalk.white(docConfig.root || "./docs"));
102
- console.log(chalk.gray(" Strategy: ") + chalk.white(docConfig.strategy));
103
- console.log(chalk.gray(" Format: ") + chalk.white(docConfig.assetFormat || "markdown"));
104
-
105
- if (docConfig.include?.length) {
106
- console.log(chalk.gray(" Include: ") + chalk.white(docConfig.include.join(", ")));
142
+ for (const job of jobs) {
143
+ console.log(
144
+ ` ${formatJobStatus(job.status)} ` +
145
+ chalk.gray(`${job.id.slice(0, 8)} `) +
146
+ chalk.white(`${job.branch || "unknown"}@${(job.commitHash || "").slice(0, 7)} `) +
147
+ chalk.gray(formatTime(job.createdAt)),
148
+ );
149
+
150
+ if (job.driftCount > 0) {
151
+ console.log(chalk.gray(` └─ ${job.driftCount} drift(s) detected`));
152
+ }
153
+ if (job.errorMessage) {
154
+ console.log(chalk.red(` └─ Error: ${job.errorMessage}`));
155
+ }
156
+ }
157
+ }
158
+
159
+ function renderDrifts(drifts, stats, projectId, limit = 10) {
160
+ if (stats.total > 0) {
161
+ console.log(
162
+ chalk.gray(" Summary: ") +
163
+ chalk.yellow(`${stats.pending || 0} pending`) + ", " +
164
+ chalk.green(`${stats.approved || 0} approved`) + ", " +
165
+ chalk.red(`${stats.rejected || 0} rejected`) + ", " +
166
+ chalk.gray(`${stats.ignored || 0} ignored`),
167
+ );
168
+ console.log();
169
+ }
170
+
171
+ if (drifts.length === 0) {
172
+ console.log(chalk.green(" ✓ No pending drifts!"));
173
+ return;
174
+ }
175
+
176
+ const displayed = drifts.slice(0, limit);
177
+ for (const drift of displayed) {
178
+ console.log(
179
+ ` ${formatDriftType(drift.driftType)} ` +
180
+ chalk.white(drift.journeyKey) + " " +
181
+ chalk.gray(`(${Math.round(drift.confidenceScore * 100)}% confidence)`),
182
+ );
183
+ }
184
+
185
+ if (drifts.length > limit) {
186
+ console.log(chalk.gray(`\n ... and ${drifts.length - limit} more.`));
187
+ }
188
+
189
+ console.log();
190
+ console.log(
191
+ chalk.gray(" View in dashboard: ") +
192
+ chalk.blue(`https://reshot.dev/app/projects/${projectId}/visuals`),
193
+ );
194
+ }
195
+
196
+ async function fetchJobsData(apiKey, projectId, limit = 5) {
197
+ try {
198
+ const response = await apiClient.getSyncJobs(apiKey, projectId, { limit });
199
+ return {
200
+ jobs: response.jobs || response.data?.jobs || [],
201
+ error: null,
202
+ };
203
+ } catch (error) {
204
+ return {
205
+ jobs: [],
206
+ error: {
207
+ message: error.message,
208
+ kind: error.reshot?.kind || null,
209
+ status: error.reshot?.status || error.response?.status || null,
210
+ },
211
+ };
212
+ }
213
+ }
214
+
215
+ async function fetchDriftsData(apiKey, projectId) {
216
+ try {
217
+ const response = await apiClient.getDrifts(apiKey, projectId, {
218
+ status: "pending",
219
+ });
220
+ return {
221
+ drifts: response.drifts || [],
222
+ stats: response.stats || {},
223
+ error: null,
224
+ };
225
+ } catch (error) {
226
+ return {
227
+ drifts: [],
228
+ stats: {},
229
+ error: {
230
+ message: error.message,
231
+ kind: error.reshot?.kind || null,
232
+ status: error.reshot?.status || error.response?.status || null,
233
+ },
234
+ };
235
+ }
236
+ }
237
+
238
+ async function buildStatusReport(options = {}) {
239
+ const report = {
240
+ generatedAt: new Date().toISOString(),
241
+ ok: true,
242
+ projectId: null,
243
+ mode: config.getModeInfo(),
244
+ config: {
245
+ exists: false,
246
+ summary: null,
247
+ validation: {
248
+ valid: false,
249
+ errors: [],
250
+ warnings: [],
251
+ },
252
+ },
253
+ auth: {
254
+ hasApiKey: false,
255
+ hasProjectId: false,
256
+ },
257
+ jobs: {
258
+ items: [],
259
+ error: null,
260
+ },
261
+ drifts: {
262
+ items: [],
263
+ stats: {},
264
+ error: null,
265
+ },
266
+ issues: [],
267
+ };
268
+
269
+ let reshotConfig = null;
270
+ try {
271
+ reshotConfig = config.readConfigLenient();
272
+ report.config.exists = true;
273
+ report.config.summary = summarizeConfig(reshotConfig);
274
+ } catch (error) {
275
+ report.issues.push(
276
+ createIssue(
277
+ "config",
278
+ "error",
279
+ "reshot.config.json not found. Run `reshot init` or `reshot setup` first.",
280
+ { detail: error.message },
281
+ ),
282
+ );
283
+ }
284
+
285
+ if (report.config.exists) {
286
+ report.config.validation = config.validateConfig();
287
+ for (const errorMessage of report.config.validation.errors) {
288
+ report.issues.push(createIssue("config", "error", errorMessage));
289
+ }
290
+ for (const warningMessage of report.config.validation.warnings) {
291
+ report.issues.push(createIssue("config", "warning", warningMessage));
292
+ }
293
+ }
294
+
295
+ const settings = readSettingsSafe();
296
+ const apiKey = process.env.RESHOT_API_KEY || settings?.apiKey || null;
297
+ const projectId =
298
+ process.env.RESHOT_PROJECT_ID ||
299
+ settings?.projectId ||
300
+ reshotConfig?._metadata?.projectId ||
301
+ null;
302
+
303
+ report.projectId = projectId;
304
+ report.auth.hasApiKey = Boolean(apiKey);
305
+ report.auth.hasProjectId = Boolean(projectId);
306
+
307
+ // Loudly warn when the linked session (settings.json) and the committed
308
+ // config (reshot.config.json) disagree on the project — settings.json wins,
309
+ // so an edited/stale config projectId would otherwise be silently ignored.
310
+ const configProjectId =
311
+ reshotConfig?.projectId || reshotConfig?._metadata?.projectId || null;
312
+ if (
313
+ projectId &&
314
+ configProjectId &&
315
+ configProjectId !== projectId &&
316
+ !process.env.RESHOT_PROJECT_ID
317
+ ) {
318
+ report.issues.push(
319
+ createIssue(
320
+ "config",
321
+ "warning",
322
+ `Project ID mismatch: using ${projectId} (from .reshot/settings.json) but reshot.config.json declares ${configProjectId}. Run \`reshot setup\` to reconcile.`,
323
+ ),
324
+ );
107
325
  }
108
- if (docConfig.exclude?.length) {
109
- console.log(chalk.gray(" Exclude: ") + chalk.white(docConfig.exclude.join(", ")));
326
+
327
+ if (!apiKey) {
328
+ report.issues.push(
329
+ createIssue(
330
+ "auth",
331
+ "error",
332
+ "API key not found. Set RESHOT_API_KEY or run `reshot auth`.",
333
+ ),
334
+ );
110
335
  }
111
-
112
- const mappingCount = Object.keys(docConfig.mappings || {}).length;
113
- if (mappingCount > 0) {
114
- console.log(chalk.gray(" Mappings: ") + chalk.white(`${mappingCount} explicit binding(s)`));
336
+
337
+ if (!projectId) {
338
+ report.issues.push(
339
+ createIssue(
340
+ "auth",
341
+ "error",
342
+ "Project ID not found. Set RESHOT_PROJECT_ID or run `reshot setup`.",
343
+ ),
344
+ );
115
345
  }
346
+
347
+ if (apiKey && projectId) {
348
+ const [jobsResult, driftsResult] = await Promise.all([
349
+ fetchJobsData(apiKey, projectId, options.limit || 5),
350
+ fetchDriftsData(apiKey, projectId),
351
+ ]);
352
+
353
+ report.jobs.items = jobsResult.jobs;
354
+ report.jobs.error = jobsResult.error;
355
+ report.drifts.items = driftsResult.drifts;
356
+ report.drifts.stats = driftsResult.stats;
357
+ report.drifts.error = driftsResult.error;
358
+
359
+ if (jobsResult.error) {
360
+ report.issues.push(
361
+ createIssue(
362
+ "jobs",
363
+ "warning",
364
+ `Could not fetch sync jobs: ${jobsResult.error.message}`,
365
+ jobsResult.error,
366
+ ),
367
+ );
368
+ }
369
+
370
+ if (driftsResult.error) {
371
+ report.issues.push(
372
+ createIssue(
373
+ "drifts",
374
+ "warning",
375
+ `Could not fetch drifts: ${driftsResult.error.message}`,
376
+ driftsResult.error,
377
+ ),
378
+ );
379
+ }
380
+ }
381
+
382
+ report.ok = !report.issues.some((issue) => issue.level === "error");
383
+ return report;
116
384
  }
117
385
 
118
386
  /**
@@ -122,32 +390,9 @@ async function displayJobs(apiKey, projectId, limit = 5) {
122
390
  console.log(chalk.bold("\n📦 Recent Sync Jobs\n"));
123
391
 
124
392
  try {
125
- // Fetch jobs via API
126
393
  const response = await apiClient.getSyncJobs(apiKey, projectId, { limit });
127
-
128
394
  const jobs = response.jobs || response.data?.jobs || [];
129
-
130
- if (jobs.length === 0) {
131
- console.log(chalk.gray(" No sync jobs found."));
132
- console.log(chalk.gray(" Run `reshot ingest` to upload traces and docs."));
133
- return;
134
- }
135
-
136
- for (const job of jobs) {
137
- console.log(
138
- ` ${formatJobStatus(job.status)} ` +
139
- chalk.gray(`${job.id.slice(0, 8)} `) +
140
- chalk.white(`${job.branch || "unknown"}@${(job.commitHash || "").slice(0, 7)} `) +
141
- chalk.gray(formatTime(job.createdAt))
142
- );
143
-
144
- if (job.driftCount > 0) {
145
- console.log(chalk.gray(` └─ ${job.driftCount} drift(s) detected`));
146
- }
147
- if (job.errorMessage) {
148
- console.log(chalk.red(` └─ Error: ${job.errorMessage}`));
149
- }
150
- }
395
+ renderJobs(jobs);
151
396
  } catch (error) {
152
397
  console.log(chalk.yellow(" Could not fetch sync jobs: " + error.message));
153
398
  console.log(chalk.gray(" This endpoint may not be available yet."));
@@ -161,48 +406,10 @@ async function displayDrifts(apiKey, projectId, limit = 10) {
161
406
  console.log(chalk.bold("\n🔄 Drift Queue\n"));
162
407
 
163
408
  try {
164
- const response = await apiClient.getDrifts(apiKey, projectId, { status: "PENDING" });
409
+ const response = await apiClient.getDrifts(apiKey, projectId, { status: "pending" });
165
410
  const drifts = response.drifts || [];
166
411
  const stats = response.stats || {};
167
-
168
- // Show stats summary
169
- if (stats.total > 0) {
170
- console.log(
171
- chalk.gray(" Summary: ") +
172
- chalk.yellow(`${stats.pending || 0} pending`) + ", " +
173
- chalk.green(`${stats.approved || 0} approved`) + ", " +
174
- chalk.red(`${stats.rejected || 0} rejected`) + ", " +
175
- chalk.gray(`${stats.ignored || 0} ignored`)
176
- );
177
- console.log();
178
- }
179
-
180
- if (drifts.length === 0) {
181
- console.log(chalk.green(" ✓ No pending drifts!"));
182
- console.log(chalk.gray(" Your documentation is in sync with the application."));
183
- return;
184
- }
185
-
186
- // Show pending drifts
187
- const displayed = drifts.slice(0, limit);
188
- for (const drift of displayed) {
189
- console.log(
190
- ` ${formatDriftType(drift.driftType)} ` +
191
- chalk.white(drift.journeyKey || drift.docPath) + " " +
192
- chalk.gray(`(${Math.round(drift.confidenceScore * 100)}% confidence)`)
193
- );
194
-
195
- if (drift.docPath) {
196
- console.log(chalk.gray(` └─ ${drift.docPath}`));
197
- }
198
- }
199
-
200
- if (drifts.length > limit) {
201
- console.log(chalk.gray(`\n ... and ${drifts.length - limit} more.`));
202
- }
203
-
204
- console.log();
205
- console.log(chalk.gray(" View in dashboard: ") + chalk.blue(`https://reshot.dev/app/projects/${projectId}/docsync`));
412
+ renderDrifts(drifts, stats, projectId, limit);
206
413
  } catch (error) {
207
414
  console.log(chalk.yellow(" Could not fetch drifts: " + error.message));
208
415
  }
@@ -212,64 +419,65 @@ async function displayDrifts(apiKey, projectId, limit = 10) {
212
419
  * Main status command
213
420
  */
214
421
  async function statusCommand(options = {}) {
215
- console.log(chalk.blue("\n📊 Reshot DocSync Status\n"));
422
+ const report = await buildStatusReport(options);
216
423
 
217
- // Read configuration
218
- let docSyncConfig;
219
- try {
220
- docSyncConfig = config.readDocSyncConfig();
221
- } catch (error) {
222
- console.error(chalk.red("Error:"), "docsync.config.json not found. Run `reshot init` first.");
223
- process.exit(1);
424
+ if (options.json) {
425
+ console.log(JSON.stringify(report, null, 2));
426
+ if (!report.ok) {
427
+ process.exitCode = 1;
428
+ }
429
+ return report;
224
430
  }
225
431
 
226
- // Get API key and project ID
227
- const settings = config.readSettings();
228
- const apiKey = process.env.RESHOT_API_KEY || settings?.apiKey;
229
- const projectId =
230
- process.env.RESHOT_PROJECT_ID ||
231
- settings?.projectId ||
232
- docSyncConfig._metadata?.projectId;
432
+ console.log(chalk.blue("\n📊 Reshot Status\n"));
233
433
 
234
- if (!apiKey) {
235
- console.error(chalk.red("Error:"), "API key not found. Set RESHOT_API_KEY or run `reshot auth`.");
236
- process.exit(1);
434
+ if (report.projectId) {
435
+ console.log(chalk.gray(` Project: ${report.projectId}`));
237
436
  }
238
437
 
239
- if (!projectId) {
240
- console.error(chalk.red("Error:"), "Project ID not found. Set RESHOT_PROJECT_ID or run `reshot init`.");
241
- process.exit(1);
438
+ if (report.config.summary && (options.config || (!options.jobs && !options.drifts))) {
439
+ await displayConfig(report.config.summary);
242
440
  }
243
441
 
244
- console.log(chalk.gray(` Project: ${projectId}`));
245
-
246
- // Display configuration
247
- if (options.config || (!options.jobs && !options.drifts)) {
248
- await displayConfig(docSyncConfig);
442
+ if (!options.config && (options.jobs || (!options.config && !options.drifts))) {
443
+ console.log(chalk.bold("\n📦 Recent Sync Jobs\n"));
444
+ if (report.jobs.error) {
445
+ console.log(chalk.yellow(" Could not fetch sync jobs: " + report.jobs.error.message));
446
+ console.log(chalk.gray(" This endpoint may not be available yet."));
447
+ } else {
448
+ renderJobs(report.jobs.items);
449
+ }
249
450
  }
250
451
 
251
- // Display jobs
252
- if (options.jobs || (!options.config && !options.drifts)) {
253
- await displayJobs(apiKey, projectId, options.limit || 5);
452
+ if (!options.jobs && (options.drifts || (!options.config && !options.jobs))) {
453
+ console.log(chalk.bold("\n🔄 Drift Queue\n"));
454
+ if (report.drifts.error) {
455
+ console.log(chalk.yellow(" Could not fetch drifts: " + report.drifts.error.message));
456
+ } else {
457
+ renderDrifts(
458
+ report.drifts.items,
459
+ report.drifts.stats,
460
+ report.projectId,
461
+ options.limit || 10,
462
+ );
463
+ }
254
464
  }
255
465
 
256
- // Display drifts
257
- if (options.drifts || (!options.config && !options.jobs)) {
258
- await displayDrifts(apiKey, projectId, options.limit || 10);
466
+ if (report.issues.length > 0) {
467
+ console.log(chalk.bold("\n⚠ Issues\n"));
468
+ for (const issue of report.issues) {
469
+ const prefix = issue.level === "error" ? chalk.red(" ✖") : chalk.yellow(" ⚠");
470
+ console.log(`${prefix} [${issue.scope}] ${issue.message}`);
471
+ }
259
472
  }
260
473
 
261
- // JSON output mode
262
- if (options.json) {
263
- const response = await apiClient.getDrifts(apiKey, projectId).catch(() => ({}));
264
- console.log(JSON.stringify({
265
- projectId,
266
- config: docSyncConfig.documentation,
267
- drifts: response.drifts || [],
268
- stats: response.stats || {},
269
- }, null, 2));
474
+ if (!report.ok) {
475
+ process.exitCode = 1;
270
476
  }
271
477
 
272
478
  console.log();
479
+ return report;
273
480
  }
274
481
 
275
482
  module.exports = statusCommand;
483
+ module.exports.buildStatusReport = buildStatusReport;