@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.
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 +359 -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
@@ -15,6 +15,7 @@ const {
15
15
  getStorageMode,
16
16
  isPlatformAvailable,
17
17
  } = require("../lib/storage-providers");
18
+ const { getLatestSuccessfulRunManifest } = require("../lib/run-manifest");
18
19
  const pkg = require("../../package.json");
19
20
 
20
21
  // Check if transactional flow should be used (R2 configured on server)
@@ -390,13 +391,13 @@ function resolveProjectContext({ settings, docSyncConfig, storageMode }) {
390
391
  if (!apiKey) {
391
392
  throw new Error(
392
393
  "No API key found. Set RESHOT_API_KEY in your environment or run `reshot auth` locally to create .reshot/settings.json.\n" +
393
- "Alternatively, configure BYOS (Bring Your Own Storage) in docsync.config.json to publish without authentication.",
394
+ "Alternatively, configure BYOS (Bring Your Own Storage) in reshot.config.json to publish without authentication.",
394
395
  );
395
396
  }
396
397
 
397
398
  if (!projectId) {
398
399
  throw new Error(
399
- "No project ID found. Set RESHOT_PROJECT_ID in your environment or ensure docsync.config.json contains _metadata.projectId.",
400
+ "No project ID found. Set RESHOT_PROJECT_ID in your environment or ensure reshot.config.json contains _metadata.projectId.",
400
401
  );
401
402
  }
402
403
 
@@ -487,6 +488,48 @@ function groupAssetsByScenario(assetFiles, outputBaseDir) {
487
488
  return Array.from(groups.values());
488
489
  }
489
490
 
491
+ function collectAssetFilesFromDirectories(directories) {
492
+ const deduped = new Set();
493
+ const files = [];
494
+
495
+ for (const directory of directories) {
496
+ if (!directory || !fs.existsSync(directory)) continue;
497
+ for (const filePath of findAssetFiles(directory)) {
498
+ if (deduped.has(filePath)) continue;
499
+ deduped.add(filePath);
500
+ files.push(filePath);
501
+ }
502
+ }
503
+
504
+ return files;
505
+ }
506
+
507
+ function resolveManifestScopedScreenshotFiles(outputBaseDir, latestRunManifest) {
508
+ if (!latestRunManifest?.success) {
509
+ throw new Error(
510
+ "No successful run manifest is available. Run `reshot run` first or use `reshot publish --all-output` to publish from the full output tree.",
511
+ );
512
+ }
513
+
514
+ const scenarioDirs = (latestRunManifest.scenarios || [])
515
+ .filter((scenario) => scenario.success !== false)
516
+ .map((scenario) => scenario.outputDir)
517
+ .filter(Boolean);
518
+
519
+ if (scenarioDirs.length === 0) {
520
+ throw new Error(
521
+ "The latest successful run manifest does not contain any scenario output directories.",
522
+ );
523
+ }
524
+
525
+ const screenshotFiles = collectAssetFilesFromDirectories(scenarioDirs);
526
+ return {
527
+ screenshotFiles,
528
+ mode: "latest-run",
529
+ scenarioCount: scenarioDirs.length,
530
+ };
531
+ }
532
+
490
533
  function buildContextForVariation(scenario, variationSlug) {
491
534
  const safeVariation = variationSlug || "default";
492
535
  if (!scenario || !scenario.contexts) {
@@ -539,17 +582,20 @@ function buildScenarioDefinition(scenario) {
539
582
  function buildPublishMetadata({
540
583
  projectId,
541
584
  publishSessionId,
585
+ tag,
542
586
  scenarioKey,
543
587
  scenarioConfig,
544
588
  variationSlug,
545
589
  contextData,
546
590
  gitInfo,
591
+ autoApprove = false,
547
592
  }) {
548
593
  const scenarioDefinition = buildScenarioDefinition(scenarioConfig);
549
594
 
550
595
  return {
551
596
  projectId,
552
597
  publishSessionId, // Unique ID for this CLI publish run
598
+ tag: tag || undefined,
553
599
  scenarioName: scenarioConfig?.name || scenarioKey,
554
600
  scenario: scenarioDefinition,
555
601
  context: {
@@ -557,6 +603,9 @@ function buildPublishMetadata({
557
603
  data: contextData,
558
604
  },
559
605
  autoCreateVisuals: true,
606
+ publish: {
607
+ autoApprove,
608
+ },
560
609
  git: {
561
610
  commitHash: gitInfo.commitHash,
562
611
  commitMessage: gitInfo.commitMessage,
@@ -578,6 +627,7 @@ async function publishWithTransactionalFlow(
578
627
  docSyncConfig,
579
628
  gitInfo,
580
629
  diffManifests = null,
630
+ { autoApprove = false } = {},
581
631
  ) {
582
632
  console.log(
583
633
  chalk.cyan(" 🚀 Using transactional upload (direct to R2)...\n"),
@@ -807,7 +857,11 @@ async function publishWithTransactionalFlow(
807
857
  });
808
858
  }
809
859
 
810
- // Commit each group
860
+ // Build all commits for batch request
861
+ // Vercel serverless functions have ~60s timeout; keep batches small enough to complete
862
+ const MAX_BATCH_SIZE = 25;
863
+ const commits = [];
864
+
811
865
  for (const { group, scenarioConfig, assets } of groupMap.values()) {
812
866
  const contextObj = buildContextForVariation(
813
867
  scenarioConfig,
@@ -816,46 +870,75 @@ async function publishWithTransactionalFlow(
816
870
  const metadata = buildPublishMetadata({
817
871
  projectId,
818
872
  publishSessionId: gitInfo.publishSessionId,
873
+ tag: gitInfo.tag,
819
874
  scenarioKey: group.scenarioKey,
820
875
  scenarioConfig,
821
876
  variationSlug: group.variationSlug,
822
877
  contextData: contextObj,
823
878
  gitInfo,
879
+ autoApprove,
824
880
  });
825
881
 
826
882
  if (metadata.cli) {
827
- metadata.cli.features = ["steps", "transactional"];
883
+ metadata.cli.features = ["steps", "transactional", "batch"];
884
+ }
885
+
886
+ commits.push({ metadata, assets });
887
+ }
888
+
889
+ // Send in batches
890
+ console.log(
891
+ chalk.gray(` Committing ${commits.length} scenario(s) to platform...`),
892
+ );
893
+
894
+ const totalBatches = Math.ceil(commits.length / MAX_BATCH_SIZE);
895
+ for (let i = 0; i < commits.length; i += MAX_BATCH_SIZE) {
896
+ const chunk = commits.slice(i, i + MAX_BATCH_SIZE);
897
+ const batchNum = Math.floor(i / MAX_BATCH_SIZE) + 1;
898
+ if (totalBatches > 1) {
899
+ console.log(chalk.gray(` Batch ${batchNum}/${totalBatches}...`));
828
900
  }
829
901
 
830
902
  try {
831
- const result = await apiClient.publishTransactional(apiKey, {
832
- metadata,
833
- assets,
903
+ const rawBatchResult = await apiClient.publishBatch(apiKey, {
904
+ commits: chunk,
905
+ autoApprove: autoApprove || false,
834
906
  });
907
+ // Unwrap API envelope: response may be { data: { results, ... } } or { results, ... }
908
+ const batchResult = rawBatchResult.data || rawBatchResult;
835
909
 
836
- const processedCount = result?.assetsProcessed ?? assets.length;
837
- console.log(
838
- chalk.green(
839
- ` ✔ Committed "${group.scenarioKey}" (${group.variationSlug}): ${processedCount} asset(s)`,
840
- ),
841
- );
842
- successCount += processedCount;
910
+ for (const r of batchResult.results || []) {
911
+ if (r.status === "ok") {
912
+ const count = r.assetsProcessed || 0;
913
+ console.log(
914
+ chalk.green(
915
+ ` ✔ Committed "${r.scenario}" (${r.context}): ${count} asset(s)`,
916
+ ),
917
+ );
918
+ successCount += count;
843
919
 
844
- // Handle skipped assets (visual limit)
845
- if (result?.skippedAssets?.length > 0) {
846
- for (const key of result.skippedAssets) {
847
- console.log(chalk.yellow(` ⚠ Skipped "${key}" (plan limit reached)`));
920
+ if (r.skippedAssets?.length > 0) {
921
+ for (const key of r.skippedAssets) {
922
+ console.log(chalk.yellow(` ⚠ Skipped "${key}" (plan limit reached)`));
923
+ }
924
+ skippedCount += r.skippedAssets.length;
925
+ }
926
+ } else {
927
+ console.log(
928
+ chalk.red(
929
+ ` ✖ "${r.scenario}" (${r.context}): ${r.error || "Unknown error"}`,
930
+ ),
931
+ );
932
+ failCount++;
848
933
  }
849
- skippedCount += result.skippedAssets.length;
850
934
  }
851
935
 
852
- // Capture viewUrl from first successful response
853
- if (!viewUrl && result?.viewUrl) {
854
- viewUrl = result.viewUrl;
936
+ if (!viewUrl && batchResult.viewUrl) {
937
+ viewUrl = batchResult.viewUrl;
855
938
  }
856
939
  } catch (error) {
857
- console.log(chalk.red(` ✖ Commit failed: ${error.message}`));
858
- failCount += assets.length;
940
+ console.log(chalk.red(` ✖ Batch request failed: ${error.message}`));
941
+ failCount += chunk.length;
859
942
  }
860
943
  }
861
944
 
@@ -871,6 +954,7 @@ async function publishWithLegacyFlow(
871
954
  groupedAssets,
872
955
  docSyncConfig,
873
956
  gitInfo,
957
+ { autoApprove = false } = {},
874
958
  ) {
875
959
  let successCount = 0;
876
960
  let failCount = 0;
@@ -887,11 +971,13 @@ async function publishWithLegacyFlow(
887
971
  const metadata = buildPublishMetadata({
888
972
  projectId,
889
973
  publishSessionId: gitInfo.publishSessionId,
974
+ tag: gitInfo.tag,
890
975
  scenarioKey: group.scenarioKey,
891
976
  scenarioConfig,
892
977
  variationSlug: group.variationSlug,
893
978
  contextData: contextObj,
894
979
  gitInfo,
980
+ autoApprove,
895
981
  });
896
982
 
897
983
  if (metadata.cli) {
@@ -1167,70 +1253,6 @@ function getRecentCommits(lastCommitHash) {
1167
1253
  }
1168
1254
  }
1169
1255
 
1170
- /**
1171
- * Recursively find all markdown files matching include/exclude patterns
1172
- */
1173
- function findDocFiles(
1174
- docsRoot,
1175
- includePatterns = ["**/*.md", "**/*.mdx"],
1176
- excludePatterns = [],
1177
- ) {
1178
- const files = [];
1179
- const rootPath = path.resolve(process.cwd(), docsRoot);
1180
-
1181
- if (!fs.existsSync(rootPath)) {
1182
- return files;
1183
- }
1184
-
1185
- function walkDir(dir, relativePath = "") {
1186
- const items = fs.readdirSync(dir);
1187
-
1188
- for (const item of items) {
1189
- const fullPath = path.join(dir, item);
1190
- const relativeItemPath = path
1191
- .join(relativePath, item)
1192
- .replace(/\\/g, "/");
1193
- const stat = fs.statSync(fullPath);
1194
-
1195
- // Check exclude patterns
1196
- const shouldExclude = excludePatterns.some((pattern) => {
1197
- const regex = new RegExp(
1198
- pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"),
1199
- );
1200
- return regex.test(relativeItemPath) || regex.test(fullPath);
1201
- });
1202
-
1203
- if (shouldExclude) {
1204
- continue;
1205
- }
1206
-
1207
- if (stat.isDirectory()) {
1208
- walkDir(fullPath, relativeItemPath);
1209
- } else if (stat.isFile()) {
1210
- const ext = path.extname(item).toLowerCase();
1211
- const matchesInclude = includePatterns.some((pattern) => {
1212
- const regex = new RegExp(
1213
- pattern.replace(/\*\*/g, ".*").replace(/\*/g, "[^/]*"),
1214
- );
1215
- return (
1216
- regex.test(relativeItemPath) || ext === ".md" || ext === ".mdx"
1217
- );
1218
- });
1219
-
1220
- if (matchesInclude && (ext === ".md" || ext === ".mdx")) {
1221
- files.push({
1222
- fullPath,
1223
- relativePath: relativeItemPath,
1224
- });
1225
- }
1226
- }
1227
- }
1228
- }
1229
-
1230
- walkDir(rootPath);
1231
- return files;
1232
- }
1233
-
1234
1256
  /**
1235
1257
  * Parse frontmatter from markdown content
1236
1258
  */
@@ -1268,7 +1290,18 @@ function parseFrontmatter(content) {
1268
1290
  }
1269
1291
 
1270
1292
  async function publishCommand(options = {}) {
1271
- const { tag, message, dryRun, force, video, outputJson } = options;
1293
+ const {
1294
+ tag,
1295
+ message,
1296
+ dryRun,
1297
+ force,
1298
+ video,
1299
+ allOutput = false,
1300
+ outputJson,
1301
+ autoApprove,
1302
+ skipReleaseDoctor = false,
1303
+ noExit = false,
1304
+ } = options;
1272
1305
 
1273
1306
  // Result tracking for --output-json and programmatic callers
1274
1307
  const publishResult = {
@@ -1277,6 +1310,7 @@ async function publishCommand(options = {}) {
1277
1310
  assetsSkipped: 0,
1278
1311
  reviewQueueItems: 0,
1279
1312
  viewUrl: null,
1313
+ releaseDoctor: null,
1280
1314
  tag: tag || null,
1281
1315
  dryRun: !!dryRun,
1282
1316
  timestamp: new Date().toISOString(),
@@ -1294,6 +1328,10 @@ async function publishCommand(options = {}) {
1294
1328
  console.log(chalk.yellow("🔍 DRY RUN MODE - No assets will be uploaded\n"));
1295
1329
  }
1296
1330
 
1331
+ if (autoApprove) {
1332
+ console.log(chalk.cyan(" ✅ Auto-approve enabled: visuals will be approved immediately\n"));
1333
+ }
1334
+
1297
1335
  // Read config + settings (if available)
1298
1336
  const settings = readSettingsSafe();
1299
1337
  let docSyncConfig = null;
@@ -1305,6 +1343,43 @@ async function publishCommand(options = {}) {
1305
1343
  );
1306
1344
  }
1307
1345
 
1346
+ if (skipReleaseDoctor) {
1347
+ publishResult.releaseDoctor = {
1348
+ skipped: true,
1349
+ success: true,
1350
+ };
1351
+ } else if (docSyncConfig) {
1352
+ console.log(chalk.cyan("🧪 Running release doctor before publish...\n"));
1353
+ const { runReleaseDoctor } = require("../lib/release-doctor");
1354
+ const releaseDoctor = await runReleaseDoctor({});
1355
+ publishResult.releaseDoctor = {
1356
+ skipped: false,
1357
+ success: releaseDoctor.ok,
1358
+ reportPath: releaseDoctor.reportPath || null,
1359
+ };
1360
+
1361
+ if (!releaseDoctor.ok) {
1362
+ console.log(chalk.red(" ✖ Release doctor failed. Fix the reported issues before publishing."));
1363
+ if (releaseDoctor.reportPath) {
1364
+ console.log(chalk.gray(` Report: ${releaseDoctor.reportPath}`));
1365
+ }
1366
+ if (!noExit) process.exit(1);
1367
+ return {
1368
+ ...publishResult,
1369
+ success: false,
1370
+ error: "Release doctor failed",
1371
+ };
1372
+ }
1373
+
1374
+ console.log(chalk.green(" ✔ Release doctor passed\n"));
1375
+ } else {
1376
+ publishResult.releaseDoctor = {
1377
+ skipped: true,
1378
+ success: true,
1379
+ reason: "config-unavailable",
1380
+ };
1381
+ }
1382
+
1308
1383
  // Determine storage mode and validate configuration
1309
1384
  const storageConfig = docSyncConfig?.storage;
1310
1385
  const storageMode = getStorageMode(docSyncConfig);
@@ -1324,7 +1399,8 @@ async function publishCommand(options = {}) {
1324
1399
  console.log(chalk.red(` • ${error}`));
1325
1400
  }
1326
1401
  console.log(getStorageSetupHelp(storageConfig?.type || "reshot"));
1327
- process.exit(1);
1402
+ if (!noExit) process.exit(1);
1403
+ return { ...publishResult, success: false, error: "Invalid storage configuration" };
1328
1404
  }
1329
1405
 
1330
1406
  // Resolve project context based on storage mode
@@ -1343,7 +1419,8 @@ async function publishCommand(options = {}) {
1343
1419
  );
1344
1420
  console.log(getStorageSetupHelp("s3"));
1345
1421
  }
1346
- process.exit(1);
1422
+ if (!noExit) process.exit(1);
1423
+ return { ...publishResult, success: false, error: error.message };
1347
1424
  }
1348
1425
 
1349
1426
  const { apiKey, projectId, storageMode: resolvedMode } = projectContext;
@@ -1368,8 +1445,6 @@ async function publishCommand(options = {}) {
1368
1445
  // Get feature toggles
1369
1446
  const features = docSyncConfig?._metadata?.features || {
1370
1447
  visuals: true,
1371
- docs: false,
1372
- changelog: true,
1373
1448
  };
1374
1449
 
1375
1450
  // Get git information
@@ -1388,7 +1463,7 @@ async function publishCommand(options = {}) {
1388
1463
  });
1389
1464
  console.log(chalk.green(` ✔ Version tag "${tag}" created`));
1390
1465
  console.log(
1391
- chalk.gray(` Pinned URL: cdn.reshot.dev/assets/{key}?tag=${tag}\n`),
1466
+ chalk.gray(` Pinned URL: cdn.reshot.dev/v1/assets/{projectId}/{visualKey}?tag=${tag}\n`),
1392
1467
  );
1393
1468
  } catch (tagError) {
1394
1469
  console.log(
@@ -1401,9 +1476,21 @@ async function publishCommand(options = {}) {
1401
1476
  if (features.visuals === true) {
1402
1477
  const projectRoot = process.cwd();
1403
1478
  const outputBaseDir = path.join(projectRoot, ".reshot", "output");
1404
- const screenshotFiles = fs.existsSync(outputBaseDir)
1405
- ? findAssetFiles(outputBaseDir)
1406
- : [];
1479
+ let screenshotFiles = [];
1480
+ let screenshotScopeLabel = "all-output";
1481
+ if (fs.existsSync(outputBaseDir)) {
1482
+ if (allOutput) {
1483
+ screenshotFiles = findAssetFiles(outputBaseDir);
1484
+ } else {
1485
+ const latestRunManifest = getLatestSuccessfulRunManifest();
1486
+ const manifestScoped = resolveManifestScopedScreenshotFiles(
1487
+ outputBaseDir,
1488
+ latestRunManifest,
1489
+ );
1490
+ screenshotFiles = manifestScoped.screenshotFiles;
1491
+ screenshotScopeLabel = `${manifestScoped.mode}:${manifestScoped.scenarioCount}`;
1492
+ }
1493
+ }
1407
1494
  const screenshotGroups =
1408
1495
  screenshotFiles.length > 0
1409
1496
  ? groupAssetsByScenario(screenshotFiles, outputBaseDir)
@@ -1434,6 +1521,11 @@ async function publishCommand(options = {}) {
1434
1521
  ` (${screenshotFiles.length} screenshots, ${exportVideoFiles.length} videos)\n`,
1435
1522
  ),
1436
1523
  );
1524
+ console.log(
1525
+ chalk.gray(
1526
+ ` Screenshot scope: ${allOutput ? "all historical output (--all-output)" : `latest successful run (${screenshotScopeLabel})`}\n`,
1527
+ ),
1528
+ );
1437
1529
 
1438
1530
  let successCount = 0;
1439
1531
  let failCount = 0;
@@ -1477,8 +1569,9 @@ async function publishCommand(options = {}) {
1477
1569
  projectId,
1478
1570
  groupedAssets,
1479
1571
  docSyncConfig,
1480
- { commitHash, commitMessage, publishSessionId },
1572
+ { commitHash, commitMessage, publishSessionId, tag },
1481
1573
  diffManifests,
1574
+ { autoApprove },
1482
1575
  );
1483
1576
  successCount = result.successCount;
1484
1577
  failCount = result.failCount;
@@ -1496,7 +1589,8 @@ async function publishCommand(options = {}) {
1496
1589
  projectId,
1497
1590
  groupedAssets,
1498
1591
  docSyncConfig,
1499
- { commitHash, commitMessage, publishSessionId },
1592
+ { commitHash, commitMessage, publishSessionId, tag },
1593
+ { autoApprove },
1500
1594
  );
1501
1595
  successCount = result.successCount;
1502
1596
  failCount = result.failCount;
@@ -1508,7 +1602,8 @@ async function publishCommand(options = {}) {
1508
1602
  projectId,
1509
1603
  groupedAssets,
1510
1604
  docSyncConfig,
1511
- { commitHash, commitMessage, publishSessionId },
1605
+ { commitHash, commitMessage, publishSessionId, tag },
1606
+ { autoApprove },
1512
1607
  );
1513
1608
  successCount = result.successCount;
1514
1609
  failCount = result.failCount;
@@ -1558,141 +1653,6 @@ async function publishCommand(options = {}) {
1558
1653
  );
1559
1654
  }
1560
1655
 
1561
- // Stream B: Documentation
1562
- if (features.docs === true) {
1563
- console.log(chalk.cyan("\n📚 Publishing documentation...\n"));
1564
-
1565
- if (!docSyncConfig || !docSyncConfig.docs) {
1566
- console.log(
1567
- chalk.yellow(
1568
- " ⚠ No docs configuration found in docsync.config.json. Skipping docs stream.",
1569
- ),
1570
- );
1571
- } else {
1572
- const docsConfig = docSyncConfig.docs;
1573
- const docsRoot = docsConfig.root || "./docs";
1574
- const includePatterns = docsConfig.include || ["**/*.md", "**/*.mdx"];
1575
- const excludePatterns = docsConfig.exclude || [
1576
- "**/node_modules/**",
1577
- "**/.git/**",
1578
- ];
1579
-
1580
- const docFiles = findDocFiles(docsRoot, includePatterns, excludePatterns);
1581
-
1582
- if (docFiles.length === 0) {
1583
- console.log(
1584
- chalk.yellow(" ⚠ No documentation files found to publish."),
1585
- );
1586
- } else {
1587
- console.log(
1588
- chalk.cyan(` Found ${docFiles.length} documentation file(s)\n`),
1589
- );
1590
-
1591
- const docsPayload = [];
1592
- for (const docFile of docFiles) {
1593
- const content = fs.readFileSync(docFile.fullPath, "utf-8");
1594
- const { frontmatter, content: docContent } =
1595
- parseFrontmatter(content);
1596
-
1597
- docsPayload.push({
1598
- path: docFile.relativePath,
1599
- content: docContent,
1600
- frontmatter:
1601
- Object.keys(frontmatter).length > 0 ? frontmatter : undefined,
1602
- status: frontmatter.status || "draft",
1603
- });
1604
- }
1605
-
1606
- // Docs publishing requires platform (no BYOS support yet)
1607
- if (resolvedMode === "byos") {
1608
- console.log(
1609
- chalk.yellow(
1610
- " ⚠ Documentation publishing requires Reshot platform.",
1611
- ),
1612
- );
1613
- console.log(
1614
- chalk.gray(
1615
- " Run 'reshot auth' to enable doc hosting with review workflow.",
1616
- ),
1617
- );
1618
- } else {
1619
- try {
1620
- const result = await apiClient.publishDocs(apiKey, {
1621
- projectId,
1622
- docs: docsPayload,
1623
- });
1624
- console.log(
1625
- chalk.green(
1626
- ` ✔ Published ${result.created || 0} new doc(s), updated ${
1627
- result.updated || 0
1628
- } doc(s)`,
1629
- ),
1630
- );
1631
- } catch (error) {
1632
- console.log(
1633
- chalk.red(` ✖ Failed to publish docs: ${error.message}`),
1634
- );
1635
- }
1636
- }
1637
- }
1638
- }
1639
- } else {
1640
- console.log(
1641
- chalk.yellow(
1642
- " ⚠ Documentation publishing is disabled for this project.",
1643
- ),
1644
- );
1645
- }
1646
-
1647
- // Stream C: Changelog
1648
- if (features.changelog === true) {
1649
- // Changelog requires platform (no BYOS support)
1650
- if (resolvedMode === "byos") {
1651
- console.log(chalk.cyan("\n📝 Changelog drafts...\n"));
1652
- console.log(
1653
- chalk.yellow(
1654
- " ⚠ Changelog requires Reshot platform for tracking and publishing.",
1655
- ),
1656
- );
1657
- console.log(
1658
- chalk.gray(" Run 'reshot auth' to enable changelog generation."),
1659
- );
1660
- } else {
1661
- console.log(chalk.cyan("\n📝 Posting changelog drafts...\n"));
1662
- const lastPublishedHash = settings?.lastPublishedCommitHash;
1663
- const recentCommits = getRecentCommits(lastPublishedHash);
1664
-
1665
- if (recentCommits.length > 0) {
1666
- try {
1667
- await apiClient.postChangelogDrafts(projectId, recentCommits, apiKey);
1668
- console.log(
1669
- chalk.green(
1670
- ` ✔ Posted ${recentCommits.length} changelog draft(s)`,
1671
- ),
1672
- );
1673
-
1674
- // Update last published commit hash
1675
- if (settings) {
1676
- settings.lastPublishedCommitHash = commitHash;
1677
- config.writeSettings(settings);
1678
- }
1679
- } catch (error) {
1680
- console.log(
1681
- chalk.yellow(
1682
- ` ⚠ Failed to post changelog drafts: ${error.message}`,
1683
- ),
1684
- );
1685
- }
1686
- } else {
1687
- console.log(chalk.gray(" No new commits to publish"));
1688
- }
1689
- }
1690
- } else {
1691
- console.log(
1692
- chalk.yellow(" ⚠ Changelog publishing is disabled for this project."),
1693
- );
1694
- }
1695
-
1696
1656
  // Print upgrade path for BYOS users
1697
1657
  if (resolvedMode === "byos") {
1698
1658
  console.log(chalk.cyan("\n💡 Upgrade to Reshot Platform for:"));
@@ -1700,7 +1660,7 @@ async function publishCommand(options = {}) {
1700
1660
  console.log(chalk.gray(" • Unbreakable URLs that never change"));
1701
1661
  console.log(chalk.gray(" • Version history and rollback"));
1702
1662
  console.log(chalk.gray(" • Team collaboration and RBAC"));
1703
- console.log(chalk.gray(" • Automatic changelog generation"));
1663
+ console.log(chalk.gray(" • Drift detection and notifications"));
1704
1664
  console.log(chalk.gray("\n Run 'reshot auth' to connect your project."));
1705
1665
  }
1706
1666
 
@@ -1715,7 +1675,14 @@ async function publishCommand(options = {}) {
1715
1675
 
1716
1676
  console.log();
1717
1677
 
1718
- return publishResult;
1678
+ return {
1679
+ ...publishResult,
1680
+ success: publishResult.assetsFailed === 0 && publishResult.assetsProcessed > 0,
1681
+ };
1719
1682
  }
1720
1683
 
1721
1684
  module.exports = publishCommand;
1685
+ module.exports.groupAssetsByScenario = groupAssetsByScenario;
1686
+ module.exports.resolveManifestScopedScreenshotFiles =
1687
+ resolveManifestScopedScreenshotFiles;
1688
+ module.exports.collectAssetFilesFromDirectories = collectAssetFilesFromDirectories;