@reshotdev/screenshot 0.0.1-beta.11 → 0.0.1-beta.13

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 (66) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +84 -51
  3. package/package.json +20 -16
  4. package/src/commands/auth.js +38 -8
  5. package/src/commands/capture-dom.js +50 -0
  6. package/src/commands/compose.js +220 -0
  7. package/src/commands/doctor-target.js +36 -4
  8. package/src/commands/drifts.js +13 -1
  9. package/src/commands/publish.js +137 -12
  10. package/src/commands/pull.js +13 -8
  11. package/src/commands/refresh.js +166 -0
  12. package/src/commands/setup-wizard.js +35 -2
  13. package/src/commands/status.js +22 -2
  14. package/src/commands/variation.js +194 -0
  15. package/src/index.js +189 -47
  16. package/src/lib/api-client.js +61 -35
  17. package/src/lib/auto-update/refresh.js +598 -0
  18. package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
  19. package/src/lib/auto-update/spec.js +89 -0
  20. package/src/lib/capture-engine.js +73 -0
  21. package/src/lib/capture-script-runner.js +280 -134
  22. package/src/lib/certification.js +23 -1
  23. package/src/lib/compose-context.js +156 -0
  24. package/src/lib/compose-pack.js +42 -0
  25. package/src/lib/compose-runtime.js +34 -0
  26. package/src/lib/compose-upload.js +142 -0
  27. package/src/lib/config.js +5 -5
  28. package/src/lib/dom-capture.js +64 -0
  29. package/src/lib/output-path-template.js +3 -3
  30. package/src/lib/record-clip.js +83 -3
  31. package/src/lib/record-config.js +0 -4
  32. package/src/lib/resolve-targets.js +60 -0
  33. package/src/lib/run-manifest.js +45 -0
  34. package/src/lib/storage-providers.js +1 -1
  35. package/src/lib/style-engine.js +5 -5
  36. package/src/lib/ui-api-helpers.js +118 -0
  37. package/src/lib/ui-api.js +28 -820
  38. package/src/lib/ui-asset-cleanup.js +62 -0
  39. package/src/lib/ui-output-versions.js +165 -0
  40. package/src/lib/ui-recorder-routes.js +341 -0
  41. package/src/lib/ui-scenario-metadata.js +161 -0
  42. package/vendor/compose/dist/auto-update.cjs +5544 -0
  43. package/vendor/compose/dist/auto-update.mjs +5518 -0
  44. package/vendor/compose/dist/capture.cjs +1450 -0
  45. package/vendor/compose/dist/capture.mjs +1416 -0
  46. package/vendor/compose/dist/eligibility.cjs +5331 -0
  47. package/vendor/compose/dist/eligibility.mjs +5313 -0
  48. package/vendor/compose/dist/index.cjs +2046 -0
  49. package/vendor/compose/dist/index.mjs +1997 -0
  50. package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
  51. package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
  52. package/vendor/compose/dist/jsx-runtime.cjs +58 -0
  53. package/vendor/compose/dist/jsx-runtime.mjs +31 -0
  54. package/vendor/compose/dist/render.cjs +558 -0
  55. package/vendor/compose/dist/render.mjs +515 -0
  56. package/vendor/compose/dist/verify-cli.cjs +3806 -0
  57. package/vendor/compose/dist/verify-cli.mjs +3812 -0
  58. package/vendor/compose/dist/verify.cjs +3880 -0
  59. package/vendor/compose/dist/verify.mjs +3858 -0
  60. package/web/manager/dist/assets/{index-D2qqcFNN.js → index-D0S2otug.js} +56 -56
  61. package/web/manager/dist/index.html +1 -1
  62. package/src/commands/ci-run.js +0 -178
  63. package/src/commands/ci-setup.js +0 -288
  64. package/src/commands/ingest.js +0 -458
  65. package/src/commands/setup.js +0 -165
  66. package/src/lib/playwright-runner.js +0 -252
@@ -1,458 +0,0 @@
1
- // ingest.js - Upload traces and documentation for Reshot processing
2
- // Implements the "Smart Handoff" protocol from the Reshot specification
3
-
4
- const chalk = require("chalk");
5
- const crypto = require("crypto");
6
- const fs = require("fs-extra");
7
- const path = require("path");
8
- const { execSync } = require("child_process");
9
- const config = require("../lib/config");
10
- const apiClient = require("../lib/api-client");
11
- const { hashFile } = require("../lib/hash");
12
- const pkg = require("../../package.json");
13
-
14
- // File extension allowlists
15
- const TRACE_EXTENSIONS = [".zip"];
16
- const DOC_EXTENSIONS = [".md", ".mdx"];
17
- const MAX_DOC_SIZE = 5 * 1024 * 1024; // 5MB per doc file
18
- const MAX_TRACE_SIZE = 100 * 1024 * 1024; // 100MB per trace
19
-
20
- /**
21
- * Parse frontmatter from a markdown file
22
- * Extracts YAML frontmatter between --- delimiters
23
- */
24
- function parseFrontmatter(content) {
25
- const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
26
- const match = content.match(frontmatterRegex);
27
-
28
- if (!match) return { frontmatter: {}, content };
29
-
30
- const frontmatter = {};
31
- const lines = match[1].split('\n');
32
-
33
- for (const line of lines) {
34
- const colonIndex = line.indexOf(':');
35
- if (colonIndex > 0) {
36
- const key = line.slice(0, colonIndex).trim();
37
- let value = line.slice(colonIndex + 1).trim();
38
- // Remove quotes if present
39
- if ((value.startsWith('"') && value.endsWith('"')) ||
40
- (value.startsWith("'") && value.endsWith("'"))) {
41
- value = value.slice(1, -1);
42
- }
43
- frontmatter[key] = value;
44
- }
45
- }
46
-
47
- return {
48
- frontmatter,
49
- content: content.slice(match[0].length).trim()
50
- };
51
- }
52
-
53
- /**
54
- * Discover documentation files based on reshot.config.json
55
- */
56
- async function discoverDocumentation(docConfig, projectRoot) {
57
- const files = [];
58
- const root = path.resolve(projectRoot, docConfig.root || './docs');
59
-
60
- if (!fs.existsSync(root)) {
61
- console.log(chalk.yellow(` ⚠ Documentation root not found: ${root}`));
62
- return files;
63
- }
64
-
65
- const include = docConfig.include || ['**/*.md', '**/*.mdx'];
66
- const exclude = docConfig.exclude || ['**/_*.mdx', 'node_modules'];
67
- const mappings = docConfig.mappings || {};
68
-
69
- // Simple glob-like recursive file discovery
70
- function walkDir(dir, relativePath = '') {
71
- const items = fs.readdirSync(dir);
72
-
73
- for (const item of items) {
74
- const fullPath = path.join(dir, item);
75
- const relPath = path.join(relativePath, item);
76
- const stat = fs.statSync(fullPath);
77
-
78
- // Check exclusions
79
- const shouldExclude = exclude.some(pattern => {
80
- if (pattern.includes('*')) {
81
- // Simple glob matching
82
- const regex = new RegExp(pattern.replace(/\*\*/g, '.*').replace(/\*/g, '[^/]*'));
83
- return regex.test(relPath);
84
- }
85
- return relPath.includes(pattern) || item === pattern;
86
- });
87
-
88
- if (shouldExclude) continue;
89
-
90
- if (stat.isDirectory()) {
91
- walkDir(fullPath, relPath);
92
- } else {
93
- const ext = path.extname(item).toLowerCase();
94
- if (DOC_EXTENSIONS.includes(ext)) {
95
- // Read file and check for reshot_journey frontmatter
96
- const content = fs.readFileSync(fullPath, 'utf-8');
97
- const { frontmatter } = parseFrontmatter(content);
98
-
99
- // Get journey key from frontmatter or explicit mappings
100
- const journeyKey = frontmatter.reshot_journey ||
101
- mappings[relPath] ||
102
- mappings[fullPath];
103
-
104
- // Only include files that have a binding
105
- if (journeyKey) {
106
- const fileSize = stat.size;
107
-
108
- if (fileSize > MAX_DOC_SIZE) {
109
- console.log(chalk.yellow(` ⚠ Skipping ${relPath}: exceeds ${MAX_DOC_SIZE / 1024 / 1024}MB limit`));
110
- continue;
111
- }
112
-
113
- files.push({
114
- path: fullPath,
115
- relativePath: relPath,
116
- journeyKey,
117
- contentHash: crypto.createHash('sha256').update(content).digest('hex'),
118
- size: fileSize,
119
- frontmatter
120
- });
121
- }
122
- }
123
- }
124
- }
125
- }
126
-
127
- walkDir(root);
128
- return files;
129
- }
130
-
131
- /**
132
- * Discover Playwright trace files
133
- */
134
- async function discoverTraces(traceDir) {
135
- const traces = [];
136
-
137
- if (!fs.existsSync(traceDir)) {
138
- console.log(chalk.yellow(` ⚠ Trace directory not found: ${traceDir}`));
139
- return traces;
140
- }
141
-
142
- function walkDir(dir) {
143
- const items = fs.readdirSync(dir);
144
-
145
- for (const item of items) {
146
- const fullPath = path.join(dir, item);
147
- const stat = fs.statSync(fullPath);
148
-
149
- if (stat.isDirectory()) {
150
- walkDir(fullPath);
151
- } else if (path.extname(item).toLowerCase() === '.zip') {
152
- const fileSize = stat.size;
153
-
154
- if (fileSize > MAX_TRACE_SIZE) {
155
- console.log(chalk.yellow(` ⚠ Skipping trace ${item}: exceeds ${MAX_TRACE_SIZE / 1024 / 1024}MB limit`));
156
- continue;
157
- }
158
-
159
- // Extract journey key from directory structure or filename
160
- // Convention: test-results/<test-name>/trace.zip
161
- const parentDir = path.basename(path.dirname(fullPath));
162
- const journeyKey = parentDir !== 'test-results' ? parentDir : path.basename(item, '.zip');
163
-
164
- traces.push({
165
- path: fullPath,
166
- filename: item,
167
- journeyKey,
168
- contentHash: hashFile(fullPath),
169
- size: fileSize
170
- });
171
- }
172
- }
173
- }
174
-
175
- walkDir(traceDir);
176
- return traces;
177
- }
178
-
179
- /**
180
- * Get git metadata for the current commit
181
- */
182
- function getGitMetadata() {
183
- try {
184
- const commitHash = execSync('git rev-parse HEAD', { encoding: 'utf-8' }).trim();
185
- const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8' }).trim();
186
- const commitMessage = execSync('git log -1 --pretty=%B', { encoding: 'utf-8' }).trim();
187
-
188
- return { commitHash, branch, commitMessage };
189
- } catch {
190
- console.log(chalk.yellow(' ⚠ Not a git repository or git not available'));
191
- return { commitHash: 'unknown', branch: 'unknown', commitMessage: '' };
192
- }
193
- }
194
-
195
- /**
196
- * Phase 1: Initialize ingestion with manifest handshake
197
- */
198
- async function initializeIngestion(apiKey, projectId, manifest) {
199
- const response = await apiClient.post('/v1/ingest/init', {
200
- projectId,
201
- manifest
202
- }, {
203
- headers: { Authorization: `Bearer ${apiKey}` }
204
- });
205
-
206
- return response;
207
- }
208
-
209
- /**
210
- * Phase 2: Upload files to presigned URLs
211
- */
212
- async function uploadFiles(files, presignedUrls, onProgress) {
213
- const results = [];
214
- let uploaded = 0;
215
-
216
- for (const file of files) {
217
- const presigned = presignedUrls[file.contentHash];
218
-
219
- if (!presigned) {
220
- // File already exists (deduplication)
221
- results.push({ ...file, skipped: true });
222
- uploaded++;
223
- onProgress?.(uploaded, files.length);
224
- continue;
225
- }
226
-
227
- try {
228
- const fileBuffer = fs.readFileSync(file.path);
229
-
230
- // Upload to presigned URL
231
- await apiClient.uploadToPresignedUrl(presigned.url, fileBuffer, {
232
- contentType: presigned.contentType || 'application/octet-stream'
233
- });
234
-
235
- results.push({ ...file, uploaded: true, storageKey: presigned.storageKey });
236
- } catch (error) {
237
- results.push({ ...file, error: error.message });
238
- }
239
-
240
- uploaded++;
241
- onProgress?.(uploaded, files.length);
242
- }
243
-
244
- return results;
245
- }
246
-
247
- /**
248
- * Phase 3: Commit the ingestion job
249
- */
250
- async function commitIngestion(apiKey, projectId, uploadResults, git, cliVersion) {
251
- const response = await apiClient.post('/v1/ingest/commit', {
252
- projectId,
253
- uploadResults,
254
- git,
255
- cli: {
256
- version: cliVersion,
257
- timestamp: new Date().toISOString()
258
- }
259
- }, {
260
- headers: { Authorization: `Bearer ${apiKey}` }
261
- });
262
-
263
- return response;
264
- }
265
-
266
- /**
267
- * Main ingest command
268
- */
269
- async function ingestCommand(options = {}) {
270
- console.log(chalk.blue('\nšŸ“„ Reshot Reshot Ingest\n'));
271
-
272
- // Read configuration
273
- let reshotConfig;
274
- try {
275
- reshotConfig = config.readConfigLenient();
276
- } catch (error) {
277
- console.error(chalk.red('Error:'), 'reshot.config.json not found. Run `reshot init` first.');
278
- process.exit(1);
279
- }
280
-
281
- // Get API key and project ID
282
- const settings = config.readSettings();
283
- const apiKey = process.env.RESHOT_API_KEY || settings?.apiKey;
284
- const projectId = process.env.RESHOT_PROJECT_ID ||
285
- settings?.projectId ||
286
- reshotConfig._metadata?.projectId;
287
-
288
- if (!apiKey) {
289
- console.error(chalk.red('Error:'), 'API key not found. Set RESHOT_API_KEY or run `reshot auth`.');
290
- process.exit(1);
291
- }
292
-
293
- if (!projectId) {
294
- console.error(chalk.red('Error:'), 'Project ID not found. Set RESHOT_PROJECT_ID or run `reshot init`.');
295
- process.exit(1);
296
- }
297
-
298
- // Validate documentation configuration
299
- const docConfig = reshotConfig.documentation;
300
- if (!docConfig) {
301
- console.error(chalk.red('Error:'), 'No "documentation" block found in reshot.config.json');
302
- process.exit(1);
303
- }
304
-
305
- if (!docConfig.strategy) {
306
- console.error(chalk.red('Error:'), 'documentation.strategy is required (git_pr or external_host)');
307
- process.exit(1);
308
- }
309
-
310
- const projectRoot = process.cwd();
311
- const traceDir = options.traceDir || path.join(projectRoot, 'test-results');
312
-
313
- // Phase 1: Discovery
314
- console.log(chalk.gray('šŸ“ Discovering files...'));
315
-
316
- const [docs, traces] = await Promise.all([
317
- discoverDocumentation(docConfig, projectRoot),
318
- discoverTraces(traceDir)
319
- ]);
320
-
321
- console.log(chalk.green(` āœ“ Found ${docs.length} documentation file(s) with journey bindings`));
322
- console.log(chalk.green(` āœ“ Found ${traces.length} trace file(s)`));
323
-
324
- if (docs.length === 0 && traces.length === 0) {
325
- console.log(chalk.yellow('\n⚠ No files to ingest. Make sure:'));
326
- console.log(chalk.yellow(' - Documentation files have reshot_journey frontmatter'));
327
- console.log(chalk.yellow(' - Playwright traces are in test-results/'));
328
- return;
329
- }
330
-
331
- // Validate unique bindings (1:1 relationship)
332
- const journeyKeys = new Set();
333
- const duplicates = [];
334
- for (const doc of docs) {
335
- if (journeyKeys.has(doc.journeyKey)) {
336
- duplicates.push(doc.journeyKey);
337
- }
338
- journeyKeys.add(doc.journeyKey);
339
- }
340
-
341
- if (duplicates.length > 0) {
342
- console.error(chalk.red('\nError: Duplicate journey bindings found:'));
343
- duplicates.forEach(key => console.error(chalk.red(` - ${key}`)));
344
- console.error(chalk.yellow('Each document must have a unique reshot_journey key.'));
345
- process.exit(1);
346
- }
347
-
348
- // Get git metadata
349
- const git = getGitMetadata();
350
- console.log(chalk.gray(`\nšŸ“ Git: ${git.branch} @ ${git.commitHash.slice(0, 7)}`));
351
-
352
- // Dry run mode
353
- if (options.dryRun) {
354
- console.log(chalk.blue('\nšŸ” Dry run - would upload:'));
355
- console.log(chalk.gray('\nDocumentation:'));
356
- docs.forEach(doc => {
357
- console.log(chalk.white(` ${doc.relativePath} → ${doc.journeyKey}`));
358
- });
359
- console.log(chalk.gray('\nTraces:'));
360
- traces.forEach(trace => {
361
- console.log(chalk.white(` ${trace.filename} → ${trace.journeyKey}`));
362
- });
363
- return;
364
- }
365
-
366
- // Phase 2: Manifest Handshake
367
- console.log(chalk.gray('\nšŸ¤ Initializing ingestion...'));
368
-
369
- const manifest = {
370
- docs: docs.map(d => ({
371
- relativePath: d.relativePath,
372
- journeyKey: d.journeyKey,
373
- contentHash: d.contentHash,
374
- size: d.size
375
- })),
376
- traces: traces.map(t => ({
377
- filename: t.filename,
378
- journeyKey: t.journeyKey,
379
- contentHash: t.contentHash,
380
- size: t.size
381
- }))
382
- };
383
-
384
- let initResult;
385
- try {
386
- initResult = await initializeIngestion(apiKey, projectId, manifest);
387
- console.log(chalk.green(' āœ“ Server acknowledged manifest'));
388
-
389
- if (initResult.skippedFiles?.length > 0) {
390
- console.log(chalk.gray(` ℹ ${initResult.skippedFiles.length} file(s) unchanged (cached)`));
391
- }
392
- } catch (error) {
393
- console.error(chalk.red('\nError during init:'), error.message);
394
- process.exit(1);
395
- }
396
-
397
- // Phase 3: Upload files to presigned URLs
398
- const filesToUpload = [...docs, ...traces].filter(
399
- f => !initResult.skippedFiles?.includes(f.contentHash)
400
- );
401
-
402
- if (filesToUpload.length > 0) {
403
- console.log(chalk.gray(`\nšŸ“¤ Uploading ${filesToUpload.length} file(s)...`));
404
-
405
- const uploadResults = await uploadFiles(
406
- filesToUpload,
407
- initResult.presignedUrls || {},
408
- (current, total) => {
409
- process.stdout.write(`\r Progress: ${current}/${total}`);
410
- }
411
- );
412
-
413
- const failed = uploadResults.filter(r => r.error);
414
- if (failed.length > 0) {
415
- console.log(chalk.red(`\n āœ— ${failed.length} upload(s) failed`));
416
- failed.forEach(f => console.log(chalk.red(` - ${f.relativePath || f.filename}: ${f.error}`)));
417
- }
418
-
419
- console.log(chalk.green(`\n āœ“ Uploaded ${filesToUpload.length - failed.length} file(s)`));
420
- }
421
-
422
- // Phase 4: Commit and trigger processing
423
- console.log(chalk.gray('\nšŸ”’ Committing ingestion job...'));
424
-
425
- try {
426
- const commitResult = await commitIngestion(apiKey, projectId, {
427
- docs: docs.map(d => ({
428
- relativePath: d.relativePath,
429
- journeyKey: d.journeyKey,
430
- storageKey: initResult.presignedUrls?.[d.contentHash]?.storageKey || d.contentHash
431
- })),
432
- traces: traces.map(t => ({
433
- filename: t.filename,
434
- journeyKey: t.journeyKey,
435
- storageKey: initResult.presignedUrls?.[t.contentHash]?.storageKey || t.contentHash
436
- }))
437
- }, git, pkg.version);
438
-
439
- console.log(chalk.green(' āœ“ Ingestion job created'));
440
- console.log(chalk.blue(`\nšŸ“Š Job ID: ${commitResult.jobId}`));
441
- console.log(chalk.gray(' The background worker will process traces and detect drift.'));
442
-
443
- if (docConfig.strategy === 'git_pr') {
444
- console.log(chalk.gray(' A Pull Request will be created if drift is detected.'));
445
- } else {
446
- console.log(chalk.gray(' Check the Sync Kit in the dashboard for update proposals.'));
447
- }
448
-
449
- console.log(chalk.blue('\n✨ Ingestion complete!\n'));
450
-
451
- return commitResult;
452
- } catch (error) {
453
- console.error(chalk.red('\nError during commit:'), error.message);
454
- process.exit(1);
455
- }
456
- }
457
-
458
- module.exports = ingestCommand;
@@ -1,165 +0,0 @@
1
- // setup.js - Consolidated command: auth + init + studio launch
2
- const chalk = require("chalk");
3
- const oraModule = require("ora");
4
- const ora = oraModule.default || oraModule;
5
- const { execSync } = require("child_process");
6
-
7
- const {
8
- writeSettings,
9
- readSettings,
10
- configExists,
11
- writeConfig,
12
- SETTINGS_DIR,
13
- } = require("../lib/config");
14
-
15
- /**
16
- * Create a default reshot.config.json for platform mode
17
- * @param {Object} settings - Project settings from auth
18
- * @returns {Object} The created config
19
- */
20
- function createDefaultConfig(settings) {
21
- const defaultConfig = {
22
- $schema: "https://reshot.dev/schemas/reshot-config.json",
23
- version: "2.0",
24
- projectId: settings.projectId,
25
- baseUrl: "http://localhost:3000",
26
- assetDir: ".reshot/output",
27
- viewport: { width: 1280, height: 720 },
28
- timeout: 30000,
29
- headless: true,
30
- scenarios: [],
31
- };
32
-
33
- return defaultConfig;
34
- }
35
-
36
- /**
37
- * Main setup command - consolidates auth, init, and launches studio
38
- * One command to rule them all!
39
- *
40
- * @param {Object} options - Command options
41
- * @param {boolean} options.noStudio - Skip launching studio after setup
42
- * @param {boolean} options.force - Force re-initialization even if already set up
43
- */
44
- async function setupCommand(options = {}) {
45
- const { noStudio = false, force = false } = options;
46
-
47
- console.log(chalk.cyan("\nšŸš€ Reshot Setup\n"));
48
- console.log(chalk.gray("Setting up your project in one step...\n"));
49
-
50
- // Step 1: Check current authentication state
51
- let existingSettings = null;
52
- let isAlreadyAuthed = false;
53
-
54
- try {
55
- existingSettings = readSettings();
56
- isAlreadyAuthed = !!(
57
- existingSettings?.apiKey && existingSettings?.projectId
58
- );
59
- } catch (e) {
60
- // No existing settings - that's fine
61
- }
62
-
63
- // Step 2: Handle authentication
64
- if (isAlreadyAuthed && !force) {
65
- console.log(
66
- chalk.green("āœ” Already authenticated as:"),
67
- chalk.cyan(existingSettings.projectName || existingSettings.projectId)
68
- );
69
- console.log(
70
- chalk.gray(
71
- " Use --force to re-authenticate with a different project.\n"
72
- )
73
- );
74
- } else {
75
- // Run browser-based authentication
76
- console.log(chalk.gray("Opening browser for authentication...\n"));
77
-
78
- const authCommand = require("./auth");
79
- await authCommand();
80
-
81
- // Re-read settings after auth
82
- try {
83
- existingSettings = readSettings();
84
- isAlreadyAuthed = !!(
85
- existingSettings?.apiKey && existingSettings?.projectId
86
- );
87
- } catch (e) {
88
- throw new Error("Authentication failed. Please try again.");
89
- }
90
-
91
- if (!isAlreadyAuthed) {
92
- throw new Error("Authentication was not completed.");
93
- }
94
-
95
- console.log(); // Add spacing after auth
96
- }
97
-
98
- // Step 3: Handle initialization (create reshot.config.json if missing)
99
- const hasConfig = configExists();
100
-
101
- if (hasConfig && !force) {
102
- console.log(chalk.green("āœ” Configuration found:"), chalk.cyan("reshot.config.json"));
103
- } else if (hasConfig && force) {
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)"));
112
- } else {
113
- // No config - create it
114
- console.log(chalk.gray("Creating reshot.config.json..."));
115
- const newConfig = createDefaultConfig(existingSettings);
116
- writeConfig(newConfig);
117
- console.log(chalk.green("āœ” Configuration created:"), chalk.cyan("reshot.config.json"));
118
- }
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
-
143
- // Step 4: Success summary
144
- console.log(chalk.green("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"));
145
- console.log(chalk.green.bold("āœ” Project initialized successfully!"));
146
- console.log(chalk.green("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
147
-
148
- // Step 5: Launch Studio (unless --no-studio)
149
- if (!noStudio) {
150
- console.log(chalk.cyan("šŸŽ¬ Launching Reshot Studio...\n"));
151
-
152
- // Small delay to let the user read the success message
153
- await new Promise((resolve) => setTimeout(resolve, 500));
154
-
155
- const uiCommand = require("./ui");
156
- await uiCommand({ open: true });
157
- } else {
158
- console.log(chalk.gray("Next steps:"));
159
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot run"), chalk.gray("to generate your first local capture"));
160
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot publish"), chalk.gray("when you are ready for hosted assets and review workflows"));
161
- console.log(chalk.gray(" • Run"), chalk.cyan("reshot studio"), chalk.gray("to inspect output locally\n"));
162
- }
163
- }
164
-
165
- module.exports = setupCommand;