@reshotdev/screenshot 0.0.1-beta.1 → 0.0.1-beta.11
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 +65 -7
- package/package.json +9 -2
- package/src/commands/auth.js +108 -26
- package/src/commands/certify.js +62 -0
- package/src/commands/ci-run.js +57 -2
- package/src/commands/ci-setup.js +5 -5
- package/src/commands/doctor-release.js +67 -0
- package/src/commands/doctor-target.js +49 -0
- package/src/commands/drifts.js +5 -70
- package/src/commands/import-tests.js +13 -13
- package/src/commands/ingest.js +10 -10
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +204 -237
- package/src/commands/pull.js +253 -23
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +277 -499
- package/src/commands/setup.js +41 -13
- package/src/commands/status.js +313 -125
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +194 -94
- package/src/lib/api-client.js +121 -35
- package/src/lib/capture-engine.js +103 -7
- package/src/lib/capture-script-runner.js +359 -58
- package/src/lib/certification.js +865 -0
- package/src/lib/config.js +181 -76
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-config.js +1 -1
- package/src/lib/release-doctor.js +313 -0
- package/src/lib/run-manifest.js +103 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +4 -4
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api.js +6 -7
- package/web/manager/dist/assets/{index--ZgioErz.js → index-D2qqcFNN.js} +1 -1
- package/web/manager/dist/index.html +1 -1
- package/src/commands/validate-docs.js +0 -529
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"));
|
|
@@ -112,17 +267,30 @@ async function pullCommand(options = {}) {
|
|
|
112
267
|
projectConfig = config.readConfig();
|
|
113
268
|
} catch (e) {
|
|
114
269
|
console.error(
|
|
115
|
-
chalk.red("Error: No
|
|
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,28 @@ 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" };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (settings.projectId && projectId !== settings.projectId) {
|
|
316
|
+
console.warn(
|
|
317
|
+
chalk.yellow(
|
|
318
|
+
` ⚠ Project ID mismatch: config uses ${projectId}, but authenticated project is ${settings.projectId}.`
|
|
319
|
+
)
|
|
320
|
+
);
|
|
321
|
+
console.warn(
|
|
322
|
+
chalk.yellow(
|
|
323
|
+
` This will cause auth failures. Run 'reshot setup --force' to re-link.\n`
|
|
324
|
+
)
|
|
325
|
+
);
|
|
143
326
|
}
|
|
144
327
|
|
|
145
328
|
try {
|
|
@@ -154,16 +337,32 @@ async function pullCommand(options = {}) {
|
|
|
154
337
|
|
|
155
338
|
if (!response || !response.assets) {
|
|
156
339
|
console.error(chalk.red("Error: Invalid response from API"));
|
|
157
|
-
process.exit(1);
|
|
340
|
+
if (!noExit) process.exit(1);
|
|
341
|
+
return { success: false, error: "Invalid response from API" };
|
|
158
342
|
}
|
|
159
343
|
|
|
160
|
-
const
|
|
161
|
-
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(
|
|
162
350
|
(count, group) => count + Object.keys(group).length,
|
|
163
351
|
0
|
|
164
352
|
);
|
|
165
353
|
|
|
166
|
-
|
|
354
|
+
if (assetCount === 0) {
|
|
355
|
+
console.log(chalk.yellow(` ⚠ Fetched 0 visuals.\n`));
|
|
356
|
+
console.log(chalk.gray(` Possible reasons:`));
|
|
357
|
+
if (status === "approved") {
|
|
358
|
+
console.log(chalk.gray(` • No visuals approved yet. Try: reshot pull --status all`));
|
|
359
|
+
}
|
|
360
|
+
console.log(chalk.gray(` • No visuals published. Run: reshot publish`));
|
|
361
|
+
console.log(chalk.gray(` • Wrong project. Config uses: ${projectId}`));
|
|
362
|
+
console.log(chalk.gray(` • Check: ${settings.platformUrl || "https://reshot.dev"}\n`));
|
|
363
|
+
} else {
|
|
364
|
+
console.log(chalk.green(` ✓ Fetched ${assetCount} visuals\n`));
|
|
365
|
+
}
|
|
167
366
|
|
|
168
367
|
// Determine output path
|
|
169
368
|
let outputPath = output;
|
|
@@ -187,7 +386,7 @@ async function pullCommand(options = {}) {
|
|
|
187
386
|
|
|
188
387
|
switch (format) {
|
|
189
388
|
case "ts":
|
|
190
|
-
content = generateTypeScript(
|
|
389
|
+
content = generateTypeScript(normalizedAssets, full);
|
|
191
390
|
contentType = "TypeScript";
|
|
192
391
|
break;
|
|
193
392
|
|
|
@@ -196,9 +395,28 @@ async function pullCommand(options = {}) {
|
|
|
196
395
|
const headers = ["Key", "Group", "Name", "Context", "Type", "Unbreakable_URL", "Width", "Height"];
|
|
197
396
|
const rows = [headers.join(",")];
|
|
198
397
|
|
|
199
|
-
for (const [group, visuals] of Object.entries(
|
|
398
|
+
for (const [group, visuals] of Object.entries(normalizedAssets)) {
|
|
200
399
|
for (const [visualKey, variants] of Object.entries(visuals)) {
|
|
201
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
|
+
|
|
202
420
|
const type = asset.type.split("/")[0]; // 'image' or 'video'
|
|
203
421
|
rows.push(
|
|
204
422
|
[
|
|
@@ -222,7 +440,7 @@ async function pullCommand(options = {}) {
|
|
|
222
440
|
|
|
223
441
|
case "json":
|
|
224
442
|
default:
|
|
225
|
-
content = JSON.stringify({ meta, assets }, null, 2);
|
|
443
|
+
content = JSON.stringify({ meta, assets: normalizedAssets }, null, 2);
|
|
226
444
|
contentType = "JSON";
|
|
227
445
|
break;
|
|
228
446
|
}
|
|
@@ -285,6 +503,15 @@ async function pullCommand(options = {}) {
|
|
|
285
503
|
console.log(chalk.gray(" This ensures your build always uses the latest approved visuals."));
|
|
286
504
|
console.log(chalk.gray(" If a visual is deleted, your build will fail (preventing broken images).\n"));
|
|
287
505
|
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
assetCount,
|
|
509
|
+
outputPath: path.resolve(outputPath),
|
|
510
|
+
normalizedAssets,
|
|
511
|
+
normalizationRepairs: normalization.repairs,
|
|
512
|
+
meta,
|
|
513
|
+
};
|
|
514
|
+
|
|
288
515
|
} catch (error) {
|
|
289
516
|
if (config.isAuthError(error)) {
|
|
290
517
|
console.error(
|
|
@@ -296,7 +523,10 @@ async function pullCommand(options = {}) {
|
|
|
296
523
|
console.error(chalk.gray(error.stack));
|
|
297
524
|
}
|
|
298
525
|
}
|
|
299
|
-
|
|
526
|
+
if (!noExit) {
|
|
527
|
+
process.exit(1);
|
|
528
|
+
}
|
|
529
|
+
return { success: false, error: error.message };
|
|
300
530
|
}
|
|
301
531
|
}
|
|
302
532
|
|