@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.
package/src/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // index.js - Reshot CLI entry point
4
- // Consolidated command structure for ease of use while maintaining power features
5
4
 
6
5
  require("dotenv").config({
7
6
  path: require("path").join(__dirname, "..", ".env"),
@@ -15,7 +14,7 @@ const program = new Command();
15
14
 
16
15
  program
17
16
  .name("reshot")
18
- .description("Visual documentation that stays in sync")
17
+ .description("Visual capture, publishing, and governance for your UI")
19
18
  .version(pkg.version);
20
19
 
21
20
  // ============================================================================
@@ -25,7 +24,7 @@ program
25
24
  // Setup: Interactive wizard for initial configuration
26
25
  program
27
26
  .command("setup")
28
- .description("Interactive setup wizard (auth, config, features)")
27
+ .description("Interactive setup wizard (auth, config)")
29
28
  .option("--offline", "Skip authentication (local-only mode)")
30
29
  .option("--force", "Force re-initialization even if already set up")
31
30
  .action(async (options) => {
@@ -38,13 +37,11 @@ program
38
37
  }
39
38
  });
40
39
 
41
- // Sync: Upload traces and documentation to platform
40
+ // Sync: Upload Playwright traces to platform
42
41
  program
43
42
  .command("sync")
44
- .description("Upload Playwright traces and documentation to Reshot")
43
+ .description("Upload Playwright traces to Reshot")
45
44
  .option("--trace-dir <path>", "Path to test-results directory")
46
- .option("--traces", "Sync traces only")
47
- .option("--docs", "Sync documentation only")
48
45
  .option("--dry-run", "Preview what would be synced")
49
46
  .option("-v, --verbose", "Show detailed output")
50
47
  .action(async (options) => {
@@ -52,8 +49,6 @@ program
52
49
  const syncCommand = require("./commands/sync");
53
50
  await syncCommand({
54
51
  traceDir: options.traceDir,
55
- traces: options.traces,
56
- docs: options.docs,
57
52
  dryRun: options.dryRun,
58
53
  verbose: options.verbose,
59
54
  });
@@ -63,7 +58,7 @@ program
63
58
  }
64
59
  });
65
60
 
66
- // Status: View project status, sync jobs, and drift summary
61
+ // Status: View project status and drift summary
67
62
  program
68
63
  .command("status")
69
64
  .description("View project status, sync history, and drift summary")
@@ -99,27 +94,6 @@ program
99
94
  }
100
95
  });
101
96
 
102
- // Validate: Check configuration and documentation bindings
103
- program
104
- .command("validate")
105
- .description("Validate configuration and documentation bindings")
106
- .option("--strict", "Fail on warnings (zombie links, unbound files)")
107
- .option("--fix", "Attempt to auto-fix issues where possible")
108
- .option("-v, --verbose", "Show detailed validation output")
109
- .action(async (options) => {
110
- try {
111
- const validateCommand = require("./commands/validate-docs");
112
- if (typeof validateCommand.validateDocSync === "function") {
113
- await validateCommand.validateDocSync(options);
114
- } else {
115
- await validateCommand.validateDocs(options);
116
- }
117
- } catch (error) {
118
- console.error(chalk.red("Error:"), error.message);
119
- process.exit(1);
120
- }
121
- });
122
-
123
97
  // ============================================================================
124
98
  // VISUAL CAPTURE COMMANDS
125
99
  // ============================================================================
@@ -304,7 +278,7 @@ ciCommand
304
278
  ciCommand
305
279
  .command("run")
306
280
  .description("Run capture + publish in one step (CI-optimized)")
307
- .option("-c, --config <path>", "Path to docsync.config.json")
281
+ .option("-c, --config <path>", "Path to reshot.config.json")
308
282
  .option("--tag <tag>", "Version tag for publish")
309
283
  .option("-m, --message <message>", "Commit message for publish")
310
284
  .option("--dry-run", "Preview without uploading")
@@ -323,10 +297,10 @@ ciCommand
323
297
  // DRIFT MANAGEMENT COMMANDS
324
298
  // ============================================================================
325
299
 
326
- // Drifts: View and manage documentation drifts
300
+ // Drifts: View and manage visual drifts
327
301
  program
328
302
  .command("drifts [action] [id]")
329
- .description("View and manage documentation drifts")
303
+ .description("View and manage visual drifts")
330
304
  .addHelpText(
331
305
  "after",
332
306
  `
@@ -339,7 +313,6 @@ Actions:
339
313
  sync <id> Mark as manually synced (external_host)
340
314
  approve-all Approve all pending drifts
341
315
  reject-all Reject all pending drifts
342
- validate Validate journey bindings
343
316
  `,
344
317
  )
345
318
  .option("--status <status>", "Filter: PENDING, APPROVED, REJECTED, IGNORED")
@@ -348,7 +321,6 @@ Actions:
348
321
  .action(async (action, id, options) => {
349
322
  try {
350
323
  const driftsCommand = require("./commands/drifts");
351
- // Map action/id to the expected subcommand/args format
352
324
  const subcommand = action || "list";
353
325
  const args = id ? [id] : [];
354
326
  await driftsCommand(subcommand, args, options);
@@ -364,7 +336,7 @@ Actions:
364
336
 
365
337
  // Auth: Standalone authentication (for re-auth scenarios)
366
338
  program
367
- .command("auth", { hidden: true })
339
+ .command("auth")
368
340
  .description("Authenticate with Reshot Cloud")
369
341
  .action(async () => {
370
342
  try {
@@ -406,57 +378,4 @@ program
406
378
  }
407
379
  });
408
380
 
409
- // Ingest: Alias for sync (backward compatibility)
410
- program
411
- .command("ingest", { hidden: true })
412
- .description("Upload traces and docs (alias for sync)")
413
- .option("--trace-dir <path>", "Path to test-results directory")
414
- .option("--dry-run", "Preview what would be synced")
415
- .action(async (options) => {
416
- console.log(
417
- chalk.yellow("Note: 'ingest' is now 'sync'. Running sync...\n"),
418
- );
419
- try {
420
- const syncCommand = require("./commands/sync");
421
- await syncCommand({
422
- traceDir: options.traceDir,
423
- dryRun: options.dryRun,
424
- });
425
- } catch (error) {
426
- console.error(chalk.red("Error:"), error.message);
427
- process.exit(1);
428
- }
429
- });
430
-
431
- // UI: Alias for studio (backward compatibility)
432
- program
433
- .command("ui", { hidden: true })
434
- .description("Launch Reshot Studio (alias for studio)")
435
- .option("--port <port>", "Port for web UI", "4300")
436
- .option("--host <host>", "Host for web UI", "127.0.0.1")
437
- .option("--no-open", "Do not automatically open browser")
438
- .action(async (options) => {
439
- try {
440
- const uiCommand = require("./commands/ui");
441
- await uiCommand(options);
442
- } catch (error) {
443
- console.error(chalk.red("Error:"), error.message);
444
- process.exit(1);
445
- }
446
- });
447
-
448
- // Init-docs: Alias hidden (merged into setup wizard)
449
- program
450
- .command("init-docs", { hidden: true })
451
- .description("Initialize documentation sync (use setup instead)")
452
- .action(async () => {
453
- try {
454
- const initDocsCommand = require("./commands/init-docs");
455
- await initDocsCommand();
456
- } catch (error) {
457
- console.error(chalk.red("Error:"), error.message);
458
- process.exit(1);
459
- }
460
- });
461
-
462
381
  program.parse(process.argv);
@@ -5,7 +5,7 @@ const fs = require("fs");
5
5
 
6
6
  const baseUrl =
7
7
  process.env.RESHOT_API_BASE_URL ||
8
- process.env.DOCSYNC_API_BASE_URL ||
8
+ process.env.RESHOT_API_BASE_URL ||
9
9
  "http://localhost:3000/api";
10
10
 
11
11
  function getApiBaseUrl() {
@@ -648,7 +648,7 @@ async function getBaselines(projectId, apiKey) {
648
648
  */
649
649
  async function exportVisuals(projectId, options = {}) {
650
650
  const { format = "json", status = "approved" } = options;
651
- const settings = require("./config").loadSettings();
651
+ const settings = require("./config").readSettings();
652
652
  const apiKey = settings?.apiKey;
653
653
 
654
654
  if (!apiKey) {
@@ -688,7 +688,7 @@ async function post(endpoint, data, options = {}) {
688
688
  }
689
689
 
690
690
  /**
691
- * DocSync: Initialize ingestion job with manifest
691
+ * Initialize ingestion job with manifest
692
692
  */
693
693
  async function initIngest(apiKey, projectId, manifest) {
694
694
  return withRetry(async () => {
@@ -705,7 +705,7 @@ async function initIngest(apiKey, projectId, manifest) {
705
705
  }
706
706
 
707
707
  /**
708
- * DocSync: Commit ingestion job after uploads complete
708
+ * Commit ingestion job after uploads complete
709
709
  */
710
710
  async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
711
711
  return withRetry(async () => {
@@ -722,7 +722,7 @@ async function commitIngest(apiKey, projectId, uploadResults, git, cli) {
722
722
  }
723
723
 
724
724
  /**
725
- * DocSync: Get drift records for a project
725
+ * Get drift records for a project
726
726
  */
727
727
  async function getDrifts(apiKey, projectId, options = {}) {
728
728
  return withRetry(async () => {
@@ -742,7 +742,7 @@ async function getDrifts(apiKey, projectId, options = {}) {
742
742
  }
743
743
 
744
744
  /**
745
- * DocSync: Get sync jobs for a project
745
+ * Get sync jobs for a project
746
746
  */
747
747
  async function getSyncJobs(apiKey, projectId, options = {}) {
748
748
  return withRetry(async () => {
@@ -762,7 +762,7 @@ async function getSyncJobs(apiKey, projectId, options = {}) {
762
762
  }
763
763
 
764
764
  /**
765
- * DocSync: Perform action on a drift record
765
+ * Perform action on a drift record
766
766
  */
767
767
  async function driftAction(apiKey, projectId, driftId, action, options = {}) {
768
768
  return withRetry(async () => {
@@ -805,7 +805,7 @@ module.exports = {
805
805
  getBaselines,
806
806
  // Export support
807
807
  exportVisuals,
808
- // DocSync
808
+ // Reshot
809
809
  post,
810
810
  initIngest,
811
811
  commitIngest,
@@ -412,11 +412,11 @@ class CaptureEngine {
412
412
  projectId = settings.urlVariables?.PROJECT_ID;
413
413
  // 2. Check settings projectId
414
414
  if (!projectId) projectId = settings.projectId;
415
- // 3. Check docsync.config.json urlVariables
415
+ // 3. Check reshot.config.json urlVariables
416
416
  if (!projectId) {
417
417
  try {
418
- const docsyncConfig = config.readConfig() || {};
419
- projectId = docsyncConfig.urlVariables?.PROJECT_ID;
418
+ const reshotConfig = config.readConfig() || {};
419
+ projectId = reshotConfig.urlVariables?.PROJECT_ID;
420
420
  } catch (_e) {
421
421
  // Config may not exist
422
422
  }
@@ -1931,12 +1931,17 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
1931
1931
 
1932
1932
  await fs.writeFile(sentinelPath, buffer);
1933
1933
  sentinelPaths.push({ index: sentinelIndex, label, path: sentinelPath });
1934
+ if (firstSentinelTimestamp === null) {
1935
+ firstSentinelTimestamp = (Date.now() - startTime) / 1000;
1936
+ debug(`First sentinel captured at ${firstSentinelTimestamp.toFixed(2)}s`);
1937
+ }
1934
1938
  sentinelIndex++;
1935
1939
  return sentinelPath;
1936
1940
  }
1937
1941
 
1938
1942
  // Capture initial state BEFORE first navigation (placeholder - actual capture after goto)
1939
1943
  let hasNavigated = false;
1944
+ let firstSentinelTimestamp = null;
1940
1945
 
1941
1946
  // Execute all steps and capture timeline
1942
1947
  for (let stepIndex = 0; stepIndex < script.length; stepIndex++) {
@@ -2292,20 +2297,32 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
2292
2297
  )
2293
2298
  );
2294
2299
 
2295
- // Convert to MP4 with ffmpeg, trimming to actual content duration
2296
- // Add a small buffer (0.5s) after the final action
2297
- const trimDuration = finalTimestamp + 0.5;
2298
- console.log(
2299
- chalk.cyan(
2300
- ` 📹 Converting to MP4 (trimmed to ${trimDuration.toFixed(1)}s)...`
2301
- )
2302
- );
2303
- debug(`Running ffmpeg conversion with trim to ${trimDuration}s...`);
2300
+ // Convert to MP4 with ffmpeg, trimming blank loading frames from start
2301
+ // and excess frames from end
2302
+ const startOffset = Math.max(0, (firstSentinelTimestamp || 0) - 0.3);
2303
+ const endTimestamp = finalTimestamp + 0.5;
2304
+ const contentDuration = endTimestamp - startOffset;
2305
+ if (startOffset > 0) {
2306
+ console.log(
2307
+ chalk.cyan(
2308
+ ` 📹 Converting to MP4 (${startOffset.toFixed(1)}s–${endTimestamp.toFixed(1)}s, ${contentDuration.toFixed(1)}s content)...`
2309
+ )
2310
+ );
2311
+ } else {
2312
+ console.log(
2313
+ chalk.cyan(
2314
+ ` 📹 Converting to MP4 (trimmed to ${contentDuration.toFixed(1)}s)...`
2315
+ )
2316
+ );
2317
+ }
2318
+ debug(`Running ffmpeg: start=${startOffset}s, duration=${contentDuration}s`);
2304
2319
  await runFFmpegConvert([
2320
+ "-ss",
2321
+ startOffset.toFixed(2),
2305
2322
  "-i",
2306
2323
  recordedVideoPath,
2307
2324
  "-t",
2308
- trimDuration.toFixed(2),
2325
+ contentDuration.toFixed(2),
2309
2326
  "-c:v",
2310
2327
  "libx264",
2311
2328
  "-preset",
@@ -2330,33 +2347,6 @@ async function runScenarioWithVideoCapture(scenario, options = {}) {
2330
2347
  )
2331
2348
  );
2332
2349
 
2333
- // Extract poster frame from the video (first frame at 0.5s for non-blank content)
2334
- const posterPath = finalVideoPath.replace(/\.mp4$/, "-poster.png");
2335
- try {
2336
- await runFFmpegConvert([
2337
- "-i",
2338
- finalVideoPath,
2339
- "-ss",
2340
- "0.5",
2341
- "-frames:v",
2342
- "1",
2343
- "-q:v",
2344
- "2",
2345
- "-y",
2346
- posterPath,
2347
- ]);
2348
- if (fs.existsSync(posterPath)) {
2349
- const posterSize = fs.statSync(posterPath).size;
2350
- console.log(
2351
- chalk.green(
2352
- ` ✔ Poster frame: ${posterPath} (${(posterSize / 1024).toFixed(1)} KB)`
2353
- )
2354
- );
2355
- }
2356
- } catch (e) {
2357
- debug(`Poster frame extraction failed: ${e.message}`);
2358
- }
2359
-
2360
2350
  // Save timeline for reference
2361
2351
  const timelinePath = path.join(
2362
2352
  outputDir || path.join(".reshot/output", scenario.key, "default"),
package/src/lib/config.js CHANGED
@@ -64,7 +64,7 @@ function createAuthErrorResponse(message) {
64
64
  code: "AUTH_REQUIRED",
65
65
  };
66
66
  }
67
- const CONFIG_PATH = path.join(process.cwd(), "docsync.config.json");
67
+ const CONFIG_PATH = path.join(process.cwd(), "reshot.config.json");
68
68
  const WORKSPACE_PATH = path.join(process.cwd(), SETTINGS_DIR, "workspace.json");
69
69
 
70
70
  /**
@@ -470,84 +470,22 @@ function readConfig() {
470
470
  }
471
471
  }
472
472
 
473
- // Validate optional docs block
474
- if (config.docs !== undefined) {
475
- if (
476
- typeof config.docs !== "object" ||
477
- config.docs === null ||
478
- Array.isArray(config.docs)
479
- ) {
480
- throw new Error("docs must be an object");
481
- }
482
- if (
483
- config.docs.root !== undefined &&
484
- typeof config.docs.root !== "string"
485
- ) {
486
- throw new Error("docs.root must be a string");
487
- }
488
- if (
489
- config.docs.include !== undefined &&
490
- !Array.isArray(config.docs.include)
491
- ) {
492
- throw new Error("docs.include must be an array of strings");
493
- }
494
- if (
495
- config.docs.exclude !== undefined &&
496
- !Array.isArray(config.docs.exclude)
497
- ) {
498
- throw new Error("docs.exclude must be an array of strings");
499
- }
500
- }
501
-
502
473
  return config;
503
474
  }
504
475
 
505
476
  /**
506
- * Read docsync.config.json with DocSync-specific configuration
507
- * Returns the full config including the documentation block for ingestion
508
- * @returns {Object} DocSync configuration
477
+ * Read reshot.config.json without requiring scenarios array
478
+ * Used by sync and other commands that don't need scenario validation
479
+ * @returns {Object} Configuration
509
480
  */
510
- function readDocSyncConfig() {
481
+ function readConfigLenient() {
511
482
  if (!fs.existsSync(CONFIG_PATH)) {
512
483
  throw new Error(
513
484
  `Config file not found at ${CONFIG_PATH}. Run \`reshot init\` to create one.`
514
485
  );
515
486
  }
516
487
 
517
- const config = fs.readJSONSync(CONFIG_PATH);
518
-
519
- // Validate documentation block if present
520
- if (config.documentation) {
521
- const doc = config.documentation;
522
-
523
- // Validate required fields
524
- if (!doc.strategy) {
525
- throw new Error('documentation.strategy is required (git_pr or external_host)');
526
- }
527
-
528
- if (!['git_pr', 'external_host'].includes(doc.strategy)) {
529
- throw new Error('documentation.strategy must be "git_pr" or "external_host"');
530
- }
531
-
532
- // Validate optional fields
533
- if (doc.assetFormat && !['cdn_link', 'markdown'].includes(doc.assetFormat)) {
534
- throw new Error('documentation.assetFormat must be "cdn_link" or "markdown"');
535
- }
536
-
537
- if (doc.include && !Array.isArray(doc.include)) {
538
- throw new Error('documentation.include must be an array of glob patterns');
539
- }
540
-
541
- if (doc.exclude && !Array.isArray(doc.exclude)) {
542
- throw new Error('documentation.exclude must be an array of glob patterns');
543
- }
544
-
545
- if (doc.mappings && typeof doc.mappings !== 'object') {
546
- throw new Error('documentation.mappings must be an object');
547
- }
548
- }
549
-
550
- return config;
488
+ return fs.readJSONSync(CONFIG_PATH);
551
489
  }
552
490
 
553
491
  /**
@@ -851,8 +789,6 @@ async function initializeProject(projectId, apiKey, options = {}) {
851
789
  contextCount: 1,
852
790
  features: {
853
791
  visuals: true,
854
- docs: false,
855
- changelog: true,
856
792
  },
857
793
  },
858
794
  };
@@ -1230,8 +1166,8 @@ module.exports = {
1230
1166
  // Mode & validation
1231
1167
  getModeInfo,
1232
1168
  validateConfig,
1233
- // DocSync configuration
1234
- readDocSyncConfig,
1169
+ // Lenient config read (no scenario validation)
1170
+ readConfigLenient,
1235
1171
  // Paths
1236
1172
  SETTINGS_PATH,
1237
1173
  SETTINGS_DIR,
@@ -600,7 +600,7 @@ async function finalizeScenarioAndWriteConfig(
600
600
 
601
601
  console.log(
602
602
  chalk.green(
603
- "\n✔ docsync.config.json has been updated. Please review and commit the changes to your repository.\n"
603
+ "\n✔ reshot.config.json has been updated. Please review and commit the changes to your repository.\n"
604
604
  )
605
605
  );
606
606
  console.log(chalk.gray(`Scenario: ${result.scenarioName}`));
@@ -103,7 +103,7 @@ const DEFAULT_STANDALONE_SETTINGS = {
103
103
  };
104
104
 
105
105
  const SETTINGS_DIR = ".reshot";
106
- const CONFIG_PATH = "docsync.config.json";
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 docsync.config.json:')}
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 docsync.config.json:')}
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 docsync.config.json:')}
168
+ 1. ${chalk.yellow('Update reshot.config.json:')}
169
169
  ${chalk.gray(JSON.stringify({
170
170
  storage: {
171
171
  type: 'local',
@@ -536,7 +536,7 @@ function createStorageProvider(config) {
536
536
 
537
537
  /**
538
538
  * Determine storage mode from config
539
- * @param {object} docSyncConfig - The docsync.config.json content
539
+ * @param {object} docSyncConfig - The reshot.config.json content
540
540
  * @returns {'platform'|'byos'}
541
541
  */
542
542
  function getStorageMode(docSyncConfig) {
package/src/lib/ui-api.js CHANGED
@@ -48,7 +48,7 @@ function getPlatformUrl(settings) {
48
48
  return settings.platformUrl;
49
49
  }
50
50
  const envUrl =
51
- process.env.RESHOT_API_BASE_URL || process.env.DOCSYNC_API_BASE_URL;
51
+ process.env.RESHOT_API_BASE_URL || process.env.RESHOT_API_BASE_URL;
52
52
  if (envUrl) {
53
53
  // Remove /api suffix if present to get platform URL
54
54
  return envUrl.replace(/\/api\/?$/, "");
@@ -431,7 +431,7 @@ function attachApiRoutes(app, context) {
431
431
 
432
432
  /**
433
433
  * PUT /api/privacy
434
- * Update privacy configuration in docsync.config.json
434
+ * Update privacy configuration in reshot.config.json
435
435
  */
436
436
  app.put("/api/privacy", async (req, res, next) => {
437
437
  try {
@@ -472,7 +472,7 @@ function attachApiRoutes(app, context) {
472
472
 
473
473
  /**
474
474
  * PUT /api/style
475
- * Update style configuration in docsync.config.json
475
+ * Update style configuration in reshot.config.json
476
476
  */
477
477
  app.put("/api/style", async (req, res, next) => {
478
478
  try {