@reshotdev/screenshot 0.0.1-beta.6 → 0.0.1-beta.7
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/package.json +1 -1
- package/src/commands/auth.js +1 -1
- package/src/commands/publish.js +52 -23
- package/src/index.js +2 -0
- package/src/lib/api-client.js +50 -5
- package/src/lib/capture-script-runner.js +24 -2
- package/src/lib/ui-api.js +4 -5
package/package.json
CHANGED
package/src/commands/auth.js
CHANGED
|
@@ -217,7 +217,7 @@ async function authCommand() {
|
|
|
217
217
|
await verifyApiKey(apiBaseUrl, status.project.apiKey);
|
|
218
218
|
|
|
219
219
|
// Derive platformUrl from apiBaseUrl (remove /api suffix)
|
|
220
|
-
const platformUrl = apiBaseUrl.replace(/\/api\/?$/, '') || '
|
|
220
|
+
const platformUrl = apiBaseUrl.replace(/\/api\/?$/, '') || 'https://reshot.dev';
|
|
221
221
|
|
|
222
222
|
writeSettings({
|
|
223
223
|
projectId: status.project.id,
|
package/src/commands/publish.js
CHANGED
|
@@ -578,6 +578,7 @@ async function publishWithTransactionalFlow(
|
|
|
578
578
|
docSyncConfig,
|
|
579
579
|
gitInfo,
|
|
580
580
|
diffManifests = null,
|
|
581
|
+
{ autoApprove = false } = {},
|
|
581
582
|
) {
|
|
582
583
|
console.log(
|
|
583
584
|
chalk.cyan(" 🚀 Using transactional upload (direct to R2)...\n"),
|
|
@@ -807,7 +808,10 @@ async function publishWithTransactionalFlow(
|
|
|
807
808
|
});
|
|
808
809
|
}
|
|
809
810
|
|
|
810
|
-
//
|
|
811
|
+
// Build all commits for batch request
|
|
812
|
+
const MAX_BATCH_SIZE = 200;
|
|
813
|
+
const commits = [];
|
|
814
|
+
|
|
811
815
|
for (const { group, scenarioConfig, assets } of groupMap.values()) {
|
|
812
816
|
const contextObj = buildContextForVariation(
|
|
813
817
|
scenarioConfig,
|
|
@@ -824,38 +828,58 @@ async function publishWithTransactionalFlow(
|
|
|
824
828
|
});
|
|
825
829
|
|
|
826
830
|
if (metadata.cli) {
|
|
827
|
-
metadata.cli.features = ["steps", "transactional"];
|
|
831
|
+
metadata.cli.features = ["steps", "transactional", "batch"];
|
|
828
832
|
}
|
|
829
833
|
|
|
834
|
+
commits.push({ metadata, assets });
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
// Send in batches
|
|
838
|
+
console.log(
|
|
839
|
+
chalk.gray(` Committing ${commits.length} scenario(s) to platform...`),
|
|
840
|
+
);
|
|
841
|
+
|
|
842
|
+
for (let i = 0; i < commits.length; i += MAX_BATCH_SIZE) {
|
|
843
|
+
const chunk = commits.slice(i, i + MAX_BATCH_SIZE);
|
|
844
|
+
|
|
830
845
|
try {
|
|
831
|
-
const
|
|
832
|
-
|
|
833
|
-
|
|
846
|
+
const batchResult = await apiClient.publishBatch(apiKey, {
|
|
847
|
+
commits: chunk,
|
|
848
|
+
autoApprove: autoApprove || false,
|
|
834
849
|
});
|
|
835
850
|
|
|
836
|
-
const
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
851
|
+
for (const r of batchResult.results || []) {
|
|
852
|
+
if (r.status === "ok") {
|
|
853
|
+
const count = r.assetsProcessed || 0;
|
|
854
|
+
console.log(
|
|
855
|
+
chalk.green(
|
|
856
|
+
` ✔ Committed "${r.scenario}" (${r.context}): ${count} asset(s)`,
|
|
857
|
+
),
|
|
858
|
+
);
|
|
859
|
+
successCount += count;
|
|
843
860
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
861
|
+
if (r.skippedAssets?.length > 0) {
|
|
862
|
+
for (const key of r.skippedAssets) {
|
|
863
|
+
console.log(chalk.yellow(` ⚠ Skipped "${key}" (plan limit reached)`));
|
|
864
|
+
}
|
|
865
|
+
skippedCount += r.skippedAssets.length;
|
|
866
|
+
}
|
|
867
|
+
} else {
|
|
868
|
+
console.log(
|
|
869
|
+
chalk.red(
|
|
870
|
+
` ✖ "${r.scenario}" (${r.context}): ${r.error || "Unknown error"}`,
|
|
871
|
+
),
|
|
872
|
+
);
|
|
873
|
+
failCount++;
|
|
848
874
|
}
|
|
849
|
-
skippedCount += result.skippedAssets.length;
|
|
850
875
|
}
|
|
851
876
|
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
viewUrl = result.viewUrl;
|
|
877
|
+
if (!viewUrl && batchResult.viewUrl) {
|
|
878
|
+
viewUrl = batchResult.viewUrl;
|
|
855
879
|
}
|
|
856
880
|
} catch (error) {
|
|
857
|
-
console.log(chalk.red(` ✖
|
|
858
|
-
failCount +=
|
|
881
|
+
console.log(chalk.red(` ✖ Batch request failed: ${error.message}`));
|
|
882
|
+
failCount += chunk.length;
|
|
859
883
|
}
|
|
860
884
|
}
|
|
861
885
|
|
|
@@ -1204,7 +1228,7 @@ function parseFrontmatter(content) {
|
|
|
1204
1228
|
}
|
|
1205
1229
|
|
|
1206
1230
|
async function publishCommand(options = {}) {
|
|
1207
|
-
const { tag, message, dryRun, force, video, outputJson } = options;
|
|
1231
|
+
const { tag, message, dryRun, force, video, outputJson, autoApprove } = options;
|
|
1208
1232
|
|
|
1209
1233
|
// Result tracking for --output-json and programmatic callers
|
|
1210
1234
|
const publishResult = {
|
|
@@ -1230,6 +1254,10 @@ async function publishCommand(options = {}) {
|
|
|
1230
1254
|
console.log(chalk.yellow("🔍 DRY RUN MODE - No assets will be uploaded\n"));
|
|
1231
1255
|
}
|
|
1232
1256
|
|
|
1257
|
+
if (autoApprove) {
|
|
1258
|
+
console.log(chalk.cyan(" ✅ Auto-approve enabled: visuals will be approved immediately\n"));
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1233
1261
|
// Read config + settings (if available)
|
|
1234
1262
|
const settings = readSettingsSafe();
|
|
1235
1263
|
let docSyncConfig = null;
|
|
@@ -1413,6 +1441,7 @@ async function publishCommand(options = {}) {
|
|
|
1413
1441
|
docSyncConfig,
|
|
1414
1442
|
{ commitHash, commitMessage, publishSessionId },
|
|
1415
1443
|
diffManifests,
|
|
1444
|
+
{ autoApprove },
|
|
1416
1445
|
);
|
|
1417
1446
|
successCount = result.successCount;
|
|
1418
1447
|
failCount = result.failCount;
|
package/src/index.js
CHANGED
|
@@ -221,12 +221,14 @@ program
|
|
|
221
221
|
.option("--dry-run", "Preview without uploading")
|
|
222
222
|
.option("-f, --force", "Skip confirmation prompts")
|
|
223
223
|
.option("--output-json", "Write structured result to .reshot/output/publish-result.json")
|
|
224
|
+
.option("--auto-approve", "Automatically approve published visuals (skip review queue)")
|
|
224
225
|
.action(async (options) => {
|
|
225
226
|
try {
|
|
226
227
|
const publishCommand = require("./commands/publish");
|
|
227
228
|
await publishCommand({
|
|
228
229
|
...options,
|
|
229
230
|
outputJson: options.outputJson,
|
|
231
|
+
autoApprove: options.autoApprove,
|
|
230
232
|
});
|
|
231
233
|
} catch (error) {
|
|
232
234
|
console.error(chalk.red("Error:"), error.message);
|
package/src/lib/api-client.js
CHANGED
|
@@ -3,15 +3,35 @@ const axios = require("axios");
|
|
|
3
3
|
const FormData = require("form-data");
|
|
4
4
|
const fs = require("fs");
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
process.env.RESHOT_API_BASE_URL ||
|
|
8
|
-
process.env.RESHOT_API_BASE_URL ||
|
|
9
|
-
"http://localhost:3000/api";
|
|
6
|
+
const PRODUCTION_API_URL = "https://reshot.dev/api";
|
|
10
7
|
|
|
11
8
|
function getApiBaseUrl() {
|
|
12
|
-
|
|
9
|
+
// 1. Explicit env var override (for CI or local dev)
|
|
10
|
+
if (process.env.RESHOT_API_BASE_URL) {
|
|
11
|
+
return process.env.RESHOT_API_BASE_URL;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// 2. Read from settings.json (set during auth/setup)
|
|
15
|
+
try {
|
|
16
|
+
const path = require("path");
|
|
17
|
+
const settingsPath = path.join(process.cwd(), ".reshot", "settings.json");
|
|
18
|
+
if (fs.existsSync(settingsPath)) {
|
|
19
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, "utf8"));
|
|
20
|
+
if (settings.platformUrl) {
|
|
21
|
+
return settings.platformUrl.replace(/\/+$/, "") + "/api";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
} catch {
|
|
25
|
+
// Settings don't exist yet (first auth) — fall through to default
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 3. Default to production
|
|
29
|
+
return PRODUCTION_API_URL;
|
|
13
30
|
}
|
|
14
31
|
|
|
32
|
+
// Resolved once at module load — all API calls use this
|
|
33
|
+
const baseUrl = getApiBaseUrl();
|
|
34
|
+
|
|
15
35
|
/**
|
|
16
36
|
* Sleep helper for retry delays
|
|
17
37
|
*/
|
|
@@ -586,6 +606,30 @@ async function publishTransactional(apiKey, payload) {
|
|
|
586
606
|
return response.data;
|
|
587
607
|
}
|
|
588
608
|
|
|
609
|
+
/**
|
|
610
|
+
* Batch publish metadata for multiple scenarios in one request
|
|
611
|
+
* @param {string} apiKey - API key for authentication
|
|
612
|
+
* @param {Object} payload - { commits: Array<{ metadata, assets }> }
|
|
613
|
+
* @returns {Promise<Object>}
|
|
614
|
+
*/
|
|
615
|
+
async function publishBatch(apiKey, payload) {
|
|
616
|
+
if (!apiKey) {
|
|
617
|
+
throw new Error("API key is required to publish");
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
const response = await axios.post(`${baseUrl}/v1/publish/batch`, payload, {
|
|
621
|
+
headers: {
|
|
622
|
+
"Content-Type": "application/json",
|
|
623
|
+
Authorization: `Bearer ${apiKey}`,
|
|
624
|
+
},
|
|
625
|
+
timeout: 60000,
|
|
626
|
+
maxBodyLength: Infinity,
|
|
627
|
+
maxContentLength: Infinity,
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
return response.data;
|
|
631
|
+
}
|
|
632
|
+
|
|
589
633
|
/**
|
|
590
634
|
* Check which hashes already exist in storage (for deduplication)
|
|
591
635
|
* @param {string} apiKey - API key for authentication
|
|
@@ -800,6 +844,7 @@ module.exports = {
|
|
|
800
844
|
signAssets,
|
|
801
845
|
uploadToPresignedUrl,
|
|
802
846
|
publishTransactional,
|
|
847
|
+
publishBatch,
|
|
803
848
|
checkExistingHashes,
|
|
804
849
|
// Diffing support
|
|
805
850
|
getBaselines,
|
|
@@ -1133,11 +1133,25 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
|
|
|
1133
1133
|
`Page error detected: ${errMsg}. The page rendered an error UI instead of expected content.`
|
|
1134
1134
|
);
|
|
1135
1135
|
} else if (retryResult.status === "timeout") {
|
|
1136
|
+
const currentUrl = engine.page.url();
|
|
1136
1137
|
console.log(
|
|
1137
1138
|
chalk.yellow(
|
|
1138
|
-
` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s)
|
|
1139
|
+
` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s): ${readySelector}`
|
|
1140
|
+
)
|
|
1141
|
+
);
|
|
1142
|
+
console.log(
|
|
1143
|
+
chalk.gray(` URL: ${currentUrl}`)
|
|
1144
|
+
);
|
|
1145
|
+
console.log(
|
|
1146
|
+
chalk.gray(
|
|
1147
|
+
` Hint: The page loaded but this selector does not exist. Check your readySelector in reshot.config.json.`
|
|
1139
1148
|
)
|
|
1140
1149
|
);
|
|
1150
|
+
throw new Error(
|
|
1151
|
+
`Scenario readySelector "${readySelector}" not found after ${retryResult.attempts} attempt(s). ` +
|
|
1152
|
+
`The page loaded at ${currentUrl} but the selector does not exist. ` +
|
|
1153
|
+
`Update readySelector in reshot.config.json or remove it to skip this check.`
|
|
1154
|
+
);
|
|
1141
1155
|
} else if (retryResult.attempts > 1) {
|
|
1142
1156
|
console.log(
|
|
1143
1157
|
chalk.green(
|
|
@@ -2185,8 +2199,16 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
|
|
|
2185
2199
|
});
|
|
2186
2200
|
} catch (e) {
|
|
2187
2201
|
if (!isOptional) {
|
|
2202
|
+
const currentUrl = page.url();
|
|
2203
|
+
console.warn(
|
|
2204
|
+
chalk.yellow(` ⚠ Element not found: ${params.target}`)
|
|
2205
|
+
);
|
|
2206
|
+
console.warn(chalk.gray(` URL: ${currentUrl}`));
|
|
2207
|
+
console.warn(chalk.gray(` Timeout: ${waitTimeout}ms`));
|
|
2188
2208
|
console.warn(
|
|
2189
|
-
chalk.
|
|
2209
|
+
chalk.gray(
|
|
2210
|
+
` Hint: Verify the selector exists on the page. Run 'reshot record' to inspect.`
|
|
2211
|
+
)
|
|
2190
2212
|
);
|
|
2191
2213
|
}
|
|
2192
2214
|
}
|
package/src/lib/ui-api.js
CHANGED
|
@@ -43,17 +43,16 @@ const {
|
|
|
43
43
|
* @returns {string} Platform URL
|
|
44
44
|
*/
|
|
45
45
|
function getPlatformUrl(settings) {
|
|
46
|
-
// Priority: settings.platformUrl > env var >
|
|
46
|
+
// Priority: settings.platformUrl > env var > production default
|
|
47
47
|
if (settings?.platformUrl) {
|
|
48
48
|
return settings.platformUrl;
|
|
49
49
|
}
|
|
50
|
-
const envUrl =
|
|
51
|
-
process.env.RESHOT_API_BASE_URL || process.env.RESHOT_API_BASE_URL;
|
|
50
|
+
const envUrl = process.env.RESHOT_API_BASE_URL;
|
|
52
51
|
if (envUrl) {
|
|
53
52
|
// Remove /api suffix if present to get platform URL
|
|
54
53
|
return envUrl.replace(/\/api\/?$/, "");
|
|
55
54
|
}
|
|
56
|
-
return "
|
|
55
|
+
return "https://reshot.dev";
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
/**
|
|
@@ -2852,7 +2851,7 @@ function attachApiRoutes(app, context) {
|
|
|
2852
2851
|
const apiBaseUrl = getApiBaseUrl();
|
|
2853
2852
|
// Derive platformUrl from apiBaseUrl (remove /api suffix)
|
|
2854
2853
|
const platformUrl =
|
|
2855
|
-
apiBaseUrl.replace(/\/api\/?$/, "") || "
|
|
2854
|
+
apiBaseUrl.replace(/\/api\/?$/, "") || "https://reshot.dev";
|
|
2856
2855
|
|
|
2857
2856
|
config.writeSettings({
|
|
2858
2857
|
projectId: project.id,
|