@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.
- package/README.md +65 -7
- package/package.json +9 -2
- package/src/commands/auth.js +108 -26
- package/src/commands/certify.js +62 -0
- package/src/commands/ci-run.js +57 -2
- package/src/commands/ci-setup.js +5 -5
- package/src/commands/doctor-release.js +67 -0
- package/src/commands/doctor-target.js +49 -0
- package/src/commands/drifts.js +5 -70
- package/src/commands/import-tests.js +13 -13
- package/src/commands/ingest.js +10 -10
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +204 -237
- package/src/commands/pull.js +253 -23
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +277 -499
- package/src/commands/setup.js +41 -13
- package/src/commands/status.js +313 -125
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +194 -94
- package/src/lib/api-client.js +121 -35
- package/src/lib/capture-engine.js +103 -7
- package/src/lib/capture-script-runner.js +359 -58
- package/src/lib/certification.js +865 -0
- package/src/lib/config.js +181 -76
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-config.js +1 -1
- package/src/lib/release-doctor.js +313 -0
- package/src/lib/run-manifest.js +103 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +4 -4
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api.js +6 -7
- package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
- package/src/commands/validate-docs.js +0 -529
package/src/commands/publish.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
832
|
-
|
|
833
|
-
|
|
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
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
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
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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
|
-
|
|
853
|
-
|
|
854
|
-
viewUrl = result.viewUrl;
|
|
936
|
+
if (!viewUrl && batchResult.viewUrl) {
|
|
937
|
+
viewUrl = batchResult.viewUrl;
|
|
855
938
|
}
|
|
856
939
|
} catch (error) {
|
|
857
|
-
console.log(chalk.red(` ✖
|
|
858
|
-
failCount +=
|
|
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 {
|
|
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/{
|
|
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
|
-
|
|
1405
|
-
|
|
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(" •
|
|
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
|
|
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;
|