@reshotdev/screenshot 0.0.1-beta.6 → 0.0.1-beta.8

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 CHANGED
@@ -76,7 +76,6 @@ Create `reshot.config.json` in your project root:
76
76
  | `reshot record [title]` | Interactive recording via Chrome DevTools | `--browser`, `--url`, `--port` |
77
77
  | `reshot sync` | Upload traces/docs to Reshot platform | `--trace-dir`, `--dry-run` |
78
78
  | `reshot studio` | Launch web management UI | `--port`, `--no-open` |
79
- | `reshot validate` | Check config and bindings | `--strict`, `--fix` |
80
79
  | `reshot status` | View project status and sync history | `--jobs`, `--drifts`, `--json` |
81
80
  | `reshot publish` | Upload assets with versioning | `--tag`, `--message`, `--dry-run` |
82
81
  | `reshot pull` | Generate asset map for builds | `--format json\|ts\|csv`, `--output`, `--status` |
@@ -225,12 +224,19 @@ The recorded scenario is appended to `reshot.config.json` automatically.
225
224
 
226
225
  ## Authentication
227
226
 
228
- ### Storage State (from Playwright)
227
+ ### Storage State
228
+
229
+ The CLI stores browser session state at `~/.reshot/session-state.json` (global).
230
+ This is automatically captured when you run `reshot record`.
231
+
232
+ To manually generate it:
229
233
 
230
234
  ```bash
231
- npx playwright codegen http://localhost:3000 --save-storage=.reshot/auth-state.json
235
+ npx playwright codegen http://localhost:3000 --save-storage=$HOME/.reshot/session-state.json
232
236
  ```
233
237
 
238
+ Or reference a project-local path in your config:
239
+
234
240
  ```json
235
241
  {
236
242
  "storageStatePath": ".reshot/auth-state.json"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reshotdev/screenshot",
3
- "version": "0.0.1-beta.6",
3
+ "version": "0.0.1-beta.8",
4
4
  "description": "CI/CD screenshot and video capture CLI",
5
5
  "author": "Reshot <hello@reshot.dev>",
6
6
  "license": "MIT",
@@ -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\/?$/, '') || 'http://localhost:3000';
220
+ const platformUrl = apiBaseUrl.replace(/\/api\/?$/, '') || 'https://reshot.dev';
221
221
 
222
222
  writeSettings({
223
223
  projectId: status.project.id,
@@ -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,11 @@ async function publishWithTransactionalFlow(
807
808
  });
808
809
  }
809
810
 
810
- // Commit each group
811
+ // Build all commits for batch request
812
+ // Vercel serverless functions have ~60s timeout; keep batches small enough to complete
813
+ const MAX_BATCH_SIZE = 25;
814
+ const commits = [];
815
+
811
816
  for (const { group, scenarioConfig, assets } of groupMap.values()) {
812
817
  const contextObj = buildContextForVariation(
813
818
  scenarioConfig,
@@ -824,38 +829,65 @@ async function publishWithTransactionalFlow(
824
829
  });
825
830
 
826
831
  if (metadata.cli) {
827
- metadata.cli.features = ["steps", "transactional"];
832
+ metadata.cli.features = ["steps", "transactional", "batch"];
833
+ }
834
+
835
+ commits.push({ metadata, assets });
836
+ }
837
+
838
+ // Send in batches
839
+ console.log(
840
+ chalk.gray(` Committing ${commits.length} scenario(s) to platform...`),
841
+ );
842
+
843
+ const totalBatches = Math.ceil(commits.length / MAX_BATCH_SIZE);
844
+ for (let i = 0; i < commits.length; i += MAX_BATCH_SIZE) {
845
+ const chunk = commits.slice(i, i + MAX_BATCH_SIZE);
846
+ const batchNum = Math.floor(i / MAX_BATCH_SIZE) + 1;
847
+ if (totalBatches > 1) {
848
+ console.log(chalk.gray(` Batch ${batchNum}/${totalBatches}...`));
828
849
  }
829
850
 
830
851
  try {
831
- const result = await apiClient.publishTransactional(apiKey, {
832
- metadata,
833
- assets,
852
+ const rawBatchResult = await apiClient.publishBatch(apiKey, {
853
+ commits: chunk,
854
+ autoApprove: autoApprove || false,
834
855
  });
856
+ // Unwrap API envelope: response may be { data: { results, ... } } or { results, ... }
857
+ const batchResult = rawBatchResult.data || rawBatchResult;
835
858
 
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;
859
+ for (const r of batchResult.results || []) {
860
+ if (r.status === "ok") {
861
+ const count = r.assetsProcessed || 0;
862
+ console.log(
863
+ chalk.green(
864
+ ` ✔ Committed "${r.scenario}" (${r.context}): ${count} asset(s)`,
865
+ ),
866
+ );
867
+ successCount += count;
843
868
 
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)`));
869
+ if (r.skippedAssets?.length > 0) {
870
+ for (const key of r.skippedAssets) {
871
+ console.log(chalk.yellow(` ⚠ Skipped "${key}" (plan limit reached)`));
872
+ }
873
+ skippedCount += r.skippedAssets.length;
874
+ }
875
+ } else {
876
+ console.log(
877
+ chalk.red(
878
+ ` ✖ "${r.scenario}" (${r.context}): ${r.error || "Unknown error"}`,
879
+ ),
880
+ );
881
+ failCount++;
848
882
  }
849
- skippedCount += result.skippedAssets.length;
850
883
  }
851
884
 
852
- // Capture viewUrl from first successful response
853
- if (!viewUrl && result?.viewUrl) {
854
- viewUrl = result.viewUrl;
885
+ if (!viewUrl && batchResult.viewUrl) {
886
+ viewUrl = batchResult.viewUrl;
855
887
  }
856
888
  } catch (error) {
857
- console.log(chalk.red(` ✖ Commit failed: ${error.message}`));
858
- failCount += assets.length;
889
+ console.log(chalk.red(` ✖ Batch request failed: ${error.message}`));
890
+ failCount += chunk.length;
859
891
  }
860
892
  }
861
893
 
@@ -1204,7 +1236,7 @@ function parseFrontmatter(content) {
1204
1236
  }
1205
1237
 
1206
1238
  async function publishCommand(options = {}) {
1207
- const { tag, message, dryRun, force, video, outputJson } = options;
1239
+ const { tag, message, dryRun, force, video, outputJson, autoApprove } = options;
1208
1240
 
1209
1241
  // Result tracking for --output-json and programmatic callers
1210
1242
  const publishResult = {
@@ -1230,6 +1262,10 @@ async function publishCommand(options = {}) {
1230
1262
  console.log(chalk.yellow("🔍 DRY RUN MODE - No assets will be uploaded\n"));
1231
1263
  }
1232
1264
 
1265
+ if (autoApprove) {
1266
+ console.log(chalk.cyan(" ✅ Auto-approve enabled: visuals will be approved immediately\n"));
1267
+ }
1268
+
1233
1269
  // Read config + settings (if available)
1234
1270
  const settings = readSettingsSafe();
1235
1271
  let docSyncConfig = null;
@@ -1413,6 +1449,7 @@ async function publishCommand(options = {}) {
1413
1449
  docSyncConfig,
1414
1450
  { commitHash, commitMessage, publishSessionId },
1415
1451
  diffManifests,
1452
+ { autoApprove },
1416
1453
  );
1417
1454
  successCount = result.successCount;
1418
1455
  failCount = result.failCount;
@@ -142,6 +142,19 @@ async function pullCommand(options = {}) {
142
142
  process.exit(1);
143
143
  }
144
144
 
145
+ if (settings.projectId && projectId !== settings.projectId) {
146
+ console.warn(
147
+ chalk.yellow(
148
+ ` ⚠ Project ID mismatch: config uses ${projectId}, but authenticated project is ${settings.projectId}.`
149
+ )
150
+ );
151
+ console.warn(
152
+ chalk.yellow(
153
+ ` This will cause auth failures. Run 'reshot setup --force' to re-link.\n`
154
+ )
155
+ );
156
+ }
157
+
145
158
  try {
146
159
  // Fetch the asset map from the API
147
160
  console.log(chalk.gray(` Fetching assets for project: ${projectId}`));
@@ -163,7 +176,18 @@ async function pullCommand(options = {}) {
163
176
  0
164
177
  );
165
178
 
166
- console.log(chalk.green(` ✓ Fetched ${assetCount} visuals\n`));
179
+ if (assetCount === 0) {
180
+ console.log(chalk.yellow(` ⚠ Fetched 0 visuals.\n`));
181
+ console.log(chalk.gray(` Possible reasons:`));
182
+ if (status === "approved") {
183
+ console.log(chalk.gray(` • No visuals approved yet. Try: reshot pull --status all`));
184
+ }
185
+ console.log(chalk.gray(` • No visuals published. Run: reshot publish`));
186
+ console.log(chalk.gray(` • Wrong project. Config uses: ${projectId}`));
187
+ console.log(chalk.gray(` • Check: ${settings.platformUrl || "https://reshot.dev"}\n`));
188
+ } else {
189
+ console.log(chalk.green(` ✓ Fetched ${assetCount} visuals\n`));
190
+ }
167
191
 
168
192
  // Determine output path
169
193
  let outputPath = output;
@@ -2,6 +2,7 @@
2
2
  const chalk = require("chalk");
3
3
  const oraModule = require("ora");
4
4
  const ora = oraModule.default || oraModule;
5
+ const { execSync } = require("child_process");
5
6
 
6
7
  const {
7
8
  writeSettings,
@@ -100,10 +101,14 @@ async function setupCommand(options = {}) {
100
101
  if (hasConfig && !force) {
101
102
  console.log(chalk.green("✔ Configuration found:"), chalk.cyan("reshot.config.json"));
102
103
  } else if (hasConfig && force) {
103
- console.log(chalk.yellow("⚠ Overwriting existing reshot.config.json..."));
104
- const newConfig = createDefaultConfig(existingSettings);
105
- writeConfig(newConfig);
106
- console.log(chalk.green("✔ Configuration updated"));
104
+ // Patch projectId in existing config instead of wiping scenarios
105
+ const fs = require("fs");
106
+ const path = require("path");
107
+ const configPath = path.join(process.cwd(), "reshot.config.json");
108
+ const existingConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
109
+ existingConfig.projectId = existingSettings.projectId;
110
+ fs.writeFileSync(configPath, JSON.stringify(existingConfig, null, 2) + "\n");
111
+ console.log(chalk.green("✔ Configuration updated (projectId synced)"));
107
112
  } else {
108
113
  // No config - create it
109
114
  console.log(chalk.gray("Creating reshot.config.json..."));
@@ -112,6 +117,29 @@ async function setupCommand(options = {}) {
112
117
  console.log(chalk.green("✔ Configuration created:"), chalk.cyan("reshot.config.json"));
113
118
  }
114
119
 
120
+ // Step 3.5: Ensure @reshotdev/screenshot is in devDependencies
121
+ const fs = require("fs");
122
+ const path = require("path");
123
+ const pkgJsonPath = path.join(process.cwd(), "package.json");
124
+ if (fs.existsSync(pkgJsonPath)) {
125
+ const pkgJson = JSON.parse(fs.readFileSync(pkgJsonPath, "utf-8"));
126
+ const hasDep =
127
+ pkgJson.devDependencies?.["@reshotdev/screenshot"] ||
128
+ pkgJson.dependencies?.["@reshotdev/screenshot"];
129
+ if (!hasDep) {
130
+ console.log(chalk.gray(" Adding @reshotdev/screenshot to devDependencies..."));
131
+ const usePnpm = fs.existsSync(path.join(process.cwd(), "pnpm-lock.yaml"));
132
+ const useYarn = fs.existsSync(path.join(process.cwd(), "yarn.lock"));
133
+ const cmd = usePnpm ? "pnpm add -D" : useYarn ? "yarn add -D" : "npm install -D";
134
+ try {
135
+ execSync(`${cmd} @reshotdev/screenshot`, { stdio: "inherit" });
136
+ console.log(chalk.green(" ✔ Added @reshotdev/screenshot to devDependencies"));
137
+ } catch {
138
+ console.log(chalk.yellow(" ⚠ Could not auto-install. Run manually: " + cmd + " @reshotdev/screenshot"));
139
+ }
140
+ }
141
+ }
142
+
115
143
  // Step 4: Success summary
116
144
  console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
117
145
  console.log(chalk.green.bold("✔ Project initialized successfully!"));
package/src/index.js CHANGED
@@ -160,12 +160,28 @@ program
160
160
  .option("--browser", "Launch Chrome with remote debugging before recording")
161
161
  .option("-p, --port <port>", "Chrome debugging port (default: 9222)")
162
162
  .option("--url <url>", "URL to open when launching browser")
163
+ .option("--refresh-session", "Only refresh the auth session (no recording prompts)")
163
164
  .option("--debug", "Enable verbose debug logging")
164
165
  .action(async (title, options) => {
165
166
  if (options.debug) {
166
167
  process.env.RESHOT_DEBUG = "1";
167
168
  }
168
169
  try {
170
+ // If --refresh-session, just sync the session and exit
171
+ if (options.refreshSession) {
172
+ const { autoSyncSessionFromCDP, getDefaultSessionPath } = require("./lib/record-cdp");
173
+ const sessionPath = getDefaultSessionPath();
174
+ console.log(chalk.gray(" Syncing session from active browser..."));
175
+ const synced = await autoSyncSessionFromCDP(sessionPath);
176
+ if (synced) {
177
+ console.log(chalk.green(" ✔ Session refreshed at " + sessionPath));
178
+ } else {
179
+ console.log(chalk.yellow(" ⚠ No active CDP browser found. Launch Chrome with remote debugging first:"));
180
+ console.log(chalk.gray(" reshot record --browser --refresh-session"));
181
+ }
182
+ return;
183
+ }
184
+
169
185
  // If --browser flag, launch Chrome first
170
186
  if (options.browser) {
171
187
  const chromeCommand = require("./commands/chrome");
@@ -221,12 +237,14 @@ program
221
237
  .option("--dry-run", "Preview without uploading")
222
238
  .option("-f, --force", "Skip confirmation prompts")
223
239
  .option("--output-json", "Write structured result to .reshot/output/publish-result.json")
240
+ .option("--auto-approve", "Automatically approve published visuals (skip review queue)")
224
241
  .action(async (options) => {
225
242
  try {
226
243
  const publishCommand = require("./commands/publish");
227
244
  await publishCommand({
228
245
  ...options,
229
246
  outputJson: options.outputJson,
247
+ autoApprove: options.autoApprove,
230
248
  });
231
249
  } catch (error) {
232
250
  console.error(chalk.red("Error:"), error.message);
@@ -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 baseUrl =
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
- return baseUrl;
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,
@@ -303,7 +303,18 @@ async function preflightAuthCheck(baseUrl, options = {}) {
303
303
  };
304
304
  }
305
305
 
306
- console.log(chalk.green(" ✔ Auth pre-flight check passed"));
306
+ // Save refreshed session back so scenarios use fresh cookies
307
+ if (storageStatePath && engine.context) {
308
+ try {
309
+ const refreshedState = await engine.context.storageState();
310
+ fs.writeFileSync(storageStatePath, JSON.stringify(refreshedState, null, 2));
311
+ console.log(chalk.green(" ✔ Auth pre-flight check passed (session refreshed)"));
312
+ } catch (_saveErr) {
313
+ console.log(chalk.green(" ✔ Auth pre-flight check passed"));
314
+ }
315
+ } else {
316
+ console.log(chalk.green(" ✔ Auth pre-flight check passed"));
317
+ }
307
318
  return { ok: true };
308
319
  } catch (e) {
309
320
  // If the error is an auth redirect thrown by the engine, handle gracefully
@@ -1133,11 +1144,25 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
1133
1144
  `Page error detected: ${errMsg}. The page rendered an error UI instead of expected content.`
1134
1145
  );
1135
1146
  } else if (retryResult.status === "timeout") {
1147
+ const currentUrl = engine.page.url();
1136
1148
  console.log(
1137
1149
  chalk.yellow(
1138
- ` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s), proceeding with current state`
1150
+ ` ⚠ Ready selector not found after ${retryResult.attempts} attempt(s): ${readySelector}`
1151
+ )
1152
+ );
1153
+ console.log(
1154
+ chalk.gray(` URL: ${currentUrl}`)
1155
+ );
1156
+ console.log(
1157
+ chalk.gray(
1158
+ ` Hint: The page loaded but this selector does not exist. Check your readySelector in reshot.config.json.`
1139
1159
  )
1140
1160
  );
1161
+ throw new Error(
1162
+ `Scenario readySelector "${readySelector}" not found after ${retryResult.attempts} attempt(s). ` +
1163
+ `The page loaded at ${currentUrl} but the selector does not exist. ` +
1164
+ `Update readySelector in reshot.config.json or remove it to skip this check.`
1165
+ );
1141
1166
  } else if (retryResult.attempts > 1) {
1142
1167
  console.log(
1143
1168
  chalk.green(
@@ -1296,6 +1321,12 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
1296
1321
  ` Hint: If data isn't loading, run 'reshot record' to refresh your session`
1297
1322
  )
1298
1323
  );
1324
+ failedSteps.push({
1325
+ stepIndex: stepIndex + 1,
1326
+ action: "waitFor",
1327
+ target: params.target,
1328
+ error: errMsg,
1329
+ });
1299
1330
  }
1300
1331
  } else if (waitResult.status === "timeout") {
1301
1332
  if (!isOptional) {
@@ -1309,9 +1340,14 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
1309
1340
  ` Hint: If content isn't loading, run 'reshot record' to refresh your session`
1310
1341
  )
1311
1342
  );
1343
+ failedSteps.push({
1344
+ stepIndex: stepIndex + 1,
1345
+ action: "waitFor",
1346
+ target: params.target,
1347
+ error: `Element not found within ${waitTimeout}ms`,
1348
+ });
1312
1349
  }
1313
1350
  }
1314
- // Continue with next steps - the scenario may still capture partial state
1315
1351
  continue;
1316
1352
  }
1317
1353
 
@@ -2185,8 +2221,16 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
2185
2221
  });
2186
2222
  } catch (e) {
2187
2223
  if (!isOptional) {
2224
+ const currentUrl = page.url();
2225
+ console.warn(
2226
+ chalk.yellow(` ⚠ Element not found: ${params.target}`)
2227
+ );
2228
+ console.warn(chalk.gray(` URL: ${currentUrl}`));
2229
+ console.warn(chalk.gray(` Timeout: ${waitTimeout}ms`));
2188
2230
  console.warn(
2189
- chalk.yellow(` ⚠ Wait for ${params.target} timed out`)
2231
+ chalk.gray(
2232
+ ` Hint: Verify the selector exists on the page. Run 'reshot record' to inspect.`
2233
+ )
2190
2234
  );
2191
2235
  }
2192
2236
  }
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 > localhost default
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 "http://localhost:3000";
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\/?$/, "") || "http://localhost:3000";
2854
+ apiBaseUrl.replace(/\/api\/?$/, "") || "https://reshot.dev";
2856
2855
 
2857
2856
  config.writeSettings({
2858
2857
  projectId: project.id,