@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.21
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/LICENSE +1 -1
- package/README.md +138 -47
- package/package.json +27 -16
- package/src/commands/auth.js +159 -30
- package/src/commands/capture-dom.js +50 -0
- package/src/commands/certify.js +62 -0
- package/src/commands/compose.js +220 -0
- package/src/commands/doctor-release.js +74 -0
- package/src/commands/doctor-target.js +108 -0
- package/src/commands/drifts.js +16 -69
- package/src/commands/import-tests.js +13 -13
- package/src/commands/init.js +16 -277
- package/src/commands/publish.js +484 -257
- package/src/commands/pull.js +302 -35
- package/src/commands/refresh.js +166 -0
- package/src/commands/run.js +292 -12
- package/src/commands/setup-wizard.js +348 -496
- package/src/commands/status.js +334 -126
- package/src/commands/sync.js +28 -236
- package/src/commands/ui.js +1 -1
- package/src/commands/variation.js +194 -0
- package/src/commands/verify-publish.js +46 -0
- package/src/index.js +383 -118
- package/src/lib/api-client.js +172 -60
- 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 +179 -9
- package/src/lib/capture-script-runner.js +639 -214
- package/src/lib/certification.js +887 -0
- 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 +186 -81
- package/src/lib/dom-capture.js +64 -0
- package/src/lib/ensure-browser.js +147 -0
- package/src/lib/output-path-template.js +3 -3
- package/src/lib/record-cdp.js +288 -16
- package/src/lib/record-clip.js +83 -3
- package/src/lib/record-config.js +1 -5
- package/src/lib/release-doctor.js +321 -0
- package/src/lib/resolve-targets.js +60 -0
- package/src/lib/run-manifest.js +148 -0
- package/src/lib/standalone-mode.js +1 -1
- package/src/lib/storage-providers.js +5 -5
- package/src/lib/style-engine.js +5 -5
- package/src/lib/target-contract.js +292 -0
- package/src/lib/ui-api-helpers.js +118 -0
- package/src/lib/ui-api.js +31 -824
- 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-D0S2otug.js +507 -0
- package/web/manager/dist/index.html +1 -1
- package/src/commands/ci-run.js +0 -123
- package/src/commands/ci-setup.js +0 -288
- package/src/commands/ingest.js +0 -458
- package/src/commands/setup.js +0 -137
- package/src/commands/validate-docs.js +0 -529
- package/src/lib/playwright-runner.js +0 -252
- package/web/manager/dist/assets/index--ZgioErz.js +0 -507
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const config = require("./config");
|
|
6
|
+
const { buildRunPreflightReport } = require("../commands/run");
|
|
7
|
+
const { runDoctorTarget } = require("./certification");
|
|
8
|
+
|
|
9
|
+
const REPORT_DIR = path.join(process.cwd(), ".reshot", "reports");
|
|
10
|
+
const RELEASE_DOCTOR_REPORT_PATH = path.join(REPORT_DIR, "release-doctor.json");
|
|
11
|
+
const DEFAULT_DOCS_ASSET_MAP_MAX_AGE_DAYS = 30;
|
|
12
|
+
const RESHOT_CDN_ORIGIN = "https://cdn.reshot.dev/";
|
|
13
|
+
|
|
14
|
+
function ensureReportDir() {
|
|
15
|
+
fs.ensureDirSync(REPORT_DIR);
|
|
16
|
+
return REPORT_DIR;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseScenarioKeys(value) {
|
|
20
|
+
if (!value) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return value.map((entry) => String(entry || "").trim()).filter(Boolean);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return String(value)
|
|
29
|
+
.split(",")
|
|
30
|
+
.map((entry) => entry.trim())
|
|
31
|
+
.filter(Boolean);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function resolveDocsAssetMapMaxAgeDays() {
|
|
35
|
+
const parsed = Number.parseInt(
|
|
36
|
+
process.env.RESHOT_DOCS_ASSET_MAP_MAX_AGE_DAYS || String(DEFAULT_DOCS_ASSET_MAP_MAX_AGE_DAYS),
|
|
37
|
+
10,
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
return Number.isFinite(parsed) && parsed >= 0
|
|
41
|
+
? parsed
|
|
42
|
+
: DEFAULT_DOCS_ASSET_MAP_MAX_AGE_DAYS;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function createDocsHealth(options = {}) {
|
|
46
|
+
return {
|
|
47
|
+
checked: false,
|
|
48
|
+
skipped: false,
|
|
49
|
+
ok: false,
|
|
50
|
+
path: null,
|
|
51
|
+
exportedAt: null,
|
|
52
|
+
ageDays: null,
|
|
53
|
+
maxAgeDays: resolveDocsAssetMapMaxAgeDays(),
|
|
54
|
+
summary: {
|
|
55
|
+
visuals: 0,
|
|
56
|
+
assets: 0,
|
|
57
|
+
steps: 0,
|
|
58
|
+
},
|
|
59
|
+
issues: [],
|
|
60
|
+
...options,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function createDocsIssueCollector(summary, issues, groupKey, visualKey, contextKey) {
|
|
65
|
+
return {
|
|
66
|
+
checkEntry(entry) {
|
|
67
|
+
summary.assets += 1;
|
|
68
|
+
|
|
69
|
+
if (!entry || typeof entry !== "object") {
|
|
70
|
+
issues.push(`Asset map entry \"${groupKey}/${visualKey}/${contextKey}\" is invalid.`);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!entry.type) {
|
|
75
|
+
issues.push(`Asset map entry \"${groupKey}/${visualKey}/${contextKey}\" is missing type.`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (!entry.alt) {
|
|
79
|
+
issues.push(`Asset map entry \"${groupKey}/${visualKey}/${contextKey}\" is missing alt text.`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (entry.src && !String(entry.src).startsWith(RESHOT_CDN_ORIGIN)) {
|
|
83
|
+
issues.push(
|
|
84
|
+
`Asset map entry \"${groupKey}/${visualKey}/${contextKey}\" does not use a direct cdn.reshot.dev URL.`,
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (entry.poster && !String(entry.poster).startsWith(RESHOT_CDN_ORIGIN)) {
|
|
89
|
+
issues.push(
|
|
90
|
+
`Asset map poster \"${groupKey}/${visualKey}/${contextKey}\" does not use a direct cdn.reshot.dev URL.`,
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const step of entry.steps || []) {
|
|
95
|
+
summary.steps += 1;
|
|
96
|
+
if (!step?.src || !String(step.src).startsWith(RESHOT_CDN_ORIGIN)) {
|
|
97
|
+
issues.push(
|
|
98
|
+
`Asset map step \"${groupKey}/${visualKey}/${contextKey}/${step?.step || "unknown"}\" does not use a direct cdn.reshot.dev URL.`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function inspectDocsAssetMap(assetMap, options = {}) {
|
|
107
|
+
const maxAgeDays = Number.isFinite(options.maxAgeDays)
|
|
108
|
+
? options.maxAgeDays
|
|
109
|
+
: resolveDocsAssetMapMaxAgeDays();
|
|
110
|
+
const now = options.now || new Date();
|
|
111
|
+
const issues = [];
|
|
112
|
+
const summary = {
|
|
113
|
+
visuals: 0,
|
|
114
|
+
assets: 0,
|
|
115
|
+
steps: 0,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
if (!assetMap || typeof assetMap !== "object") {
|
|
119
|
+
return createDocsHealth({
|
|
120
|
+
checked: true,
|
|
121
|
+
ok: false,
|
|
122
|
+
maxAgeDays,
|
|
123
|
+
issues: ["Asset map is missing or invalid."],
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const exportedAt = typeof assetMap.meta?.exportedAt === "string"
|
|
128
|
+
? assetMap.meta.exportedAt
|
|
129
|
+
: null;
|
|
130
|
+
const exportedAtMs = exportedAt ? Date.parse(exportedAt) : Number.NaN;
|
|
131
|
+
let ageDays = null;
|
|
132
|
+
|
|
133
|
+
if (!assetMap.meta?.projectId) {
|
|
134
|
+
issues.push("Asset map meta.projectId is missing.");
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!exportedAt || Number.isNaN(exportedAtMs)) {
|
|
138
|
+
issues.push("Asset map meta.exportedAt is missing or invalid.");
|
|
139
|
+
} else {
|
|
140
|
+
ageDays = Math.max(0, Math.floor((now.getTime() - exportedAtMs) / 86_400_000));
|
|
141
|
+
if (ageDays > maxAgeDays) {
|
|
142
|
+
issues.push(`Asset map is stale: exported ${ageDays} day(s) ago (max ${maxAgeDays}).`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const groups = assetMap.assets && typeof assetMap.assets === "object"
|
|
147
|
+
? Object.entries(assetMap.assets)
|
|
148
|
+
: [];
|
|
149
|
+
if (groups.length === 0) {
|
|
150
|
+
issues.push("Asset map has no assets.");
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const [groupKey, visuals] of groups) {
|
|
154
|
+
if (!visuals || typeof visuals !== "object") {
|
|
155
|
+
issues.push(`Asset map group \"${groupKey}\" is invalid.`);
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [visualKey, contexts] of Object.entries(visuals)) {
|
|
160
|
+
summary.visuals += 1;
|
|
161
|
+
|
|
162
|
+
if (!contexts || typeof contexts !== "object") {
|
|
163
|
+
issues.push(`Asset map visual \"${groupKey}/${visualKey}\" is invalid.`);
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (const [contextKey, entry] of Object.entries(contexts)) {
|
|
168
|
+
createDocsIssueCollector(summary, issues, groupKey, visualKey, contextKey)
|
|
169
|
+
.checkEntry(entry);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (
|
|
175
|
+
typeof assetMap.meta?.totalVisuals === "number" &&
|
|
176
|
+
assetMap.meta.totalVisuals !== summary.visuals
|
|
177
|
+
) {
|
|
178
|
+
issues.push(`Asset map meta.totalVisuals=${assetMap.meta.totalVisuals} but counted ${summary.visuals}.`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (
|
|
182
|
+
typeof assetMap.meta?.totalAssets === "number" &&
|
|
183
|
+
assetMap.meta.totalAssets !== summary.assets
|
|
184
|
+
) {
|
|
185
|
+
issues.push(`Asset map meta.totalAssets=${assetMap.meta.totalAssets} but counted ${summary.assets}.`);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (
|
|
189
|
+
typeof assetMap.meta?.totalSteps === "number" &&
|
|
190
|
+
assetMap.meta.totalSteps !== summary.steps
|
|
191
|
+
) {
|
|
192
|
+
issues.push(`Asset map meta.totalSteps=${assetMap.meta.totalSteps} but counted ${summary.steps}.`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return createDocsHealth({
|
|
196
|
+
checked: true,
|
|
197
|
+
ok: issues.length === 0,
|
|
198
|
+
exportedAt,
|
|
199
|
+
ageDays,
|
|
200
|
+
maxAgeDays,
|
|
201
|
+
summary,
|
|
202
|
+
issues,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveDocsAssetMapCandidates(cwd = process.cwd()) {
|
|
207
|
+
return [
|
|
208
|
+
path.join(cwd, "src", "data", "reshot-assets.json"),
|
|
209
|
+
path.join(cwd, "app", "src", "data", "reshot-assets.json"),
|
|
210
|
+
];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function inspectDocsAssetMapFile(options = {}) {
|
|
214
|
+
const cwd = options.cwd || process.cwd();
|
|
215
|
+
const candidates = resolveDocsAssetMapCandidates(cwd);
|
|
216
|
+
const assetMapPath = candidates.find((candidate) => fs.existsSync(candidate));
|
|
217
|
+
|
|
218
|
+
if (!assetMapPath) {
|
|
219
|
+
return createDocsHealth({
|
|
220
|
+
checked: false,
|
|
221
|
+
skipped: true,
|
|
222
|
+
ok: true,
|
|
223
|
+
issues: ["No docs asset map found; skipping docs asset verification."],
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const assetMap = fs.readJsonSync(assetMapPath);
|
|
229
|
+
return {
|
|
230
|
+
...inspectDocsAssetMap(assetMap, options),
|
|
231
|
+
path: assetMapPath,
|
|
232
|
+
};
|
|
233
|
+
} catch (error) {
|
|
234
|
+
return createDocsHealth({
|
|
235
|
+
checked: true,
|
|
236
|
+
ok: false,
|
|
237
|
+
path: assetMapPath,
|
|
238
|
+
issues: [`Failed to read docs asset map: ${error.message}`],
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function runReleaseDoctor(options = {}) {
|
|
244
|
+
ensureReportDir();
|
|
245
|
+
const scenarioKeys = parseScenarioKeys(options.scenarioKeys || options.scenarios);
|
|
246
|
+
const docSyncConfig = config.readConfig();
|
|
247
|
+
const preflight = await buildRunPreflightReport(docSyncConfig, { scenarioKeys });
|
|
248
|
+
const targetDoctor = docSyncConfig.target?.tier === "certified"
|
|
249
|
+
? await runDoctorTarget({ scenarioKeys })
|
|
250
|
+
: {
|
|
251
|
+
skipped: true,
|
|
252
|
+
ok: true,
|
|
253
|
+
target: docSyncConfig.target,
|
|
254
|
+
summary: {
|
|
255
|
+
overallSeverity: "info",
|
|
256
|
+
blockingIssues: [],
|
|
257
|
+
advisories: [],
|
|
258
|
+
info: [{ message: "Target doctor skipped for non-certified target." }],
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
const docsAssetMap = inspectDocsAssetMapFile({ cwd: process.cwd() });
|
|
262
|
+
|
|
263
|
+
const blockingIssues = [];
|
|
264
|
+
const advisories = [];
|
|
265
|
+
|
|
266
|
+
for (const error of preflight.errors || []) {
|
|
267
|
+
blockingIssues.push({ scope: "run-preflight", message: error });
|
|
268
|
+
}
|
|
269
|
+
for (const warning of preflight.warnings || []) {
|
|
270
|
+
advisories.push({ scope: "run-preflight", message: warning });
|
|
271
|
+
}
|
|
272
|
+
for (const issue of targetDoctor.summary?.blockingIssues || []) {
|
|
273
|
+
blockingIssues.push({ scope: "target-doctor", ...issue });
|
|
274
|
+
}
|
|
275
|
+
for (const issue of targetDoctor.summary?.advisories || []) {
|
|
276
|
+
advisories.push({ scope: "target-doctor", ...issue });
|
|
277
|
+
}
|
|
278
|
+
// A stale/mismatched docs asset map (e.g. src/data/reshot-assets.json left
|
|
279
|
+
// behind by an earlier `reshot pull`) describes a generated artifact, not the
|
|
280
|
+
// config being published. It must NOT hard-block a publish of the current
|
|
281
|
+
// config — surface it as an advisory with a concrete remedy instead.
|
|
282
|
+
if (!docsAssetMap.skipped && !docsAssetMap.ok) {
|
|
283
|
+
const remedy =
|
|
284
|
+
docsAssetMap.path
|
|
285
|
+
? `Re-run \`reshot pull\` to regenerate it, or delete ${docsAssetMap.path}.`
|
|
286
|
+
: "Re-run `reshot pull` to regenerate it, or delete the stale src/data/reshot-assets.json.";
|
|
287
|
+
for (const issue of docsAssetMap.issues) {
|
|
288
|
+
advisories.push({ scope: "docs-asset-map", message: `${issue} ${remedy}` });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const ok = preflight.ok && targetDoctor.ok;
|
|
293
|
+
const report = {
|
|
294
|
+
type: "ReleaseDoctorReport",
|
|
295
|
+
stage: "doctor-release",
|
|
296
|
+
generatedAt: new Date().toISOString(),
|
|
297
|
+
ok,
|
|
298
|
+
scenarioKeys: scenarioKeys || null,
|
|
299
|
+
target: docSyncConfig.target || null,
|
|
300
|
+
runPreflight: preflight,
|
|
301
|
+
targetDoctor,
|
|
302
|
+
docsAssetMap,
|
|
303
|
+
summary: {
|
|
304
|
+
blockingIssues,
|
|
305
|
+
advisories,
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
|
|
309
|
+
await fs.writeJson(RELEASE_DOCTOR_REPORT_PATH, report, { spaces: 2 });
|
|
310
|
+
report.reportPath = RELEASE_DOCTOR_REPORT_PATH;
|
|
311
|
+
return report;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
module.exports = {
|
|
315
|
+
RELEASE_DOCTOR_REPORT_PATH,
|
|
316
|
+
parseScenarioKeys,
|
|
317
|
+
resolveDocsAssetMapCandidates,
|
|
318
|
+
inspectDocsAssetMap,
|
|
319
|
+
inspectDocsAssetMapFile,
|
|
320
|
+
runReleaseDoctor,
|
|
321
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// resolve-targets.js - Resolve compose target coordinates from a Playwright page
|
|
2
|
+
|
|
3
|
+
async function resolveTargets(page, targets = {}) {
|
|
4
|
+
const resolved = {};
|
|
5
|
+
|
|
6
|
+
for (const [name, config] of Object.entries(targets || {})) {
|
|
7
|
+
const target = normalizeTarget(config);
|
|
8
|
+
|
|
9
|
+
for (const step of target.navigate) {
|
|
10
|
+
if (step.clickText) {
|
|
11
|
+
await page.getByText(step.clickText, { exact: true }).first().click();
|
|
12
|
+
} else if (step.selector) {
|
|
13
|
+
await page.locator(step.selector).first().click();
|
|
14
|
+
}
|
|
15
|
+
if (step.waitMs) {
|
|
16
|
+
await page.waitForTimeout(step.waitMs);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const locator = page.locator(target.selector).first();
|
|
21
|
+
await locator.waitFor({ state: 'visible', timeout: target.timeoutMs });
|
|
22
|
+
const box = await locator.boundingBox();
|
|
23
|
+
if (!box) {
|
|
24
|
+
throw new Error(`Could not resolve target "${name}"`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
resolved[name] = {
|
|
28
|
+
x: Math.round(box.x),
|
|
29
|
+
y: Math.round(box.y),
|
|
30
|
+
w: Math.round(box.width),
|
|
31
|
+
h: Math.round(box.height),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return resolved;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeTarget(config) {
|
|
39
|
+
if (typeof config === 'string') {
|
|
40
|
+
return {
|
|
41
|
+
selector: config,
|
|
42
|
+
navigate: [],
|
|
43
|
+
timeoutMs: 10000,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!config || typeof config !== 'object' || !config.selector) {
|
|
48
|
+
throw new Error('Target config must be a selector string or an object with selector');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
selector: config.selector,
|
|
53
|
+
navigate: Array.isArray(config.navigate) ? config.navigate : [],
|
|
54
|
+
timeoutMs: config.timeoutMs || 10000,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = {
|
|
59
|
+
resolveTargets,
|
|
60
|
+
};
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs-extra");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const RUN_MANIFEST_DIR = path.join(
|
|
7
|
+
process.cwd(),
|
|
8
|
+
".reshot",
|
|
9
|
+
"manifests",
|
|
10
|
+
"runs",
|
|
11
|
+
);
|
|
12
|
+
const LATEST_RUN_MANIFEST_PATH = path.join(RUN_MANIFEST_DIR, "run-latest.json");
|
|
13
|
+
|
|
14
|
+
function ensureRunManifestDir() {
|
|
15
|
+
fs.ensureDirSync(RUN_MANIFEST_DIR);
|
|
16
|
+
return RUN_MANIFEST_DIR;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function normalizeScenarioResults(results = []) {
|
|
20
|
+
return results.map((result) => ({
|
|
21
|
+
key: result.key || result.scenario || null,
|
|
22
|
+
scenario: result.scenario || result.key || null,
|
|
23
|
+
success: result.success !== false,
|
|
24
|
+
timestamp: result.timestamp || null,
|
|
25
|
+
outputDir: result.outputDir || null,
|
|
26
|
+
variant: result.variant || null,
|
|
27
|
+
assetCount: Array.isArray(result.assets) ? result.assets.length : 0,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildRunManifest(payload = {}) {
|
|
32
|
+
const generatedAt = payload.generatedAt || new Date().toISOString();
|
|
33
|
+
return {
|
|
34
|
+
type: "ReshotRunManifest",
|
|
35
|
+
runId: payload.runId || generatedAt.replace(/[:.]/g, "-"),
|
|
36
|
+
generatedAt,
|
|
37
|
+
success: payload.success !== false,
|
|
38
|
+
outputBaseDir: payload.outputBaseDir || null,
|
|
39
|
+
selectedScenarioKeys: payload.selectedScenarioKeys || [],
|
|
40
|
+
diffEnabled: Boolean(payload.diffEnabled),
|
|
41
|
+
scenarios: normalizeScenarioResults(payload.scenarios || []),
|
|
42
|
+
preflight: payload.preflight || null,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function writeRunManifest(payload = {}) {
|
|
47
|
+
const manifest = buildRunManifest(payload);
|
|
48
|
+
ensureRunManifestDir();
|
|
49
|
+
const manifestPath = path.join(RUN_MANIFEST_DIR, `run-${manifest.runId}.json`);
|
|
50
|
+
fs.writeJsonSync(manifestPath, manifest, { spaces: 2 });
|
|
51
|
+
fs.writeJsonSync(LATEST_RUN_MANIFEST_PATH, manifest, { spaces: 2 });
|
|
52
|
+
return { manifest, manifestPath, latestPath: LATEST_RUN_MANIFEST_PATH };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function readRunManifest(manifestPath) {
|
|
56
|
+
if (!manifestPath || !fs.existsSync(manifestPath)) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return fs.readJsonSync(manifestPath);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function listRunManifestPaths() {
|
|
64
|
+
if (!fs.existsSync(RUN_MANIFEST_DIR)) {
|
|
65
|
+
return [];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return fs
|
|
69
|
+
.readdirSync(RUN_MANIFEST_DIR)
|
|
70
|
+
.filter(
|
|
71
|
+
(entry) =>
|
|
72
|
+
/^run-.*\.json$/.test(entry) && entry !== path.basename(LATEST_RUN_MANIFEST_PATH),
|
|
73
|
+
)
|
|
74
|
+
.map((entry) => path.join(RUN_MANIFEST_DIR, entry))
|
|
75
|
+
.sort((left, right) => right.localeCompare(left));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function getLatestSuccessfulRunManifest() {
|
|
79
|
+
const latest = readRunManifest(LATEST_RUN_MANIFEST_PATH);
|
|
80
|
+
if (latest?.success) {
|
|
81
|
+
return latest;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
for (const manifestPath of listRunManifestPaths()) {
|
|
85
|
+
const manifest = readRunManifest(manifestPath);
|
|
86
|
+
if (manifest?.success) {
|
|
87
|
+
return manifest;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Returns the latest run manifest that has at least one successful scenario,
|
|
96
|
+
* even if the overall run was not fully successful. This prevents falling back
|
|
97
|
+
* to stale manifests when only some scenarios failed.
|
|
98
|
+
*
|
|
99
|
+
* Returns { manifest, isFallback, isPartialSuccess } where:
|
|
100
|
+
* - isFallback: true if the returned manifest is NOT the latest run
|
|
101
|
+
* - isPartialSuccess: true if the manifest has both succeeded and failed scenarios
|
|
102
|
+
*/
|
|
103
|
+
function getLatestUsableRunManifest() {
|
|
104
|
+
const latest = readRunManifest(LATEST_RUN_MANIFEST_PATH);
|
|
105
|
+
|
|
106
|
+
if (latest) {
|
|
107
|
+
const successfulScenarios = (latest.scenarios || []).filter(
|
|
108
|
+
(s) => s.success !== false,
|
|
109
|
+
);
|
|
110
|
+
if (successfulScenarios.length > 0) {
|
|
111
|
+
return {
|
|
112
|
+
manifest: latest,
|
|
113
|
+
isFallback: false,
|
|
114
|
+
isPartialSuccess: !latest.success,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Latest has zero successful scenarios — search historical manifests
|
|
120
|
+
for (const manifestPath of listRunManifestPaths()) {
|
|
121
|
+
const manifest = readRunManifest(manifestPath);
|
|
122
|
+
if (!manifest) continue;
|
|
123
|
+
const successfulScenarios = (manifest.scenarios || []).filter(
|
|
124
|
+
(s) => s.success !== false,
|
|
125
|
+
);
|
|
126
|
+
if (successfulScenarios.length > 0) {
|
|
127
|
+
return {
|
|
128
|
+
manifest,
|
|
129
|
+
isFallback: true,
|
|
130
|
+
isPartialSuccess: !manifest.success,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
module.exports = {
|
|
139
|
+
RUN_MANIFEST_DIR,
|
|
140
|
+
LATEST_RUN_MANIFEST_PATH,
|
|
141
|
+
buildRunManifest,
|
|
142
|
+
writeRunManifest,
|
|
143
|
+
readRunManifest,
|
|
144
|
+
listRunManifestPaths,
|
|
145
|
+
getLatestSuccessfulRunManifest,
|
|
146
|
+
getLatestUsableRunManifest,
|
|
147
|
+
normalizeScenarioResults,
|
|
148
|
+
};
|
|
@@ -103,7 +103,7 @@ const DEFAULT_STANDALONE_SETTINGS = {
|
|
|
103
103
|
};
|
|
104
104
|
|
|
105
105
|
const SETTINGS_DIR = ".reshot";
|
|
106
|
-
const CONFIG_PATH = "
|
|
106
|
+
const CONFIG_PATH = "reshot.config.json";
|
|
107
107
|
const SETTINGS_PATH = path.join(SETTINGS_DIR, "settings.json");
|
|
108
108
|
|
|
109
109
|
/**
|
|
@@ -114,7 +114,7 @@ ${chalk.cyan('AWS S3 Setup:')}
|
|
|
114
114
|
${chalk.gray('export AWS_SECRET_ACCESS_KEY="your-secret-access-key"')}
|
|
115
115
|
${chalk.gray('export AWS_REGION="us-east-1" # optional, defaults to us-east-1')}
|
|
116
116
|
|
|
117
|
-
3. ${chalk.yellow('Update
|
|
117
|
+
3. ${chalk.yellow('Update reshot.config.json:')}
|
|
118
118
|
${chalk.gray(JSON.stringify({
|
|
119
119
|
storage: {
|
|
120
120
|
type: 's3',
|
|
@@ -143,7 +143,7 @@ ${chalk.cyan('Cloudflare R2 Setup:')}
|
|
|
143
143
|
${chalk.gray('export R2_ACCESS_KEY_ID="your-r2-access-key"')}
|
|
144
144
|
${chalk.gray('export R2_SECRET_ACCESS_KEY="your-r2-secret-key"')}
|
|
145
145
|
|
|
146
|
-
3. ${chalk.yellow('Update
|
|
146
|
+
3. ${chalk.yellow('Update reshot.config.json:')}
|
|
147
147
|
${chalk.gray(JSON.stringify({
|
|
148
148
|
storage: {
|
|
149
149
|
type: 'r2',
|
|
@@ -165,7 +165,7 @@ ${chalk.cyan('Local Storage Setup:')}
|
|
|
165
165
|
|
|
166
166
|
For local testing or self-hosted scenarios:
|
|
167
167
|
|
|
168
|
-
1. ${chalk.yellow('Update
|
|
168
|
+
1. ${chalk.yellow('Update reshot.config.json:')}
|
|
169
169
|
${chalk.gray(JSON.stringify({
|
|
170
170
|
storage: {
|
|
171
171
|
type: 'local',
|
|
@@ -191,7 +191,7 @@ Use Reshot for full governance features (review queue, version control, etc.):
|
|
|
191
191
|
1. ${chalk.yellow('Authenticate:')}
|
|
192
192
|
${chalk.gray('reshot auth')}
|
|
193
193
|
|
|
194
|
-
2. ${chalk.yellow('Or set environment variable
|
|
194
|
+
2. ${chalk.yellow('Or set environment variable:')}
|
|
195
195
|
${chalk.gray('export RESHOT_API_KEY="your-api-key"')}
|
|
196
196
|
|
|
197
197
|
3. ${chalk.yellow('Config (optional):')}
|
|
@@ -536,7 +536,7 @@ function createStorageProvider(config) {
|
|
|
536
536
|
|
|
537
537
|
/**
|
|
538
538
|
* Determine storage mode from config
|
|
539
|
-
* @param {object} docSyncConfig - The
|
|
539
|
+
* @param {object} docSyncConfig - The reshot.config.json content
|
|
540
540
|
* @returns {'platform'|'byos'}
|
|
541
541
|
*/
|
|
542
542
|
function getStorageMode(docSyncConfig) {
|
package/src/lib/style-engine.js
CHANGED
|
@@ -16,10 +16,10 @@ try {
|
|
|
16
16
|
* Default style configuration
|
|
17
17
|
*/
|
|
18
18
|
const DEFAULT_STYLE_CONFIG = {
|
|
19
|
-
enabled:
|
|
19
|
+
enabled: false,
|
|
20
20
|
frame: "none",
|
|
21
|
-
shadow: "
|
|
22
|
-
padding:
|
|
21
|
+
shadow: "none",
|
|
22
|
+
padding: 0,
|
|
23
23
|
background: "transparent",
|
|
24
24
|
borderRadius: 0,
|
|
25
25
|
};
|
|
@@ -479,8 +479,8 @@ async function applyStyle(inputBuffer, styleConfig, logger, dpr = 1) {
|
|
|
479
479
|
|
|
480
480
|
const {
|
|
481
481
|
frame = "none",
|
|
482
|
-
shadow = "
|
|
483
|
-
padding =
|
|
482
|
+
shadow = "none",
|
|
483
|
+
padding = 0,
|
|
484
484
|
background = "transparent",
|
|
485
485
|
borderRadius = 0,
|
|
486
486
|
} = styleConfig;
|