@_deep4wee/agent-lens 1.0.1 → 1.1.0
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 +227 -221
- package/dist/cli.d.mts +2 -1
- package/dist/cli.d.ts +2 -1
- package/dist/cli.js +255 -200
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +255 -205
- package/dist/cli.mjs.map +1 -1
- package/dist/index.d.mts +32 -9
- package/dist/index.d.ts +32 -9
- package/dist/index.js.map +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/skills/agent-lens/SKILL.md +24 -6
- package/skills/agent-lens/examples/06-state-testing-with-mock-ipc.md +13 -12
package/dist/cli.mjs
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
1
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
4
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
9
2
|
var __esm = (fn, res) => function __init() {
|
|
10
3
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
11
4
|
};
|
|
12
|
-
var __commonJS = (cb, mod) => function
|
|
5
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
13
6
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
14
7
|
};
|
|
15
8
|
|
|
@@ -180,9 +173,9 @@ var init_reporter = __esm({
|
|
|
180
173
|
burstGroups.set(s.burstGroup, list);
|
|
181
174
|
});
|
|
182
175
|
for (const snap of regularSnapshots) {
|
|
183
|
-
const
|
|
176
|
+
const relativeLink = snap.relativeUri || `./${snap.fileName}`;
|
|
184
177
|
rows.push(
|
|
185
|
-
`| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${
|
|
178
|
+
`| **${String(snap.index).padStart(2, "0")}** | \`${snap.viewport.width}x${snap.viewport.height}\` | ${snap.name} | [${snap.fileName}](${relativeLink}) |`
|
|
186
179
|
);
|
|
187
180
|
}
|
|
188
181
|
let burstSections = "";
|
|
@@ -199,8 +192,8 @@ var init_reporter = __esm({
|
|
|
199
192
|
| :--- | :--- | :--- | :--- |
|
|
200
193
|
`;
|
|
201
194
|
for (const frame of frames) {
|
|
202
|
-
const
|
|
203
|
-
burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${
|
|
195
|
+
const relativeLink = frame.relativeUri || `./${frame.fileName}`;
|
|
196
|
+
burstSections += `| Frame ${frame.frameIndex} | \`${frame.viewport.width}x${frame.viewport.height}\` | [${frame.fileName}](${relativeLink}) |  |
|
|
204
197
|
`;
|
|
205
198
|
}
|
|
206
199
|
burstSections += `
|
|
@@ -289,7 +282,9 @@ ${consoleSections}
|
|
|
289
282
|
|
|
290
283
|
## \u{1F4CB} AI Agent Verification Checklist:
|
|
291
284
|
- [ ] **Console Errors**: ${consoleErrors.length === 0 ? "\u2705 No errors found" : `\u274C ${consoleErrors.length} errors \u2014 MUST REVIEW`}
|
|
285
|
+
- [ ] **Visual Layout Check**: Inspect snapshots (e.g. view_image on \`./01_${regularSnapshots[0]?.fileName || "quick_snap"}\`) for layout shifts or clipped elements.
|
|
292
286
|
- [ ] **Responsiveness at \`1024x768\`**: Elements do not overflow the screen, no unwanted horizontal scroll.
|
|
287
|
+
|
|
293
288
|
- [ ] **Typography & Spacing**: Spacing matches the design system and layout grids.
|
|
294
289
|
- [ ] **Color Palette & Theme**: Background tints and button accent colors match the concept.
|
|
295
290
|
- [ ] **Component States**: Modals open centered, dropdowns do not overlap with other layers (z-index).
|
|
@@ -320,8 +315,6 @@ var init_consoleTracker = __esm({
|
|
|
320
315
|
ConsoleTracker = class {
|
|
321
316
|
entries = [];
|
|
322
317
|
attached = false;
|
|
323
|
-
/**
|
|
324
|
-
*/
|
|
325
318
|
attach(page) {
|
|
326
319
|
if (this.attached) return;
|
|
327
320
|
this.attached = true;
|
|
@@ -390,8 +383,6 @@ var init_consoleTracker = __esm({
|
|
|
390
383
|
return "log";
|
|
391
384
|
}
|
|
392
385
|
}
|
|
393
|
-
/**
|
|
394
|
-
*/
|
|
395
386
|
isIgnoredWarning(text) {
|
|
396
387
|
const ignoredPatterns = [
|
|
397
388
|
"findDOMNode is deprecated",
|
|
@@ -411,10 +402,30 @@ var init_consoleTracker = __esm({
|
|
|
411
402
|
});
|
|
412
403
|
|
|
413
404
|
// src/shared/lib/playwrightLoader.ts
|
|
414
|
-
import { chromium } from "playwright";
|
|
405
|
+
import { chromium as baseChromium } from "playwright";
|
|
406
|
+
var chromium;
|
|
415
407
|
var init_playwrightLoader = __esm({
|
|
416
408
|
"src/shared/lib/playwrightLoader.ts"() {
|
|
417
409
|
"use strict";
|
|
410
|
+
chromium = new Proxy(baseChromium, {
|
|
411
|
+
get(target, prop, receiver) {
|
|
412
|
+
if (prop === "launch") {
|
|
413
|
+
return async (...args) => {
|
|
414
|
+
try {
|
|
415
|
+
return await target.launch(...args);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
if (err.message?.includes("Executable doesn't exist") || err.message?.includes("playwright install") || err.message?.includes("browser has not been downloaded")) {
|
|
418
|
+
console.error("\n\u274C [AgentLens] Playwright Chromium browser binary is missing!");
|
|
419
|
+
console.error("\u{1F449} Please install it by running: npx playwright install chromium\n");
|
|
420
|
+
}
|
|
421
|
+
throw err;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
const val = Reflect.get(target, prop, receiver);
|
|
426
|
+
return typeof val === "function" ? val.bind(target) : val;
|
|
427
|
+
}
|
|
428
|
+
});
|
|
418
429
|
}
|
|
419
430
|
});
|
|
420
431
|
|
|
@@ -423,6 +434,7 @@ import { spawn } from "child_process";
|
|
|
423
434
|
import http from "http";
|
|
424
435
|
import path3 from "path";
|
|
425
436
|
import fs3 from "fs";
|
|
437
|
+
import treeKill from "tree-kill";
|
|
426
438
|
var DesktopDriver;
|
|
427
439
|
var init_desktopDriver = __esm({
|
|
428
440
|
"src/shared/drivers/desktopDriver.ts"() {
|
|
@@ -501,6 +513,11 @@ ${this.processStderr.trim() || "(no stderr output)"}`
|
|
|
501
513
|
);
|
|
502
514
|
}
|
|
503
515
|
console.log(`[DesktopDriver] Launching: ${resolvedExe}`);
|
|
516
|
+
const finalArgs = [...this.args];
|
|
517
|
+
const hasDebugPort = finalArgs.some((a) => a.startsWith("--remote-debugging-port="));
|
|
518
|
+
if (!hasDebugPort) {
|
|
519
|
+
finalArgs.push(`--remote-debugging-port=${this.port}`);
|
|
520
|
+
}
|
|
504
521
|
const mergedEnv = {
|
|
505
522
|
...process.env,
|
|
506
523
|
...this.env,
|
|
@@ -509,6 +526,16 @@ ${this.processStderr.trim() || "(no stderr output)"}`
|
|
|
509
526
|
this.processStderr = "";
|
|
510
527
|
this.processExited = false;
|
|
511
528
|
this.exitCode = null;
|
|
529
|
+
this.childProcess = spawn(resolvedExe, finalArgs, {
|
|
530
|
+
env: mergedEnv,
|
|
531
|
+
cwd: this.cwd || path3.dirname(resolvedExe),
|
|
532
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
533
|
+
detached: false
|
|
534
|
+
});
|
|
535
|
+
;
|
|
536
|
+
this.processStderr = "";
|
|
537
|
+
this.processExited = false;
|
|
538
|
+
this.exitCode = null;
|
|
512
539
|
this.childProcess = spawn(resolvedExe, this.args, {
|
|
513
540
|
env: mergedEnv,
|
|
514
541
|
cwd: this.cwd || path3.dirname(resolvedExe),
|
|
@@ -552,16 +579,12 @@ ${this.processStderr.trim() || "(no stderr output)"}`
|
|
|
552
579
|
});
|
|
553
580
|
this.browser = null;
|
|
554
581
|
}
|
|
555
|
-
if (this.childProcess && !this.childProcess.killed) {
|
|
556
|
-
console.log("[DesktopDriver] Terminating spawned desktop process...");
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
this.childProcess.kill("SIGTERM");
|
|
562
|
-
}
|
|
563
|
-
} catch {
|
|
564
|
-
}
|
|
582
|
+
if (this.childProcess && !this.childProcess.killed && this.childProcess.pid) {
|
|
583
|
+
console.log("[DesktopDriver] Terminating spawned desktop process tree...");
|
|
584
|
+
const pid = this.childProcess.pid;
|
|
585
|
+
await new Promise((resolve) => {
|
|
586
|
+
treeKill(pid, "SIGTERM", () => resolve());
|
|
587
|
+
});
|
|
565
588
|
this.childProcess = null;
|
|
566
589
|
}
|
|
567
590
|
}
|
|
@@ -600,42 +623,45 @@ function generateMockIpcScript(registry) {
|
|
|
600
623
|
}
|
|
601
624
|
};
|
|
602
625
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
window.external
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
const mock = window.__visualRunnerMocks[action];
|
|
626
|
+
// Safe fallback bridge for hybrid webviews (Photino / CEF / WebView2)
|
|
627
|
+
try {
|
|
628
|
+
if (!window.external) {
|
|
629
|
+
(window as any).external = {};
|
|
630
|
+
}
|
|
631
|
+
(window.external as any).sendMessage = (msg) => {
|
|
632
|
+
try {
|
|
633
|
+
const parsed = typeof msg === 'string' ? JSON.parse(msg) : msg;
|
|
634
|
+
const action = parsed.Action || parsed.action;
|
|
635
|
+
const id = parsed.Id || parsed.id;
|
|
636
|
+
const mock = (window as any).__visualRunnerMocks[action];
|
|
615
637
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
638
|
+
if (mock) {
|
|
639
|
+
const response = { Id: id, Type: mock.type || 'SUCCESS', Data: mock.data };
|
|
640
|
+
setTimeout(() => {
|
|
641
|
+
const cb = (window as any).__mockCallback;
|
|
642
|
+
if (typeof cb === 'function') cb(JSON.stringify(response));
|
|
643
|
+
}, mock.delayMs || 20);
|
|
644
|
+
} else {
|
|
645
|
+
setTimeout(() => {
|
|
646
|
+
const cb = (window as any).__mockCallback;
|
|
647
|
+
if (typeof cb === 'function') cb(JSON.stringify({ Id: id, Type: 'SUCCESS', Data: null }));
|
|
648
|
+
}, 20);
|
|
649
|
+
}
|
|
650
|
+
} catch (e) {
|
|
651
|
+
console.error('[Mock IPC] Failed to process message:', e);
|
|
627
652
|
}
|
|
628
|
-
}
|
|
629
|
-
console.error('[Mock IPC] Failed to process legacy message:', e);
|
|
630
|
-
}
|
|
631
|
-
};
|
|
653
|
+
};
|
|
632
654
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
655
|
+
(window.external as any).receiveMessage = (callback) => {
|
|
656
|
+
(window as any).__mockCallback = callback;
|
|
657
|
+
};
|
|
658
|
+
} catch {
|
|
659
|
+
// Ignored if window.external is read-only in strict Chromium sandboxes
|
|
660
|
+
}
|
|
636
661
|
|
|
637
|
-
console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys(window.__visualRunnerMocks).length, 'mocked actions');
|
|
662
|
+
console.log('[Visual Runner] Mock IPC bridge initialized with', Object.keys((window as any).__visualRunnerMocks).length, 'mocked actions');
|
|
638
663
|
})();
|
|
664
|
+
|
|
639
665
|
`;
|
|
640
666
|
}
|
|
641
667
|
var MockIpcRegistry;
|
|
@@ -685,22 +711,22 @@ var init_mockIpc = __esm({
|
|
|
685
711
|
});
|
|
686
712
|
|
|
687
713
|
// src/shared/lib/config.ts
|
|
688
|
-
import fs4 from "fs";
|
|
689
714
|
import path4 from "path";
|
|
715
|
+
import fs4 from "fs";
|
|
690
716
|
function loadConfig(cwd = process.cwd()) {
|
|
691
|
-
const
|
|
692
|
-
if (fs4.existsSync(
|
|
717
|
+
const jsonConfigPath = path4.join(cwd, "agent-lens.json");
|
|
718
|
+
if (fs4.existsSync(jsonConfigPath)) {
|
|
693
719
|
try {
|
|
694
|
-
const raw = fs4.readFileSync(
|
|
720
|
+
const raw = fs4.readFileSync(jsonConfigPath, "utf-8");
|
|
695
721
|
return JSON.parse(raw);
|
|
696
722
|
} catch (e) {
|
|
697
|
-
console.warn(`\u26A0\uFE0F
|
|
723
|
+
console.warn(`\u26A0\uFE0F [Config] Failed to parse agent-lens.json: ${e.message}`);
|
|
698
724
|
}
|
|
699
725
|
}
|
|
700
|
-
const
|
|
701
|
-
if (fs4.existsSync(
|
|
726
|
+
const packageJsonPath = path4.join(cwd, "package.json");
|
|
727
|
+
if (fs4.existsSync(packageJsonPath)) {
|
|
702
728
|
try {
|
|
703
|
-
const raw = fs4.readFileSync(
|
|
729
|
+
const raw = fs4.readFileSync(packageJsonPath, "utf-8");
|
|
704
730
|
const pkg = JSON.parse(raw);
|
|
705
731
|
if (pkg.agentLens && typeof pkg.agentLens === "object") {
|
|
706
732
|
return pkg.agentLens;
|
|
@@ -710,78 +736,60 @@ function loadConfig(cwd = process.cwd()) {
|
|
|
710
736
|
}
|
|
711
737
|
return {};
|
|
712
738
|
}
|
|
713
|
-
function
|
|
714
|
-
if (
|
|
715
|
-
|
|
739
|
+
function detectStartCwd(providedCwd) {
|
|
740
|
+
if (providedCwd) {
|
|
741
|
+
const resolved = path4.resolve(process.cwd(), providedCwd);
|
|
742
|
+
if (fs4.existsSync(resolved)) {
|
|
743
|
+
return providedCwd;
|
|
744
|
+
}
|
|
716
745
|
}
|
|
717
|
-
const
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
"web/dist",
|
|
725
|
-
"ui/dist"
|
|
726
|
-
];
|
|
727
|
-
for (const rel of standardDirs) {
|
|
728
|
-
const candidate = path4.resolve(cwd, rel);
|
|
729
|
-
if (fs4.existsSync(candidate) && fs4.existsSync(path4.join(candidate, "index.html"))) {
|
|
730
|
-
return candidate;
|
|
746
|
+
const rootPkgPath = path4.join(process.cwd(), "package.json");
|
|
747
|
+
let rootHasDevScript = false;
|
|
748
|
+
if (fs4.existsSync(rootPkgPath)) {
|
|
749
|
+
try {
|
|
750
|
+
const rootPkg = JSON.parse(fs4.readFileSync(rootPkgPath, "utf-8"));
|
|
751
|
+
rootHasDevScript = Boolean(rootPkg.scripts?.dev || rootPkg.scripts?.start);
|
|
752
|
+
} catch {
|
|
731
753
|
}
|
|
732
754
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
return foundDeepWwwroot;
|
|
755
|
+
if (rootHasDevScript) {
|
|
756
|
+
return void 0;
|
|
736
757
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
758
|
+
const candidates = ["Frontend", "frontend", "client", "web", "ui", "apps/web", "src/frontend"];
|
|
759
|
+
for (const candidate of candidates) {
|
|
760
|
+
const candidatePkg = path4.join(process.cwd(), candidate, "package.json");
|
|
761
|
+
if (fs4.existsSync(candidatePkg)) {
|
|
762
|
+
return `./${candidate}`;
|
|
741
763
|
}
|
|
742
764
|
}
|
|
743
|
-
return
|
|
765
|
+
return void 0;
|
|
744
766
|
}
|
|
745
|
-
function
|
|
746
|
-
if (
|
|
747
|
-
return path4.resolve(
|
|
767
|
+
function resolveWwwrootDir(customDir) {
|
|
768
|
+
if (customDir) {
|
|
769
|
+
return path4.resolve(process.cwd(), customDir);
|
|
748
770
|
}
|
|
749
|
-
const
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
771
|
+
const candidates = [
|
|
772
|
+
"dist",
|
|
773
|
+
"build",
|
|
774
|
+
"out",
|
|
775
|
+
"wwwroot",
|
|
776
|
+
"Frontend/dist",
|
|
777
|
+
"frontend/dist",
|
|
778
|
+
"client/dist"
|
|
779
|
+
];
|
|
780
|
+
for (const c of candidates) {
|
|
781
|
+
const candidatePath = path4.resolve(process.cwd(), c);
|
|
782
|
+
if (fs4.existsSync(candidatePath) && fs4.existsSync(path4.join(candidatePath, "index.html"))) {
|
|
783
|
+
return candidatePath;
|
|
760
784
|
}
|
|
761
785
|
}
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
try {
|
|
767
|
-
const entries = fs4.readdirSync(dir, { withFileTypes: true });
|
|
768
|
-
for (const entry of entries) {
|
|
769
|
-
if (entry.isDirectory()) {
|
|
770
|
-
if (entry.name === "node_modules" || entry.name === ".git" || entry.name === "artifacts") {
|
|
771
|
-
continue;
|
|
772
|
-
}
|
|
773
|
-
const subDir = path4.join(dir, entry.name);
|
|
774
|
-
if (entry.name === "wwwroot" && fs4.existsSync(path4.join(subDir, "index.html"))) {
|
|
775
|
-
return subDir;
|
|
776
|
-
}
|
|
777
|
-
const found = findDeepIndexHtmlDir(subDir, maxDepth, currentDepth + 1);
|
|
778
|
-
if (found) return found;
|
|
779
|
-
}
|
|
786
|
+
for (const c of candidates) {
|
|
787
|
+
const candidatePath = path4.resolve(process.cwd(), c);
|
|
788
|
+
if (fs4.existsSync(candidatePath)) {
|
|
789
|
+
return candidatePath;
|
|
780
790
|
}
|
|
781
|
-
} catch {
|
|
782
|
-
return null;
|
|
783
791
|
}
|
|
784
|
-
return
|
|
792
|
+
return path4.resolve(process.cwd(), "dist");
|
|
785
793
|
}
|
|
786
794
|
var init_config = __esm({
|
|
787
795
|
"src/shared/lib/config.ts"() {
|
|
@@ -823,6 +831,7 @@ var init_previewDriver = __esm({
|
|
|
823
831
|
serverPort = 0;
|
|
824
832
|
options;
|
|
825
833
|
_baseUrl = "";
|
|
834
|
+
initialRouteMocks = [];
|
|
826
835
|
mockRegistry;
|
|
827
836
|
constructor(options) {
|
|
828
837
|
this.options = options || {};
|
|
@@ -892,9 +901,41 @@ var init_previewDriver = __esm({
|
|
|
892
901
|
await this.context.addInitScript(mockScript);
|
|
893
902
|
}
|
|
894
903
|
this.page = await this.context.newPage();
|
|
904
|
+
if (this.initialRouteMocks.length > 0) {
|
|
905
|
+
for (const entry of this.initialRouteMocks) {
|
|
906
|
+
await this.addRouteMock(entry);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
895
909
|
await this.page.goto(targetUrl, { waitUntil: "domcontentloaded" });
|
|
896
910
|
return { page: this.page, context: this.context, browser: this.browser };
|
|
897
911
|
}
|
|
912
|
+
async addRouteMock(entry) {
|
|
913
|
+
if (!this.page) {
|
|
914
|
+
this.initialRouteMocks.push(entry);
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
await this.page.route(entry.url, async (route) => {
|
|
918
|
+
const req = route.request();
|
|
919
|
+
if (entry.method && req.method().toUpperCase() !== entry.method.toUpperCase()) {
|
|
920
|
+
return route.continue();
|
|
921
|
+
}
|
|
922
|
+
if (entry.delayMs) {
|
|
923
|
+
await new Promise((r) => setTimeout(r, entry.delayMs));
|
|
924
|
+
}
|
|
925
|
+
const isJson = typeof entry.body === "object" && entry.body !== null;
|
|
926
|
+
await route.fulfill({
|
|
927
|
+
status: entry.status ?? 200,
|
|
928
|
+
contentType: isJson ? "application/json" : "text/plain; charset=utf-8",
|
|
929
|
+
body: isJson ? JSON.stringify(entry.body) : String(entry.body ?? ""),
|
|
930
|
+
headers: entry.headers
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
async setupRouteMocks(routes) {
|
|
935
|
+
for (const r of routes) {
|
|
936
|
+
await this.addRouteMock(r);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
898
939
|
async updateMockIpc(action, data, options) {
|
|
899
940
|
if (!this.page) {
|
|
900
941
|
throw new Error("PreviewDriver not started. Call start() first.");
|
|
@@ -941,108 +982,79 @@ var init_previewDriver = __esm({
|
|
|
941
982
|
|
|
942
983
|
// src/shared/lib/processManager.ts
|
|
943
984
|
import { spawn as spawn2 } from "child_process";
|
|
944
|
-
import
|
|
985
|
+
import treeKill2 from "tree-kill";
|
|
945
986
|
var ProcessManager;
|
|
946
987
|
var init_processManager = __esm({
|
|
947
988
|
"src/shared/lib/processManager.ts"() {
|
|
948
989
|
"use strict";
|
|
949
990
|
ProcessManager = class {
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
stdoutOutput = "";
|
|
953
|
-
hasExited = false;
|
|
954
|
-
exitCode = null;
|
|
955
|
-
/**
|
|
956
|
-
* Spawns a background process (e.g. dev server, backend, or app)
|
|
957
|
-
*/
|
|
991
|
+
childProcess = null;
|
|
992
|
+
isStopped = false;
|
|
958
993
|
async start(command, options) {
|
|
959
|
-
this.stderrOutput = "";
|
|
960
|
-
this.stdoutOutput = "";
|
|
961
|
-
this.hasExited = false;
|
|
962
|
-
this.exitCode = null;
|
|
963
994
|
const cwd = options?.cwd || process.cwd();
|
|
964
|
-
const env = { ...process.env, ...options?.env };
|
|
965
995
|
console.log(`\u{1F680} [ProcessManager] Starting command: "${command}" in ${cwd}`);
|
|
966
|
-
this.
|
|
996
|
+
this.childProcess = spawn2(command, {
|
|
967
997
|
cwd,
|
|
968
|
-
env,
|
|
969
|
-
shell:
|
|
998
|
+
env: { ...process.env, ...options?.env },
|
|
999
|
+
shell: true,
|
|
970
1000
|
stdio: ["ignore", "pipe", "pipe"]
|
|
971
1001
|
});
|
|
972
|
-
this.
|
|
973
|
-
const
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
this.child.stderr?.on("data", (chunk) => {
|
|
977
|
-
const str = chunk.toString();
|
|
978
|
-
this.stderrOutput += str;
|
|
1002
|
+
this.childProcess.stdout?.on("data", (chunk) => {
|
|
1003
|
+
const line = chunk.toString().trim();
|
|
1004
|
+
if (line) {
|
|
1005
|
+
}
|
|
979
1006
|
});
|
|
980
|
-
this.
|
|
981
|
-
|
|
982
|
-
|
|
1007
|
+
this.childProcess.stderr?.on("data", (chunk) => {
|
|
1008
|
+
const line = chunk.toString().trim();
|
|
1009
|
+
if (line && !line.includes("ExperimentalWarning")) {
|
|
1010
|
+
}
|
|
983
1011
|
});
|
|
984
|
-
this.
|
|
985
|
-
|
|
1012
|
+
this.childProcess.on("exit", (code, signal) => {
|
|
1013
|
+
if (!this.isStopped && code !== 0 && code !== null) {
|
|
1014
|
+
console.warn(`\u26A0\uFE0F [ProcessManager] Subprocess exited prematurely with code ${code}, signal ${signal}`);
|
|
1015
|
+
}
|
|
986
1016
|
});
|
|
987
1017
|
}
|
|
988
|
-
/**
|
|
989
|
-
* Polls a URL until it starts responding or until timeout is reached.
|
|
990
|
-
*/
|
|
991
1018
|
async waitForUrl(url, timeoutMs = 3e4) {
|
|
1019
|
+
console.log(`\u23F3 [ProcessManager] Waiting for ${url} to respond...`);
|
|
992
1020
|
const startTime = Date.now();
|
|
993
|
-
console.log(`\u23F3 [ProcessManager] Waiting for URL to become available: ${url} (timeout: ${timeoutMs / 1e3}s)...`);
|
|
994
1021
|
while (Date.now() - startTime < timeoutMs) {
|
|
995
|
-
if (this.
|
|
1022
|
+
if (this.childProcess && this.childProcess.exitCode !== null) {
|
|
996
1023
|
throw new Error(
|
|
997
|
-
`[ProcessManager]
|
|
998
|
-
Stderr:
|
|
999
|
-
${this.stderrOutput.trim() || "(no stderr)"}
|
|
1000
|
-
Stdout:
|
|
1001
|
-
${this.stdoutOutput.slice(-500).trim()}`
|
|
1024
|
+
`[ProcessManager] Server process exited with code ${this.childProcess.exitCode} while waiting for ${url}`
|
|
1002
1025
|
);
|
|
1003
1026
|
}
|
|
1004
1027
|
try {
|
|
1005
|
-
const response = await fetch(url, {
|
|
1028
|
+
const response = await fetch(url, { signal: AbortSignal.timeout(1e3) });
|
|
1006
1029
|
if (response.status) {
|
|
1007
|
-
console.log(`\u2705 [ProcessManager]
|
|
1008
|
-
return;
|
|
1030
|
+
console.log(`\u2705 [ProcessManager] Target ${url} is ready (status: ${response.status})!`);
|
|
1031
|
+
return true;
|
|
1009
1032
|
}
|
|
1010
1033
|
} catch {
|
|
1011
1034
|
}
|
|
1012
|
-
await new Promise((
|
|
1035
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
1013
1036
|
}
|
|
1014
|
-
throw new Error(
|
|
1015
|
-
`[ProcessManager] Timeout (${timeoutMs / 1e3}s) waiting for server at ${url}.
|
|
1016
|
-
Last stdout:
|
|
1017
|
-
${this.stdoutOutput.slice(-500).trim()}
|
|
1018
|
-
Last stderr:
|
|
1019
|
-
${this.stderrOutput.trim()}`
|
|
1020
|
-
);
|
|
1037
|
+
throw new Error(`[ProcessManager] Timeout after ${timeoutMs}ms waiting for ${url} to respond.`);
|
|
1021
1038
|
}
|
|
1022
|
-
/**
|
|
1023
|
-
* Gracefully and forcefully kills the process and all its children.
|
|
1024
|
-
*/
|
|
1025
1039
|
async stop() {
|
|
1026
|
-
if (
|
|
1027
|
-
this.child = null;
|
|
1040
|
+
if (this.isStopped || !this.childProcess || !this.childProcess.pid) {
|
|
1028
1041
|
return;
|
|
1029
1042
|
}
|
|
1030
|
-
|
|
1031
|
-
|
|
1043
|
+
this.isStopped = true;
|
|
1044
|
+
const pid = this.childProcess.pid;
|
|
1045
|
+
console.log(`\u{1F6D1} [ProcessManager] Terminating process tree for PID ${pid}...`);
|
|
1032
1046
|
await new Promise((resolve) => {
|
|
1033
|
-
|
|
1047
|
+
treeKill2(pid, "SIGTERM", (err) => {
|
|
1034
1048
|
if (err) {
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
} catch {
|
|
1039
|
-
}
|
|
1049
|
+
try {
|
|
1050
|
+
treeKill2(pid, "SIGKILL");
|
|
1051
|
+
} catch {
|
|
1040
1052
|
}
|
|
1041
1053
|
}
|
|
1042
1054
|
resolve();
|
|
1043
1055
|
});
|
|
1044
1056
|
});
|
|
1045
|
-
this.
|
|
1057
|
+
this.childProcess = null;
|
|
1046
1058
|
}
|
|
1047
1059
|
};
|
|
1048
1060
|
}
|
|
@@ -1117,9 +1129,16 @@ async function runVisualScenario(options) {
|
|
|
1117
1129
|
console.log(`\u{1F4E6} [Mock IPC] Applying ${mergedMocks.length} mocks`);
|
|
1118
1130
|
previewDriver.mockRegistry.setBatch(mergedMocks);
|
|
1119
1131
|
}
|
|
1132
|
+
if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
|
|
1133
|
+
console.log(`\u{1F310} [Mock Network] Queuing ${scenario.mockRoutes.length} route mock(s)`);
|
|
1134
|
+
await previewDriver.setupRouteMocks(scenario.mockRoutes);
|
|
1135
|
+
}
|
|
1120
1136
|
const res = await previewDriver.start(currentViewport);
|
|
1121
1137
|
page = res.page;
|
|
1122
1138
|
context = res.context;
|
|
1139
|
+
if (scenario.mockRoutes && scenario.mockRoutes.length > 0) {
|
|
1140
|
+
await previewDriver.setupRouteMocks(scenario.mockRoutes);
|
|
1141
|
+
}
|
|
1123
1142
|
}
|
|
1124
1143
|
consoleTracker.attach(page);
|
|
1125
1144
|
console.log(`\u{1F50D} [Console Tracker] Attached \u2014 errors and warnings will be captured
|
|
@@ -1265,6 +1284,25 @@ async function runVisualScenario(options) {
|
|
|
1265
1284
|
console.log(`\u{1F4E6} [Mock IPC] Set ${action} -> ${typeof data === "string" ? data : JSON.stringify(data).slice(0, 80)}...`);
|
|
1266
1285
|
await previewDriver.updateMockIpc(action, data, mockOptions);
|
|
1267
1286
|
},
|
|
1287
|
+
setMockRoute: async (url, body, routeOptions) => {
|
|
1288
|
+
if (targetMode === "desktop") {
|
|
1289
|
+
console.log(`\u26A0\uFE0F [Mock Route] setMockRoute ignored in desktop mode`);
|
|
1290
|
+
return;
|
|
1291
|
+
}
|
|
1292
|
+
if (!previewDriver) {
|
|
1293
|
+
console.log(`\u26A0\uFE0F [Mock Route] PreviewDriver not available`);
|
|
1294
|
+
return;
|
|
1295
|
+
}
|
|
1296
|
+
console.log(`\u{1F310} [Mock Route] Intercepting ${routeOptions?.method || "ALL"} ${url} -> ${routeOptions?.status ?? 200}`);
|
|
1297
|
+
await previewDriver.addRouteMock({
|
|
1298
|
+
url,
|
|
1299
|
+
body,
|
|
1300
|
+
method: routeOptions?.method,
|
|
1301
|
+
status: routeOptions?.status,
|
|
1302
|
+
delayMs: routeOptions?.delayMs,
|
|
1303
|
+
headers: routeOptions?.headers
|
|
1304
|
+
});
|
|
1305
|
+
},
|
|
1268
1306
|
getConsoleErrors: () => consoleTracker.getErrors(),
|
|
1269
1307
|
getConsoleWarnings: () => consoleTracker.getWarnings(),
|
|
1270
1308
|
hasConsoleErrors: () => consoleTracker.hasErrors,
|
|
@@ -1464,17 +1502,24 @@ async function isPortResponding(url) {
|
|
|
1464
1502
|
return false;
|
|
1465
1503
|
}
|
|
1466
1504
|
}
|
|
1505
|
+
async function detectActiveDevServer() {
|
|
1506
|
+
for (const port of CANDIDATE_PORTS) {
|
|
1507
|
+
const url = `http://localhost:${port}`;
|
|
1508
|
+
if (await isPortResponding(url)) {
|
|
1509
|
+
return url;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return null;
|
|
1513
|
+
}
|
|
1467
1514
|
async function runQuickSnap(options) {
|
|
1468
1515
|
let targetUrl = options.url;
|
|
1469
1516
|
let useStaticPreview = false;
|
|
1470
1517
|
let wwwrootDir;
|
|
1471
1518
|
if (!targetUrl && !options.start) {
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
targetUrl = "http://localhost:3000";
|
|
1477
|
-
console.log(`\u{1F310} [Quick Snap] Detected active dev server on http://localhost:3000`);
|
|
1519
|
+
const activeUrl = await detectActiveDevServer();
|
|
1520
|
+
if (activeUrl) {
|
|
1521
|
+
targetUrl = activeUrl;
|
|
1522
|
+
console.log(`\u{1F310} [Quick Snap] Detected active dev server on ${targetUrl}`);
|
|
1478
1523
|
} else {
|
|
1479
1524
|
const candidateDir = resolveWwwrootDir();
|
|
1480
1525
|
if (fs7.existsSync(candidateDir) && fs7.existsSync(path7.join(candidateDir, "index.html"))) {
|
|
@@ -1483,7 +1528,7 @@ async function runQuickSnap(options) {
|
|
|
1483
1528
|
console.log(`\u{1F4E6} [Quick Snap] No active server found. Detected built static directory at "${candidateDir}". Launching static preview...`);
|
|
1484
1529
|
} else {
|
|
1485
1530
|
console.error(`
|
|
1486
|
-
\u274C [Quick Snap] No active server found on
|
|
1531
|
+
\u274C [Quick Snap] No active server found on common ports (${CANDIDATE_PORTS.join(", ")}), and no static build found.`);
|
|
1487
1532
|
console.log(`\u{1F4A1} Suggested actions:`);
|
|
1488
1533
|
console.log(` 1. Pass a start command: npx agent-lens snap --start="npm run dev"`);
|
|
1489
1534
|
console.log(` 2. Specify your URL: npx agent-lens snap --url=http://localhost:8080`);
|
|
@@ -1565,7 +1610,7 @@ async function runQuickSnap(options) {
|
|
|
1565
1610
|
`);
|
|
1566
1611
|
return result.success && result.consoleErrors === 0;
|
|
1567
1612
|
}
|
|
1568
|
-
var PRESET_MAP;
|
|
1613
|
+
var PRESET_MAP, CANDIDATE_PORTS;
|
|
1569
1614
|
var init_snap = __esm({
|
|
1570
1615
|
"src/features/snap/snap.ts"() {
|
|
1571
1616
|
"use strict";
|
|
@@ -1582,12 +1627,14 @@ var init_snap = __esm({
|
|
|
1582
1627
|
tablet: { name: "tablet", width: 768, height: 1024 },
|
|
1583
1628
|
"full-hd": VIEWPORT_PRESETS.FULL_HD
|
|
1584
1629
|
};
|
|
1630
|
+
CANDIDATE_PORTS = [5173, 3e3, 4321, 4200, 8080, 8e3, 3001];
|
|
1585
1631
|
}
|
|
1586
1632
|
});
|
|
1587
1633
|
|
|
1588
1634
|
// src/app/cli.ts
|
|
1589
1635
|
import path8 from "path";
|
|
1590
1636
|
import fs8 from "fs";
|
|
1637
|
+
import { execSync } from "child_process";
|
|
1591
1638
|
import { createJiti } from "jiti";
|
|
1592
1639
|
var require_cli = __commonJS({
|
|
1593
1640
|
"src/app/cli.ts"() {
|
|
@@ -1651,15 +1698,19 @@ var require_cli = __commonJS({
|
|
|
1651
1698
|
} else if (arg.startsWith("--selector=")) {
|
|
1652
1699
|
options.selector = arg.split("=")[1];
|
|
1653
1700
|
} else if (arg.startsWith("--viewports=")) {
|
|
1654
|
-
|
|
1701
|
+
const raw = arg.slice("--viewports=".length);
|
|
1702
|
+
options.viewports = raw.split(",").map((v) => v.trim()).filter(Boolean);
|
|
1655
1703
|
} else if (arg.startsWith("--wait=")) {
|
|
1656
|
-
options.waitMs = parseInt(arg.
|
|
1704
|
+
options.waitMs = parseInt(arg.slice("--wait=".length), 10);
|
|
1657
1705
|
} else if (arg.startsWith("--name=")) {
|
|
1658
|
-
options.name = arg.
|
|
1659
|
-
} else if (arg.startsWith("--exe=")
|
|
1660
|
-
options.exe = arg.
|
|
1706
|
+
options.name = arg.slice("--name=".length);
|
|
1707
|
+
} else if (arg.startsWith("--exe=")) {
|
|
1708
|
+
options.exe = arg.slice("--exe=".length);
|
|
1709
|
+
} else if (arg.startsWith("--executable=")) {
|
|
1710
|
+
options.exe = arg.slice("--executable=".length);
|
|
1661
1711
|
} else if (arg.startsWith("--clean=") || arg.startsWith("--cleanup=")) {
|
|
1662
|
-
const
|
|
1712
|
+
const prefix = arg.startsWith("--clean=") ? "--clean=" : "--cleanup=";
|
|
1713
|
+
const rawPaths = arg.slice(prefix.length);
|
|
1663
1714
|
options.clean = rawPaths.split(",").map((p) => p.trim()).filter(Boolean);
|
|
1664
1715
|
} else if (arg.startsWith("--dir=")) {
|
|
1665
1716
|
options.dir = arg.split("=")[1];
|
|
@@ -1845,7 +1896,6 @@ export default [
|
|
|
1845
1896
|
const buildCmd = typeof options.build === "string" ? options.build : fileConfig.buildCommand || "npm run build";
|
|
1846
1897
|
console.log(`
|
|
1847
1898
|
\u{1F528} [Build] Running build process: "${buildCmd}"...`);
|
|
1848
|
-
const { execSync } = __require("child_process");
|
|
1849
1899
|
try {
|
|
1850
1900
|
execSync(buildCmd, { stdio: "inherit", cwd: options.startCwd || process.cwd() });
|
|
1851
1901
|
} catch (e) {
|