@reshotdev/screenshot 0.0.1-beta.7 → 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.7",
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",
@@ -809,7 +809,8 @@ async function publishWithTransactionalFlow(
809
809
  }
810
810
 
811
811
  // Build all commits for batch request
812
- const MAX_BATCH_SIZE = 200;
812
+ // Vercel serverless functions have ~60s timeout; keep batches small enough to complete
813
+ const MAX_BATCH_SIZE = 25;
813
814
  const commits = [];
814
815
 
815
816
  for (const { group, scenarioConfig, assets } of groupMap.values()) {
@@ -839,14 +840,21 @@ async function publishWithTransactionalFlow(
839
840
  chalk.gray(` Committing ${commits.length} scenario(s) to platform...`),
840
841
  );
841
842
 
843
+ const totalBatches = Math.ceil(commits.length / MAX_BATCH_SIZE);
842
844
  for (let i = 0; i < commits.length; i += MAX_BATCH_SIZE) {
843
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}...`));
849
+ }
844
850
 
845
851
  try {
846
- const batchResult = await apiClient.publishBatch(apiKey, {
852
+ const rawBatchResult = await apiClient.publishBatch(apiKey, {
847
853
  commits: chunk,
848
854
  autoApprove: autoApprove || false,
849
855
  });
856
+ // Unwrap API envelope: response may be { data: { results, ... } } or { results, ... }
857
+ const batchResult = rawBatchResult.data || rawBatchResult;
850
858
 
851
859
  for (const r of batchResult.results || []) {
852
860
  if (r.status === "ok") {
@@ -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");
@@ -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
@@ -1310,6 +1321,12 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
1310
1321
  ` Hint: If data isn't loading, run 'reshot record' to refresh your session`
1311
1322
  )
1312
1323
  );
1324
+ failedSteps.push({
1325
+ stepIndex: stepIndex + 1,
1326
+ action: "waitFor",
1327
+ target: params.target,
1328
+ error: errMsg,
1329
+ });
1313
1330
  }
1314
1331
  } else if (waitResult.status === "timeout") {
1315
1332
  if (!isOptional) {
@@ -1323,9 +1340,14 @@ async function runScenarioWithStepByStepCapture(scenario, options = {}) {
1323
1340
  ` Hint: If content isn't loading, run 'reshot record' to refresh your session`
1324
1341
  )
1325
1342
  );
1343
+ failedSteps.push({
1344
+ stepIndex: stepIndex + 1,
1345
+ action: "waitFor",
1346
+ target: params.target,
1347
+ error: `Element not found within ${waitTimeout}ms`,
1348
+ });
1326
1349
  }
1327
1350
  }
1328
- // Continue with next steps - the scenario may still capture partial state
1329
1351
  continue;
1330
1352
  }
1331
1353