@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.6

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.
@@ -16,7 +16,7 @@
16
16
  document.documentElement.style.colorScheme = isDark ? 'dark' : 'light';
17
17
  })();
18
18
  </script>
19
- <script type="module" crossorigin src="/assets/index--ZgioErz.js"></script>
19
+ <script type="module" crossorigin src="/assets/index-8H7P9ANi.js"></script>
20
20
  <link rel="stylesheet" crossorigin href="/assets/index-n468W0Wr.css">
21
21
  </head>
22
22
  <body>
@@ -1,529 +0,0 @@
1
- /**
2
- * validate-docs.js - Validate docsync configuration and documentation bindings
3
- *
4
- * Checks:
5
- * 1. Configuration file syntax and schema
6
- * 2. Scenario definitions and selectors
7
- * 3. Documentation file bindings (zombie links)
8
- * 4. Asset references and file integrity
9
- * 5. Variant/dimension configuration
10
- */
11
-
12
- const chalk = require("chalk");
13
- const fs = require("fs-extra");
14
- const path = require("path");
15
- const config = require("../lib/config");
16
- const glob = require("glob");
17
-
18
- /**
19
- * Validation issue severity
20
- */
21
- const Severity = {
22
- ERROR: "error",
23
- WARNING: "warning",
24
- INFO: "info",
25
- };
26
-
27
- /**
28
- * Validation result
29
- */
30
- class ValidationResult {
31
- constructor() {
32
- this.issues = [];
33
- this.stats = {
34
- scenarios: 0,
35
- steps: 0,
36
- assets: 0,
37
- bindings: 0,
38
- };
39
- }
40
-
41
- addIssue(severity, code, message, context = {}) {
42
- this.issues.push({
43
- severity,
44
- code,
45
- message,
46
- ...context,
47
- });
48
- }
49
-
50
- hasErrors() {
51
- return this.issues.some((i) => i.severity === Severity.ERROR);
52
- }
53
-
54
- hasWarnings() {
55
- return this.issues.some((i) => i.severity === Severity.WARNING);
56
- }
57
-
58
- getErrors() {
59
- return this.issues.filter((i) => i.severity === Severity.ERROR);
60
- }
61
-
62
- getWarnings() {
63
- return this.issues.filter((i) => i.severity === Severity.WARNING);
64
- }
65
- }
66
-
67
- /**
68
- * Validate configuration schema
69
- */
70
- function validateConfigSchema(docSyncConfig, result) {
71
- // Check required fields
72
- if (!docSyncConfig.project?.baseUrl) {
73
- result.addIssue(
74
- Severity.ERROR,
75
- "MISSING_BASE_URL",
76
- "Missing project.baseUrl in configuration"
77
- );
78
- }
79
-
80
- // Validate scenarios
81
- if (!docSyncConfig.scenarios || !Array.isArray(docSyncConfig.scenarios)) {
82
- result.addIssue(
83
- Severity.ERROR,
84
- "MISSING_SCENARIOS",
85
- "No scenarios array found in configuration"
86
- );
87
- return;
88
- }
89
-
90
- if (docSyncConfig.scenarios.length === 0) {
91
- result.addIssue(
92
- Severity.WARNING,
93
- "EMPTY_SCENARIOS",
94
- "Scenarios array is empty - nothing to capture"
95
- );
96
- }
97
-
98
- // Validate each scenario
99
- const seenKeys = new Set();
100
-
101
- for (const scenario of docSyncConfig.scenarios) {
102
- // Check for duplicate keys
103
- if (seenKeys.has(scenario.key)) {
104
- result.addIssue(
105
- Severity.ERROR,
106
- "DUPLICATE_SCENARIO_KEY",
107
- `Duplicate scenario key: ${scenario.key}`,
108
- { scenario: scenario.key }
109
- );
110
- }
111
- seenKeys.add(scenario.key);
112
-
113
- // Validate scenario structure
114
- if (!scenario.key) {
115
- result.addIssue(
116
- Severity.ERROR,
117
- "MISSING_SCENARIO_KEY",
118
- "Scenario missing required 'key' field"
119
- );
120
- }
121
-
122
- if (!scenario.name) {
123
- result.addIssue(
124
- Severity.WARNING,
125
- "MISSING_SCENARIO_NAME",
126
- `Scenario '${scenario.key}' missing 'name' field`,
127
- { scenario: scenario.key }
128
- );
129
- }
130
-
131
- // Validate steps
132
- if (!scenario.steps || !Array.isArray(scenario.steps)) {
133
- result.addIssue(
134
- Severity.WARNING,
135
- "MISSING_STEPS",
136
- `Scenario '${scenario.key}' has no steps defined`,
137
- { scenario: scenario.key }
138
- );
139
- continue;
140
- }
141
-
142
- result.stats.scenarios++;
143
-
144
- for (let i = 0; i < scenario.steps.length; i++) {
145
- const step = scenario.steps[i];
146
- validateStep(step, scenario.key, i, result);
147
- result.stats.steps++;
148
- }
149
- }
150
-
151
- // Validate dimensions
152
- if (docSyncConfig.dimensions) {
153
- validateDimensions(docSyncConfig.dimensions, result);
154
- }
155
- }
156
-
157
- /**
158
- * Validate a single step
159
- */
160
- function validateStep(step, scenarioKey, stepIndex, result) {
161
- // Check action
162
- const validActions = ["navigate", "click", "type", "hover", "scroll", "wait", "capture"];
163
- if (!step.action) {
164
- result.addIssue(
165
- Severity.ERROR,
166
- "MISSING_STEP_ACTION",
167
- `Step ${stepIndex + 1} in '${scenarioKey}' missing 'action' field`,
168
- { scenario: scenarioKey, step: stepIndex + 1 }
169
- );
170
- } else if (!validActions.includes(step.action)) {
171
- result.addIssue(
172
- Severity.WARNING,
173
- "UNKNOWN_STEP_ACTION",
174
- `Step ${stepIndex + 1} in '${scenarioKey}' has unknown action: ${step.action}`,
175
- { scenario: scenarioKey, step: stepIndex + 1, action: step.action }
176
- );
177
- }
178
-
179
- // Check selector for interactive actions
180
- const actionsRequiringSelector = ["click", "type", "hover"];
181
- if (actionsRequiringSelector.includes(step.action) && !step.selector) {
182
- result.addIssue(
183
- Severity.ERROR,
184
- "MISSING_STEP_SELECTOR",
185
- `Step ${stepIndex + 1} in '${scenarioKey}' (${step.action}) missing 'selector'`,
186
- { scenario: scenarioKey, step: stepIndex + 1, action: step.action }
187
- );
188
- }
189
-
190
- // Validate selector syntax (basic check)
191
- if (step.selector) {
192
- try {
193
- // Check for common selector issues
194
- if (step.selector.includes("{{") && !step.selector.includes("}}")) {
195
- result.addIssue(
196
- Severity.ERROR,
197
- "MALFORMED_SELECTOR_TEMPLATE",
198
- `Step ${stepIndex + 1} in '${scenarioKey}' has unclosed template in selector`,
199
- { scenario: scenarioKey, step: stepIndex + 1, selector: step.selector }
200
- );
201
- }
202
- } catch (e) {
203
- result.addIssue(
204
- Severity.ERROR,
205
- "INVALID_SELECTOR",
206
- `Step ${stepIndex + 1} in '${scenarioKey}' has invalid selector: ${e.message}`,
207
- { scenario: scenarioKey, step: stepIndex + 1, selector: step.selector }
208
- );
209
- }
210
- }
211
-
212
- // Check for navigate without URL
213
- if (step.action === "navigate" && !step.url) {
214
- result.addIssue(
215
- Severity.ERROR,
216
- "MISSING_NAVIGATE_URL",
217
- `Step ${stepIndex + 1} in '${scenarioKey}' (navigate) missing 'url'`,
218
- { scenario: scenarioKey, step: stepIndex + 1 }
219
- );
220
- }
221
-
222
- // Check for type without value
223
- if (step.action === "type" && !step.value && step.value !== "") {
224
- result.addIssue(
225
- Severity.WARNING,
226
- "MISSING_TYPE_VALUE",
227
- `Step ${stepIndex + 1} in '${scenarioKey}' (type) missing 'value'`,
228
- { scenario: scenarioKey, step: stepIndex + 1 }
229
- );
230
- }
231
-
232
- // Check capture with name
233
- if (step.action === "capture" && !step.name) {
234
- result.addIssue(
235
- Severity.INFO,
236
- "UNNAMED_CAPTURE",
237
- `Step ${stepIndex + 1} in '${scenarioKey}' capture without 'name' (will use step index)`,
238
- { scenario: scenarioKey, step: stepIndex + 1 }
239
- );
240
- }
241
- }
242
-
243
- /**
244
- * Validate dimensions configuration
245
- */
246
- function validateDimensions(dimensions, result) {
247
- for (const [key, dim] of Object.entries(dimensions)) {
248
- if (!dim.options || Object.keys(dim.options).length === 0) {
249
- result.addIssue(
250
- Severity.WARNING,
251
- "EMPTY_DIMENSION",
252
- `Dimension '${key}' has no options defined`,
253
- { dimension: key }
254
- );
255
- continue;
256
- }
257
-
258
- // Check each option
259
- for (const [optKey, optValue] of Object.entries(dim.options)) {
260
- if (typeof optValue !== "object") {
261
- result.addIssue(
262
- Severity.WARNING,
263
- "INVALID_DIMENSION_OPTION",
264
- `Dimension '${key}' option '${optKey}' should be an object`,
265
- { dimension: key, option: optKey }
266
- );
267
- }
268
- }
269
- }
270
- }
271
-
272
- /**
273
- * Validate asset references and files
274
- */
275
- async function validateAssets(docSyncConfig, result) {
276
- const outputDir = config.getOutputDir();
277
-
278
- if (!fs.existsSync(outputDir)) {
279
- result.addIssue(
280
- Severity.INFO,
281
- "NO_OUTPUT_DIR",
282
- `Output directory does not exist: ${outputDir}. Run 'reshot run' first.`
283
- );
284
- return;
285
- }
286
-
287
- // Find all generated assets
288
- const assets = glob.sync("**/*.{png,jpg,jpeg,gif,mp4,webm}", {
289
- cwd: outputDir,
290
- });
291
-
292
- result.stats.assets = assets.length;
293
-
294
- if (assets.length === 0) {
295
- result.addIssue(
296
- Severity.WARNING,
297
- "NO_ASSETS",
298
- `No visual assets found in ${outputDir}. Run 'reshot run' to generate.`
299
- );
300
- }
301
-
302
- // Check for orphaned assets (assets without matching scenario)
303
- const scenarioKeys = new Set(docSyncConfig.scenarios?.map((s) => s.key) || []);
304
-
305
- for (const asset of assets) {
306
- const parts = asset.split(path.sep);
307
- if (parts.length > 0) {
308
- const assetScenario = parts[0];
309
- if (!scenarioKeys.has(assetScenario) && assetScenario !== "_shared") {
310
- result.addIssue(
311
- Severity.WARNING,
312
- "ORPHANED_ASSET",
313
- `Asset may be orphaned (scenario '${assetScenario}' not in config): ${asset}`,
314
- { asset, scenario: assetScenario }
315
- );
316
- }
317
- }
318
- }
319
- }
320
-
321
- /**
322
- * Validate documentation bindings
323
- */
324
- async function validateBindings(docSyncConfig, result) {
325
- // Check if bindings are configured
326
- const bindings = docSyncConfig.bindings || {};
327
- const bindingEntries = Object.entries(bindings);
328
-
329
- if (bindingEntries.length === 0) {
330
- result.addIssue(
331
- Severity.INFO,
332
- "NO_BINDINGS",
333
- "No documentation bindings configured. Visual assets are standalone."
334
- );
335
- return;
336
- }
337
-
338
- result.stats.bindings = bindingEntries.length;
339
-
340
- // Validate each binding
341
- for (const [docPath, binding] of bindingEntries) {
342
- // Check if documentation file exists
343
- if (!fs.existsSync(docPath)) {
344
- result.addIssue(
345
- Severity.ERROR,
346
- "MISSING_DOC_FILE",
347
- `Binding target file does not exist: ${docPath}`,
348
- { binding: docPath }
349
- );
350
- continue;
351
- }
352
-
353
- // Check if bound scenarios exist
354
- if (binding.scenarios) {
355
- for (const scenarioKey of binding.scenarios) {
356
- const scenario = docSyncConfig.scenarios?.find((s) => s.key === scenarioKey);
357
- if (!scenario) {
358
- result.addIssue(
359
- Severity.ERROR,
360
- "INVALID_BINDING_SCENARIO",
361
- `Binding '${docPath}' references non-existent scenario: ${scenarioKey}`,
362
- { binding: docPath, scenario: scenarioKey }
363
- );
364
- }
365
- }
366
- }
367
- }
368
- }
369
-
370
- /**
371
- * Attempt to auto-fix issues
372
- */
373
- async function autoFix(docSyncConfig, result) {
374
- let fixCount = 0;
375
- const fixes = [];
376
-
377
- // Auto-fix: Add missing scenario names
378
- for (const issue of result.getWarnings()) {
379
- if (issue.code === "MISSING_SCENARIO_NAME" && issue.scenario) {
380
- const scenario = docSyncConfig.scenarios?.find((s) => s.key === issue.scenario);
381
- if (scenario) {
382
- scenario.name = scenario.key
383
- .replace(/-/g, " ")
384
- .replace(/\b\w/g, (l) => l.toUpperCase());
385
- fixes.push(`Added name '${scenario.name}' to scenario '${scenario.key}'`);
386
- fixCount++;
387
- }
388
- }
389
- }
390
-
391
- // Save if fixes were made
392
- if (fixCount > 0) {
393
- config.writeConfig(docSyncConfig);
394
- console.log(chalk.green(`\n✓ Applied ${fixCount} auto-fixes:`));
395
- fixes.forEach((fix) => console.log(` - ${fix}`));
396
- }
397
-
398
- return fixCount;
399
- }
400
-
401
- /**
402
- * Format and print validation results
403
- */
404
- function printResults(result, verbose) {
405
- console.log(chalk.bold("\n📋 Validation Results\n"));
406
-
407
- // Print stats
408
- console.log(chalk.dim("Statistics:"));
409
- console.log(` Scenarios: ${result.stats.scenarios}`);
410
- console.log(` Steps: ${result.stats.steps}`);
411
- console.log(` Assets: ${result.stats.assets}`);
412
- console.log(` Bindings: ${result.stats.bindings}`);
413
- console.log();
414
-
415
- // Print errors
416
- const errors = result.getErrors();
417
- if (errors.length > 0) {
418
- console.log(chalk.red(`✗ ${errors.length} Error(s):`));
419
- errors.forEach((e) => {
420
- console.log(chalk.red(` [${e.code}] ${e.message}`));
421
- if (verbose && e.scenario) {
422
- console.log(chalk.dim(` Scenario: ${e.scenario}`));
423
- }
424
- });
425
- console.log();
426
- }
427
-
428
- // Print warnings
429
- const warnings = result.getWarnings();
430
- if (warnings.length > 0) {
431
- console.log(chalk.yellow(`⚠ ${warnings.length} Warning(s):`));
432
- warnings.forEach((w) => {
433
- console.log(chalk.yellow(` [${w.code}] ${w.message}`));
434
- if (verbose && w.scenario) {
435
- console.log(chalk.dim(` Scenario: ${w.scenario}`));
436
- }
437
- });
438
- console.log();
439
- }
440
-
441
- // Print info
442
- if (verbose) {
443
- const infos = result.issues.filter((i) => i.severity === Severity.INFO);
444
- if (infos.length > 0) {
445
- console.log(chalk.blue(`ℹ ${infos.length} Info:`));
446
- infos.forEach((i) => {
447
- console.log(chalk.blue(` [${i.code}] ${i.message}`));
448
- });
449
- console.log();
450
- }
451
- }
452
-
453
- // Summary
454
- if (!result.hasErrors() && !result.hasWarnings()) {
455
- console.log(chalk.green("✓ All validations passed!"));
456
- } else if (!result.hasErrors()) {
457
- console.log(chalk.yellow("⚠ Validation completed with warnings"));
458
- } else {
459
- console.log(chalk.red("✗ Validation failed with errors"));
460
- }
461
- }
462
-
463
- /**
464
- * Main validation command
465
- */
466
- async function validateDocs(options = {}) {
467
- const { strict = false, fix = false, verbose = false } = options;
468
-
469
- console.log(chalk.bold("🔍 Validating Reshot Configuration\n"));
470
-
471
- // Read configuration
472
- let docSyncConfig;
473
- try {
474
- docSyncConfig = config.readConfig();
475
- } catch (error) {
476
- console.log(chalk.red("✗ Failed to read configuration:"), error.message);
477
- process.exit(1);
478
- }
479
-
480
- const configPath = config.getConfigPath();
481
- console.log(chalk.dim(`Config file: ${configPath}\n`));
482
-
483
- // Run validations
484
- const result = new ValidationResult();
485
-
486
- // 1. Validate schema
487
- console.log(chalk.dim("Checking configuration schema..."));
488
- validateConfigSchema(docSyncConfig, result);
489
-
490
- // 2. Validate assets
491
- console.log(chalk.dim("Checking generated assets..."));
492
- await validateAssets(docSyncConfig, result);
493
-
494
- // 3. Validate bindings
495
- console.log(chalk.dim("Checking documentation bindings..."));
496
- await validateBindings(docSyncConfig, result);
497
-
498
- // Auto-fix if requested
499
- if (fix) {
500
- await autoFix(docSyncConfig, result);
501
- }
502
-
503
- // Print results
504
- printResults(result, verbose);
505
-
506
- // Exit code
507
- if (result.hasErrors()) {
508
- process.exit(1);
509
- }
510
-
511
- if (strict && result.hasWarnings()) {
512
- console.log(chalk.yellow("\n--strict mode: Exiting with error due to warnings"));
513
- process.exit(1);
514
- }
515
- }
516
-
517
- /**
518
- * Alias for validateDocs (for backward compatibility)
519
- */
520
- async function validateDocSync(options = {}) {
521
- return validateDocs(options);
522
- }
523
-
524
- module.exports = {
525
- validateDocs,
526
- validateDocSync,
527
- ValidationResult,
528
- Severity,
529
- };