@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.10

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 +305 -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
@@ -1,42 +1,12 @@
1
- // setup-wizard.js - Comprehensive interactive setup wizard
2
- // Consolidates the initial setup flow: auth + init + feature configuration
3
- // Individual commands (auth, init) still exist for power users
1
+ // setup-wizard.js - Interactive setup wizard
2
+ // Streamlined flow: choose lane, create config, optional Studio launch
4
3
 
5
4
  const inquirer = require("inquirer");
6
5
  const chalk = require("chalk");
7
6
  const fs = require("fs-extra");
8
7
  const path = require("path");
9
8
  const config = require("../lib/config");
10
- const {
11
- validateStorageConfig,
12
- getStorageSetupHelp,
13
- } = require("../lib/storage-providers");
14
-
15
- /**
16
- * Auto-detect documentation directories
17
- */
18
- function detectDocumentationRoot() {
19
- const commonPaths = [
20
- "docs",
21
- "documentation",
22
- "content",
23
- "doc",
24
- "guides",
25
- ".vitepress/content",
26
- ".docusaurus/docs",
27
- ];
28
-
29
- for (const dir of commonPaths) {
30
- const fullPath = path.join(process.cwd(), dir);
31
- if (fs.existsSync(fullPath) && fs.statSync(fullPath).isDirectory()) {
32
- const files = fs.readdirSync(fullPath);
33
- if (files.some((f) => f.endsWith(".md") || f.endsWith(".mdx"))) {
34
- return dir;
35
- }
36
- }
37
- }
38
- return null;
39
- }
9
+ const { normalizeConfigContract } = require("../lib/target-contract");
40
10
 
41
11
  /**
42
12
  * Detect if this is a Git repository and if it's GitHub
@@ -91,184 +61,104 @@ function detectPlaywright() {
91
61
  return { hasPlaywright: false, configFile: null };
92
62
  }
93
63
 
94
- /**
95
- * Generate documentation block configuration
96
- */
97
- function generateDocumentationConfig(root = "./docs", strategy = "git_pr") {
98
- return {
99
- root,
100
- include: ["**/*.md", "**/*.mdx"],
101
- exclude: ["**/_*.mdx", "**/node_modules/**", "**/.next/**"],
102
- strategy,
103
- assetFormat: "markdown",
104
- mappings: {},
105
- };
106
- }
107
-
108
- /**
109
- * Scaffold a basic documentation structure
110
- */
111
- async function scaffoldDocumentation(template = "basic") {
112
- const docsDir = path.join(process.cwd(), "docs");
113
- fs.ensureDirSync(docsDir);
114
-
115
- const readmeContent = `# Documentation
116
-
117
- This directory contains documentation for your project.
118
-
119
- ## Linking Docs to Visual Journeys
120
-
121
- Add \`reshot_journey\` frontmatter to your markdown files to link them to Playwright test captures:
122
-
123
- \`\`\`markdown
124
- ---
125
- title: "Feature Name"
126
- reshot_journey: "feature/workflow-name"
127
- ---
128
-
129
- # Feature Documentation
130
-
131
- Your content here...
132
- \`\`\`
133
-
134
- The journey key should match your Playwright test structure. For example:
135
- - Test file: \`tests/01-auth-flows.spec.ts\` with "Login Flow" describe block
136
- - Journey key: \`auth-flows/login-flow\`
137
-
138
- ## Commands
139
-
140
- - \`reshot import-tests\` - Import existing Playwright tests
141
- - \`reshot sync\` - Sync visuals and documentation
142
- - \`reshot status\` - View sync status and drifts
143
- - \`reshot validate\` - Validate configuration
144
- - \`reshot studio\` - Open visual management UI
64
+ function detectSetupMode(projectConfig, useCloud) {
65
+ const normalizedConfig =
66
+ projectConfig && typeof projectConfig === "object"
67
+ ? normalizeConfigContract(projectConfig)
68
+ : null;
69
+ const targetTier = normalizedConfig?.target?.tier || null;
145
70
 
146
- For more information, visit: https://docs.reshot.dev
147
- `;
148
-
149
- fs.writeFileSync(path.join(docsDir, "README.md"), readmeContent);
150
-
151
- const exampleContent = `---
152
- title: "Getting Started"
153
- reshot_journey: "onboarding/first-steps"
154
- description: "Quick start guide for new users"
155
- ---
156
-
157
- # Getting Started
158
-
159
- Welcome! This is an example documentation file linked to the \`onboarding/first-steps\` journey.
160
-
161
- ## Overview
162
-
163
- When Reshot detects UI changes in your Playwright tests, it will automatically propose updates to this document with new screenshots and descriptions.
164
-
165
- ## Steps
166
-
167
- 1. **Step 1**: Description of first step
168
-
169
- <!-- Reshot will inject screenshots here when visual drift is detected -->
170
-
171
- 2. **Step 2**: Description of second step
172
-
173
- 3. **Step 3**: Description of third step
174
-
175
- ## Next Steps
71
+ if (targetTier === "certified") {
72
+ return "certified-target";
73
+ }
176
74
 
177
- - Link more documents to journeys
178
- - Run \`reshot sync\` to upload traces
179
- - Check \`reshot status\` for drift notifications
180
- `;
75
+ if (targetTier === "candidate") {
76
+ return "candidate-target";
77
+ }
181
78
 
182
- fs.writeFileSync(path.join(docsDir, "getting-started.md"), exampleContent);
183
- return "docs";
79
+ return useCloud ? "cloud-connected" : "local-only";
184
80
  }
185
81
 
186
- /**
187
- * Check if a documentation framework is already set up
188
- */
189
- function detectDocumentationFramework() {
190
- const frameworks = [
191
- {
192
- name: "Astro/Starlight",
193
- files: ["astro.config.mjs", "astro.config.ts"],
194
- deps: ["@astrojs/starlight"],
195
- },
196
- {
197
- name: "Docusaurus",
198
- files: ["docusaurus.config.js", "docusaurus.config.ts"],
199
- deps: ["@docusaurus/core"],
200
- },
201
- {
202
- name: "VitePress",
203
- files: [".vitepress/config.js", ".vitepress/config.ts"],
204
- deps: ["vitepress"],
205
- },
206
- { name: "Mintlify", files: ["mint.json"], deps: [] },
207
- { name: "GitBook", files: [".gitbook.yaml"], deps: [] },
208
- ];
82
+ function getRecommendedLocalServerCommand(normalizedConfig) {
83
+ const explicitCommand = normalizedConfig?.target?.supportedLocalCommand;
84
+ if (explicitCommand) {
85
+ return explicitCommand;
86
+ }
209
87
 
210
- // Check for config files
211
- for (const fw of frameworks) {
212
- for (const file of fw.files) {
213
- if (fs.existsSync(path.join(process.cwd(), file))) {
214
- return { framework: fw.name, detected: true };
215
- }
216
- }
88
+ if (normalizedConfig?.target?.tier === "certified") {
89
+ return "npm run build && npm run start";
217
90
  }
218
91
 
219
- // Check package.json for deps
220
- try {
221
- const pkg = fs.readJsonSync(path.join(process.cwd(), "package.json"));
222
- const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
92
+ return null;
93
+ }
223
94
 
224
- for (const fw of frameworks) {
225
- for (const dep of fw.deps) {
226
- if (allDeps[dep]) {
227
- return { framework: fw.name, detected: true };
228
- }
229
- }
230
- }
231
- } catch {
232
- // No package.json
95
+ function getNextRecommendedCommand(mode, useCloud) {
96
+ if (mode === "certified-target" || mode === "candidate-target") {
97
+ return "reshot doctor target";
233
98
  }
234
99
 
235
- return { framework: null, detected: false };
100
+ return useCloud ? "reshot publish" : "reshot run";
236
101
  }
237
102
 
238
- /**
239
- * Count documentation files in a directory
240
- */
241
- function countDocFiles(dir) {
242
- if (!fs.existsSync(dir)) return 0;
243
-
244
- let count = 0;
245
- function walk(d) {
246
- for (const item of fs.readdirSync(d)) {
247
- const full = path.join(d, item);
248
- if (fs.statSync(full).isDirectory()) {
249
- if (!item.startsWith(".") && item !== "node_modules") walk(full);
250
- } else if (item.endsWith(".md") || item.endsWith(".mdx")) {
251
- count++;
252
- }
253
- }
254
- }
255
- walk(dir);
256
- return count;
103
+ function writeSetupReport({
104
+ mode,
105
+ useCloud,
106
+ projectId,
107
+ projectName,
108
+ configCreated,
109
+ playwrightDetected,
110
+ supportedLocalCommand,
111
+ }) {
112
+ const reportPath = path.join(
113
+ process.cwd(),
114
+ ".reshot",
115
+ "reports",
116
+ "self-serve-setup.json",
117
+ );
118
+
119
+ fs.ensureDirSync(path.dirname(reportPath));
120
+ fs.writeJSONSync(
121
+ reportPath,
122
+ {
123
+ generatedAt: new Date().toISOString(),
124
+ mode,
125
+ nextRecommendedCommand: getNextRecommendedCommand(mode, useCloud),
126
+ useCloud,
127
+ projectId: projectId || null,
128
+ projectName: projectName || null,
129
+ configCreated,
130
+ playwrightDetected,
131
+ supportedEnvironment: {
132
+ launchSupported: "production-like localhost",
133
+ launchUnsupported: ["next dev"],
134
+ supportedLocalCommand: supportedLocalCommand || null,
135
+ },
136
+ blockingIssues: [],
137
+ advisories: [
138
+ "Run your target app with a production-like local server for launch-grade captures.",
139
+ ],
140
+ nextMilestones: [
141
+ "reshot setup",
142
+ "reshot run",
143
+ ...(useCloud ? ["reshot publish"] : []),
144
+ ],
145
+ },
146
+ { spaces: 2 },
147
+ );
148
+
149
+ return reportPath;
257
150
  }
258
151
 
259
152
  /**
260
153
  * Main setup wizard
261
154
  */
262
155
  async function setupWizard(options = {}) {
263
- const { offline = false, force = false } = options;
156
+ const { offline = false, force = false, noStudio = false } = options;
264
157
 
265
- console.log(chalk.cyan.bold("\n🚀 Reshot Setup Wizard\n"));
266
- console.log(chalk.gray("Let's configure Reshot for your project.\n"));
158
+ console.log(chalk.cyan.bold("\n🚀 Reshot Setup\n"));
267
159
 
268
160
  // Detect project context
269
- const gitInfo = detectGitInfo();
270
161
  const playwrightInfo = detectPlaywright();
271
- const existingDocsRoot = detectDocumentationRoot();
272
162
 
273
163
  // Check existing setup
274
164
  let existingSettings = null;
@@ -292,7 +182,7 @@ async function setupWizard(options = {}) {
292
182
  // No existing config
293
183
  }
294
184
 
295
- // If already set up and not forcing, confirm continue
185
+ // If already set up and not forcing, show status and offer options
296
186
  if ((isAlreadyAuthed || existingConfig) && !force) {
297
187
  console.log(
298
188
  chalk.yellow("⚠ Reshot is already configured in this project.\n"),
@@ -307,20 +197,73 @@ async function setupWizard(options = {}) {
307
197
  if (existingConfig) {
308
198
  console.log(
309
199
  chalk.green(" ✔ Config found:"),
310
- chalk.cyan("docsync.config.json"),
200
+ chalk.cyan("reshot.config.json"),
311
201
  );
312
202
  }
313
203
 
314
- const { continueSetup } = await inquirer.prompt([
204
+ const choices = [];
205
+
206
+ if (isAlreadyAuthed) {
207
+ choices.push({
208
+ name: "Re-authenticate (connect to a different project)",
209
+ value: "reauth",
210
+ });
211
+ }
212
+
213
+ choices.push({
214
+ name: "Reconfigure (regenerate reshot.config.json)",
215
+ value: "reconfig",
216
+ });
217
+
218
+ choices.push({
219
+ name: "Exit",
220
+ value: "exit",
221
+ });
222
+
223
+ const { action } = await inquirer.prompt([
315
224
  {
316
- type: "confirm",
317
- name: "continueSetup",
318
- message: "Would you like to reconfigure?",
319
- default: false,
225
+ type: "list",
226
+ name: "action",
227
+ message: "What would you like to do?",
228
+ choices,
320
229
  },
321
230
  ]);
322
231
 
323
- if (!continueSetup) {
232
+ if (action === "exit") {
233
+ console.log(
234
+ chalk.gray("\nRun"),
235
+ chalk.cyan("reshot studio"),
236
+ chalk.gray("to manage your visuals.\n"),
237
+ );
238
+ return;
239
+ }
240
+
241
+ if (action === "reauth") {
242
+ console.log(chalk.gray("\nOpening browser for authentication...\n"));
243
+ const authCommand = require("./auth");
244
+ const authResult = await authCommand();
245
+
246
+ // Re-read settings after auth
247
+ try {
248
+ existingSettings = config.readSettings();
249
+ isAlreadyAuthed = !!(
250
+ existingSettings?.apiKey && existingSettings?.projectId
251
+ );
252
+ } catch {
253
+ throw new Error("Authentication failed. Please try again.");
254
+ }
255
+
256
+ if (!isAlreadyAuthed) {
257
+ throw new Error("Authentication was not completed.");
258
+ }
259
+
260
+ console.log(
261
+ chalk.green("\n✔ Connected to"),
262
+ chalk.cyan(existingSettings.projectName || existingSettings.projectId),
263
+ );
264
+ if (authResult?.mode) {
265
+ console.log(chalk.gray(` Mode: ${authResult.mode}`));
266
+ }
324
267
  console.log(
325
268
  chalk.gray("\nRun"),
326
269
  chalk.cyan("reshot studio"),
@@ -328,19 +271,16 @@ async function setupWizard(options = {}) {
328
271
  );
329
272
  return;
330
273
  }
274
+
275
+ // action === "reconfig" — fall through to config generation below
331
276
  }
332
277
 
333
278
  // ========================================
334
279
  // STEP 1: Platform Connection
335
280
  // ========================================
336
- console.log(chalk.cyan("\n━━━ Step 1: Platform Connection ━━━\n"));
337
-
338
- let useCloud = false;
339
- let projectId = existingSettings?.projectId;
340
- let apiKey = existingSettings?.apiKey;
341
- let projectName = existingSettings?.projectName;
281
+ if (!isAlreadyAuthed && !offline) {
282
+ console.log(chalk.cyan("\n━━━ Step 1: Choose Your Lane ━━━\n"));
342
283
 
343
- if (!offline) {
344
284
  const { connectToCloud } = await inquirer.prompt([
345
285
  {
346
286
  type: "list",
@@ -348,33 +288,29 @@ async function setupWizard(options = {}) {
348
288
  message: "How would you like to use Reshot?",
349
289
  choices: [
350
290
  {
351
- name: `${chalk.cyan("Connect to Reshot Cloud")} - Governance, CDN hosting, team collaboration`,
291
+ name: `${chalk.cyan("Set up hosted pipeline")} - Connect to Reshot Cloud for hosted assets, review workflows, and team collaboration`,
352
292
  value: true,
353
293
  },
354
294
  {
355
- name: `${chalk.gray("Offline mode")} - Local-only visuals (no cloud sync)`,
295
+ name: `${chalk.gray("Start locally")} - Get a first capture working on your machine before you add hosted delivery`,
356
296
  value: false,
357
297
  },
358
298
  ],
359
- default: isAlreadyAuthed ? 0 : 1,
360
299
  },
361
300
  ]);
362
301
 
363
- useCloud = connectToCloud;
364
-
365
- if (useCloud && !isAlreadyAuthed) {
302
+ if (connectToCloud) {
366
303
  console.log(chalk.gray("\nOpening browser for authentication...\n"));
367
304
 
368
305
  const authCommand = require("./auth");
369
- await authCommand();
306
+ const authResult = await authCommand();
370
307
 
371
308
  // Re-read settings after auth
372
309
  try {
373
310
  existingSettings = config.readSettings();
374
- projectId = existingSettings?.projectId;
375
- apiKey = existingSettings?.apiKey;
376
- projectName = existingSettings?.projectName;
377
- isAlreadyAuthed = !!(apiKey && projectId);
311
+ isAlreadyAuthed = !!(
312
+ existingSettings?.apiKey && existingSettings?.projectId
313
+ );
378
314
  } catch {
379
315
  throw new Error("Authentication failed. Please try again.");
380
316
  }
@@ -385,105 +321,46 @@ async function setupWizard(options = {}) {
385
321
 
386
322
  console.log(
387
323
  chalk.green("\n✔ Connected to"),
388
- chalk.cyan(projectName || projectId),
324
+ chalk.cyan(existingSettings.projectName || existingSettings.projectId),
389
325
  );
390
- } else if (useCloud && isAlreadyAuthed) {
326
+ if (authResult?.mode) {
327
+ console.log(chalk.gray(` Mode: ${authResult.mode}`));
328
+ }
329
+ } else {
391
330
  console.log(
392
- chalk.green("✔ Already connected to"),
393
- chalk.cyan(projectName || projectId),
331
+ chalk.gray(
332
+ "Running in local-only mode — hosted publish and pull stay unavailable until you run `reshot auth`.",
333
+ ),
394
334
  );
395
335
  }
396
- } else {
397
- console.log(chalk.gray("Running in offline mode - no cloud sync."));
336
+ } else if (offline) {
337
+ console.log(
338
+ chalk.gray(
339
+ "Running in local-only mode — hosted publish and pull stay unavailable until you run `reshot auth`.",
340
+ ),
341
+ );
398
342
  }
399
343
 
400
- // ========================================
401
- // STEP 2: Feature Selection
402
- // ========================================
403
- console.log(chalk.cyan("\n━━━ Step 2: Features ━━━\n"));
404
-
405
- const { features } = await inquirer.prompt([
406
- {
407
- type: "checkbox",
408
- name: "features",
409
- message: "Which features would you like to enable?",
410
- choices: [
411
- {
412
- name: "Visual capture from Playwright tests",
413
- value: "visuals",
414
- checked: playwrightInfo.hasPlaywright,
415
- },
416
- {
417
- name: "Documentation sync (detect drift, auto-update docs)",
418
- value: "docsync",
419
- checked: !!existingDocsRoot,
420
- },
421
- ],
422
- validate: (answers) => {
423
- if (answers.length === 0) {
424
- return "Please select at least one feature";
425
- }
426
- return true;
427
- },
428
- },
429
- ]);
430
-
431
- const enableVisuals = features.includes("visuals");
432
- const enableDocSync = features.includes("docsync");
344
+ const useCloud = isAlreadyAuthed;
345
+ const projectId = existingSettings?.projectId;
346
+ const normalizedSetupConfig = normalizeConfigContract(
347
+ existingConfig && typeof existingConfig === "object" ? existingConfig : {},
348
+ );
349
+ const supportedLocalCommand =
350
+ getRecommendedLocalServerCommand(normalizedSetupConfig);
433
351
 
434
352
  // ========================================
435
- // STEP 3: Visuals Configuration (if enabled)
353
+ // STEP 2: Project Defaults
436
354
  // ========================================
437
- let traceDir = "./test-results";
438
- let journeyMappings = {};
355
+ console.log(chalk.cyan("\n━━━ Step 2: Project Defaults ━━━\n"));
439
356
 
440
- if (enableVisuals) {
441
- console.log(chalk.cyan("\n━━━ Step 3: Visual Capture ━━━\n"));
442
-
443
- if (playwrightInfo.hasPlaywright) {
444
- console.log(chalk.green("✔ Playwright detected"));
445
- if (playwrightInfo.configFile) {
446
- console.log(chalk.gray(` Config: ${playwrightInfo.configFile}`));
447
- }
357
+ let traceDir = "./test-results";
448
358
 
449
- // Offer to import existing tests
450
- const { importTests } = await inquirer.prompt([
451
- {
452
- type: "confirm",
453
- name: "importTests",
454
- message:
455
- "Import existing Playwright tests to create journey mappings?",
456
- default: true,
457
- },
458
- ]);
459
-
460
- if (importTests) {
461
- console.log(chalk.gray("\nScanning Playwright tests..."));
462
- try {
463
- const importTestsCommand = require("./import-tests");
464
- const result = await importTestsCommand({
465
- interactive: false,
466
- dryRun: false,
467
- });
468
- if (result?.journeyMappings) {
469
- journeyMappings = result.journeyMappings;
470
- console.log(
471
- chalk.green(
472
- `✔ Imported ${Object.keys(journeyMappings).length} journey mapping(s)`,
473
- ),
474
- );
475
- }
476
- } catch (err) {
477
- console.log(chalk.yellow(`⚠ Could not import tests: ${err.message}`));
478
- }
479
- }
480
- } else {
481
- console.log(chalk.yellow("⚠ Playwright not detected"));
482
- console.log(
483
- chalk.gray(" Install with: npm install -D @playwright/test"),
484
- );
359
+ if (playwrightInfo.hasPlaywright) {
360
+ console.log(chalk.green("✔ Playwright detected"));
361
+ if (playwrightInfo.configFile) {
362
+ console.log(chalk.gray(` Config: ${playwrightInfo.configFile}`));
485
363
  }
486
-
487
364
  const { customTraceDir } = await inquirer.prompt([
488
365
  {
489
366
  type: "input",
@@ -494,245 +371,146 @@ async function setupWizard(options = {}) {
494
371
  ]);
495
372
 
496
373
  traceDir = customTraceDir;
374
+ } else {
375
+ console.log(chalk.green("✔ No Playwright setup detected"));
376
+ console.log(
377
+ chalk.gray(
378
+ " That is okay for the local-first workflow. You can define or record scenarios and run them directly.",
379
+ ),
380
+ );
497
381
  }
498
382
 
499
383
  // ========================================
500
- // STEP 4: Documentation Configuration (if enabled)
501
- // ========================================
502
- let docConfig = null;
503
-
504
- if (enableDocSync) {
505
- console.log(chalk.cyan("\n━━━ Step 4: Documentation Sync ━━━\n"));
506
-
507
- // Check for existing documentation framework
508
- const docFramework = detectDocumentationFramework();
509
- let docsRoot = existingDocsRoot;
510
-
511
- if (docFramework.detected) {
512
- console.log(chalk.green(`✔ ${docFramework.framework} detected`));
513
- }
514
-
515
- if (docsRoot) {
516
- const docCount = countDocFiles(path.join(process.cwd(), docsRoot));
517
- console.log(
518
- chalk.green(
519
- `✔ Found documentation at: ${docsRoot}/ (${docCount} files)`,
520
- ),
521
- );
522
-
523
- const { useDetected } = await inquirer.prompt([
524
- {
525
- type: "confirm",
526
- name: "useDetected",
527
- message: `Use ${docsRoot}/ for documentation sync?`,
528
- default: true,
529
- },
530
- ]);
531
-
532
- if (!useDetected) {
533
- docsRoot = null;
534
- }
535
- }
536
-
537
- if (!docsRoot) {
538
- const choices = [
539
- { name: "Create docs/ directory with examples", value: "scaffold" },
540
- { name: "Specify existing directory path", value: "specify" },
541
- ];
542
-
543
- // Add Astro Starlight option if not already a docs framework
544
- if (!docFramework.detected) {
545
- choices.push({
546
- name:
547
- chalk.cyan("Create Astro Starlight project") +
548
- chalk.gray(" (recommended for new docs)"),
549
- value: "starlight",
550
- });
551
- }
552
-
553
- const { docAction } = await inquirer.prompt([
554
- {
555
- type: "list",
556
- name: "docAction",
557
- message: "Documentation setup:",
558
- choices,
559
- },
560
- ]);
561
-
562
- if (docAction === "scaffold") {
563
- console.log(chalk.gray("\nCreating docs/ directory..."));
564
- docsRoot = await scaffoldDocumentation();
565
- console.log(chalk.green("✔ Created docs/ with example files"));
566
- } else if (docAction === "starlight") {
567
- console.log(chalk.cyan("\nTo create an Astro Starlight project:\n"));
568
- console.log(
569
- chalk.white(" npm create astro@latest -- --template starlight"),
570
- );
571
- console.log(
572
- chalk.gray(
573
- "\nRun that command, then re-run reshot setup to link it.\n",
574
- ),
575
- );
576
-
577
- // Still create a basic docs folder as fallback
578
- const { createBasic } = await inquirer.prompt([
579
- {
580
- type: "confirm",
581
- name: "createBasic",
582
- message: "Create a basic docs/ folder for now?",
583
- default: true,
584
- },
585
- ]);
586
-
587
- if (createBasic) {
588
- docsRoot = await scaffoldDocumentation();
589
- console.log(chalk.green("✔ Created docs/ with example files"));
590
- }
591
- } else {
592
- const { customPath } = await inquirer.prompt([
593
- {
594
- type: "input",
595
- name: "customPath",
596
- message: "Documentation directory path:",
597
- default: "./docs",
598
- validate: (input) => {
599
- const fullPath = path.join(process.cwd(), input);
600
- if (!fs.existsSync(fullPath)) {
601
- return `Directory ${input} does not exist. Create it first or use scaffolding.`;
602
- }
603
- return true;
604
- },
605
- },
606
- ]);
607
- docsRoot = customPath;
608
- }
609
- }
610
-
611
- // Determine strategy
612
- let strategy = "git_pr";
613
-
614
- if (useCloud && docsRoot) {
615
- if (gitInfo.isGitHub) {
616
- console.log(chalk.green("\n✔ GitHub repository detected"));
617
- const { useGitPR } = await inquirer.prompt([
618
- {
619
- type: "confirm",
620
- name: "useGitPR",
621
- message: "Auto-create Pull Requests for documentation updates?",
622
- default: true,
623
- },
624
- ]);
625
- strategy = useGitPR ? "git_pr" : "external_host";
626
- } else {
627
- const { strategyChoice } = await inquirer.prompt([
628
- {
629
- type: "list",
630
- name: "strategyChoice",
631
- message: "How should documentation updates be delivered?",
632
- choices: [
633
- { name: "Git Pull Requests", value: "git_pr" },
634
- {
635
- name: "Notifications only (external CMS)",
636
- value: "external_host",
637
- },
638
- ],
639
- },
640
- ]);
641
- strategy = strategyChoice;
642
- }
643
- }
644
-
645
- docConfig = generateDocumentationConfig(docsRoot, strategy);
646
- }
647
-
648
- // ========================================
649
- // STEP 5: Generate Configuration
384
+ // Generate Configuration
650
385
  // ========================================
651
386
  console.log(chalk.cyan("\n━━━ Generating Configuration ━━━\n"));
652
387
 
653
388
  const newConfig = {
654
- $schema: "https://reshot.dev/schemas/docsync-config.json",
389
+ $schema: "https://reshot.dev/schemas/reshot-config.json",
655
390
  version: "2.0",
656
391
  baseUrl: existingConfig?.baseUrl || "http://localhost:3000",
657
392
  viewport: existingConfig?.viewport || { width: 1280, height: 720 },
658
- _metadata: {
659
- features: {
660
- visuals: enableVisuals,
661
- docsync: enableDocSync,
662
- },
663
- },
664
393
  };
665
394
 
666
395
  if (useCloud && projectId) {
667
396
  newConfig.projectId = projectId;
668
397
  }
669
398
 
670
- if (enableVisuals) {
671
- newConfig.visuals = {
672
- traceDir,
673
- journeyMappings:
674
- Object.keys(journeyMappings).length > 0 ? journeyMappings : undefined,
675
- };
676
-
677
- if (!useCloud) {
678
- const { customAssetDir } = await inquirer.prompt([
679
- {
680
- type: "input",
681
- name: "customAssetDir",
682
- message: "Where should screenshots be saved?",
683
- default: ".reshot/output",
684
- },
685
- ]);
686
- newConfig.assetDir = customAssetDir;
687
- } else {
688
- newConfig.assetDir = ".reshot/output";
689
- }
399
+ newConfig.visuals = {
400
+ traceDir,
401
+ };
690
402
 
691
- newConfig.scenarios = existingConfig?.scenarios || [];
403
+ if (!useCloud) {
404
+ const { customAssetDir } = await inquirer.prompt([
405
+ {
406
+ type: "input",
407
+ name: "customAssetDir",
408
+ message: "Where should screenshots be saved?",
409
+ default: ".reshot/output",
410
+ },
411
+ ]);
412
+ newConfig.assetDir = customAssetDir;
413
+ } else {
414
+ newConfig.assetDir = ".reshot/output";
692
415
  }
693
416
 
694
- if (enableDocSync && docConfig) {
695
- newConfig.documentation = docConfig;
696
- }
417
+ newConfig.scenarios = existingConfig?.scenarios || [];
697
418
 
698
419
  // Write configuration
699
420
  config.writeConfig(newConfig);
700
- console.log(chalk.green("✔ Created docsync.config.json"));
421
+ console.log(chalk.green("✔ Created reshot.config.json"));
422
+
423
+ const combinedConfig =
424
+ existingConfig && typeof existingConfig === "object"
425
+ ? { ...existingConfig, ...newConfig }
426
+ : newConfig;
427
+ const normalizedCombinedConfig = normalizeConfigContract(combinedConfig);
428
+ const setupMode = detectSetupMode(combinedConfig, useCloud);
429
+ const finalSupportedLocalCommand =
430
+ getRecommendedLocalServerCommand(normalizedCombinedConfig) ||
431
+ supportedLocalCommand;
432
+ const reportPath = writeSetupReport({
433
+ mode: setupMode,
434
+ useCloud,
435
+ projectId,
436
+ projectName: existingSettings?.projectName,
437
+ configCreated: true,
438
+ playwrightDetected: playwrightInfo.hasPlaywright,
439
+ supportedLocalCommand: finalSupportedLocalCommand,
440
+ });
701
441
 
702
442
  // ========================================
703
- // STEP 6: Success & Next Steps
443
+ // Success & Next Steps
704
444
  // ========================================
705
445
  console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
706
446
  console.log(chalk.green.bold("✔ Reshot setup complete!"));
707
447
  console.log(chalk.green("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
708
448
 
709
- console.log(chalk.cyan("Next steps:\n"));
449
+ console.log(chalk.cyan("Supported launch path:\n"));
450
+ console.log(
451
+ ` ${chalk.gray("Run your target app with a production-like local server before capture.")}`,
452
+ );
453
+ console.log(
454
+ ` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
455
+ );
456
+ console.log(
457
+ ` ${chalk.gray("Unsupported for launch reliability:")} ${chalk.yellow("next dev")}\n`,
458
+ );
459
+
460
+ console.log(chalk.cyan("Setup mode:\n"));
461
+ console.log(` ${chalk.green(setupMode)}\n`);
710
462
 
463
+ console.log(chalk.cyan("Next steps:\n"));
464
+ console.log(
465
+ ` 1. ${chalk.gray("Review")} ${chalk.cyan("reshot.config.json")} ${chalk.gray("and add your first scenario or recording")}`,
466
+ );
467
+ console.log(
468
+ ` 2. ${chalk.gray("Start your app in the supported environment:")}`,
469
+ );
711
470
  console.log(
712
- ` 1. ${chalk.gray("Review")} ${chalk.cyan("docsync.config.json")} ${chalk.gray("and commit to your repo")}`,
471
+ ` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
713
472
  );
473
+ console.log(` 3. ${chalk.gray("Generate your first local capture:")}`);
474
+ console.log(` ${chalk.cyan("reshot run")}`);
714
475
 
715
- if (enableVisuals) {
476
+ if (useCloud) {
716
477
  console.log(
717
- ` 2. ${chalk.gray("Run Playwright tests to generate traces:")}`,
478
+ `\n 4. ${chalk.gray("Upgrade to hosted assets when you are ready:")}`,
718
479
  );
719
- console.log(` ${chalk.cyan("npx playwright test")}`);
720
- }
721
-
722
- if (enableDocSync) {
480
+ console.log(` ${chalk.cyan("reshot publish")}`);
481
+ } else {
723
482
  console.log(
724
- ` ${enableVisuals ? "3" : "2"}. ${chalk.gray("Add")} ${chalk.cyan("reshot_journey")} ${chalk.gray("frontmatter to your markdown files")}`,
483
+ `\n 4. ${chalk.gray("Connect hosted delivery later when you are ready:")}`,
725
484
  );
485
+ console.log(` ${chalk.cyan("reshot auth")}`);
486
+ }
487
+
488
+ if (setupMode === "certified-target" || setupMode === "candidate-target") {
726
489
  console.log(
727
- ` ${chalk.gray('Example: reshot_journey: "auth/login-flow"')}`,
490
+ `\n 5. ${chalk.gray("Use advanced target checks when you need them:")}`,
728
491
  );
492
+ console.log(` ${chalk.cyan("reshot doctor target")}`);
493
+ console.log(` ${chalk.cyan("reshot verify publish")}`);
494
+ if (setupMode === "certified-target") {
495
+ console.log(` ${chalk.cyan("reshot certify")}`);
496
+ }
729
497
  }
730
498
 
731
- console.log(`\n ${chalk.gray("Sync visuals and docs to Reshot:")}`);
732
- console.log(` ${chalk.cyan("reshot sync")}`);
499
+ console.log(`\n ${chalk.gray("Open Studio to inspect output locally:")}`);
500
+ console.log(` ${chalk.cyan("reshot studio")}`);
501
+ console.log(
502
+ `\n ${chalk.gray("Supported environments guide:")} ${chalk.cyan("https://reshot.dev/docs/cli/getting-started/supported-environments")}`,
503
+ );
504
+ console.log(
505
+ `\n ${chalk.gray("Setup report written to:")} ${chalk.cyan(path.relative(process.cwd(), reportPath))}\n`,
506
+ );
733
507
 
734
- console.log(`\n ${chalk.gray("Launch the visual management UI:")}`);
735
- console.log(` ${chalk.cyan("reshot studio")}\n`);
508
+ if (noStudio) {
509
+ console.log(
510
+ chalk.gray("Studio launch skipped. Run `reshot studio` when you want the local UI.\n"),
511
+ );
512
+ return;
513
+ }
736
514
 
737
515
  // Offer to launch studio
738
516
  const { launchStudio } = await inquirer.prompt([
@@ -740,7 +518,7 @@ async function setupWizard(options = {}) {
740
518
  type: "confirm",
741
519
  name: "launchStudio",
742
520
  message: "Launch Reshot Studio now?",
743
- default: true,
521
+ default: false,
744
522
  },
745
523
  ]);
746
524