@reshotdev/screenshot 0.0.1-beta.8 → 0.0.1-beta.9
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 +54 -2
- package/package.json +1 -1
- package/src/commands/auth.js +106 -22
- package/src/commands/certify.js +54 -0
- package/src/commands/ci-setup.js +3 -3
- package/src/commands/doctor-target.js +42 -0
- package/src/commands/publish.js +35 -8
- package/src/commands/pull.js +227 -21
- package/src/commands/setup-wizard.js +187 -29
- package/src/commands/setup.js +3 -3
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +135 -5
- package/src/lib/api-client.js +64 -23
- package/src/lib/capture-engine.js +64 -3
- package/src/lib/capture-script-runner.js +72 -8
- package/src/lib/certification.js +739 -0
- package/src/lib/config.js +16 -3
- package/src/lib/record-cdp.js +16 -2
- package/src/lib/target-contract.js +278 -0
- package/web/manager/dist/assets/{index-8H7P9ANi.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
package/src/commands/pull.js
CHANGED
|
@@ -16,6 +16,137 @@ function toCamelCase(str) {
|
|
|
16
16
|
.replace(/^[A-Z]/, (chr) => chr.toLowerCase());
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
function normalizeHostedAssetUrl(urlString) {
|
|
20
|
+
if (!urlString || typeof urlString !== "string") {
|
|
21
|
+
return urlString;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let normalized = urlString;
|
|
25
|
+
if (!normalized.includes("?") && normalized.includes("&")) {
|
|
26
|
+
const firstAmp = normalized.indexOf("&");
|
|
27
|
+
normalized = `${normalized.slice(0, firstAmp)}?${normalized.slice(firstAmp + 1)}`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parsed = new URL(normalized);
|
|
31
|
+
const capture = parsed.searchParams.get("capture");
|
|
32
|
+
if (capture && !parsed.searchParams.get("step")) {
|
|
33
|
+
parsed.searchParams.set("step", capture);
|
|
34
|
+
}
|
|
35
|
+
parsed.searchParams.delete("capture");
|
|
36
|
+
|
|
37
|
+
return parsed.toString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeHostedAssetUrlWithMeta(urlString) {
|
|
41
|
+
const normalized = normalizeHostedAssetUrl(urlString);
|
|
42
|
+
return {
|
|
43
|
+
url: normalized,
|
|
44
|
+
repaired: normalized !== urlString,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeAssetEntry(entry) {
|
|
49
|
+
if (!entry || typeof entry !== "object") {
|
|
50
|
+
return { entry, repairs: 0 };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (Array.isArray(entry.steps)) {
|
|
54
|
+
return {
|
|
55
|
+
entry: {
|
|
56
|
+
...entry,
|
|
57
|
+
steps: entry.steps.map((step) => {
|
|
58
|
+
const src = normalizeHostedAssetUrlWithMeta(step.src);
|
|
59
|
+
const poster = step.poster
|
|
60
|
+
? normalizeHostedAssetUrlWithMeta(step.poster)
|
|
61
|
+
: null;
|
|
62
|
+
return {
|
|
63
|
+
...step,
|
|
64
|
+
src: src.url,
|
|
65
|
+
poster: poster ? poster.url : step.poster,
|
|
66
|
+
};
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
repairs: entry.steps.reduce((count, step) => {
|
|
70
|
+
const src = normalizeHostedAssetUrlWithMeta(step.src);
|
|
71
|
+
const poster = step.poster
|
|
72
|
+
? normalizeHostedAssetUrlWithMeta(step.poster)
|
|
73
|
+
: null;
|
|
74
|
+
return count + (src.repaired ? 1 : 0) + (poster?.repaired ? 1 : 0);
|
|
75
|
+
}, 0),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
entry: {
|
|
81
|
+
...entry,
|
|
82
|
+
src: entry.src ? normalizeHostedAssetUrl(entry.src) : entry.src,
|
|
83
|
+
poster: entry.poster ? normalizeHostedAssetUrl(entry.poster) : entry.poster,
|
|
84
|
+
},
|
|
85
|
+
repairs:
|
|
86
|
+
(entry.src && normalizeHostedAssetUrlWithMeta(entry.src).repaired ? 1 : 0) +
|
|
87
|
+
(entry.poster && normalizeHostedAssetUrlWithMeta(entry.poster).repaired ? 1 : 0),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function normalizeAssetMap(assets) {
|
|
92
|
+
const normalized = {};
|
|
93
|
+
let repairs = 0;
|
|
94
|
+
|
|
95
|
+
for (const [group, visuals] of Object.entries(assets || {})) {
|
|
96
|
+
normalized[group] = {};
|
|
97
|
+
|
|
98
|
+
for (const [visualKey, variants] of Object.entries(visuals || {})) {
|
|
99
|
+
normalized[group][visualKey] = {};
|
|
100
|
+
|
|
101
|
+
for (const [variant, entry] of Object.entries(variants || {})) {
|
|
102
|
+
const normalizedEntry = normalizeAssetEntry(entry);
|
|
103
|
+
normalized[group][visualKey][variant] = normalizedEntry.entry;
|
|
104
|
+
repairs += normalizedEntry.repairs;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return { assets: normalized, repairs };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function validateHostedAssetUrl(urlString) {
|
|
113
|
+
if (!urlString || typeof urlString !== "string") {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!urlString.includes("?") && (urlString.includes("&step=") || urlString.includes("&poster="))) {
|
|
118
|
+
throw new Error(`Malformed asset URL emitted without query delimiter: ${urlString}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const parsed = new URL(urlString);
|
|
122
|
+
if (parsed.searchParams.has("capture")) {
|
|
123
|
+
throw new Error(`Legacy capture param emitted in normalized output: ${urlString}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function validateAssetMap(assets) {
|
|
128
|
+
for (const visuals of Object.values(assets || {})) {
|
|
129
|
+
for (const variants of Object.values(visuals || {})) {
|
|
130
|
+
for (const entry of Object.values(variants || {})) {
|
|
131
|
+
if (entry && typeof entry === "object" && Array.isArray(entry.steps)) {
|
|
132
|
+
for (const step of entry.steps) {
|
|
133
|
+
validateHostedAssetUrl(step.src);
|
|
134
|
+
if (step.poster) {
|
|
135
|
+
validateHostedAssetUrl(step.poster);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
validateHostedAssetUrl(entry?.src);
|
|
142
|
+
if (entry?.poster) {
|
|
143
|
+
validateHostedAssetUrl(entry.poster);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
19
150
|
/**
|
|
20
151
|
* Generate TypeScript code from the assets object
|
|
21
152
|
* This provides type safety - if a visual is deleted, the build will fail
|
|
@@ -39,12 +170,27 @@ function generateTypeScript(assets, includeMetadata = false) {
|
|
|
39
170
|
|
|
40
171
|
for (const [variant, asset] of Object.entries(variants)) {
|
|
41
172
|
lines.push(` ${variant}: {`);
|
|
42
|
-
lines.push(` src: "${asset.src}",`);
|
|
43
173
|
lines.push(` type: "${asset.type}",`);
|
|
44
|
-
if (asset.width) lines.push(` width: ${asset.width},`);
|
|
45
|
-
if (asset.height) lines.push(` height: ${asset.height},`);
|
|
46
174
|
lines.push(` alt: "${asset.alt.replace(/"/g, '\\"')}",`);
|
|
47
|
-
if (asset.
|
|
175
|
+
if (Array.isArray(asset.steps)) {
|
|
176
|
+
lines.push(` steps: [`);
|
|
177
|
+
for (const step of asset.steps) {
|
|
178
|
+
lines.push(" {");
|
|
179
|
+
lines.push(` step: "${step.step}",`);
|
|
180
|
+
lines.push(` src: "${step.src}",`);
|
|
181
|
+
lines.push(` type: "${step.type}",`);
|
|
182
|
+
if (step.width) lines.push(` width: ${step.width},`);
|
|
183
|
+
if (step.height) lines.push(` height: ${step.height},`);
|
|
184
|
+
if (step.poster) lines.push(` poster: "${step.poster}",`);
|
|
185
|
+
lines.push(" },");
|
|
186
|
+
}
|
|
187
|
+
lines.push(" ],");
|
|
188
|
+
} else {
|
|
189
|
+
lines.push(` src: "${asset.src}",`);
|
|
190
|
+
if (asset.width) lines.push(` width: ${asset.width},`);
|
|
191
|
+
if (asset.height) lines.push(` height: ${asset.height},`);
|
|
192
|
+
if (asset.poster) lines.push(` poster: "${asset.poster}",`);
|
|
193
|
+
}
|
|
48
194
|
lines.push(` },`);
|
|
49
195
|
}
|
|
50
196
|
|
|
@@ -67,11 +213,19 @@ function generateTypeScript(assets, includeMetadata = false) {
|
|
|
67
213
|
for (const [visualKey, variants] of Object.entries(visuals)) {
|
|
68
214
|
// If there's only a default variant, flatten it
|
|
69
215
|
if (Object.keys(variants).length === 1 && variants.default) {
|
|
70
|
-
|
|
216
|
+
if (Array.isArray(variants.default.steps)) {
|
|
217
|
+
lines.push(` ${visualKey}: ${JSON.stringify(variants.default.steps)},`);
|
|
218
|
+
} else {
|
|
219
|
+
lines.push(` ${visualKey}: "${variants.default.src}",`);
|
|
220
|
+
}
|
|
71
221
|
} else {
|
|
72
222
|
lines.push(` ${visualKey}: {`);
|
|
73
223
|
for (const [variant, asset] of Object.entries(variants)) {
|
|
74
|
-
|
|
224
|
+
if (Array.isArray(asset.steps)) {
|
|
225
|
+
lines.push(` ${variant}: ${JSON.stringify(asset.steps)},`);
|
|
226
|
+
} else {
|
|
227
|
+
lines.push(` ${variant}: "${asset.src}",`);
|
|
228
|
+
}
|
|
75
229
|
}
|
|
76
230
|
lines.push(` },`);
|
|
77
231
|
}
|
|
@@ -102,6 +256,7 @@ async function pullCommand(options = {}) {
|
|
|
102
256
|
output = null,
|
|
103
257
|
full = false,
|
|
104
258
|
status = "approved",
|
|
259
|
+
noExit = false,
|
|
105
260
|
} = options;
|
|
106
261
|
|
|
107
262
|
console.log(chalk.blue("⬇ Pulling asset map from Reshot...\n"));
|
|
@@ -114,15 +269,28 @@ async function pullCommand(options = {}) {
|
|
|
114
269
|
console.error(
|
|
115
270
|
chalk.red("Error: No reshot.config.json found. Run 'reshot init' first.")
|
|
116
271
|
);
|
|
117
|
-
process.exit(1);
|
|
272
|
+
if (!noExit) process.exit(1);
|
|
273
|
+
return { success: false, error: "No reshot.config.json found" };
|
|
118
274
|
}
|
|
119
275
|
|
|
120
|
-
|
|
276
|
+
let projectId = projectConfig._metadata?.projectId || projectConfig.projectId;
|
|
121
277
|
if (!projectId) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
278
|
+
// Fall back to reading projectId from .reshot/settings.json (written by `reshot auth`)
|
|
279
|
+
try {
|
|
280
|
+
const settings = config.readSettings();
|
|
281
|
+
if (settings?.projectId) {
|
|
282
|
+
projectId = settings.projectId;
|
|
283
|
+
}
|
|
284
|
+
} catch (e) {
|
|
285
|
+
// No settings file either
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (!projectId) {
|
|
289
|
+
console.error(
|
|
290
|
+
chalk.red("Error: No projectId found. Add it to reshot.config.json or run 'reshot auth' first.")
|
|
291
|
+
);
|
|
292
|
+
if (!noExit) process.exit(1);
|
|
293
|
+
return { success: false, error: "No projectId found" };
|
|
126
294
|
}
|
|
127
295
|
|
|
128
296
|
// Check authentication
|
|
@@ -133,13 +301,15 @@ async function pullCommand(options = {}) {
|
|
|
133
301
|
console.error(
|
|
134
302
|
chalk.red("Error: Not authenticated. Run 'reshot auth' first.")
|
|
135
303
|
);
|
|
136
|
-
process.exit(1);
|
|
304
|
+
if (!noExit) process.exit(1);
|
|
305
|
+
return { success: false, error: "Not authenticated" };
|
|
137
306
|
}
|
|
138
307
|
if (!settings?.apiKey) {
|
|
139
308
|
console.error(
|
|
140
309
|
chalk.red("Error: Not authenticated. Run 'reshot auth' first.")
|
|
141
310
|
);
|
|
142
|
-
process.exit(1);
|
|
311
|
+
if (!noExit) process.exit(1);
|
|
312
|
+
return { success: false, error: "Not authenticated" };
|
|
143
313
|
}
|
|
144
314
|
|
|
145
315
|
if (settings.projectId && projectId !== settings.projectId) {
|
|
@@ -167,11 +337,16 @@ async function pullCommand(options = {}) {
|
|
|
167
337
|
|
|
168
338
|
if (!response || !response.assets) {
|
|
169
339
|
console.error(chalk.red("Error: Invalid response from API"));
|
|
170
|
-
process.exit(1);
|
|
340
|
+
if (!noExit) process.exit(1);
|
|
341
|
+
return { success: false, error: "Invalid response from API" };
|
|
171
342
|
}
|
|
172
343
|
|
|
173
|
-
const
|
|
174
|
-
const
|
|
344
|
+
const normalization = normalizeAssetMap(response.assets);
|
|
345
|
+
const normalizedAssets = normalization.assets;
|
|
346
|
+
validateAssetMap(normalizedAssets);
|
|
347
|
+
|
|
348
|
+
const { meta } = response;
|
|
349
|
+
const assetCount = Object.values(normalizedAssets).reduce(
|
|
175
350
|
(count, group) => count + Object.keys(group).length,
|
|
176
351
|
0
|
|
177
352
|
);
|
|
@@ -211,7 +386,7 @@ async function pullCommand(options = {}) {
|
|
|
211
386
|
|
|
212
387
|
switch (format) {
|
|
213
388
|
case "ts":
|
|
214
|
-
content = generateTypeScript(
|
|
389
|
+
content = generateTypeScript(normalizedAssets, full);
|
|
215
390
|
contentType = "TypeScript";
|
|
216
391
|
break;
|
|
217
392
|
|
|
@@ -220,9 +395,28 @@ async function pullCommand(options = {}) {
|
|
|
220
395
|
const headers = ["Key", "Group", "Name", "Context", "Type", "Unbreakable_URL", "Width", "Height"];
|
|
221
396
|
const rows = [headers.join(",")];
|
|
222
397
|
|
|
223
|
-
for (const [group, visuals] of Object.entries(
|
|
398
|
+
for (const [group, visuals] of Object.entries(normalizedAssets)) {
|
|
224
399
|
for (const [visualKey, variants] of Object.entries(visuals)) {
|
|
225
400
|
for (const [variant, asset] of Object.entries(variants)) {
|
|
401
|
+
if (Array.isArray(asset.steps)) {
|
|
402
|
+
for (const step of asset.steps) {
|
|
403
|
+
const type = step.type.split("/")[0];
|
|
404
|
+
rows.push(
|
|
405
|
+
[
|
|
406
|
+
`${group}/${visualKey}/${step.step}`,
|
|
407
|
+
group,
|
|
408
|
+
visualKey,
|
|
409
|
+
variant,
|
|
410
|
+
type,
|
|
411
|
+
step.src,
|
|
412
|
+
step.width || "",
|
|
413
|
+
step.height || "",
|
|
414
|
+
].join(",")
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
|
|
226
420
|
const type = asset.type.split("/")[0]; // 'image' or 'video'
|
|
227
421
|
rows.push(
|
|
228
422
|
[
|
|
@@ -246,7 +440,7 @@ async function pullCommand(options = {}) {
|
|
|
246
440
|
|
|
247
441
|
case "json":
|
|
248
442
|
default:
|
|
249
|
-
content = JSON.stringify({ meta, assets }, null, 2);
|
|
443
|
+
content = JSON.stringify({ meta, assets: normalizedAssets }, null, 2);
|
|
250
444
|
contentType = "JSON";
|
|
251
445
|
break;
|
|
252
446
|
}
|
|
@@ -309,6 +503,15 @@ async function pullCommand(options = {}) {
|
|
|
309
503
|
console.log(chalk.gray(" This ensures your build always uses the latest approved visuals."));
|
|
310
504
|
console.log(chalk.gray(" If a visual is deleted, your build will fail (preventing broken images).\n"));
|
|
311
505
|
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
assetCount,
|
|
509
|
+
outputPath: path.resolve(outputPath),
|
|
510
|
+
normalizedAssets,
|
|
511
|
+
normalizationRepairs: normalization.repairs,
|
|
512
|
+
meta,
|
|
513
|
+
};
|
|
514
|
+
|
|
312
515
|
} catch (error) {
|
|
313
516
|
if (config.isAuthError(error)) {
|
|
314
517
|
console.error(
|
|
@@ -320,7 +523,10 @@ async function pullCommand(options = {}) {
|
|
|
320
523
|
console.error(chalk.gray(error.stack));
|
|
321
524
|
}
|
|
322
525
|
}
|
|
323
|
-
|
|
526
|
+
if (!noExit) {
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
return { success: false, error: error.message };
|
|
324
530
|
}
|
|
325
531
|
}
|
|
326
532
|
|
|
@@ -6,6 +6,7 @@ const chalk = require("chalk");
|
|
|
6
6
|
const fs = require("fs-extra");
|
|
7
7
|
const path = require("path");
|
|
8
8
|
const config = require("../lib/config");
|
|
9
|
+
const { normalizeConfigContract } = require("../lib/target-contract");
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Detect if this is a Git repository and if it's GitHub
|
|
@@ -60,6 +61,94 @@ function detectPlaywright() {
|
|
|
60
61
|
return { hasPlaywright: false, configFile: null };
|
|
61
62
|
}
|
|
62
63
|
|
|
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;
|
|
70
|
+
|
|
71
|
+
if (targetTier === "certified") {
|
|
72
|
+
return "certified-target";
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (targetTier === "candidate") {
|
|
76
|
+
return "candidate-target";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return useCloud ? "cloud-connected" : "local-only";
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getRecommendedLocalServerCommand(normalizedConfig) {
|
|
83
|
+
const explicitCommand = normalizedConfig?.target?.supportedLocalCommand;
|
|
84
|
+
if (explicitCommand) {
|
|
85
|
+
return explicitCommand;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (normalizedConfig?.target?.tier === "certified") {
|
|
89
|
+
return "npm run build && npm run start";
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getNextRecommendedCommand(mode, useCloud) {
|
|
96
|
+
if (mode === "certified-target" || mode === "candidate-target") {
|
|
97
|
+
return "reshot doctor target";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return useCloud ? "reshot publish" : "reshot run";
|
|
101
|
+
}
|
|
102
|
+
|
|
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;
|
|
150
|
+
}
|
|
151
|
+
|
|
63
152
|
/**
|
|
64
153
|
* Main setup wizard
|
|
65
154
|
*/
|
|
@@ -152,7 +241,7 @@ async function setupWizard(options = {}) {
|
|
|
152
241
|
if (action === "reauth") {
|
|
153
242
|
console.log(chalk.gray("\nOpening browser for authentication...\n"));
|
|
154
243
|
const authCommand = require("./auth");
|
|
155
|
-
await authCommand();
|
|
244
|
+
const authResult = await authCommand();
|
|
156
245
|
|
|
157
246
|
// Re-read settings after auth
|
|
158
247
|
try {
|
|
@@ -172,6 +261,9 @@ async function setupWizard(options = {}) {
|
|
|
172
261
|
chalk.green("\n✔ Connected to"),
|
|
173
262
|
chalk.cyan(existingSettings.projectName || existingSettings.projectId),
|
|
174
263
|
);
|
|
264
|
+
if (authResult?.mode) {
|
|
265
|
+
console.log(chalk.gray(` Mode: ${authResult.mode}`));
|
|
266
|
+
}
|
|
175
267
|
console.log(
|
|
176
268
|
chalk.gray("\nRun"),
|
|
177
269
|
chalk.cyan("reshot studio"),
|
|
@@ -187,7 +279,7 @@ async function setupWizard(options = {}) {
|
|
|
187
279
|
// STEP 1: Platform Connection
|
|
188
280
|
// ========================================
|
|
189
281
|
if (!isAlreadyAuthed && !offline) {
|
|
190
|
-
|
|
282
|
+
console.log(chalk.cyan("\n━━━ Step 1: Choose Your Lane ━━━\n"));
|
|
191
283
|
|
|
192
284
|
const { connectToCloud } = await inquirer.prompt([
|
|
193
285
|
{
|
|
@@ -196,11 +288,11 @@ async function setupWizard(options = {}) {
|
|
|
196
288
|
message: "How would you like to use Reshot?",
|
|
197
289
|
choices: [
|
|
198
290
|
{
|
|
199
|
-
name: `${chalk.cyan("
|
|
291
|
+
name: `${chalk.cyan("Set up hosted pipeline")} - Connect to Reshot Cloud for hosted assets, review workflows, and team collaboration`,
|
|
200
292
|
value: true,
|
|
201
293
|
},
|
|
202
294
|
{
|
|
203
|
-
name: `${chalk.gray("
|
|
295
|
+
name: `${chalk.gray("Start locally")} - Get a first capture working on your machine before you add hosted delivery`,
|
|
204
296
|
value: false,
|
|
205
297
|
},
|
|
206
298
|
],
|
|
@@ -211,7 +303,7 @@ async function setupWizard(options = {}) {
|
|
|
211
303
|
console.log(chalk.gray("\nOpening browser for authentication...\n"));
|
|
212
304
|
|
|
213
305
|
const authCommand = require("./auth");
|
|
214
|
-
await authCommand();
|
|
306
|
+
const authResult = await authCommand();
|
|
215
307
|
|
|
216
308
|
// Re-read settings after auth
|
|
217
309
|
try {
|
|
@@ -231,6 +323,9 @@ async function setupWizard(options = {}) {
|
|
|
231
323
|
chalk.green("\n✔ Connected to"),
|
|
232
324
|
chalk.cyan(existingSettings.projectName || existingSettings.projectId),
|
|
233
325
|
);
|
|
326
|
+
if (authResult?.mode) {
|
|
327
|
+
console.log(chalk.gray(` Mode: ${authResult.mode}`));
|
|
328
|
+
}
|
|
234
329
|
} else {
|
|
235
330
|
console.log(chalk.gray("Running in offline mode — no cloud sync."));
|
|
236
331
|
}
|
|
@@ -240,11 +335,16 @@ async function setupWizard(options = {}) {
|
|
|
240
335
|
|
|
241
336
|
const useCloud = isAlreadyAuthed;
|
|
242
337
|
const projectId = existingSettings?.projectId;
|
|
338
|
+
const normalizedSetupConfig = normalizeConfigContract(
|
|
339
|
+
existingConfig && typeof existingConfig === "object" ? existingConfig : {},
|
|
340
|
+
);
|
|
341
|
+
const supportedLocalCommand =
|
|
342
|
+
getRecommendedLocalServerCommand(normalizedSetupConfig);
|
|
243
343
|
|
|
244
344
|
// ========================================
|
|
245
|
-
// STEP 2:
|
|
345
|
+
// STEP 2: Project Defaults
|
|
246
346
|
// ========================================
|
|
247
|
-
console.log(chalk.cyan("\n━━━
|
|
347
|
+
console.log(chalk.cyan("\n━━━ Step 2: Project Defaults ━━━\n"));
|
|
248
348
|
|
|
249
349
|
let traceDir = "./test-results";
|
|
250
350
|
|
|
@@ -253,24 +353,25 @@ async function setupWizard(options = {}) {
|
|
|
253
353
|
if (playwrightInfo.configFile) {
|
|
254
354
|
console.log(chalk.gray(` Config: ${playwrightInfo.configFile}`));
|
|
255
355
|
}
|
|
356
|
+
const { customTraceDir } = await inquirer.prompt([
|
|
357
|
+
{
|
|
358
|
+
type: "input",
|
|
359
|
+
name: "customTraceDir",
|
|
360
|
+
message: "Playwright test-results directory:",
|
|
361
|
+
default: "./test-results",
|
|
362
|
+
},
|
|
363
|
+
]);
|
|
364
|
+
|
|
365
|
+
traceDir = customTraceDir;
|
|
256
366
|
} else {
|
|
257
|
-
console.log(chalk.
|
|
367
|
+
console.log(chalk.green("✔ No Playwright setup detected"));
|
|
258
368
|
console.log(
|
|
259
|
-
chalk.gray(
|
|
369
|
+
chalk.gray(
|
|
370
|
+
" That is okay for the local-first workflow. You can define or record scenarios and run them directly.",
|
|
371
|
+
),
|
|
260
372
|
);
|
|
261
373
|
}
|
|
262
374
|
|
|
263
|
-
const { customTraceDir } = await inquirer.prompt([
|
|
264
|
-
{
|
|
265
|
-
type: "input",
|
|
266
|
-
name: "customTraceDir",
|
|
267
|
-
message: "Playwright test-results directory:",
|
|
268
|
-
default: "./test-results",
|
|
269
|
-
},
|
|
270
|
-
]);
|
|
271
|
-
|
|
272
|
-
traceDir = customTraceDir;
|
|
273
|
-
|
|
274
375
|
// ========================================
|
|
275
376
|
// Generate Configuration
|
|
276
377
|
// ========================================
|
|
@@ -311,6 +412,25 @@ async function setupWizard(options = {}) {
|
|
|
311
412
|
config.writeConfig(newConfig);
|
|
312
413
|
console.log(chalk.green("✔ Created reshot.config.json"));
|
|
313
414
|
|
|
415
|
+
const combinedConfig =
|
|
416
|
+
existingConfig && typeof existingConfig === "object"
|
|
417
|
+
? { ...existingConfig, ...newConfig }
|
|
418
|
+
: newConfig;
|
|
419
|
+
const normalizedCombinedConfig = normalizeConfigContract(combinedConfig);
|
|
420
|
+
const setupMode = detectSetupMode(combinedConfig, useCloud);
|
|
421
|
+
const finalSupportedLocalCommand =
|
|
422
|
+
getRecommendedLocalServerCommand(normalizedCombinedConfig) ||
|
|
423
|
+
supportedLocalCommand;
|
|
424
|
+
const reportPath = writeSetupReport({
|
|
425
|
+
mode: setupMode,
|
|
426
|
+
useCloud,
|
|
427
|
+
projectId,
|
|
428
|
+
projectName: existingSettings?.projectName,
|
|
429
|
+
configCreated: true,
|
|
430
|
+
playwrightDetected: playwrightInfo.hasPlaywright,
|
|
431
|
+
supportedLocalCommand: finalSupportedLocalCommand,
|
|
432
|
+
});
|
|
433
|
+
|
|
314
434
|
// ========================================
|
|
315
435
|
// Success & Next Steps
|
|
316
436
|
// ========================================
|
|
@@ -318,21 +438,59 @@ async function setupWizard(options = {}) {
|
|
|
318
438
|
console.log(chalk.green.bold("✔ Reshot setup complete!"));
|
|
319
439
|
console.log(chalk.green("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n"));
|
|
320
440
|
|
|
321
|
-
console.log(chalk.cyan("
|
|
441
|
+
console.log(chalk.cyan("Supported launch path:\n"));
|
|
442
|
+
console.log(
|
|
443
|
+
` ${chalk.gray("Run your target app with a production-like local server before capture.")}`,
|
|
444
|
+
);
|
|
445
|
+
console.log(
|
|
446
|
+
` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
|
|
447
|
+
);
|
|
448
|
+
console.log(
|
|
449
|
+
` ${chalk.gray("Unsupported for launch reliability:")} ${chalk.yellow("next dev")}\n`,
|
|
450
|
+
);
|
|
451
|
+
|
|
452
|
+
console.log(chalk.cyan("Setup mode:\n"));
|
|
453
|
+
console.log(` ${chalk.green(setupMode)}\n`);
|
|
322
454
|
|
|
455
|
+
console.log(chalk.cyan("Next steps:\n"));
|
|
456
|
+
console.log(
|
|
457
|
+
` 1. ${chalk.gray("Review")} ${chalk.cyan("reshot.config.json")} ${chalk.gray("and add your first scenario or recording")}`,
|
|
458
|
+
);
|
|
323
459
|
console.log(
|
|
324
|
-
`
|
|
460
|
+
` 2. ${chalk.gray("Start your app in the supported environment:")}`,
|
|
325
461
|
);
|
|
326
462
|
console.log(
|
|
327
|
-
`
|
|
463
|
+
` ${chalk.cyan(finalSupportedLocalCommand || "npm run build && npm run start")}`,
|
|
328
464
|
);
|
|
329
|
-
console.log(`
|
|
465
|
+
console.log(` 3. ${chalk.gray("Generate your first local capture:")}`);
|
|
466
|
+
console.log(` ${chalk.cyan("reshot run")}`);
|
|
330
467
|
|
|
331
|
-
|
|
332
|
-
|
|
468
|
+
if (useCloud) {
|
|
469
|
+
console.log(
|
|
470
|
+
`\n 4. ${chalk.gray("Upgrade to hosted assets when you are ready:")}`,
|
|
471
|
+
);
|
|
472
|
+
console.log(` ${chalk.cyan("reshot publish")}`);
|
|
473
|
+
}
|
|
333
474
|
|
|
334
|
-
|
|
335
|
-
|
|
475
|
+
if (setupMode === "certified-target" || setupMode === "candidate-target") {
|
|
476
|
+
console.log(
|
|
477
|
+
`\n 5. ${chalk.gray("Use advanced target checks when you need them:")}`,
|
|
478
|
+
);
|
|
479
|
+
console.log(` ${chalk.cyan("reshot doctor target")}`);
|
|
480
|
+
console.log(` ${chalk.cyan("reshot verify publish")}`);
|
|
481
|
+
if (setupMode === "certified-target") {
|
|
482
|
+
console.log(` ${chalk.cyan("reshot certify")}`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
console.log(`\n ${chalk.gray("Open Studio to inspect output locally:")}`);
|
|
487
|
+
console.log(` ${chalk.cyan("reshot studio")}`);
|
|
488
|
+
console.log(
|
|
489
|
+
`\n ${chalk.gray("Supported environments guide:")} ${chalk.cyan("https://reshot.dev/docs/cli/getting-started/supported-environments")}`,
|
|
490
|
+
);
|
|
491
|
+
console.log(
|
|
492
|
+
`\n ${chalk.gray("Setup report written to:")} ${chalk.cyan(path.relative(process.cwd(), reportPath))}\n`,
|
|
493
|
+
);
|
|
336
494
|
|
|
337
495
|
// Offer to launch studio
|
|
338
496
|
const { launchStudio } = await inquirer.prompt([
|
|
@@ -340,7 +498,7 @@ async function setupWizard(options = {}) {
|
|
|
340
498
|
type: "confirm",
|
|
341
499
|
name: "launchStudio",
|
|
342
500
|
message: "Launch Reshot Studio now?",
|
|
343
|
-
default:
|
|
501
|
+
default: false,
|
|
344
502
|
},
|
|
345
503
|
]);
|
|
346
504
|
|
package/src/commands/setup.js
CHANGED
|
@@ -156,9 +156,9 @@ async function setupCommand(options = {}) {
|
|
|
156
156
|
await uiCommand({ open: true });
|
|
157
157
|
} else {
|
|
158
158
|
console.log(chalk.gray("Next steps:"));
|
|
159
|
-
console.log(chalk.gray(" • Run"), chalk.cyan("reshot
|
|
160
|
-
console.log(chalk.gray(" • Run"), chalk.cyan("reshot
|
|
161
|
-
console.log(chalk.gray(" • Run"), chalk.cyan("reshot
|
|
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
162
|
}
|
|
163
163
|
}
|
|
164
164
|
|