@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.
Files changed (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +138 -47
  3. package/package.json +27 -16
  4. package/src/commands/auth.js +159 -30
  5. package/src/commands/capture-dom.js +50 -0
  6. package/src/commands/certify.js +62 -0
  7. package/src/commands/compose.js +220 -0
  8. package/src/commands/doctor-release.js +74 -0
  9. package/src/commands/doctor-target.js +108 -0
  10. package/src/commands/drifts.js +16 -69
  11. package/src/commands/import-tests.js +13 -13
  12. package/src/commands/init.js +16 -277
  13. package/src/commands/publish.js +484 -257
  14. package/src/commands/pull.js +302 -35
  15. package/src/commands/refresh.js +166 -0
  16. package/src/commands/run.js +292 -12
  17. package/src/commands/setup-wizard.js +348 -496
  18. package/src/commands/status.js +334 -126
  19. package/src/commands/sync.js +28 -236
  20. package/src/commands/ui.js +1 -1
  21. package/src/commands/variation.js +194 -0
  22. package/src/commands/verify-publish.js +46 -0
  23. package/src/index.js +383 -118
  24. package/src/lib/api-client.js +172 -60
  25. package/src/lib/auto-update/refresh.js +598 -0
  26. package/src/lib/auto-update/scene-runtime.compose.tsx +73 -0
  27. package/src/lib/auto-update/spec.js +89 -0
  28. package/src/lib/capture-engine.js +179 -9
  29. package/src/lib/capture-script-runner.js +639 -214
  30. package/src/lib/certification.js +887 -0
  31. package/src/lib/compose-context.js +156 -0
  32. package/src/lib/compose-pack.js +42 -0
  33. package/src/lib/compose-runtime.js +34 -0
  34. package/src/lib/compose-upload.js +142 -0
  35. package/src/lib/config.js +186 -81
  36. package/src/lib/dom-capture.js +64 -0
  37. package/src/lib/ensure-browser.js +147 -0
  38. package/src/lib/output-path-template.js +3 -3
  39. package/src/lib/record-cdp.js +288 -16
  40. package/src/lib/record-clip.js +83 -3
  41. package/src/lib/record-config.js +1 -5
  42. package/src/lib/release-doctor.js +321 -0
  43. package/src/lib/resolve-targets.js +60 -0
  44. package/src/lib/run-manifest.js +148 -0
  45. package/src/lib/standalone-mode.js +1 -1
  46. package/src/lib/storage-providers.js +5 -5
  47. package/src/lib/style-engine.js +5 -5
  48. package/src/lib/target-contract.js +292 -0
  49. package/src/lib/ui-api-helpers.js +118 -0
  50. package/src/lib/ui-api.js +31 -824
  51. package/src/lib/ui-asset-cleanup.js +62 -0
  52. package/src/lib/ui-output-versions.js +165 -0
  53. package/src/lib/ui-recorder-routes.js +341 -0
  54. package/src/lib/ui-scenario-metadata.js +161 -0
  55. package/vendor/compose/dist/auto-update.cjs +5544 -0
  56. package/vendor/compose/dist/auto-update.mjs +5518 -0
  57. package/vendor/compose/dist/capture.cjs +1450 -0
  58. package/vendor/compose/dist/capture.mjs +1416 -0
  59. package/vendor/compose/dist/eligibility.cjs +5331 -0
  60. package/vendor/compose/dist/eligibility.mjs +5313 -0
  61. package/vendor/compose/dist/index.cjs +2046 -0
  62. package/vendor/compose/dist/index.mjs +1997 -0
  63. package/vendor/compose/dist/jsx-dev-runtime.cjs +55 -0
  64. package/vendor/compose/dist/jsx-dev-runtime.mjs +27 -0
  65. package/vendor/compose/dist/jsx-runtime.cjs +58 -0
  66. package/vendor/compose/dist/jsx-runtime.mjs +31 -0
  67. package/vendor/compose/dist/render.cjs +558 -0
  68. package/vendor/compose/dist/render.mjs +515 -0
  69. package/vendor/compose/dist/verify-cli.cjs +3806 -0
  70. package/vendor/compose/dist/verify-cli.mjs +3812 -0
  71. package/vendor/compose/dist/verify.cjs +3880 -0
  72. package/vendor/compose/dist/verify.mjs +3858 -0
  73. package/web/manager/dist/assets/index-D0S2otug.js +507 -0
  74. package/web/manager/dist/index.html +1 -1
  75. package/src/commands/ci-run.js +0 -123
  76. package/src/commands/ci-setup.js +0 -288
  77. package/src/commands/ingest.js +0 -458
  78. package/src/commands/setup.js +0 -137
  79. package/src/commands/validate-docs.js +0 -529
  80. package/src/lib/playwright-runner.js +0 -252
  81. package/web/manager/dist/assets/index--ZgioErz.js +0 -507
@@ -0,0 +1,292 @@
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 normalizeOneOrManyStrings(value) {
114
+ if (Array.isArray(value)) {
115
+ return normalizeStringArray(value);
116
+ }
117
+
118
+ const normalized = String(value || "").trim();
119
+ return normalized ? [normalized] : [];
120
+ }
121
+
122
+ function inferExpectedArtifacts(scenario) {
123
+ return (scenario.steps || [])
124
+ .filter((step) => step.action === "screenshot" && step.key)
125
+ .map((step) => String(step.key));
126
+ }
127
+
128
+ function normalizePublishPolicy(value) {
129
+ const normalized = String(value || "required").trim().toLowerCase();
130
+ return PUBLISH_POLICIES.has(normalized) ? normalized : "required";
131
+ }
132
+
133
+ function normalizeScenarioContract(scenario, target) {
134
+ const defaultAuthMode = target?.defaultAuthMode || "public";
135
+ const captureClass = normalizeScenarioCaptureClass(
136
+ scenario.captureClass,
137
+ Boolean(scenario.requiresAuth),
138
+ defaultAuthMode,
139
+ );
140
+ const ready = normalizeReadyContract(scenario);
141
+ const requiredSelectors = normalizeStringArray(scenario.requiredSelectors);
142
+ const readySelector = ready?.selector || null;
143
+ const expectedArtifacts = normalizeStringArray(scenario.expectedArtifacts);
144
+
145
+ return {
146
+ ...scenario,
147
+ captureClass,
148
+ requiresAuth: captureClass !== "public",
149
+ ready,
150
+ readySelector: readySelector || undefined,
151
+ readyExpression: ready?.expression || undefined,
152
+ readyTimeout: ready?.timeout || scenario.readyTimeout,
153
+ waitForReady: ready || scenario.waitForReady || null,
154
+ requiredRoutes: normalizeStringArray(
155
+ scenario.requiredRoutes && scenario.requiredRoutes.length > 0
156
+ ? scenario.requiredRoutes
157
+ : scenario.url
158
+ ? [scenario.url]
159
+ : [],
160
+ ),
161
+ requiredSelectors:
162
+ requiredSelectors.length > 0
163
+ ? requiredSelectors
164
+ : readySelector
165
+ ? [readySelector]
166
+ : [],
167
+ needsWorkspaceInjection:
168
+ scenario.needsWorkspaceInjection !== undefined
169
+ ? Boolean(scenario.needsWorkspaceInjection)
170
+ : captureClass !== "public",
171
+ expectedArtifacts:
172
+ expectedArtifacts.length > 0 ? expectedArtifacts : inferExpectedArtifacts(scenario),
173
+ publishPolicy: normalizePublishPolicy(scenario.publishPolicy),
174
+ };
175
+ }
176
+
177
+ function normalizeTargetBlock(config) {
178
+ const raw = config.target && typeof config.target === "object"
179
+ ? config.target
180
+ : {};
181
+
182
+ const fixture =
183
+ raw.fixture && typeof raw.fixture === "object" ? raw.fixture : {};
184
+ const authPreflightUrls = normalizeOneOrManyStrings(
185
+ raw.authPreflightUrls || raw.authPreflightUrl,
186
+ );
187
+
188
+ return {
189
+ key: String(raw.key || config.name || config.projectId || "local-target"),
190
+ displayName: String(raw.displayName || raw.key || config.name || "Local Target"),
191
+ tier: normalizeTargetTier(raw.tier),
192
+ owner: String(raw.owner || "local"),
193
+ baseUrl: String(raw.baseUrl || config.baseUrl || "").replace(/\/+$/, ""),
194
+ captureSafe: Boolean(raw.captureSafe),
195
+ defaultAuthMode: normalizeTargetAuthMode(raw.defaultAuthMode),
196
+ supportedLocalCommand: String(
197
+ raw.supportedLocalCommand ||
198
+ raw.localServerCommand ||
199
+ raw.startCommand ||
200
+ "",
201
+ ).trim() || null,
202
+ fixture:
203
+ fixture.command || fixture.script || fixture.healthUrl
204
+ ? {
205
+ ...(fixture.command ? { command: String(fixture.command) } : {}),
206
+ ...(fixture.script ? { script: String(fixture.script) } : {}),
207
+ ...(fixture.healthUrl ? { healthUrl: String(fixture.healthUrl) } : {}),
208
+ }
209
+ : null,
210
+ requiredEnv: normalizeStringArray(raw.requiredEnv),
211
+ certificationScenarioKeys: normalizeStringArray(
212
+ raw.certificationScenarioKeys || raw.certifiedScenarios || [],
213
+ ),
214
+ authPreflightUrl: authPreflightUrls[0] || null,
215
+ authPreflightUrls,
216
+ };
217
+ }
218
+
219
+ function normalizeConfigContract(config) {
220
+ const target = normalizeTargetBlock(config);
221
+ const scenarios = Array.isArray(config.scenarios)
222
+ ? config.scenarios.map((scenario) => normalizeScenarioContract(scenario, target))
223
+ : [];
224
+
225
+ return {
226
+ ...config,
227
+ target,
228
+ scenarios,
229
+ };
230
+ }
231
+
232
+ function validateNormalizedConfig(config) {
233
+ const errors = [];
234
+
235
+ if (!config.target || typeof config.target !== "object") {
236
+ return {
237
+ valid: true,
238
+ errors,
239
+ };
240
+ }
241
+
242
+ if (!TARGET_TIERS.has(config.target.tier)) {
243
+ errors.push(`Invalid target tier "${config.target.tier}"`);
244
+ }
245
+
246
+ if (!TARGET_AUTH_MODES.has(config.target.defaultAuthMode)) {
247
+ errors.push(`Invalid target.defaultAuthMode "${config.target.defaultAuthMode}"`);
248
+ }
249
+
250
+ for (const scenario of config.scenarios || []) {
251
+ if (!SCENARIO_CAPTURE_CLASSES.has(scenario.captureClass)) {
252
+ errors.push(
253
+ `Scenario "${scenario.key}" has invalid captureClass "${scenario.captureClass}"`,
254
+ );
255
+ }
256
+ if (!PUBLISH_POLICIES.has(scenario.publishPolicy)) {
257
+ errors.push(
258
+ `Scenario "${scenario.key}" has invalid publishPolicy "${scenario.publishPolicy}"`,
259
+ );
260
+ }
261
+ }
262
+
263
+ return {
264
+ valid: errors.length === 0,
265
+ errors,
266
+ };
267
+ }
268
+
269
+ function getCertifiedScenarioKeys(config, explicitScenarioKeys = null) {
270
+ if (Array.isArray(explicitScenarioKeys) && explicitScenarioKeys.length > 0) {
271
+ return explicitScenarioKeys;
272
+ }
273
+
274
+ const configured = normalizeStringArray(config?.target?.certificationScenarioKeys);
275
+ if (configured.length > 0) {
276
+ return configured;
277
+ }
278
+
279
+ return (config?.scenarios || []).map((scenario) => scenario.key);
280
+ }
281
+
282
+ module.exports = {
283
+ TARGET_TIERS,
284
+ TARGET_AUTH_MODES,
285
+ SCENARIO_CAPTURE_CLASSES,
286
+ PUBLISH_POLICIES,
287
+ normalizeConfigContract,
288
+ normalizeTargetBlock,
289
+ normalizeScenarioContract,
290
+ validateNormalizedConfig,
291
+ getCertifiedScenarioKeys,
292
+ };
@@ -0,0 +1,118 @@
1
+ const path = require("path");
2
+ const config = require("./config");
3
+
4
+ /**
5
+ * Get the platform URL from settings, falling back to production.
6
+ * @param {Object} settings - CLI settings object
7
+ * @returns {string} Platform URL
8
+ */
9
+ function getPlatformUrl(settings) {
10
+ if (settings?.platformUrl) {
11
+ return settings.platformUrl;
12
+ }
13
+
14
+ const envUrl = process.env.RESHOT_API_BASE_URL;
15
+ if (envUrl) {
16
+ return envUrl.replace(/\/api\/?$/, "");
17
+ }
18
+
19
+ return "https://reshot.dev";
20
+ }
21
+
22
+ /**
23
+ * Handle API errors and detect if re-auth is needed.
24
+ * @param {Error} error - The error from API call
25
+ * @param {Object} res - Express response object
26
+ * @returns {Object|null} Response if error was handled, null otherwise
27
+ */
28
+ function handleApiError(error, res) {
29
+ if (config.isAuthError(error)) {
30
+ const errorMsg =
31
+ error.response?.data?.error ||
32
+ error.message ||
33
+ "API key is invalid or expired";
34
+ return res.status(401).json(config.createAuthErrorResponse(errorMsg));
35
+ }
36
+
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * Generate all possible variant combinations from dimensions.
42
+ * @param {Object} dimensions - Variant dimensions config
43
+ * @param {string[]} dimensionKeys - Which dimensions to include
44
+ * @returns {Array<Object>} Array of variant objects
45
+ */
46
+ function generateVariantCombinations(dimensions, dimensionKeys = []) {
47
+ if (!dimensions || dimensionKeys.length === 0) {
48
+ return [];
49
+ }
50
+
51
+ const dimensionOptions = dimensionKeys
52
+ .map((key) => {
53
+ const dim = dimensions[key];
54
+ if (!dim?.options) return [];
55
+ return Object.keys(dim.options).map((optKey) => ({
56
+ dimension: key,
57
+ option: optKey,
58
+ }));
59
+ })
60
+ .filter((opts) => opts.length > 0);
61
+
62
+ if (dimensionOptions.length === 0) {
63
+ return [];
64
+ }
65
+
66
+ const combinations = cartesian(...dimensionOptions);
67
+
68
+ return combinations.map((combo) => {
69
+ const variant = {};
70
+ for (const { dimension, option } of combo) {
71
+ variant[dimension] = option;
72
+ }
73
+ return variant;
74
+ });
75
+ }
76
+
77
+ function cartesian(...arrays) {
78
+ return arrays.reduce(
79
+ (acc, arr) => acc.flatMap((combo) => arr.map((item) => [...combo, item])),
80
+ [[]],
81
+ );
82
+ }
83
+
84
+ /**
85
+ * Validate a path segment to prevent directory traversal attacks.
86
+ * @param {string} segment - Path segment to validate
87
+ * @returns {boolean} True if safe, false if potentially malicious
88
+ */
89
+ function isValidPathSegment(segment) {
90
+ if (!segment || typeof segment !== "string") return false;
91
+ if (segment === "." || segment === "..") return false;
92
+ if (segment.includes("/") || segment.includes("\\")) return false;
93
+ if (segment.includes("\0")) return false;
94
+ return true;
95
+ }
96
+
97
+ /**
98
+ * Validate that a resolved path stays within the expected base directory.
99
+ * @param {string} resolvedPath - Fully resolved path
100
+ * @param {string} baseDir - Expected base directory
101
+ * @returns {boolean} True if path is within base, false otherwise
102
+ */
103
+ function isPathWithinBase(resolvedPath, baseDir) {
104
+ const normalizedBase = path.resolve(baseDir);
105
+ const normalizedPath = path.resolve(resolvedPath);
106
+ return (
107
+ normalizedPath.startsWith(normalizedBase + path.sep) ||
108
+ normalizedPath === normalizedBase
109
+ );
110
+ }
111
+
112
+ module.exports = {
113
+ generateVariantCombinations,
114
+ getPlatformUrl,
115
+ handleApiError,
116
+ isPathWithinBase,
117
+ isValidPathSegment,
118
+ };