@reshotdev/screenshot 0.0.1-beta.0

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 (59) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +388 -0
  3. package/package.json +64 -0
  4. package/src/commands/auth.js +259 -0
  5. package/src/commands/chrome.js +140 -0
  6. package/src/commands/ci-run.js +123 -0
  7. package/src/commands/ci-setup.js +288 -0
  8. package/src/commands/drifts.js +423 -0
  9. package/src/commands/import-tests.js +309 -0
  10. package/src/commands/ingest.js +458 -0
  11. package/src/commands/init.js +633 -0
  12. package/src/commands/publish.js +1721 -0
  13. package/src/commands/pull.js +303 -0
  14. package/src/commands/record.js +94 -0
  15. package/src/commands/run.js +476 -0
  16. package/src/commands/setup-wizard.js +740 -0
  17. package/src/commands/setup.js +137 -0
  18. package/src/commands/status.js +275 -0
  19. package/src/commands/sync.js +621 -0
  20. package/src/commands/ui.js +248 -0
  21. package/src/commands/validate-docs.js +529 -0
  22. package/src/index.js +462 -0
  23. package/src/lib/api-client.js +815 -0
  24. package/src/lib/capture-engine.js +1623 -0
  25. package/src/lib/capture-script-runner.js +3120 -0
  26. package/src/lib/ci-detect.js +137 -0
  27. package/src/lib/config.js +1240 -0
  28. package/src/lib/diff-engine.js +642 -0
  29. package/src/lib/hash.js +74 -0
  30. package/src/lib/image-crop.js +396 -0
  31. package/src/lib/matrix.js +89 -0
  32. package/src/lib/output-path-template.js +318 -0
  33. package/src/lib/playwright-runner.js +252 -0
  34. package/src/lib/polished-clip.js +553 -0
  35. package/src/lib/privacy-engine.js +408 -0
  36. package/src/lib/progress-tracker.js +142 -0
  37. package/src/lib/record-browser-injection.js +654 -0
  38. package/src/lib/record-cdp.js +612 -0
  39. package/src/lib/record-clip.js +343 -0
  40. package/src/lib/record-config.js +623 -0
  41. package/src/lib/record-screenshot.js +360 -0
  42. package/src/lib/record-terminal.js +123 -0
  43. package/src/lib/recorder-service.js +781 -0
  44. package/src/lib/secrets.js +51 -0
  45. package/src/lib/selector-strategies.js +859 -0
  46. package/src/lib/standalone-mode.js +400 -0
  47. package/src/lib/storage-providers.js +569 -0
  48. package/src/lib/style-engine.js +684 -0
  49. package/src/lib/ui-api.js +4677 -0
  50. package/src/lib/ui-assets.js +373 -0
  51. package/src/lib/ui-executor.js +587 -0
  52. package/src/lib/variant-injector.js +591 -0
  53. package/src/lib/viewport-presets.js +454 -0
  54. package/src/lib/worker-pool.js +118 -0
  55. package/web/cropper/index.html +436 -0
  56. package/web/manager/dist/assets/index--ZgioErz.js +507 -0
  57. package/web/manager/dist/assets/index-n468W0Wr.css +1 -0
  58. package/web/manager/dist/index.html +27 -0
  59. package/web/subtitle-editor/index.html +295 -0
@@ -0,0 +1,740 @@
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
4
+
5
+ const inquirer = require("inquirer");
6
+ const chalk = require("chalk");
7
+ const fs = require("fs-extra");
8
+ const path = require("path");
9
+ 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
+ }
40
+
41
+ /**
42
+ * Detect if this is a Git repository and if it's GitHub
43
+ */
44
+ function detectGitInfo() {
45
+ const isGitRepo = fs.existsSync(path.join(process.cwd(), ".git"));
46
+ let isGitHub = false;
47
+ let remoteUrl = null;
48
+
49
+ if (isGitRepo) {
50
+ try {
51
+ const { execSync } = require("child_process");
52
+ remoteUrl = execSync("git remote get-url origin", {
53
+ encoding: "utf-8",
54
+ }).trim();
55
+ isGitHub = remoteUrl.includes("github.com");
56
+ } catch {
57
+ // No remote configured
58
+ }
59
+ }
60
+
61
+ return { isGitRepo, isGitHub, remoteUrl };
62
+ }
63
+
64
+ /**
65
+ * Detect Playwright configuration
66
+ */
67
+ function detectPlaywright() {
68
+ const configFiles = [
69
+ "playwright.config.ts",
70
+ "playwright.config.js",
71
+ "playwright.config.mjs",
72
+ ];
73
+
74
+ for (const file of configFiles) {
75
+ if (fs.existsSync(path.join(process.cwd(), file))) {
76
+ return { hasPlaywright: true, configFile: file };
77
+ }
78
+ }
79
+
80
+ // Check package.json for playwright dependency
81
+ try {
82
+ const pkg = fs.readJsonSync(path.join(process.cwd(), "package.json"));
83
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
84
+ if (deps["@playwright/test"] || deps["playwright"]) {
85
+ return { hasPlaywright: true, configFile: null };
86
+ }
87
+ } catch {
88
+ // No package.json
89
+ }
90
+
91
+ return { hasPlaywright: false, configFile: null };
92
+ }
93
+
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
145
+
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
176
+
177
+ - Link more documents to journeys
178
+ - Run \`reshot sync\` to upload traces
179
+ - Check \`reshot status\` for drift notifications
180
+ `;
181
+
182
+ fs.writeFileSync(path.join(docsDir, "getting-started.md"), exampleContent);
183
+ return "docs";
184
+ }
185
+
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
+ ];
209
+
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
+ }
217
+ }
218
+
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 };
223
+
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
233
+ }
234
+
235
+ return { framework: null, detected: false };
236
+ }
237
+
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;
257
+ }
258
+
259
+ /**
260
+ * Main setup wizard
261
+ */
262
+ async function setupWizard(options = {}) {
263
+ const { offline = false, force = false } = options;
264
+
265
+ console.log(chalk.cyan.bold("\n🚀 Reshot Setup Wizard\n"));
266
+ console.log(chalk.gray("Let's configure Reshot for your project.\n"));
267
+
268
+ // Detect project context
269
+ const gitInfo = detectGitInfo();
270
+ const playwrightInfo = detectPlaywright();
271
+ const existingDocsRoot = detectDocumentationRoot();
272
+
273
+ // Check existing setup
274
+ let existingSettings = null;
275
+ let existingConfig = null;
276
+ let isAlreadyAuthed = false;
277
+
278
+ try {
279
+ existingSettings = config.readSettings();
280
+ isAlreadyAuthed = !!(
281
+ existingSettings?.apiKey && existingSettings?.projectId
282
+ );
283
+ } catch {
284
+ // No existing settings
285
+ }
286
+
287
+ try {
288
+ if (config.configExists()) {
289
+ existingConfig = config.readConfig();
290
+ }
291
+ } catch {
292
+ // No existing config
293
+ }
294
+
295
+ // If already set up and not forcing, confirm continue
296
+ if ((isAlreadyAuthed || existingConfig) && !force) {
297
+ console.log(
298
+ chalk.yellow("⚠ Reshot is already configured in this project.\n"),
299
+ );
300
+
301
+ if (isAlreadyAuthed) {
302
+ console.log(
303
+ chalk.green(" ✔ Authenticated:"),
304
+ chalk.cyan(existingSettings.projectName || existingSettings.projectId),
305
+ );
306
+ }
307
+ if (existingConfig) {
308
+ console.log(
309
+ chalk.green(" ✔ Config found:"),
310
+ chalk.cyan("docsync.config.json"),
311
+ );
312
+ }
313
+
314
+ const { continueSetup } = await inquirer.prompt([
315
+ {
316
+ type: "confirm",
317
+ name: "continueSetup",
318
+ message: "Would you like to reconfigure?",
319
+ default: false,
320
+ },
321
+ ]);
322
+
323
+ if (!continueSetup) {
324
+ console.log(
325
+ chalk.gray("\nRun"),
326
+ chalk.cyan("reshot studio"),
327
+ chalk.gray("to manage your visuals.\n"),
328
+ );
329
+ return;
330
+ }
331
+ }
332
+
333
+ // ========================================
334
+ // STEP 1: Platform Connection
335
+ // ========================================
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;
342
+
343
+ if (!offline) {
344
+ const { connectToCloud } = await inquirer.prompt([
345
+ {
346
+ type: "list",
347
+ name: "connectToCloud",
348
+ message: "How would you like to use Reshot?",
349
+ choices: [
350
+ {
351
+ name: `${chalk.cyan("Connect to Reshot Cloud")} - Governance, CDN hosting, team collaboration`,
352
+ value: true,
353
+ },
354
+ {
355
+ name: `${chalk.gray("Offline mode")} - Local-only visuals (no cloud sync)`,
356
+ value: false,
357
+ },
358
+ ],
359
+ default: isAlreadyAuthed ? 0 : 1,
360
+ },
361
+ ]);
362
+
363
+ useCloud = connectToCloud;
364
+
365
+ if (useCloud && !isAlreadyAuthed) {
366
+ console.log(chalk.gray("\nOpening browser for authentication...\n"));
367
+
368
+ const authCommand = require("./auth");
369
+ await authCommand();
370
+
371
+ // Re-read settings after auth
372
+ try {
373
+ existingSettings = config.readSettings();
374
+ projectId = existingSettings?.projectId;
375
+ apiKey = existingSettings?.apiKey;
376
+ projectName = existingSettings?.projectName;
377
+ isAlreadyAuthed = !!(apiKey && projectId);
378
+ } catch {
379
+ throw new Error("Authentication failed. Please try again.");
380
+ }
381
+
382
+ if (!isAlreadyAuthed) {
383
+ throw new Error("Authentication was not completed.");
384
+ }
385
+
386
+ console.log(
387
+ chalk.green("\n✔ Connected to"),
388
+ chalk.cyan(projectName || projectId),
389
+ );
390
+ } else if (useCloud && isAlreadyAuthed) {
391
+ console.log(
392
+ chalk.green("✔ Already connected to"),
393
+ chalk.cyan(projectName || projectId),
394
+ );
395
+ }
396
+ } else {
397
+ console.log(chalk.gray("Running in offline mode - no cloud sync."));
398
+ }
399
+
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");
433
+
434
+ // ========================================
435
+ // STEP 3: Visuals Configuration (if enabled)
436
+ // ========================================
437
+ let traceDir = "./test-results";
438
+ let journeyMappings = {};
439
+
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
+ }
448
+
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
+ );
485
+ }
486
+
487
+ const { customTraceDir } = await inquirer.prompt([
488
+ {
489
+ type: "input",
490
+ name: "customTraceDir",
491
+ message: "Playwright test-results directory:",
492
+ default: "./test-results",
493
+ },
494
+ ]);
495
+
496
+ traceDir = customTraceDir;
497
+ }
498
+
499
+ // ========================================
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
650
+ // ========================================
651
+ console.log(chalk.cyan("\n━━━ Generating Configuration ━━━\n"));
652
+
653
+ const newConfig = {
654
+ $schema: "https://reshot.dev/schemas/docsync-config.json",
655
+ version: "2.0",
656
+ baseUrl: existingConfig?.baseUrl || "http://localhost:3000",
657
+ viewport: existingConfig?.viewport || { width: 1280, height: 720 },
658
+ _metadata: {
659
+ features: {
660
+ visuals: enableVisuals,
661
+ docsync: enableDocSync,
662
+ },
663
+ },
664
+ };
665
+
666
+ if (useCloud && projectId) {
667
+ newConfig.projectId = projectId;
668
+ }
669
+
670
+ if (enableVisuals) {
671
+ newConfig.visuals = {
672
+ traceDir,
673
+ journeyMappings:
674
+ Object.keys(journeyMappings).length > 0 ? journeyMappings : undefined,
675
+ };
676
+ newConfig.assetDir = ".reshot/output";
677
+ newConfig.scenarios = existingConfig?.scenarios || [];
678
+ }
679
+
680
+ if (enableDocSync && docConfig) {
681
+ newConfig.documentation = docConfig;
682
+ }
683
+
684
+ // Write configuration
685
+ config.writeConfig(newConfig);
686
+ console.log(chalk.green("✔ Created docsync.config.json"));
687
+
688
+ // ========================================
689
+ // STEP 6: Success & Next Steps
690
+ // ========================================
691
+ console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
692
+ console.log(chalk.green.bold("✔ Reshot setup complete!"));
693
+ console.log(chalk.green("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
694
+
695
+ console.log(chalk.cyan("Next steps:\n"));
696
+
697
+ console.log(
698
+ ` 1. ${chalk.gray("Review")} ${chalk.cyan("docsync.config.json")} ${chalk.gray("and commit to your repo")}`,
699
+ );
700
+
701
+ if (enableVisuals) {
702
+ console.log(
703
+ ` 2. ${chalk.gray("Run Playwright tests to generate traces:")}`,
704
+ );
705
+ console.log(` ${chalk.cyan("npx playwright test")}`);
706
+ }
707
+
708
+ if (enableDocSync) {
709
+ console.log(
710
+ ` ${enableVisuals ? "3" : "2"}. ${chalk.gray("Add")} ${chalk.cyan("reshot_journey")} ${chalk.gray("frontmatter to your markdown files")}`,
711
+ );
712
+ console.log(
713
+ ` ${chalk.gray('Example: reshot_journey: "auth/login-flow"')}`,
714
+ );
715
+ }
716
+
717
+ console.log(`\n ${chalk.gray("Sync visuals and docs to Reshot:")}`);
718
+ console.log(` ${chalk.cyan("reshot sync")}`);
719
+
720
+ console.log(`\n ${chalk.gray("Launch the visual management UI:")}`);
721
+ console.log(` ${chalk.cyan("reshot studio")}\n`);
722
+
723
+ // Offer to launch studio
724
+ const { launchStudio } = await inquirer.prompt([
725
+ {
726
+ type: "confirm",
727
+ name: "launchStudio",
728
+ message: "Launch Reshot Studio now?",
729
+ default: true,
730
+ },
731
+ ]);
732
+
733
+ if (launchStudio) {
734
+ console.log(chalk.cyan("\n🎬 Launching Reshot Studio...\n"));
735
+ const uiCommand = require("./ui");
736
+ await uiCommand({ open: true });
737
+ }
738
+ }
739
+
740
+ module.exports = setupWizard;