@reshotdev/screenshot 0.0.1-beta.12 ā 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.
- package/README.md +67 -22
- package/package.json +18 -14
- package/src/commands/auth.js +37 -7
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-target.js +36 -4
- package/src/commands/drifts.js +13 -1
- package/src/commands/publish.js +137 -12
- package/src/commands/pull.js +9 -4
- package/src/commands/refresh.js +166 -0
- package/src/commands/setup-wizard.js +35 -2
- package/src/commands/status.js +22 -2
- package/src/commands/variation.js +194 -0
- package/src/index.js +187 -9
- package/src/lib/api-client.js +61 -35
- package/src/lib/auto-update/refresh.js +598 -0
- package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
- package/src/lib/auto-update/spec.js +89 -0
- package/src/lib/capture-engine.js +73 -0
- package/src/lib/capture-script-runner.js +280 -134
- package/src/lib/certification.js +23 -1
- package/src/lib/compose-context.js +156 -0
- package/src/lib/compose-pack.js +42 -0
- package/src/lib/compose-runtime.js +34 -0
- package/src/lib/compose-upload.js +142 -0
- package/src/lib/config.js +2 -2
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +0 -4
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +45 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +28 -820
- package/src/lib/ui-asset-cleanup.js +62 -0
- package/src/lib/ui-output-versions.js +165 -0
- package/src/lib/ui-recorder-routes.js +341 -0
- package/src/lib/ui-scenario-metadata.js +161 -0
- package/vendor/compose/dist/auto-update.cjs +5544 -0
- package/vendor/compose/dist/auto-update.mjs +5518 -0
- package/vendor/compose/dist/capture.cjs +1450 -0
- package/vendor/compose/dist/capture.mjs +1416 -0
- package/vendor/compose/dist/eligibility.cjs +5331 -0
- package/vendor/compose/dist/eligibility.mjs +5313 -0
- package/vendor/compose/dist/index.cjs +2046 -0
- package/vendor/compose/dist/index.mjs +1997 -0
- package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
- package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
- package/vendor/compose/dist/jsx-runtime.cjs +58 -0
- package/vendor/compose/dist/jsx-runtime.mjs +31 -0
- package/vendor/compose/dist/render.cjs +558 -0
- package/vendor/compose/dist/render.mjs +515 -0
- package/vendor/compose/dist/verify-cli.cjs +3806 -0
- package/vendor/compose/dist/verify-cli.mjs +3812 -0
- package/vendor/compose/dist/verify.cjs +3880 -0
- package/vendor/compose/dist/verify.mjs +3858 -0
- package/web/manager/dist/assets/{index-CvleJUur.js ā index-D0S2otug.js} +56 -56
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -165
- package/src/lib/playwright-runner.js +0 -252
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
|
|
17
17
|
})();
|
|
18
18
|
</script>
|
|
19
|
-
<script type="module" crossorigin src="/assets/index-
|
|
19
|
+
<script type="module" crossorigin src="/assets/index-D0S2otug.js"></script>
|
|
20
20
|
<link rel="stylesheet" crossorigin href="/assets/index-n468W0Wr.css">
|
|
21
21
|
</head>
|
|
22
22
|
<body>
|
package/src/commands/ingest.js
DELETED
|
@@ -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;
|
package/src/commands/setup.js
DELETED
|
@@ -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;
|