@reshotdev/screenshot 0.0.1-beta.2 → 0.0.1-beta.20
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
package/src/lib/record-cdp.js
CHANGED
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
const { chromium } = require("playwright");
|
|
3
3
|
const chalk = require("chalk");
|
|
4
4
|
const http = require("http");
|
|
5
|
+
const fs = require("fs-extra");
|
|
6
|
+
const path = require("path");
|
|
7
|
+
const os = require("os");
|
|
5
8
|
|
|
6
9
|
/**
|
|
7
10
|
* Check if Chrome CDP endpoint is available
|
|
@@ -418,15 +421,14 @@ async function saveSessionState(outputPath) {
|
|
|
418
421
|
console.log(chalk.gray(` Sanitized cookies: ${stats.fixed} fixed, ${stats.removed} removed, ${stats.stripped} stripped`));
|
|
419
422
|
}
|
|
420
423
|
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
// Write storage state to file
|
|
427
|
-
fs.writeJsonSync(outputPath, storageState, { spaces: 2 });
|
|
424
|
+
const activePageUrl = page?.url?.() || null;
|
|
425
|
+
const artifactInfo = writeSessionArtifacts(outputPath, storageState, {
|
|
426
|
+
pageUrl: activePageUrl,
|
|
427
|
+
baseUrl: activePageUrl,
|
|
428
|
+
});
|
|
428
429
|
|
|
429
430
|
console.log(chalk.green(` ✔ Session saved to: ${outputPath}`));
|
|
431
|
+
console.log(chalk.gray(` Metadata: ${artifactInfo.metadataPath}`));
|
|
430
432
|
console.log(chalk.gray(` Cookies: ${storageState.cookies?.length || 0}`));
|
|
431
433
|
console.log(chalk.gray(` Origins with localStorage: ${storageState.origins?.length || 0}`));
|
|
432
434
|
|
|
@@ -445,11 +447,249 @@ async function saveSessionState(outputPath) {
|
|
|
445
447
|
* @returns {string}
|
|
446
448
|
*/
|
|
447
449
|
function getDefaultSessionPath() {
|
|
448
|
-
const path = require("path");
|
|
449
|
-
const os = require("os");
|
|
450
450
|
return path.join(os.homedir(), ".reshot", "session-state.json");
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
function getSessionMetadataPath(sessionPath = getDefaultSessionPath()) {
|
|
454
|
+
if (sessionPath.endsWith(".json")) {
|
|
455
|
+
return sessionPath.replace(/\.json$/i, ".meta.json");
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return `${sessionPath}.meta.json`;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
function normalizeOrigin(value) {
|
|
462
|
+
if (!value || typeof value !== "string") {
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
try {
|
|
467
|
+
return new URL(value).origin.toLowerCase();
|
|
468
|
+
} catch {
|
|
469
|
+
return null;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
function normalizeCookieDomain(domain) {
|
|
474
|
+
if (!domain || typeof domain !== "string") {
|
|
475
|
+
return null;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return domain.replace(/^\./, "").toLowerCase();
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function extractSessionEvidence(storageState) {
|
|
482
|
+
const storageOrigins = Array.from(
|
|
483
|
+
new Set(
|
|
484
|
+
(storageState?.origins || [])
|
|
485
|
+
.map((origin) => normalizeOrigin(origin?.origin))
|
|
486
|
+
.filter(Boolean),
|
|
487
|
+
),
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
const cookieDomains = Array.from(
|
|
491
|
+
new Set(
|
|
492
|
+
(storageState?.cookies || [])
|
|
493
|
+
.map((cookie) => normalizeCookieDomain(cookie?.domain))
|
|
494
|
+
.filter(Boolean),
|
|
495
|
+
),
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
return { storageOrigins, cookieDomains };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function buildSessionMetadata(options = {}) {
|
|
502
|
+
const {
|
|
503
|
+
baseUrl = null,
|
|
504
|
+
pageUrl = null,
|
|
505
|
+
storageState = null,
|
|
506
|
+
capturedAt = new Date().toISOString(),
|
|
507
|
+
} = options;
|
|
508
|
+
const evidence = extractSessionEvidence(storageState);
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
version: 1,
|
|
512
|
+
capturedAt,
|
|
513
|
+
sourceUrl: pageUrl || baseUrl || null,
|
|
514
|
+
sourceOrigin:
|
|
515
|
+
normalizeOrigin(pageUrl) ||
|
|
516
|
+
normalizeOrigin(baseUrl) ||
|
|
517
|
+
evidence.storageOrigins[0] ||
|
|
518
|
+
null,
|
|
519
|
+
storageOrigins: evidence.storageOrigins,
|
|
520
|
+
cookieDomains: evidence.cookieDomains,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
function writeSessionArtifacts(sessionPath, storageState, options = {}) {
|
|
525
|
+
const metadataPath = getSessionMetadataPath(sessionPath);
|
|
526
|
+
const metadata = buildSessionMetadata({ ...options, storageState });
|
|
527
|
+
|
|
528
|
+
fs.ensureDirSync(path.dirname(sessionPath));
|
|
529
|
+
fs.writeJsonSync(sessionPath, storageState, { spaces: 2 });
|
|
530
|
+
fs.writeJsonSync(metadataPath, metadata, { spaces: 2 });
|
|
531
|
+
|
|
532
|
+
return { sessionPath, metadataPath, metadata };
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function readSessionMetadata(sessionPath = getDefaultSessionPath()) {
|
|
536
|
+
const metadataPath = getSessionMetadataPath(sessionPath);
|
|
537
|
+
|
|
538
|
+
if (!fs.existsSync(metadataPath)) {
|
|
539
|
+
return {
|
|
540
|
+
exists: false,
|
|
541
|
+
metadataPath,
|
|
542
|
+
metadata: null,
|
|
543
|
+
error: null,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
try {
|
|
548
|
+
return {
|
|
549
|
+
exists: true,
|
|
550
|
+
metadataPath,
|
|
551
|
+
metadata: fs.readJsonSync(metadataPath),
|
|
552
|
+
error: null,
|
|
553
|
+
};
|
|
554
|
+
} catch (error) {
|
|
555
|
+
return {
|
|
556
|
+
exists: true,
|
|
557
|
+
metadataPath,
|
|
558
|
+
metadata: null,
|
|
559
|
+
error: error.message,
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function sessionHostMatchesCookieDomains(hostname, cookieDomains) {
|
|
565
|
+
if (!hostname) {
|
|
566
|
+
return false;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
return cookieDomains.some(
|
|
570
|
+
(domain) => hostname === domain || hostname.endsWith(`.${domain}`),
|
|
571
|
+
);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function assessSessionHealth(
|
|
575
|
+
sessionPath = getDefaultSessionPath(),
|
|
576
|
+
baseUrl = null,
|
|
577
|
+
options = {},
|
|
578
|
+
) {
|
|
579
|
+
const maxAgeMinutes = Number.isFinite(options.maxAgeMinutes)
|
|
580
|
+
? options.maxAgeMinutes
|
|
581
|
+
: 360;
|
|
582
|
+
const expectedOrigin = normalizeOrigin(baseUrl);
|
|
583
|
+
const result = {
|
|
584
|
+
ok: false,
|
|
585
|
+
exists: fs.existsSync(sessionPath),
|
|
586
|
+
sessionPath,
|
|
587
|
+
metadataPath: getSessionMetadataPath(sessionPath),
|
|
588
|
+
expectedOrigin,
|
|
589
|
+
compatible: true,
|
|
590
|
+
stale: false,
|
|
591
|
+
ageMinutes: null,
|
|
592
|
+
metadata: null,
|
|
593
|
+
warnings: [],
|
|
594
|
+
issues: [],
|
|
595
|
+
evidence: {
|
|
596
|
+
sourceOrigin: null,
|
|
597
|
+
storageOrigins: [],
|
|
598
|
+
cookieDomains: [],
|
|
599
|
+
matchSource: null,
|
|
600
|
+
},
|
|
601
|
+
};
|
|
602
|
+
|
|
603
|
+
if (!result.exists) {
|
|
604
|
+
return result;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
let storageState = null;
|
|
608
|
+
try {
|
|
609
|
+
storageState = fs.readJsonSync(sessionPath);
|
|
610
|
+
} catch (error) {
|
|
611
|
+
result.compatible = false;
|
|
612
|
+
result.issues.push(`Cached session file is unreadable: ${error.message}`);
|
|
613
|
+
return result;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const metadataInfo = readSessionMetadata(sessionPath);
|
|
617
|
+
if (metadataInfo.error) {
|
|
618
|
+
result.warnings.push(`Session metadata is unreadable: ${metadataInfo.error}`);
|
|
619
|
+
} else if (!metadataInfo.exists) {
|
|
620
|
+
result.warnings.push(
|
|
621
|
+
"Session metadata is missing; compatibility was inferred from stored cookies and origins.",
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const inferredEvidence = extractSessionEvidence(storageState);
|
|
626
|
+
const metadata = metadataInfo.metadata || null;
|
|
627
|
+
const storageOrigins =
|
|
628
|
+
metadata?.storageOrigins
|
|
629
|
+
?.map((origin) => normalizeOrigin(origin))
|
|
630
|
+
.filter(Boolean) || inferredEvidence.storageOrigins;
|
|
631
|
+
const cookieDomains =
|
|
632
|
+
metadata?.cookieDomains
|
|
633
|
+
?.map((domain) => normalizeCookieDomain(domain))
|
|
634
|
+
.filter(Boolean) || inferredEvidence.cookieDomains;
|
|
635
|
+
const sourceOrigin =
|
|
636
|
+
normalizeOrigin(metadata?.sourceOrigin) ||
|
|
637
|
+
normalizeOrigin(metadata?.sourceUrl) ||
|
|
638
|
+
inferredEvidence.storageOrigins[0] ||
|
|
639
|
+
null;
|
|
640
|
+
|
|
641
|
+
result.metadata = metadata;
|
|
642
|
+
result.evidence = {
|
|
643
|
+
sourceOrigin,
|
|
644
|
+
storageOrigins,
|
|
645
|
+
cookieDomains,
|
|
646
|
+
matchSource: null,
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
const capturedAtMs = Date.parse(metadata?.capturedAt || "");
|
|
650
|
+
const sessionStat = fs.statSync(sessionPath);
|
|
651
|
+
const referenceTime = Number.isFinite(capturedAtMs)
|
|
652
|
+
? capturedAtMs
|
|
653
|
+
: sessionStat.mtimeMs;
|
|
654
|
+
result.ageMinutes = Math.max(
|
|
655
|
+
0,
|
|
656
|
+
Math.round((Date.now() - referenceTime) / 60000),
|
|
657
|
+
);
|
|
658
|
+
result.stale = result.ageMinutes >= maxAgeMinutes;
|
|
659
|
+
|
|
660
|
+
if (expectedOrigin) {
|
|
661
|
+
const expectedHost = new URL(baseUrl).hostname.toLowerCase();
|
|
662
|
+
const matchesSourceOrigin = sourceOrigin === expectedOrigin;
|
|
663
|
+
const matchesStorageOrigin = storageOrigins.includes(expectedOrigin);
|
|
664
|
+
const matchesCookieDomain = sessionHostMatchesCookieDomains(
|
|
665
|
+
expectedHost,
|
|
666
|
+
cookieDomains,
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
result.evidence.matchSource = matchesSourceOrigin
|
|
670
|
+
? "sourceOrigin"
|
|
671
|
+
: matchesStorageOrigin
|
|
672
|
+
? "storageOrigins"
|
|
673
|
+
: matchesCookieDomain
|
|
674
|
+
? "cookieDomains"
|
|
675
|
+
: null;
|
|
676
|
+
|
|
677
|
+
const hasEvidence =
|
|
678
|
+
Boolean(sourceOrigin) ||
|
|
679
|
+
storageOrigins.length > 0 ||
|
|
680
|
+
cookieDomains.length > 0;
|
|
681
|
+
if (hasEvidence && !result.evidence.matchSource) {
|
|
682
|
+
result.compatible = false;
|
|
683
|
+
result.issues.push(
|
|
684
|
+
`Cached session targets ${sourceOrigin || storageOrigins[0] || cookieDomains[0]}, not ${expectedOrigin}.`,
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
result.ok = result.compatible && result.issues.length === 0;
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
|
|
453
693
|
/**
|
|
454
694
|
* Quietly check if CDP is available and sync the session if so.
|
|
455
695
|
* This is called automatically before captures to use the live browser session.
|
|
@@ -459,13 +699,24 @@ function getDefaultSessionPath() {
|
|
|
459
699
|
*/
|
|
460
700
|
async function autoSyncSessionFromCDP(outputPath = null, logger = null) {
|
|
461
701
|
const log = logger || (() => {}); // Quiet by default
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
702
|
+
|
|
703
|
+
// Allow disabling CDP sync via env var (useful when session was created programmatically)
|
|
704
|
+
if (process.env.RESHOT_SKIP_CDP_SYNC === "1") {
|
|
705
|
+
return { synced: false, reason: "disabled" };
|
|
706
|
+
}
|
|
707
|
+
|
|
466
708
|
const sessionPath = outputPath || path.join(os.homedir(), ".reshot", "session-state.json");
|
|
467
|
-
|
|
709
|
+
|
|
468
710
|
try {
|
|
711
|
+
// Skip sync if cached session is very recent (likely just generated programmatically)
|
|
712
|
+
if (fs.existsSync(sessionPath)) {
|
|
713
|
+
const cachedAge = Date.now() - fs.statSync(sessionPath).mtimeMs;
|
|
714
|
+
if (cachedAge < 5 * 60 * 1000) {
|
|
715
|
+
log(chalk.gray(" → Cached session is fresh (<5min), skipping CDP sync"));
|
|
716
|
+
return { synced: false, reason: "cached_fresh" };
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
469
720
|
// Step 1: Check if CDP endpoint is available (quietly)
|
|
470
721
|
const endpointCheck = await checkCdpEndpoint("localhost", 9222);
|
|
471
722
|
|
|
@@ -513,9 +764,25 @@ async function autoSyncSessionFromCDP(outputPath = null, logger = null) {
|
|
|
513
764
|
return { synced: false, reason: "empty_session" };
|
|
514
765
|
}
|
|
515
766
|
|
|
767
|
+
const activePage =
|
|
768
|
+
context
|
|
769
|
+
.pages()
|
|
770
|
+
.find((candidate) => {
|
|
771
|
+
const currentUrl = candidate.url();
|
|
772
|
+
return (
|
|
773
|
+
currentUrl &&
|
|
774
|
+
!currentUrl.startsWith("chrome://") &&
|
|
775
|
+
!currentUrl.startsWith("devtools://") &&
|
|
776
|
+
!currentUrl.startsWith("chrome-extension://")
|
|
777
|
+
);
|
|
778
|
+
}) || context.pages()[0] || null;
|
|
779
|
+
const activePageUrl = activePage?.url?.() || null;
|
|
780
|
+
|
|
516
781
|
// Step 4: Save to file
|
|
517
|
-
|
|
518
|
-
|
|
782
|
+
writeSessionArtifacts(sessionPath, storageState, {
|
|
783
|
+
pageUrl: activePageUrl,
|
|
784
|
+
baseUrl: activePageUrl,
|
|
785
|
+
});
|
|
519
786
|
|
|
520
787
|
log(chalk.green(` ✔ Auto-synced session from CDP browser`));
|
|
521
788
|
log(chalk.gray(` Cookies: ${storageState.cookies?.length || 0}, localStorage origins: ${storageState.origins?.length || 0}`));
|
|
@@ -607,6 +874,11 @@ module.exports = {
|
|
|
607
874
|
printChromeInstructions,
|
|
608
875
|
saveSessionState,
|
|
609
876
|
getDefaultSessionPath,
|
|
877
|
+
getSessionMetadataPath,
|
|
610
878
|
autoSyncSessionFromCDP,
|
|
611
879
|
sanitizeStorageState,
|
|
880
|
+
buildSessionMetadata,
|
|
881
|
+
writeSessionArtifacts,
|
|
882
|
+
readSessionMetadata,
|
|
883
|
+
assessSessionHealth,
|
|
612
884
|
};
|
package/src/lib/record-clip.js
CHANGED
|
@@ -8,13 +8,27 @@ const { chromium } = require('playwright');
|
|
|
8
8
|
const { updateBrowserMode } = require('./record-browser-injection');
|
|
9
9
|
const { runPolishedClip } = require('./polished-clip');
|
|
10
10
|
const { saveScenarioProgress } = require('./record-config');
|
|
11
|
+
const { resolveTargets } = require('./resolve-targets');
|
|
12
|
+
const { captureDomArtifact } = require('./dom-capture');
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Start clip recording flow
|
|
14
16
|
* @param {Object} sessionState - Recording session state
|
|
15
17
|
* @param {Page} page - Playwright page object
|
|
16
18
|
*/
|
|
17
|
-
async function startClipRecording(sessionState, page) {
|
|
19
|
+
async function startClipRecording(sessionState, page, config = {}) {
|
|
20
|
+
const metadataRecorder = config.emitMetadata
|
|
21
|
+
? createClipMetadataRecorder({
|
|
22
|
+
slug: config.slug || sessionState.visualKey,
|
|
23
|
+
captureSize: config.captureSize || { width: 1280, height: 720 },
|
|
24
|
+
})
|
|
25
|
+
: null;
|
|
26
|
+
|
|
27
|
+
if (metadataRecorder) {
|
|
28
|
+
sessionState.logEvent = metadataRecorder.logEvent;
|
|
29
|
+
metadataRecorder.logEvent('workflow_start');
|
|
30
|
+
}
|
|
31
|
+
|
|
18
32
|
// Ask about container element
|
|
19
33
|
const { useContainer } = await inquirer.prompt([
|
|
20
34
|
{
|
|
@@ -127,6 +141,10 @@ async function startClipRecording(sessionState, page) {
|
|
|
127
141
|
|
|
128
142
|
// Set up action replay handler to sync video with real user actions
|
|
129
143
|
sessionState.replayActionToRecording = async (action, selector, text) => {
|
|
144
|
+
if (metadataRecorder) {
|
|
145
|
+
metadataRecorder.logEvent(action, { selector, text });
|
|
146
|
+
}
|
|
147
|
+
|
|
130
148
|
try {
|
|
131
149
|
if (action === 'click') {
|
|
132
150
|
await recordingPage.click(selector);
|
|
@@ -233,6 +251,17 @@ async function startClipRecording(sessionState, page) {
|
|
|
233
251
|
|
|
234
252
|
sessionState.capturedSteps.push(clipStep);
|
|
235
253
|
await saveScenarioProgress(sessionState, page, { finalize: false });
|
|
254
|
+
|
|
255
|
+
if (metadataRecorder) {
|
|
256
|
+
await writeClipMetadata({
|
|
257
|
+
page,
|
|
258
|
+
outputDir: config.outputDir || sessionState.outputDir || process.cwd(),
|
|
259
|
+
slug: metadataRecorder.slug,
|
|
260
|
+
captureSize: metadataRecorder.captureSize,
|
|
261
|
+
timeline: metadataRecorder.timeline,
|
|
262
|
+
targets: config.targets,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
236
265
|
|
|
237
266
|
// Clean up temp video
|
|
238
267
|
fs.removeSync(rawVideoPath);
|
|
@@ -242,6 +271,9 @@ async function startClipRecording(sessionState, page) {
|
|
|
242
271
|
sessionState.clipEvents = null;
|
|
243
272
|
sessionState.recordingStart = null;
|
|
244
273
|
sessionState.stopClipRecording = false;
|
|
274
|
+
if (metadataRecorder) {
|
|
275
|
+
delete sessionState.logEvent;
|
|
276
|
+
}
|
|
245
277
|
|
|
246
278
|
console.log(
|
|
247
279
|
chalk.green(
|
|
@@ -250,6 +282,53 @@ async function startClipRecording(sessionState, page) {
|
|
|
250
282
|
);
|
|
251
283
|
}
|
|
252
284
|
|
|
285
|
+
function createClipMetadataRecorder({ slug, captureSize }) {
|
|
286
|
+
const startedAt = Date.now();
|
|
287
|
+
const timeline = [];
|
|
288
|
+
|
|
289
|
+
return {
|
|
290
|
+
slug,
|
|
291
|
+
captureSize,
|
|
292
|
+
timeline,
|
|
293
|
+
logEvent(type, payload = {}) {
|
|
294
|
+
timeline.push({
|
|
295
|
+
tMs: Date.now() - startedAt,
|
|
296
|
+
type,
|
|
297
|
+
payload,
|
|
298
|
+
});
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function writeClipMetadata({
|
|
304
|
+
page,
|
|
305
|
+
outputDir,
|
|
306
|
+
slug,
|
|
307
|
+
captureSize,
|
|
308
|
+
timeline,
|
|
309
|
+
targets,
|
|
310
|
+
}) {
|
|
311
|
+
const resolvedTargets = targets ? await resolveTargets(page, targets) : {};
|
|
312
|
+
|
|
313
|
+
// Tier-3: alongside the video, emit a self-contained DOM reconstruction
|
|
314
|
+
// artifact (<slug>.dom.html + sidecars) for eligible screens. Additive and
|
|
315
|
+
// best-effort — it must never break the video path, so failures are swallowed.
|
|
316
|
+
await fs.ensureDir(outputDir);
|
|
317
|
+
const domArtifact = await captureDomArtifact({ page, outputDir, slug });
|
|
318
|
+
|
|
319
|
+
const metadata = {
|
|
320
|
+
slug,
|
|
321
|
+
version: 1,
|
|
322
|
+
captureSize,
|
|
323
|
+
timeline,
|
|
324
|
+
targets: resolvedTargets,
|
|
325
|
+
...(domArtifact ? { domArtifact } : {}),
|
|
326
|
+
};
|
|
327
|
+
const metadataPath = path.join(outputDir, `${slug}.metadata.json`);
|
|
328
|
+
await fs.writeJson(metadataPath, metadata, { spaces: 2 });
|
|
329
|
+
return metadataPath;
|
|
330
|
+
}
|
|
331
|
+
|
|
253
332
|
/**
|
|
254
333
|
* Run subtitle editor mini-server
|
|
255
334
|
* @param {Array} events - Clip events with timestamps
|
|
@@ -338,6 +417,7 @@ function buildClipKey(visualKey, filename) {
|
|
|
338
417
|
}
|
|
339
418
|
|
|
340
419
|
module.exports = {
|
|
341
|
-
|
|
420
|
+
createClipMetadataRecorder,
|
|
421
|
+
startClipRecording,
|
|
422
|
+
writeClipMetadata
|
|
342
423
|
};
|
|
343
|
-
|
package/src/lib/record-config.js
CHANGED
|
@@ -492,10 +492,6 @@ async function persistFinalScenario(sessionState, page, options = {}) {
|
|
|
492
492
|
// Default output configuration for automatic step-by-step image generation
|
|
493
493
|
const defaultOutput = {
|
|
494
494
|
format: "step-by-step-images",
|
|
495
|
-
highlight: {
|
|
496
|
-
color: "rgba(255, 255, 0, 0.5)",
|
|
497
|
-
style: "box",
|
|
498
|
-
},
|
|
499
495
|
};
|
|
500
496
|
|
|
501
497
|
// Parse groupPath from session state or existing scenario
|
|
@@ -600,7 +596,7 @@ async function finalizeScenarioAndWriteConfig(
|
|
|
600
596
|
|
|
601
597
|
console.log(
|
|
602
598
|
chalk.green(
|
|
603
|
-
"\n✔
|
|
599
|
+
"\n✔ reshot.config.json has been updated. Please review and commit the changes to your repository.\n"
|
|
604
600
|
)
|
|
605
601
|
);
|
|
606
602
|
console.log(chalk.gray(`Scenario: ${result.scenarioName}`));
|