@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/lib/config.js
CHANGED
|
@@ -21,6 +21,11 @@ const {
|
|
|
21
21
|
getConfigDefaults,
|
|
22
22
|
validateCaptureRequirements,
|
|
23
23
|
} = require("./standalone-mode");
|
|
24
|
+
const {
|
|
25
|
+
normalizeConfigContract,
|
|
26
|
+
validateNormalizedConfig,
|
|
27
|
+
getCertifiedScenarioKeys,
|
|
28
|
+
} = require("./target-contract");
|
|
24
29
|
|
|
25
30
|
const SETTINGS_DIR = ".reshot";
|
|
26
31
|
const SETTINGS_PATH = path.join(process.cwd(), SETTINGS_DIR, "settings.json");
|
|
@@ -279,13 +284,19 @@ function readConfig() {
|
|
|
279
284
|
);
|
|
280
285
|
}
|
|
281
286
|
|
|
282
|
-
const
|
|
287
|
+
const rawConfig = fs.readJSONSync(CONFIG_PATH);
|
|
288
|
+
const config = normalizeConfigContract(rawConfig);
|
|
283
289
|
|
|
284
290
|
// Validate required fields
|
|
285
291
|
if (!config.scenarios || !Array.isArray(config.scenarios)) {
|
|
286
292
|
throw new Error('Config must have a "scenarios" array');
|
|
287
293
|
}
|
|
288
294
|
|
|
295
|
+
const targetValidation = validateNormalizedConfig(config);
|
|
296
|
+
if (!targetValidation.valid) {
|
|
297
|
+
throw new Error(targetValidation.errors.join("\n"));
|
|
298
|
+
}
|
|
299
|
+
|
|
289
300
|
const validActions = [
|
|
290
301
|
"click",
|
|
291
302
|
"type",
|
|
@@ -485,7 +496,7 @@ function readConfigLenient() {
|
|
|
485
496
|
);
|
|
486
497
|
}
|
|
487
498
|
|
|
488
|
-
return fs.readJSONSync(CONFIG_PATH);
|
|
499
|
+
return normalizeConfigContract(fs.readJSONSync(CONFIG_PATH));
|
|
489
500
|
}
|
|
490
501
|
|
|
491
502
|
/**
|
|
@@ -493,7 +504,7 @@ function readConfigLenient() {
|
|
|
493
504
|
* @param {Object} config - Config object to write
|
|
494
505
|
*/
|
|
495
506
|
function writeConfig(config) {
|
|
496
|
-
fs.writeJSONSync(CONFIG_PATH, config, { spaces: 2 });
|
|
507
|
+
fs.writeJSONSync(CONFIG_PATH, normalizeConfigContract(config), { spaces: 2 });
|
|
497
508
|
}
|
|
498
509
|
|
|
499
510
|
/**
|
|
@@ -1168,6 +1179,8 @@ module.exports = {
|
|
|
1168
1179
|
validateConfig,
|
|
1169
1180
|
// Lenient config read (no scenario validation)
|
|
1170
1181
|
readConfigLenient,
|
|
1182
|
+
// Certified target contract helpers
|
|
1183
|
+
getCertifiedScenarioKeys,
|
|
1171
1184
|
// Paths
|
|
1172
1185
|
SETTINGS_PATH,
|
|
1173
1186
|
SETTINGS_DIR,
|
package/src/lib/record-cdp.js
CHANGED
|
@@ -462,10 +462,24 @@ async function autoSyncSessionFromCDP(outputPath = null, logger = null) {
|
|
|
462
462
|
const fs = require("fs-extra");
|
|
463
463
|
const path = require("path");
|
|
464
464
|
const os = require("os");
|
|
465
|
-
|
|
465
|
+
|
|
466
|
+
// Allow disabling CDP sync via env var (useful when session was created programmatically)
|
|
467
|
+
if (process.env.RESHOT_SKIP_CDP_SYNC === "1") {
|
|
468
|
+
return { synced: false, reason: "disabled" };
|
|
469
|
+
}
|
|
470
|
+
|
|
466
471
|
const sessionPath = outputPath || path.join(os.homedir(), ".reshot", "session-state.json");
|
|
467
|
-
|
|
472
|
+
|
|
468
473
|
try {
|
|
474
|
+
// Skip sync if cached session is very recent (likely just generated programmatically)
|
|
475
|
+
if (fs.existsSync(sessionPath)) {
|
|
476
|
+
const cachedAge = Date.now() - fs.statSync(sessionPath).mtimeMs;
|
|
477
|
+
if (cachedAge < 5 * 60 * 1000) {
|
|
478
|
+
log(chalk.gray(" → Cached session is fresh (<5min), skipping CDP sync"));
|
|
479
|
+
return { synced: false, reason: "cached_fresh" };
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
469
483
|
// Step 1: Check if CDP endpoint is available (quietly)
|
|
470
484
|
const endpointCheck = await checkCdpEndpoint("localhost", 9222);
|
|
471
485
|
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const TARGET_TIERS = new Set(["certified", "candidate", "custom"]);
|
|
4
|
+
const TARGET_AUTH_MODES = new Set(["public", "fixture", "live-auth"]);
|
|
5
|
+
const SCENARIO_CAPTURE_CLASSES = new Set([
|
|
6
|
+
"public",
|
|
7
|
+
"fixture-auth",
|
|
8
|
+
"live-auth",
|
|
9
|
+
]);
|
|
10
|
+
const PUBLISH_POLICIES = new Set(["required", "optional"]);
|
|
11
|
+
|
|
12
|
+
function normalizeTargetTier(value) {
|
|
13
|
+
const normalized = String(value || "custom").trim().toLowerCase();
|
|
14
|
+
return TARGET_TIERS.has(normalized) ? normalized : "custom";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function normalizeTargetAuthMode(value) {
|
|
18
|
+
const normalized = String(value || "public").trim().toLowerCase();
|
|
19
|
+
if (normalized === "fixture-auth") return "fixture";
|
|
20
|
+
if (normalized === "authenticated" || normalized === "auth") {
|
|
21
|
+
return "live-auth";
|
|
22
|
+
}
|
|
23
|
+
return TARGET_AUTH_MODES.has(normalized) ? normalized : "public";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function normalizeScenarioCaptureClass(value, requiresAuth, defaultAuthMode) {
|
|
27
|
+
const normalized = String(value || "").trim().toLowerCase();
|
|
28
|
+
|
|
29
|
+
if (!normalized) {
|
|
30
|
+
if (requiresAuth) {
|
|
31
|
+
return defaultAuthMode === "fixture" ? "fixture-auth" : "live-auth";
|
|
32
|
+
}
|
|
33
|
+
return "public";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (
|
|
37
|
+
normalized === "fixture" ||
|
|
38
|
+
normalized === "docs-fixture" ||
|
|
39
|
+
normalized === "fixture-auth"
|
|
40
|
+
) {
|
|
41
|
+
return "fixture-auth";
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (
|
|
45
|
+
normalized === "live-auth" ||
|
|
46
|
+
normalized === "auth" ||
|
|
47
|
+
normalized === "authenticated"
|
|
48
|
+
) {
|
|
49
|
+
return "live-auth";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
normalized === "public" ||
|
|
54
|
+
normalized === "public-docs" ||
|
|
55
|
+
normalized === "public-explorer"
|
|
56
|
+
) {
|
|
57
|
+
return "public";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return requiresAuth
|
|
61
|
+
? defaultAuthMode === "fixture"
|
|
62
|
+
? "fixture-auth"
|
|
63
|
+
: "live-auth"
|
|
64
|
+
: "public";
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function normalizeReadyContract(scenario) {
|
|
68
|
+
const ready = scenario.ready && typeof scenario.ready === "object"
|
|
69
|
+
? scenario.ready
|
|
70
|
+
: {};
|
|
71
|
+
const waitForReady =
|
|
72
|
+
scenario.waitForReady && typeof scenario.waitForReady === "object"
|
|
73
|
+
? scenario.waitForReady
|
|
74
|
+
: {};
|
|
75
|
+
|
|
76
|
+
const selector =
|
|
77
|
+
ready.selector ||
|
|
78
|
+
scenario.readySelector ||
|
|
79
|
+
waitForReady.selector ||
|
|
80
|
+
null;
|
|
81
|
+
const expression =
|
|
82
|
+
ready.expression ||
|
|
83
|
+
scenario.readyExpression ||
|
|
84
|
+
waitForReady.expression ||
|
|
85
|
+
null;
|
|
86
|
+
const timeout =
|
|
87
|
+
ready.timeout ||
|
|
88
|
+
waitForReady.timeout ||
|
|
89
|
+
scenario.readyTimeout ||
|
|
90
|
+
null;
|
|
91
|
+
|
|
92
|
+
if (!selector && !expression && !timeout) {
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
...(selector ? { selector } : {}),
|
|
98
|
+
...(expression ? { expression } : {}),
|
|
99
|
+
...(timeout ? { timeout } : {}),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function normalizeStringArray(value) {
|
|
104
|
+
if (!Array.isArray(value)) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return value
|
|
109
|
+
.map((item) => String(item || "").trim())
|
|
110
|
+
.filter(Boolean);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function inferExpectedArtifacts(scenario) {
|
|
114
|
+
return (scenario.steps || [])
|
|
115
|
+
.filter((step) => step.action === "screenshot" && step.key)
|
|
116
|
+
.map((step) => String(step.key));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function normalizePublishPolicy(value) {
|
|
120
|
+
const normalized = String(value || "required").trim().toLowerCase();
|
|
121
|
+
return PUBLISH_POLICIES.has(normalized) ? normalized : "required";
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function normalizeScenarioContract(scenario, target) {
|
|
125
|
+
const defaultAuthMode = target?.defaultAuthMode || "public";
|
|
126
|
+
const captureClass = normalizeScenarioCaptureClass(
|
|
127
|
+
scenario.captureClass,
|
|
128
|
+
Boolean(scenario.requiresAuth),
|
|
129
|
+
defaultAuthMode,
|
|
130
|
+
);
|
|
131
|
+
const ready = normalizeReadyContract(scenario);
|
|
132
|
+
const requiredSelectors = normalizeStringArray(scenario.requiredSelectors);
|
|
133
|
+
const readySelector = ready?.selector || null;
|
|
134
|
+
const expectedArtifacts = normalizeStringArray(scenario.expectedArtifacts);
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
...scenario,
|
|
138
|
+
captureClass,
|
|
139
|
+
requiresAuth: captureClass !== "public",
|
|
140
|
+
ready,
|
|
141
|
+
readySelector: readySelector || undefined,
|
|
142
|
+
readyExpression: ready?.expression || undefined,
|
|
143
|
+
readyTimeout: ready?.timeout || scenario.readyTimeout,
|
|
144
|
+
waitForReady: ready || scenario.waitForReady || null,
|
|
145
|
+
requiredRoutes: normalizeStringArray(
|
|
146
|
+
scenario.requiredRoutes && scenario.requiredRoutes.length > 0
|
|
147
|
+
? scenario.requiredRoutes
|
|
148
|
+
: scenario.url
|
|
149
|
+
? [scenario.url]
|
|
150
|
+
: [],
|
|
151
|
+
),
|
|
152
|
+
requiredSelectors:
|
|
153
|
+
requiredSelectors.length > 0
|
|
154
|
+
? requiredSelectors
|
|
155
|
+
: readySelector
|
|
156
|
+
? [readySelector]
|
|
157
|
+
: [],
|
|
158
|
+
needsWorkspaceInjection:
|
|
159
|
+
scenario.needsWorkspaceInjection !== undefined
|
|
160
|
+
? Boolean(scenario.needsWorkspaceInjection)
|
|
161
|
+
: captureClass !== "public",
|
|
162
|
+
expectedArtifacts:
|
|
163
|
+
expectedArtifacts.length > 0 ? expectedArtifacts : inferExpectedArtifacts(scenario),
|
|
164
|
+
publishPolicy: normalizePublishPolicy(scenario.publishPolicy),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function normalizeTargetBlock(config) {
|
|
169
|
+
const raw = config.target && typeof config.target === "object"
|
|
170
|
+
? config.target
|
|
171
|
+
: {};
|
|
172
|
+
|
|
173
|
+
const fixture =
|
|
174
|
+
raw.fixture && typeof raw.fixture === "object" ? raw.fixture : {};
|
|
175
|
+
|
|
176
|
+
return {
|
|
177
|
+
key: String(raw.key || config.name || config.projectId || "local-target"),
|
|
178
|
+
displayName: String(raw.displayName || raw.key || config.name || "Local Target"),
|
|
179
|
+
tier: normalizeTargetTier(raw.tier),
|
|
180
|
+
owner: String(raw.owner || "local"),
|
|
181
|
+
baseUrl: String(raw.baseUrl || config.baseUrl || "").replace(/\/+$/, ""),
|
|
182
|
+
captureSafe: Boolean(raw.captureSafe),
|
|
183
|
+
defaultAuthMode: normalizeTargetAuthMode(raw.defaultAuthMode),
|
|
184
|
+
supportedLocalCommand: String(
|
|
185
|
+
raw.supportedLocalCommand ||
|
|
186
|
+
raw.localServerCommand ||
|
|
187
|
+
raw.startCommand ||
|
|
188
|
+
"",
|
|
189
|
+
).trim() || null,
|
|
190
|
+
fixture:
|
|
191
|
+
fixture.command || fixture.script || fixture.healthUrl
|
|
192
|
+
? {
|
|
193
|
+
...(fixture.command ? { command: String(fixture.command) } : {}),
|
|
194
|
+
...(fixture.script ? { script: String(fixture.script) } : {}),
|
|
195
|
+
...(fixture.healthUrl ? { healthUrl: String(fixture.healthUrl) } : {}),
|
|
196
|
+
}
|
|
197
|
+
: null,
|
|
198
|
+
requiredEnv: normalizeStringArray(raw.requiredEnv),
|
|
199
|
+
certificationScenarioKeys: normalizeStringArray(
|
|
200
|
+
raw.certificationScenarioKeys || raw.certifiedScenarios || [],
|
|
201
|
+
),
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function normalizeConfigContract(config) {
|
|
206
|
+
const target = normalizeTargetBlock(config);
|
|
207
|
+
const scenarios = Array.isArray(config.scenarios)
|
|
208
|
+
? config.scenarios.map((scenario) => normalizeScenarioContract(scenario, target))
|
|
209
|
+
: [];
|
|
210
|
+
|
|
211
|
+
return {
|
|
212
|
+
...config,
|
|
213
|
+
target,
|
|
214
|
+
scenarios,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function validateNormalizedConfig(config) {
|
|
219
|
+
const errors = [];
|
|
220
|
+
|
|
221
|
+
if (!config.target || typeof config.target !== "object") {
|
|
222
|
+
return {
|
|
223
|
+
valid: true,
|
|
224
|
+
errors,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!TARGET_TIERS.has(config.target.tier)) {
|
|
229
|
+
errors.push(`Invalid target tier "${config.target.tier}"`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (!TARGET_AUTH_MODES.has(config.target.defaultAuthMode)) {
|
|
233
|
+
errors.push(`Invalid target.defaultAuthMode "${config.target.defaultAuthMode}"`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
for (const scenario of config.scenarios || []) {
|
|
237
|
+
if (!SCENARIO_CAPTURE_CLASSES.has(scenario.captureClass)) {
|
|
238
|
+
errors.push(
|
|
239
|
+
`Scenario "${scenario.key}" has invalid captureClass "${scenario.captureClass}"`,
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
if (!PUBLISH_POLICIES.has(scenario.publishPolicy)) {
|
|
243
|
+
errors.push(
|
|
244
|
+
`Scenario "${scenario.key}" has invalid publishPolicy "${scenario.publishPolicy}"`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return {
|
|
250
|
+
valid: errors.length === 0,
|
|
251
|
+
errors,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
function getCertifiedScenarioKeys(config, explicitScenarioKeys = null) {
|
|
256
|
+
if (Array.isArray(explicitScenarioKeys) && explicitScenarioKeys.length > 0) {
|
|
257
|
+
return explicitScenarioKeys;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const configured = normalizeStringArray(config?.target?.certificationScenarioKeys);
|
|
261
|
+
if (configured.length > 0) {
|
|
262
|
+
return configured;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return (config?.scenarios || []).map((scenario) => scenario.key);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
module.exports = {
|
|
269
|
+
TARGET_TIERS,
|
|
270
|
+
TARGET_AUTH_MODES,
|
|
271
|
+
SCENARIO_CAPTURE_CLASSES,
|
|
272
|
+
PUBLISH_POLICIES,
|
|
273
|
+
normalizeConfigContract,
|
|
274
|
+
normalizeTargetBlock,
|
|
275
|
+
normalizeScenarioContract,
|
|
276
|
+
validateNormalizedConfig,
|
|
277
|
+
getCertifiedScenarioKeys,
|
|
278
|
+
};
|