@ulpi/browse 2.3.1 → 2.3.3
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/dist/browse.cjs +368 -319
- package/package.json +1 -1
package/dist/browse.cjs
CHANGED
|
@@ -864,7 +864,7 @@ var require_package = __commonJS({
|
|
|
864
864
|
"package.json"(exports2, module2) {
|
|
865
865
|
module2.exports = {
|
|
866
866
|
name: "@ulpi/browse",
|
|
867
|
-
version: "2.3.
|
|
867
|
+
version: "2.3.3",
|
|
868
868
|
homepage: "https://browse.ulpi.io",
|
|
869
869
|
repository: {
|
|
870
870
|
type: "git",
|
|
@@ -1089,13 +1089,19 @@ __export(bridge_exports, {
|
|
|
1089
1089
|
installAdb: () => installAdb
|
|
1090
1090
|
});
|
|
1091
1091
|
function resolveDriverApkPath() {
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
"../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1092
|
+
const candidates = [
|
|
1093
|
+
// 1. Local dev build
|
|
1094
|
+
path7.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk"),
|
|
1095
|
+
// 2. Installed alongside source (bin/ at project root)
|
|
1096
|
+
path7.resolve(__dirname2, "../../bin/browse-android.apk"),
|
|
1097
|
+
// 3. Bundled build (dist/browse.cjs → ../bin/)
|
|
1098
|
+
path7.resolve(__dirname2, "../bin/browse-android.apk"),
|
|
1099
|
+
// 4. Same directory as binary
|
|
1100
|
+
path7.resolve(__dirname2, "browse-android.apk")
|
|
1101
|
+
];
|
|
1102
|
+
for (const p of candidates) {
|
|
1103
|
+
if (fs7.existsSync(p)) return p;
|
|
1104
|
+
}
|
|
1099
1105
|
const lazyPath = path7.join(
|
|
1100
1106
|
process.env.BROWSE_LOCAL_DIR || path7.join(process.cwd(), ".browse"),
|
|
1101
1107
|
"bin",
|
|
@@ -1103,9 +1109,21 @@ function resolveDriverApkPath() {
|
|
|
1103
1109
|
);
|
|
1104
1110
|
if (fs7.existsSync(lazyPath)) return lazyPath;
|
|
1105
1111
|
throw new Error(
|
|
1106
|
-
"browse-android APK not found.
|
|
1112
|
+
"browse-android APK not found. Run: browse enable android\nOr build manually: cd browse-android && ./gradlew :app:assembleDebugAndroidTest"
|
|
1107
1113
|
);
|
|
1108
1114
|
}
|
|
1115
|
+
function resolveAppApkPath() {
|
|
1116
|
+
const candidates = [
|
|
1117
|
+
path7.resolve(__dirname2, "../../../browse-android/app/build/outputs/apk/debug/app-debug.apk"),
|
|
1118
|
+
path7.resolve(__dirname2, "../../bin/browse-android-app.apk"),
|
|
1119
|
+
path7.resolve(__dirname2, "../bin/browse-android-app.apk"),
|
|
1120
|
+
path7.resolve(__dirname2, "browse-android-app.apk")
|
|
1121
|
+
];
|
|
1122
|
+
for (const p of candidates) {
|
|
1123
|
+
if (fs7.existsSync(p)) return p;
|
|
1124
|
+
}
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1109
1127
|
function adbExec(serial, ...args) {
|
|
1110
1128
|
return (0, import_child_process3.execSync)(["adb", "-s", serial, ...args].join(" "), {
|
|
1111
1129
|
encoding: "utf-8",
|
|
@@ -1242,7 +1260,7 @@ function installDriverApk(serial, apkPath) {
|
|
|
1242
1260
|
if (installedBase?.includes(`package:${DRIVER_PACKAGE}`) && installedTest?.includes(DRIVER_TEST_PACKAGE)) {
|
|
1243
1261
|
return;
|
|
1244
1262
|
}
|
|
1245
|
-
const appApkPath = apkPath.replace("-androidTest", "");
|
|
1263
|
+
const appApkPath = resolveAppApkPath() || apkPath.replace("-androidTest", "");
|
|
1246
1264
|
if (fs7.existsSync(appApkPath)) {
|
|
1247
1265
|
try {
|
|
1248
1266
|
adbExec(serial, "install", "-t", "-r", `"${appApkPath}"`);
|
|
@@ -2486,7 +2504,12 @@ async function startIOS(opts = {}) {
|
|
|
2486
2504
|
} catch {
|
|
2487
2505
|
}
|
|
2488
2506
|
}
|
|
2489
|
-
const
|
|
2507
|
+
const runnerCandidates = [
|
|
2508
|
+
path10.resolve(__dirname_svc, "../../../browse-ios-runner"),
|
|
2509
|
+
path10.resolve(__dirname_svc, "../../browse-ios-runner"),
|
|
2510
|
+
path10.resolve(__dirname_svc, "../bin/browse-ios-runner")
|
|
2511
|
+
];
|
|
2512
|
+
const runnerDir = runnerCandidates.find((d) => fs10.existsSync(path10.join(d, "project.yml"))) || runnerCandidates[0];
|
|
2490
2513
|
const { execSync: execSync7 } = await import("child_process");
|
|
2491
2514
|
if (!fs10.existsSync(path10.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
|
|
2492
2515
|
log2("Generating Xcode project...");
|
|
@@ -2661,158 +2684,282 @@ var init_sim_cli = __esm({
|
|
|
2661
2684
|
}
|
|
2662
2685
|
});
|
|
2663
2686
|
|
|
2687
|
+
// src/app/macos/bridge.ts
|
|
2688
|
+
var bridge_exports3 = {};
|
|
2689
|
+
__export(bridge_exports3, {
|
|
2690
|
+
createMacOSBridge: () => createMacOSBridge,
|
|
2691
|
+
ensureMacOSBridge: () => ensureMacOSBridge,
|
|
2692
|
+
resolveBridgePath: () => resolveBridgePath
|
|
2693
|
+
});
|
|
2694
|
+
function resolveBridgePath() {
|
|
2695
|
+
const candidates = [
|
|
2696
|
+
// 1. Local dev build
|
|
2697
|
+
path11.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax"),
|
|
2698
|
+
path11.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax"),
|
|
2699
|
+
// 2. Installed alongside source (bin/ at project root)
|
|
2700
|
+
path11.resolve(__dirname_bridge, "../../bin/browse-ax"),
|
|
2701
|
+
// 3. Bundled build (dist/browse.cjs → ../bin/)
|
|
2702
|
+
path11.resolve(__dirname_bridge, "../bin/browse-ax")
|
|
2703
|
+
];
|
|
2704
|
+
for (const p of candidates) {
|
|
2705
|
+
if (fs11.existsSync(p)) return p;
|
|
2706
|
+
}
|
|
2707
|
+
const lazyPath = path11.join(
|
|
2708
|
+
process.env.BROWSE_LOCAL_DIR || path11.join(process.cwd(), ".browse"),
|
|
2709
|
+
"bin",
|
|
2710
|
+
"browse-ax"
|
|
2711
|
+
);
|
|
2712
|
+
if (fs11.existsSync(lazyPath)) return lazyPath;
|
|
2713
|
+
throw new Error(
|
|
2714
|
+
"browse-ax binary not found. Run: browse enable macos\nOr build manually: cd browse-ax && swift build -c release"
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
async function ensureMacOSBridge() {
|
|
2718
|
+
if (process.platform !== "darwin") {
|
|
2719
|
+
throw new Error("App automation requires macOS (uses Accessibility API)");
|
|
2720
|
+
}
|
|
2721
|
+
return resolveBridgePath();
|
|
2722
|
+
}
|
|
2723
|
+
async function execBridge(bridgePath, args) {
|
|
2724
|
+
return new Promise((resolve9, reject) => {
|
|
2725
|
+
const proc = (0, import_child_process8.spawn)(bridgePath, args, {
|
|
2726
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
2727
|
+
});
|
|
2728
|
+
const chunks = [];
|
|
2729
|
+
const errChunks = [];
|
|
2730
|
+
proc.stdout.on("data", (c) => chunks.push(c));
|
|
2731
|
+
proc.stderr.on("data", (c) => errChunks.push(c));
|
|
2732
|
+
proc.on("close", (code) => {
|
|
2733
|
+
const stdout = Buffer.concat(chunks).toString("utf-8").trim();
|
|
2734
|
+
const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
|
|
2735
|
+
if (code !== 0) {
|
|
2736
|
+
try {
|
|
2737
|
+
const err = JSON.parse(stderr || stdout);
|
|
2738
|
+
reject(new Error(err.error || `Bridge exited with code ${code}`));
|
|
2739
|
+
} catch {
|
|
2740
|
+
reject(new Error(stderr || `Bridge exited with code ${code}`));
|
|
2741
|
+
}
|
|
2742
|
+
return;
|
|
2743
|
+
}
|
|
2744
|
+
try {
|
|
2745
|
+
resolve9(JSON.parse(stdout));
|
|
2746
|
+
} catch {
|
|
2747
|
+
reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
|
|
2748
|
+
}
|
|
2749
|
+
});
|
|
2750
|
+
});
|
|
2751
|
+
}
|
|
2752
|
+
function createMacOSBridge(bridgePath, pid) {
|
|
2753
|
+
const base = ["--pid", String(pid)];
|
|
2754
|
+
return {
|
|
2755
|
+
async tree() {
|
|
2756
|
+
return execBridge(bridgePath, [...base, "tree"]);
|
|
2757
|
+
},
|
|
2758
|
+
async action(nodePath, actionName) {
|
|
2759
|
+
return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
|
|
2760
|
+
},
|
|
2761
|
+
async setValue(nodePath, value) {
|
|
2762
|
+
return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
|
|
2763
|
+
},
|
|
2764
|
+
async type(text) {
|
|
2765
|
+
return execBridge(bridgePath, [...base, "type", text]);
|
|
2766
|
+
},
|
|
2767
|
+
async press(key) {
|
|
2768
|
+
return execBridge(bridgePath, [...base, "press", key]);
|
|
2769
|
+
},
|
|
2770
|
+
async screenshot(outputPath) {
|
|
2771
|
+
return execBridge(bridgePath, [...base, "screenshot", outputPath]);
|
|
2772
|
+
},
|
|
2773
|
+
async state() {
|
|
2774
|
+
return execBridge(bridgePath, [...base, "state"]);
|
|
2775
|
+
}
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
var import_child_process8, path11, fs11, import_url5, __filename_bridge2, __dirname_bridge;
|
|
2779
|
+
var init_bridge3 = __esm({
|
|
2780
|
+
"src/app/macos/bridge.ts"() {
|
|
2781
|
+
"use strict";
|
|
2782
|
+
import_child_process8 = require("child_process");
|
|
2783
|
+
path11 = __toESM(require("path"), 1);
|
|
2784
|
+
fs11 = __toESM(require("fs"), 1);
|
|
2785
|
+
import_url5 = require("url");
|
|
2786
|
+
__filename_bridge2 = (0, import_url5.fileURLToPath)(__import_meta_url);
|
|
2787
|
+
__dirname_bridge = path11.dirname(__filename_bridge2);
|
|
2788
|
+
}
|
|
2789
|
+
});
|
|
2790
|
+
|
|
2664
2791
|
// src/enable.ts
|
|
2665
2792
|
var enable_exports = {};
|
|
2666
2793
|
__export(enable_exports, {
|
|
2667
2794
|
handleEnable: () => handleEnable
|
|
2668
2795
|
});
|
|
2796
|
+
function findPath(candidates) {
|
|
2797
|
+
return candidates.find((p) => fs12.existsSync(p)) || null;
|
|
2798
|
+
}
|
|
2669
2799
|
async function enableAndroid() {
|
|
2670
2800
|
log("Enabling Android...");
|
|
2671
|
-
const
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2801
|
+
const prebuiltApk = findPath([
|
|
2802
|
+
path12.resolve(__dirname_enable, "../bin/browse-android.apk"),
|
|
2803
|
+
path12.resolve(__dirname_enable, "../../bin/browse-android.apk"),
|
|
2804
|
+
path12.resolve(__dirname_enable, "browse-android.apk")
|
|
2805
|
+
]);
|
|
2806
|
+
if (prebuiltApk) {
|
|
2807
|
+
log(`Driver APK: ${prebuiltApk}`);
|
|
2808
|
+
log("Android ready (pre-built).");
|
|
2809
|
+
return;
|
|
2680
2810
|
}
|
|
2681
|
-
|
|
2682
|
-
ensureJavaHome2();
|
|
2811
|
+
log("Pre-built APK not found, building from source...");
|
|
2812
|
+
const { ensureJavaHome: ensureJavaHome2, findSdkRoot: findSdkRoot2, installSdk: installSdk2, createAvd: createAvd2 } = await Promise.resolve().then(() => (init_emulator(), emulator_exports));
|
|
2813
|
+
const { installAdb: installAdb2 } = await Promise.resolve().then(() => (init_bridge(), bridge_exports));
|
|
2683
2814
|
try {
|
|
2684
|
-
(0,
|
|
2685
|
-
log("
|
|
2815
|
+
(0, import_child_process9.execSync)("adb version", { stdio: "ignore", timeout: 5e3 });
|
|
2816
|
+
log("adb: available");
|
|
2686
2817
|
} catch {
|
|
2687
|
-
log("
|
|
2818
|
+
log("Installing adb...");
|
|
2819
|
+
await installAdb2(log);
|
|
2688
2820
|
}
|
|
2821
|
+
ensureJavaHome2();
|
|
2822
|
+
const hasJava = () => {
|
|
2823
|
+
try {
|
|
2824
|
+
(0, import_child_process9.execSync)("java -version", { stdio: "ignore", timeout: 5e3 });
|
|
2825
|
+
return true;
|
|
2826
|
+
} catch {
|
|
2827
|
+
return false;
|
|
2828
|
+
}
|
|
2829
|
+
};
|
|
2830
|
+
if (!hasJava() && process.platform === "darwin") {
|
|
2831
|
+
log("Installing Java (JDK 21)...");
|
|
2832
|
+
try {
|
|
2833
|
+
(0, import_child_process9.execSync)("brew install openjdk@21", { stdio: ["ignore", "pipe", "pipe"], timeout: 3e5 });
|
|
2834
|
+
} catch {
|
|
2835
|
+
}
|
|
2836
|
+
ensureJavaHome2();
|
|
2837
|
+
}
|
|
2838
|
+
if (!hasJava()) throw new Error("Java not found. Install: brew install openjdk@21");
|
|
2839
|
+
log("Java: available");
|
|
2689
2840
|
let sdkRoot = findSdkRoot2();
|
|
2690
|
-
if (!sdkRoot)
|
|
2691
|
-
|
|
2692
|
-
}
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2841
|
+
if (!sdkRoot) sdkRoot = await installSdk2(log);
|
|
2842
|
+
if (!sdkRoot) throw new Error("Android SDK not found.");
|
|
2843
|
+
log(`SDK: ${sdkRoot}`);
|
|
2844
|
+
const sdkMgr = findPath([
|
|
2845
|
+
path12.join(sdkRoot, "cmdline-tools/latest/bin/sdkmanager"),
|
|
2846
|
+
path12.join(sdkRoot, "bin/sdkmanager")
|
|
2847
|
+
]);
|
|
2848
|
+
if (sdkMgr) {
|
|
2849
|
+
try {
|
|
2850
|
+
(0, import_child_process9.execSync)(`yes | "${sdkMgr}" --licenses`, { stdio: "ignore", timeout: 6e4, shell: "/bin/bash" });
|
|
2851
|
+
} catch {
|
|
2852
|
+
}
|
|
2853
|
+
for (const comp of ["emulator", "platform-tools", "platforms;android-35", "build-tools;35.0.0", "system-images;android-35;google_apis;arm64-v8a"]) {
|
|
2700
2854
|
try {
|
|
2701
|
-
(0,
|
|
2855
|
+
(0, import_child_process9.execSync)(`"${sdkMgr}" --install "${comp}"`, { stdio: "ignore", timeout: 3e5 });
|
|
2702
2856
|
} catch {
|
|
2703
2857
|
}
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
const avds = (0, import_child_process8.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
2719
|
-
if (!avds.includes("browse_default")) {
|
|
2720
|
-
createAvd2(sdkRoot, avdMgr, log);
|
|
2721
|
-
} else {
|
|
2722
|
-
log("AVD browse_default: exists");
|
|
2723
|
-
}
|
|
2724
|
-
} catch {
|
|
2725
|
-
}
|
|
2858
|
+
}
|
|
2859
|
+
log("SDK components: installed");
|
|
2860
|
+
}
|
|
2861
|
+
const avdMgr = findPath([
|
|
2862
|
+
path12.join(sdkRoot, "cmdline-tools/latest/bin/avdmanager"),
|
|
2863
|
+
path12.join(sdkRoot, "bin/avdmanager")
|
|
2864
|
+
]);
|
|
2865
|
+
if (avdMgr) {
|
|
2866
|
+
try {
|
|
2867
|
+
const avds = (0, import_child_process9.execSync)(`"${avdMgr}" list avd -c`, { encoding: "utf-8", timeout: 1e4, stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
2868
|
+
if (!avds.includes("browse_default")) {
|
|
2869
|
+
createAvd2(sdkRoot, avdMgr, log);
|
|
2870
|
+
} else {
|
|
2871
|
+
log("AVD: browse_default exists");
|
|
2726
2872
|
}
|
|
2873
|
+
} catch {
|
|
2727
2874
|
}
|
|
2728
2875
|
}
|
|
2729
|
-
const driverDir =
|
|
2730
|
-
|
|
2731
|
-
|
|
2732
|
-
|
|
2733
|
-
|
|
2734
|
-
|
|
2735
|
-
if (sdkRoot2) process.env.ANDROID_HOME = sdkRoot2;
|
|
2876
|
+
const driverDir = findPath([
|
|
2877
|
+
path12.resolve(__dirname_enable, "../browse-android"),
|
|
2878
|
+
path12.resolve(__dirname_enable, "../../browse-android")
|
|
2879
|
+
].filter((d) => fs12.existsSync(path12.join(d, "gradlew"))));
|
|
2880
|
+
if (driverDir) {
|
|
2881
|
+
if (!process.env.ANDROID_HOME) process.env.ANDROID_HOME = sdkRoot;
|
|
2736
2882
|
log("Building Android driver APK...");
|
|
2737
2883
|
try {
|
|
2738
|
-
(0,
|
|
2884
|
+
(0, import_child_process9.execSync)("./gradlew :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon", {
|
|
2739
2885
|
cwd: driverDir,
|
|
2740
2886
|
stdio: ["ignore", "pipe", "pipe"],
|
|
2741
2887
|
timeout: 3e5
|
|
2742
2888
|
});
|
|
2743
|
-
log("
|
|
2889
|
+
log("APK built.");
|
|
2744
2890
|
} catch (err) {
|
|
2745
2891
|
log(`APK build failed: ${err.message?.split("\n")[0]}`);
|
|
2746
2892
|
}
|
|
2747
|
-
} else if (fs11.existsSync(apkPath)) {
|
|
2748
|
-
log("Android driver APK: already built");
|
|
2749
2893
|
}
|
|
2750
2894
|
log("Android enabled.");
|
|
2751
2895
|
}
|
|
2752
2896
|
async function enableIOS() {
|
|
2753
2897
|
log("Enabling iOS...");
|
|
2754
|
-
if (process.platform !== "darwin")
|
|
2755
|
-
throw new Error("iOS requires macOS with Xcode installed.");
|
|
2756
|
-
}
|
|
2898
|
+
if (process.platform !== "darwin") throw new Error("iOS requires macOS with Xcode.");
|
|
2757
2899
|
try {
|
|
2758
|
-
(0,
|
|
2900
|
+
(0, import_child_process9.execSync)("xcodebuild -version", { stdio: "pipe", timeout: 1e4 });
|
|
2759
2901
|
log("Xcode: available");
|
|
2760
2902
|
} catch {
|
|
2761
|
-
throw new Error("Xcode not found. Install from the App Store
|
|
2903
|
+
throw new Error("Xcode not found. Install from the App Store.");
|
|
2762
2904
|
}
|
|
2763
2905
|
try {
|
|
2764
|
-
(0,
|
|
2765
|
-
log("xcodegen: available");
|
|
2906
|
+
(0, import_child_process9.execSync)("which xcodegen", { stdio: "pipe", timeout: 5e3 });
|
|
2766
2907
|
} catch {
|
|
2767
2908
|
log("Installing xcodegen...");
|
|
2768
2909
|
try {
|
|
2769
|
-
(0,
|
|
2910
|
+
(0, import_child_process9.execSync)("brew install xcodegen", { stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
|
|
2770
2911
|
} catch {
|
|
2771
2912
|
throw new Error("xcodegen not found. Install: brew install xcodegen");
|
|
2772
2913
|
}
|
|
2773
2914
|
}
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
|
|
2779
|
-
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2915
|
+
log("xcodegen: available");
|
|
2916
|
+
const runnerDir = findPath([
|
|
2917
|
+
path12.resolve(__dirname_enable, "../browse-ios-runner"),
|
|
2918
|
+
path12.resolve(__dirname_enable, "../../browse-ios-runner"),
|
|
2919
|
+
path12.resolve(__dirname_enable, "../bin/browse-ios-runner")
|
|
2920
|
+
].filter((d) => fs12.existsSync(path12.join(d, "project.yml"))));
|
|
2921
|
+
if (!runnerDir) {
|
|
2922
|
+
throw new Error("iOS runner source not found. Reinstall: npm install -g @ulpi/browse");
|
|
2923
|
+
}
|
|
2924
|
+
if (!fs12.existsSync(path12.join(runnerDir, "BrowseRunner.xcodeproj", "project.pbxproj"))) {
|
|
2925
|
+
log("Generating Xcode project...");
|
|
2926
|
+
(0, import_child_process9.execSync)("xcodegen generate --spec project.yml", { cwd: runnerDir, stdio: "pipe" });
|
|
2927
|
+
}
|
|
2928
|
+
const { resolveSimulator: resolveSimulator2 } = await Promise.resolve().then(() => (init_controller(), controller_exports));
|
|
2929
|
+
try {
|
|
2930
|
+
const sim = await resolveSimulator2();
|
|
2790
2931
|
log("Building iOS runner...");
|
|
2791
|
-
(0,
|
|
2792
|
-
`xcodebuild build-for-testing -project BrowseRunner.xcodeproj -scheme BrowseRunnerApp -sdk iphonesimulator -destination "id=${udid}" -derivedDataPath .build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO -quiet`,
|
|
2932
|
+
(0, import_child_process9.execSync)(
|
|
2933
|
+
`xcodebuild build-for-testing -project BrowseRunner.xcodeproj -scheme BrowseRunnerApp -sdk iphonesimulator -destination "id=${sim.udid}" -derivedDataPath .build CODE_SIGN_IDENTITY="" CODE_SIGNING_ALLOWED=NO -quiet`,
|
|
2793
2934
|
{ cwd: runnerDir, stdio: "pipe", timeout: 12e4 }
|
|
2794
2935
|
);
|
|
2795
2936
|
log("iOS runner built.");
|
|
2937
|
+
} catch {
|
|
2938
|
+
log("No simulator found. Runner will build on first sim start.");
|
|
2796
2939
|
}
|
|
2797
2940
|
log("iOS enabled.");
|
|
2798
2941
|
}
|
|
2799
2942
|
async function enableMacOS() {
|
|
2800
2943
|
log("Enabling macOS...");
|
|
2801
|
-
if (process.platform !== "darwin")
|
|
2802
|
-
|
|
2944
|
+
if (process.platform !== "darwin") throw new Error("macOS app automation requires macOS.");
|
|
2945
|
+
const { resolveBridgePath: resolveBridgePath2 } = await Promise.resolve().then(() => (init_bridge3(), bridge_exports3));
|
|
2946
|
+
try {
|
|
2947
|
+
const existing = resolveBridgePath2();
|
|
2948
|
+
log(`browse-ax: ${existing}`);
|
|
2949
|
+
log("macOS enabled.");
|
|
2950
|
+
return;
|
|
2951
|
+
} catch {
|
|
2803
2952
|
}
|
|
2804
|
-
const axDir =
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
log("browse-ax built.");
|
|
2813
|
-
}
|
|
2953
|
+
const axDir = findPath([
|
|
2954
|
+
path12.resolve(__dirname_enable, "../browse-ax"),
|
|
2955
|
+
path12.resolve(__dirname_enable, "../../browse-ax")
|
|
2956
|
+
].filter((d) => fs12.existsSync(path12.join(d, "Package.swift"))));
|
|
2957
|
+
if (axDir) {
|
|
2958
|
+
log("Building browse-ax...");
|
|
2959
|
+
(0, import_child_process9.execSync)("swift build -c release", { cwd: axDir, stdio: ["ignore", "pipe", "pipe"], timeout: 12e4 });
|
|
2960
|
+
log("browse-ax built.");
|
|
2814
2961
|
} else {
|
|
2815
|
-
throw new Error("browse-ax
|
|
2962
|
+
throw new Error("browse-ax not found. Reinstall: npm install -g @ulpi/browse");
|
|
2816
2963
|
}
|
|
2817
2964
|
log("macOS enabled.");
|
|
2818
2965
|
}
|
|
@@ -2821,8 +2968,8 @@ async function handleEnable(args) {
|
|
|
2821
2968
|
if (!platform || platform === "--help") {
|
|
2822
2969
|
console.log("Usage: browse enable android|ios|macos|all");
|
|
2823
2970
|
console.log("");
|
|
2824
|
-
console.log("
|
|
2825
|
-
console.log("
|
|
2971
|
+
console.log("Verifies native drivers are ready. Pre-built binaries ship with npm install.");
|
|
2972
|
+
console.log("If missing (building from source), installs dependencies and builds them.");
|
|
2826
2973
|
return;
|
|
2827
2974
|
}
|
|
2828
2975
|
const platforms = platform === "all" ? ["android", "ios", "macos"] : [platform];
|
|
@@ -2849,15 +2996,15 @@ async function handleEnable(args) {
|
|
|
2849
2996
|
console.log("");
|
|
2850
2997
|
}
|
|
2851
2998
|
}
|
|
2852
|
-
var
|
|
2999
|
+
var import_child_process9, fs12, path12, import_url6, __dirname_enable, log;
|
|
2853
3000
|
var init_enable = __esm({
|
|
2854
3001
|
"src/enable.ts"() {
|
|
2855
3002
|
"use strict";
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
__dirname_enable =
|
|
3003
|
+
import_child_process9 = require("child_process");
|
|
3004
|
+
fs12 = __toESM(require("fs"), 1);
|
|
3005
|
+
path12 = __toESM(require("path"), 1);
|
|
3006
|
+
import_url6 = require("url");
|
|
3007
|
+
__dirname_enable = path12.dirname((0, import_url6.fileURLToPath)(__import_meta_url));
|
|
2861
3008
|
log = (msg) => process.stderr.write(`[browse] ${msg}
|
|
2862
3009
|
`);
|
|
2863
3010
|
}
|
|
@@ -3151,8 +3298,8 @@ async function handleReadCommand(command, args, bm, buffers) {
|
|
|
3151
3298
|
case "eval": {
|
|
3152
3299
|
const filePath = args[0];
|
|
3153
3300
|
if (!filePath) throw new Error("Usage: browse eval <js-file>");
|
|
3154
|
-
if (!
|
|
3155
|
-
const code =
|
|
3301
|
+
if (!fs13.existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
|
|
3302
|
+
const code = fs13.readFileSync(filePath, "utf-8");
|
|
3156
3303
|
const result = await evalCtx.evaluate(code);
|
|
3157
3304
|
return typeof result === "object" ? JSON.stringify(result, null, 2) : String(result ?? "");
|
|
3158
3305
|
}
|
|
@@ -3492,13 +3639,13 @@ function registerReadDefinitions(registry4) {
|
|
|
3492
3639
|
});
|
|
3493
3640
|
}
|
|
3494
3641
|
}
|
|
3495
|
-
var
|
|
3642
|
+
var fs13;
|
|
3496
3643
|
var init_read = __esm({
|
|
3497
3644
|
"src/commands/read.ts"() {
|
|
3498
3645
|
"use strict";
|
|
3499
3646
|
init_emulation();
|
|
3500
3647
|
init_constants();
|
|
3501
|
-
|
|
3648
|
+
fs13 = __toESM(require("fs"), 1);
|
|
3502
3649
|
}
|
|
3503
3650
|
});
|
|
3504
3651
|
|
|
@@ -4190,14 +4337,14 @@ async function handleWriteCommand(command, args, bm, domainFilter) {
|
|
|
4190
4337
|
const file = args[1];
|
|
4191
4338
|
if (!file) throw new Error("Usage: browse cookie export <file>");
|
|
4192
4339
|
const cookies = await page.context().cookies();
|
|
4193
|
-
|
|
4340
|
+
fs14.writeFileSync(file, JSON.stringify(cookies, null, 2));
|
|
4194
4341
|
return `Exported ${cookies.length} cookie(s) to ${file}`;
|
|
4195
4342
|
}
|
|
4196
4343
|
if (cookieStr === "import") {
|
|
4197
4344
|
const file = args[1];
|
|
4198
4345
|
if (!file) throw new Error("Usage: browse cookie import <file>");
|
|
4199
|
-
if (!
|
|
4200
|
-
const cookies = JSON.parse(
|
|
4346
|
+
if (!fs14.existsSync(file)) throw new Error(`File not found: ${file}`);
|
|
4347
|
+
const cookies = JSON.parse(fs14.readFileSync(file, "utf-8"));
|
|
4201
4348
|
if (!Array.isArray(cookies)) throw new Error("Cookie file must contain a JSON array of cookie objects");
|
|
4202
4349
|
await page.context().addCookies(cookies);
|
|
4203
4350
|
return `Imported ${cookies.length} cookie(s) from ${file}`;
|
|
@@ -4264,7 +4411,7 @@ Note: Cookies and tab URLs preserved. localStorage/sessionStorage were reset (Pl
|
|
|
4264
4411
|
const [selector, ...filePaths] = args;
|
|
4265
4412
|
if (!selector || filePaths.length === 0) throw new Error("Usage: browse upload <selector> <file1> [file2] ...");
|
|
4266
4413
|
for (const fp of filePaths) {
|
|
4267
|
-
if (!
|
|
4414
|
+
if (!fs14.existsSync(fp)) throw new Error(`File not found: ${fp}`);
|
|
4268
4415
|
}
|
|
4269
4416
|
const resolved = bm.resolveRef(selector);
|
|
4270
4417
|
if ("locator" in resolved) {
|
|
@@ -4724,13 +4871,13 @@ function registerWriteDefinitions(registry4) {
|
|
|
4724
4871
|
});
|
|
4725
4872
|
}
|
|
4726
4873
|
}
|
|
4727
|
-
var
|
|
4874
|
+
var fs14;
|
|
4728
4875
|
var init_write = __esm({
|
|
4729
4876
|
"src/commands/write.ts"() {
|
|
4730
4877
|
"use strict";
|
|
4731
4878
|
init_emulation();
|
|
4732
4879
|
init_constants();
|
|
4733
|
-
|
|
4880
|
+
fs14 = __toESM(require("fs"), 1);
|
|
4734
4881
|
}
|
|
4735
4882
|
});
|
|
4736
4883
|
|
|
@@ -5104,13 +5251,13 @@ ${legend.join("\n")}`;
|
|
|
5104
5251
|
const diffArgs = args.filter((a) => a !== "--full");
|
|
5105
5252
|
const baseline = diffArgs[0];
|
|
5106
5253
|
if (!baseline) throw new Error("Usage: browse screenshot-diff <baseline> [current] [--threshold 0.1] [--full]");
|
|
5107
|
-
if (!
|
|
5254
|
+
if (!fs15.existsSync(baseline)) throw new Error(`Baseline file not found: ${baseline}`);
|
|
5108
5255
|
let thresholdPct = 0.1;
|
|
5109
5256
|
const threshIdx = diffArgs.indexOf("--threshold");
|
|
5110
5257
|
if (threshIdx !== -1 && diffArgs[threshIdx + 1]) {
|
|
5111
5258
|
thresholdPct = parseFloat(diffArgs[threshIdx + 1]);
|
|
5112
5259
|
}
|
|
5113
|
-
const baselineBuffer =
|
|
5260
|
+
const baselineBuffer = fs15.readFileSync(baseline);
|
|
5114
5261
|
let currentBuffer;
|
|
5115
5262
|
let currentPath;
|
|
5116
5263
|
for (let i = 1; i < diffArgs.length; i++) {
|
|
@@ -5124,8 +5271,8 @@ ${legend.join("\n")}`;
|
|
|
5124
5271
|
}
|
|
5125
5272
|
}
|
|
5126
5273
|
if (currentPath) {
|
|
5127
|
-
if (!
|
|
5128
|
-
currentBuffer =
|
|
5274
|
+
if (!fs15.existsSync(currentPath)) throw new Error(`Current screenshot not found: ${currentPath}`);
|
|
5275
|
+
currentBuffer = fs15.readFileSync(currentPath);
|
|
5129
5276
|
} else {
|
|
5130
5277
|
const page = bm.getPage();
|
|
5131
5278
|
currentBuffer = await page.screenshot({ fullPage: isFullPageDiff });
|
|
@@ -5135,7 +5282,7 @@ ${legend.join("\n")}`;
|
|
|
5135
5282
|
const extIdx = baseline.lastIndexOf(".");
|
|
5136
5283
|
const diffPath = extIdx > 0 ? baseline.slice(0, extIdx) + "-diff" + baseline.slice(extIdx) : baseline + "-diff.png";
|
|
5137
5284
|
if (!result.passed && result.diffImage) {
|
|
5138
|
-
|
|
5285
|
+
fs15.writeFileSync(diffPath, result.diffImage);
|
|
5139
5286
|
}
|
|
5140
5287
|
return [
|
|
5141
5288
|
`Pixels: ${result.totalPixels}`,
|
|
@@ -5150,11 +5297,11 @@ ${legend.join("\n")}`;
|
|
|
5150
5297
|
throw new Error(`Unknown screenshots command: ${command}`);
|
|
5151
5298
|
}
|
|
5152
5299
|
}
|
|
5153
|
-
var
|
|
5300
|
+
var fs15, LOCAL_DIR;
|
|
5154
5301
|
var init_screenshots = __esm({
|
|
5155
5302
|
"src/commands/meta/screenshots.ts"() {
|
|
5156
5303
|
"use strict";
|
|
5157
|
-
|
|
5304
|
+
fs15 = __toESM(require("fs"), 1);
|
|
5158
5305
|
LOCAL_DIR = process.env.BROWSE_LOCAL_DIR || "/tmp";
|
|
5159
5306
|
}
|
|
5160
5307
|
});
|
|
@@ -12920,7 +13067,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
|
|
|
12920
13067
|
throw new Error(`Unknown format: ${format}. Use "browse" (chain JSON), "replay" (Puppeteer), "playwright" (Playwright Test), or "flow" (YAML).`);
|
|
12921
13068
|
}
|
|
12922
13069
|
if (filePath) {
|
|
12923
|
-
|
|
13070
|
+
fs16.writeFileSync(filePath, output);
|
|
12924
13071
|
return `Exported ${steps.length} steps as ${format}: ${filePath}`;
|
|
12925
13072
|
}
|
|
12926
13073
|
return output;
|
|
@@ -12945,7 +13092,7 @@ async function handleRecordingCommand(command, args, target, currentSession) {
|
|
|
12945
13092
|
const { formatAsHar: formatAsHar2 } = await Promise.resolve().then(() => (init_har(), har_exports));
|
|
12946
13093
|
const har = formatAsHar2(sessionBuffers.networkBuffer, recording.startTime);
|
|
12947
13094
|
const harPath = args[1] || (currentSession ? `${currentSession.outputDir}/recording.har` : `${LOCAL_DIR2}/browse-recording.har`);
|
|
12948
|
-
|
|
13095
|
+
fs16.writeFileSync(harPath, JSON.stringify(har, null, 2));
|
|
12949
13096
|
const entryCount = har.log.entries.length;
|
|
12950
13097
|
return `HAR saved: ${harPath} (${entryCount} entries)`;
|
|
12951
13098
|
}
|
|
@@ -12981,11 +13128,11 @@ async function handleRecordingCommand(command, args, target, currentSession) {
|
|
|
12981
13128
|
throw new Error(`Unknown recording command: ${command}`);
|
|
12982
13129
|
}
|
|
12983
13130
|
}
|
|
12984
|
-
var
|
|
13131
|
+
var fs16, LOCAL_DIR2;
|
|
12985
13132
|
var init_recording = __esm({
|
|
12986
13133
|
"src/commands/meta/recording.ts"() {
|
|
12987
13134
|
"use strict";
|
|
12988
|
-
|
|
13135
|
+
fs16 = __toESM(require("fs"), 1);
|
|
12989
13136
|
LOCAL_DIR2 = process.env.BROWSE_LOCAL_DIR || "/tmp";
|
|
12990
13137
|
}
|
|
12991
13138
|
});
|
|
@@ -13009,20 +13156,20 @@ async function saveSessionState(sessionDir, context, encryptionKey) {
|
|
|
13009
13156
|
} else {
|
|
13010
13157
|
content = json;
|
|
13011
13158
|
}
|
|
13012
|
-
|
|
13013
|
-
|
|
13159
|
+
fs17.mkdirSync(sessionDir, { recursive: true });
|
|
13160
|
+
fs17.writeFileSync(path13.join(sessionDir, STATE_FILENAME), content, { mode: 384 });
|
|
13014
13161
|
} catch (err) {
|
|
13015
13162
|
console.log(`[session-persist] Warning: failed to save state: ${err.message}`);
|
|
13016
13163
|
}
|
|
13017
13164
|
}
|
|
13018
13165
|
async function loadSessionState(sessionDir, context, encryptionKey) {
|
|
13019
|
-
const statePath =
|
|
13020
|
-
if (!
|
|
13166
|
+
const statePath = path13.join(sessionDir, STATE_FILENAME);
|
|
13167
|
+
if (!fs17.existsSync(statePath)) {
|
|
13021
13168
|
return false;
|
|
13022
13169
|
}
|
|
13023
13170
|
let stateData;
|
|
13024
13171
|
try {
|
|
13025
|
-
const raw =
|
|
13172
|
+
const raw = fs17.readFileSync(statePath, "utf-8");
|
|
13026
13173
|
const parsed = JSON.parse(raw);
|
|
13027
13174
|
if (parsed.encrypted) {
|
|
13028
13175
|
if (!encryptionKey) {
|
|
@@ -13078,24 +13225,24 @@ async function loadSessionState(sessionDir, context, encryptionKey) {
|
|
|
13078
13225
|
}
|
|
13079
13226
|
}
|
|
13080
13227
|
function hasPersistedState(sessionDir) {
|
|
13081
|
-
return
|
|
13228
|
+
return fs17.existsSync(path13.join(sessionDir, STATE_FILENAME));
|
|
13082
13229
|
}
|
|
13083
13230
|
function cleanOldStates(localDir, maxAgeDays) {
|
|
13084
13231
|
const maxAgeMs = maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
13085
13232
|
const now = Date.now();
|
|
13086
13233
|
let deleted = 0;
|
|
13087
|
-
const statesDir =
|
|
13088
|
-
if (
|
|
13234
|
+
const statesDir = path13.join(localDir, "states");
|
|
13235
|
+
if (fs17.existsSync(statesDir)) {
|
|
13089
13236
|
try {
|
|
13090
|
-
const entries =
|
|
13237
|
+
const entries = fs17.readdirSync(statesDir);
|
|
13091
13238
|
for (const entry of entries) {
|
|
13092
13239
|
if (!entry.endsWith(".json")) continue;
|
|
13093
|
-
const filePath =
|
|
13240
|
+
const filePath = path13.join(statesDir, entry);
|
|
13094
13241
|
try {
|
|
13095
|
-
const stat =
|
|
13242
|
+
const stat = fs17.statSync(filePath);
|
|
13096
13243
|
if (!stat.isFile()) continue;
|
|
13097
13244
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
13098
|
-
|
|
13245
|
+
fs17.unlinkSync(filePath);
|
|
13099
13246
|
deleted++;
|
|
13100
13247
|
}
|
|
13101
13248
|
} catch (_) {
|
|
@@ -13104,23 +13251,23 @@ function cleanOldStates(localDir, maxAgeDays) {
|
|
|
13104
13251
|
} catch (_) {
|
|
13105
13252
|
}
|
|
13106
13253
|
}
|
|
13107
|
-
const sessionsDir =
|
|
13108
|
-
if (
|
|
13254
|
+
const sessionsDir = path13.join(localDir, "sessions");
|
|
13255
|
+
if (fs17.existsSync(sessionsDir)) {
|
|
13109
13256
|
try {
|
|
13110
|
-
const sessionDirs =
|
|
13257
|
+
const sessionDirs = fs17.readdirSync(sessionsDir);
|
|
13111
13258
|
for (const dir of sessionDirs) {
|
|
13112
|
-
const dirPath =
|
|
13259
|
+
const dirPath = path13.join(sessionsDir, dir);
|
|
13113
13260
|
try {
|
|
13114
|
-
const dirStat =
|
|
13261
|
+
const dirStat = fs17.statSync(dirPath);
|
|
13115
13262
|
if (!dirStat.isDirectory()) continue;
|
|
13116
13263
|
} catch (_) {
|
|
13117
13264
|
continue;
|
|
13118
13265
|
}
|
|
13119
|
-
const statePath =
|
|
13266
|
+
const statePath = path13.join(dirPath, STATE_FILENAME);
|
|
13120
13267
|
try {
|
|
13121
|
-
const stat =
|
|
13268
|
+
const stat = fs17.statSync(statePath);
|
|
13122
13269
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
13123
|
-
|
|
13270
|
+
fs17.unlinkSync(statePath);
|
|
13124
13271
|
deleted++;
|
|
13125
13272
|
}
|
|
13126
13273
|
} catch (_) {
|
|
@@ -13131,12 +13278,12 @@ function cleanOldStates(localDir, maxAgeDays) {
|
|
|
13131
13278
|
}
|
|
13132
13279
|
return { deleted };
|
|
13133
13280
|
}
|
|
13134
|
-
var
|
|
13281
|
+
var fs17, path13, STATE_FILENAME;
|
|
13135
13282
|
var init_persist = __esm({
|
|
13136
13283
|
"src/session/persist.ts"() {
|
|
13137
13284
|
"use strict";
|
|
13138
|
-
|
|
13139
|
-
|
|
13285
|
+
fs17 = __toESM(require("fs"), 1);
|
|
13286
|
+
path13 = __toESM(require("path"), 1);
|
|
13140
13287
|
init_encryption();
|
|
13141
13288
|
STATE_FILENAME = "state.json";
|
|
13142
13289
|
}
|
|
@@ -13571,7 +13718,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
|
|
|
13571
13718
|
const lines = entries.map(
|
|
13572
13719
|
(e) => `[${new Date(e.timestamp).toISOString()}] [${e.level}] ${e.text}`
|
|
13573
13720
|
).join("\n") + "\n";
|
|
13574
|
-
|
|
13721
|
+
fs18.appendFileSync(consolePath, lines);
|
|
13575
13722
|
buffers.lastConsoleFlushed = buffers.consoleTotalAdded;
|
|
13576
13723
|
}
|
|
13577
13724
|
const newNetworkCount = buffers.networkTotalAdded - buffers.lastNetworkFlushed;
|
|
@@ -13581,7 +13728,7 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
|
|
|
13581
13728
|
const lines = entries.map(
|
|
13582
13729
|
(e) => `[${new Date(e.timestamp).toISOString()}] ${e.method} ${e.url} \u2192 ${e.status || "pending"} (${e.duration || "?"}ms, ${e.size || "?"}B)`
|
|
13583
13730
|
).join("\n") + "\n";
|
|
13584
|
-
|
|
13731
|
+
fs18.appendFileSync(networkPath, lines);
|
|
13585
13732
|
buffers.lastNetworkFlushed = buffers.networkTotalAdded;
|
|
13586
13733
|
}
|
|
13587
13734
|
}
|
|
@@ -13597,22 +13744,22 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
|
|
|
13597
13744
|
const statesDir = `${LOCAL_DIR3}/states`;
|
|
13598
13745
|
const statePath = `${statesDir}/${name}.json`;
|
|
13599
13746
|
if (subcommand === "list") {
|
|
13600
|
-
if (!
|
|
13601
|
-
const files =
|
|
13747
|
+
if (!fs18.existsSync(statesDir)) return "(no saved states)";
|
|
13748
|
+
const files = fs18.readdirSync(statesDir).filter((f) => f.endsWith(".json"));
|
|
13602
13749
|
if (files.length === 0) return "(no saved states)";
|
|
13603
13750
|
const lines = [];
|
|
13604
13751
|
for (const file of files) {
|
|
13605
13752
|
const fp = `${statesDir}/${file}`;
|
|
13606
|
-
const stat =
|
|
13753
|
+
const stat = fs18.statSync(fp);
|
|
13607
13754
|
lines.push(` ${file.replace(".json", "")} ${stat.size}B ${new Date(stat.mtimeMs).toISOString()}`);
|
|
13608
13755
|
}
|
|
13609
13756
|
return lines.join("\n");
|
|
13610
13757
|
}
|
|
13611
13758
|
if (subcommand === "show") {
|
|
13612
|
-
if (!
|
|
13759
|
+
if (!fs18.existsSync(statePath)) {
|
|
13613
13760
|
throw new Error(`State file not found: ${statePath}`);
|
|
13614
13761
|
}
|
|
13615
|
-
const data = JSON.parse(
|
|
13762
|
+
const data = JSON.parse(fs18.readFileSync(statePath, "utf-8"));
|
|
13616
13763
|
const cookieCount = data.cookies?.length || 0;
|
|
13617
13764
|
const originCount = data.origins?.length || 0;
|
|
13618
13765
|
const storageItems = (data.origins || []).reduce((sum, o) => sum + (o.localStorage?.length || 0), 0);
|
|
@@ -13637,15 +13784,15 @@ async function handleSessionsCommand(command, args, bm, sessionManager2, current
|
|
|
13637
13784
|
const context = bm.getContext();
|
|
13638
13785
|
if (!context) throw new Error("No browser context");
|
|
13639
13786
|
const state = await context.storageState();
|
|
13640
|
-
|
|
13641
|
-
|
|
13787
|
+
fs18.mkdirSync(statesDir, { recursive: true });
|
|
13788
|
+
fs18.writeFileSync(statePath, JSON.stringify(state, null, 2), { mode: 384 });
|
|
13642
13789
|
return `State saved: ${statePath}`;
|
|
13643
13790
|
}
|
|
13644
13791
|
if (subcommand === "load") {
|
|
13645
|
-
if (!
|
|
13792
|
+
if (!fs18.existsSync(statePath)) {
|
|
13646
13793
|
throw new Error(`State file not found: ${statePath}. Run "browse state save ${name}" first.`);
|
|
13647
13794
|
}
|
|
13648
|
-
const stateData = JSON.parse(
|
|
13795
|
+
const stateData = JSON.parse(fs18.readFileSync(statePath, "utf-8"));
|
|
13649
13796
|
const context = bm.getContext();
|
|
13650
13797
|
if (!context) throw new Error("No browser context");
|
|
13651
13798
|
const warnings = [];
|
|
@@ -13698,12 +13845,12 @@ ${snapshot}`;
|
|
|
13698
13845
|
throw new Error(`Unknown sessions command: ${command}`);
|
|
13699
13846
|
}
|
|
13700
13847
|
}
|
|
13701
|
-
var
|
|
13848
|
+
var fs18, LOCAL_DIR3;
|
|
13702
13849
|
var init_sessions = __esm({
|
|
13703
13850
|
"src/commands/meta/sessions.ts"() {
|
|
13704
13851
|
"use strict";
|
|
13705
13852
|
init_sanitize();
|
|
13706
|
-
|
|
13853
|
+
fs18 = __toESM(require("fs"), 1);
|
|
13707
13854
|
LOCAL_DIR3 = process.env.BROWSE_LOCAL_DIR || "/tmp";
|
|
13708
13855
|
}
|
|
13709
13856
|
});
|
|
@@ -14106,7 +14253,7 @@ var init_lib = __esm({
|
|
|
14106
14253
|
tokenize: function tokenize(value) {
|
|
14107
14254
|
return Array.from(value);
|
|
14108
14255
|
},
|
|
14109
|
-
join: function
|
|
14256
|
+
join: function join14(chars) {
|
|
14110
14257
|
return chars.join("");
|
|
14111
14258
|
},
|
|
14112
14259
|
postProcess: function postProcess(changeObjects) {
|
|
@@ -19667,19 +19814,19 @@ __export(detection_exports, {
|
|
|
19667
19814
|
detectStack: () => detectStack
|
|
19668
19815
|
});
|
|
19669
19816
|
function loadCustomSignatures(localDir) {
|
|
19670
|
-
const detectionDir =
|
|
19671
|
-
if (!
|
|
19817
|
+
const detectionDir = path14.join(localDir, "detections");
|
|
19818
|
+
if (!fs19.existsSync(detectionDir)) return [];
|
|
19672
19819
|
const sigs = [];
|
|
19673
19820
|
let entries;
|
|
19674
19821
|
try {
|
|
19675
|
-
entries =
|
|
19822
|
+
entries = fs19.readdirSync(detectionDir).filter((f) => f.endsWith(".json"));
|
|
19676
19823
|
} catch {
|
|
19677
19824
|
return [];
|
|
19678
19825
|
}
|
|
19679
19826
|
for (const entry of entries) {
|
|
19680
|
-
const filePath =
|
|
19827
|
+
const filePath = path14.join(detectionDir, entry);
|
|
19681
19828
|
try {
|
|
19682
|
-
const raw =
|
|
19829
|
+
const raw = fs19.readFileSync(filePath, "utf-8");
|
|
19683
19830
|
const parsed = JSON.parse(raw);
|
|
19684
19831
|
if (parsed == null || typeof parsed !== "object" || parsed.version !== 1 || typeof parsed.name !== "string" || typeof parsed.detect !== "string") {
|
|
19685
19832
|
process.stderr.write(
|
|
@@ -19805,15 +19952,15 @@ async function detectStack(page, networkEntries, localDir) {
|
|
|
19805
19952
|
const thirdParty = buildThirdPartyInventory(page, networkEntries ?? []);
|
|
19806
19953
|
return { frameworks, saas, infrastructure, thirdParty, custom };
|
|
19807
19954
|
}
|
|
19808
|
-
var
|
|
19955
|
+
var fs19, path14, THIRD_PARTY_DOMAINS;
|
|
19809
19956
|
var init_detection = __esm({
|
|
19810
19957
|
"src/detection/index.ts"() {
|
|
19811
19958
|
"use strict";
|
|
19812
19959
|
init_frameworks();
|
|
19813
19960
|
init_saas();
|
|
19814
19961
|
init_infrastructure();
|
|
19815
|
-
|
|
19816
|
-
|
|
19962
|
+
fs19 = __toESM(require("fs"), 1);
|
|
19963
|
+
path14 = __toESM(require("path"), 1);
|
|
19817
19964
|
THIRD_PARTY_DOMAINS = {
|
|
19818
19965
|
// Analytics
|
|
19819
19966
|
"google-analytics.com": "analytics",
|
|
@@ -21895,35 +22042,35 @@ __export(persist_exports2, {
|
|
|
21895
22042
|
saveAudit: () => saveAudit
|
|
21896
22043
|
});
|
|
21897
22044
|
function auditsDir(localDir) {
|
|
21898
|
-
return
|
|
22045
|
+
return path15.join(localDir, "audits");
|
|
21899
22046
|
}
|
|
21900
22047
|
function auditPath(localDir, name) {
|
|
21901
|
-
return
|
|
22048
|
+
return path15.join(auditsDir(localDir), `${sanitizeName(name)}.json`);
|
|
21902
22049
|
}
|
|
21903
22050
|
function saveAudit(localDir, name, report) {
|
|
21904
22051
|
const dir = auditsDir(localDir);
|
|
21905
|
-
|
|
22052
|
+
fs20.mkdirSync(dir, { recursive: true });
|
|
21906
22053
|
const filePath = auditPath(localDir, name);
|
|
21907
|
-
|
|
22054
|
+
fs20.writeFileSync(filePath, JSON.stringify(report, null, 2), { mode: 384 });
|
|
21908
22055
|
return filePath;
|
|
21909
22056
|
}
|
|
21910
22057
|
function loadAudit(localDir, name) {
|
|
21911
22058
|
const filePath = auditPath(localDir, name);
|
|
21912
|
-
if (!
|
|
22059
|
+
if (!fs20.existsSync(filePath)) {
|
|
21913
22060
|
throw new Error(
|
|
21914
22061
|
`Audit not found: ${filePath}. Run "browse perf-audit save ${name}" first.`
|
|
21915
22062
|
);
|
|
21916
22063
|
}
|
|
21917
|
-
return JSON.parse(
|
|
22064
|
+
return JSON.parse(fs20.readFileSync(filePath, "utf-8"));
|
|
21918
22065
|
}
|
|
21919
22066
|
function listAudits(localDir) {
|
|
21920
22067
|
const dir = auditsDir(localDir);
|
|
21921
|
-
if (!
|
|
21922
|
-
const files =
|
|
22068
|
+
if (!fs20.existsSync(dir)) return [];
|
|
22069
|
+
const files = fs20.readdirSync(dir).filter((f) => f.endsWith(".json"));
|
|
21923
22070
|
if (files.length === 0) return [];
|
|
21924
22071
|
const entries = files.map((file) => {
|
|
21925
|
-
const fp =
|
|
21926
|
-
const stat =
|
|
22072
|
+
const fp = path15.join(dir, file);
|
|
22073
|
+
const stat = fs20.statSync(fp);
|
|
21927
22074
|
return {
|
|
21928
22075
|
name: file.replace(".json", ""),
|
|
21929
22076
|
sizeBytes: stat.size,
|
|
@@ -21935,19 +22082,19 @@ function listAudits(localDir) {
|
|
|
21935
22082
|
}
|
|
21936
22083
|
function deleteAudit(localDir, name) {
|
|
21937
22084
|
const filePath = auditPath(localDir, name);
|
|
21938
|
-
if (!
|
|
22085
|
+
if (!fs20.existsSync(filePath)) {
|
|
21939
22086
|
throw new Error(
|
|
21940
22087
|
`Audit not found: ${filePath}. Nothing to delete.`
|
|
21941
22088
|
);
|
|
21942
22089
|
}
|
|
21943
|
-
|
|
22090
|
+
fs20.unlinkSync(filePath);
|
|
21944
22091
|
}
|
|
21945
|
-
var
|
|
22092
|
+
var fs20, path15;
|
|
21946
22093
|
var init_persist2 = __esm({
|
|
21947
22094
|
"src/perf-audit/persist.ts"() {
|
|
21948
22095
|
"use strict";
|
|
21949
|
-
|
|
21950
|
-
|
|
22096
|
+
fs20 = __toESM(require("fs"), 1);
|
|
22097
|
+
path15 = __toESM(require("path"), 1);
|
|
21951
22098
|
init_sanitize();
|
|
21952
22099
|
}
|
|
21953
22100
|
});
|
|
@@ -23394,12 +23541,12 @@ async function autoDetectSelector(page, field) {
|
|
|
23394
23541
|
}
|
|
23395
23542
|
throw new Error("Could not auto-detect submit button.");
|
|
23396
23543
|
}
|
|
23397
|
-
var
|
|
23544
|
+
var fs21, path16, AuthVault;
|
|
23398
23545
|
var init_auth_vault = __esm({
|
|
23399
23546
|
"src/security/auth-vault.ts"() {
|
|
23400
23547
|
"use strict";
|
|
23401
|
-
|
|
23402
|
-
|
|
23548
|
+
fs21 = __toESM(require("fs"), 1);
|
|
23549
|
+
path16 = __toESM(require("path"), 1);
|
|
23403
23550
|
init_constants();
|
|
23404
23551
|
init_encryption();
|
|
23405
23552
|
init_sanitize();
|
|
@@ -23407,11 +23554,11 @@ var init_auth_vault = __esm({
|
|
|
23407
23554
|
authDir;
|
|
23408
23555
|
encryptionKey;
|
|
23409
23556
|
constructor(localDir) {
|
|
23410
|
-
this.authDir =
|
|
23557
|
+
this.authDir = path16.join(localDir, "auth");
|
|
23411
23558
|
this.encryptionKey = resolveEncryptionKey(localDir);
|
|
23412
23559
|
}
|
|
23413
23560
|
save(name, url, username, password, selectors) {
|
|
23414
|
-
|
|
23561
|
+
fs21.mkdirSync(this.authDir, { recursive: true });
|
|
23415
23562
|
const { ciphertext, iv, authTag } = encrypt(password, this.encryptionKey);
|
|
23416
23563
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
23417
23564
|
const credential = {
|
|
@@ -23428,15 +23575,15 @@ var init_auth_vault = __esm({
|
|
|
23428
23575
|
createdAt: now,
|
|
23429
23576
|
updatedAt: now
|
|
23430
23577
|
};
|
|
23431
|
-
const filePath =
|
|
23432
|
-
|
|
23578
|
+
const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
|
|
23579
|
+
fs21.writeFileSync(filePath, JSON.stringify(credential, null, 2), { mode: 384 });
|
|
23433
23580
|
}
|
|
23434
23581
|
load(name) {
|
|
23435
|
-
const filePath =
|
|
23436
|
-
if (!
|
|
23582
|
+
const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
|
|
23583
|
+
if (!fs21.existsSync(filePath)) {
|
|
23437
23584
|
throw new Error(`Credential "${name}" not found. Run "browse auth list" to see saved credentials.`);
|
|
23438
23585
|
}
|
|
23439
|
-
return JSON.parse(
|
|
23586
|
+
return JSON.parse(fs21.readFileSync(filePath, "utf-8"));
|
|
23440
23587
|
}
|
|
23441
23588
|
async login(name, bm) {
|
|
23442
23589
|
const cred = this.load(name);
|
|
@@ -23457,11 +23604,11 @@ var init_auth_vault = __esm({
|
|
|
23457
23604
|
return `Logged in as ${cred.username} at ${page.url()}`;
|
|
23458
23605
|
}
|
|
23459
23606
|
list() {
|
|
23460
|
-
if (!
|
|
23461
|
-
const files =
|
|
23607
|
+
if (!fs21.existsSync(this.authDir)) return [];
|
|
23608
|
+
const files = fs21.readdirSync(this.authDir).filter((f) => f.endsWith(".json"));
|
|
23462
23609
|
return files.map((f) => {
|
|
23463
23610
|
try {
|
|
23464
|
-
const data = JSON.parse(
|
|
23611
|
+
const data = JSON.parse(fs21.readFileSync(path16.join(this.authDir, f), "utf-8"));
|
|
23465
23612
|
return {
|
|
23466
23613
|
name: data.name,
|
|
23467
23614
|
url: data.url,
|
|
@@ -23475,11 +23622,11 @@ var init_auth_vault = __esm({
|
|
|
23475
23622
|
}).filter(Boolean);
|
|
23476
23623
|
}
|
|
23477
23624
|
delete(name) {
|
|
23478
|
-
const filePath =
|
|
23479
|
-
if (!
|
|
23625
|
+
const filePath = path16.join(this.authDir, `${sanitizeName(name)}.json`);
|
|
23626
|
+
if (!fs21.existsSync(filePath)) {
|
|
23480
23627
|
throw new Error(`Credential "${name}" not found.`);
|
|
23481
23628
|
}
|
|
23482
|
-
|
|
23629
|
+
fs21.unlinkSync(filePath);
|
|
23483
23630
|
}
|
|
23484
23631
|
};
|
|
23485
23632
|
}
|
|
@@ -23616,20 +23763,20 @@ __export(policy_exports, {
|
|
|
23616
23763
|
function findFileUpward(filename) {
|
|
23617
23764
|
let dir = process.cwd();
|
|
23618
23765
|
for (let i = 0; i < 20; i++) {
|
|
23619
|
-
const candidate =
|
|
23620
|
-
if (
|
|
23621
|
-
const parent =
|
|
23766
|
+
const candidate = path17.join(dir, filename);
|
|
23767
|
+
if (fs22.existsSync(candidate)) return candidate;
|
|
23768
|
+
const parent = path17.dirname(dir);
|
|
23622
23769
|
if (parent === dir) break;
|
|
23623
23770
|
dir = parent;
|
|
23624
23771
|
}
|
|
23625
23772
|
return null;
|
|
23626
23773
|
}
|
|
23627
|
-
var
|
|
23774
|
+
var fs22, path17, PolicyChecker;
|
|
23628
23775
|
var init_policy = __esm({
|
|
23629
23776
|
"src/security/policy.ts"() {
|
|
23630
23777
|
"use strict";
|
|
23631
|
-
|
|
23632
|
-
|
|
23778
|
+
fs22 = __toESM(require("fs"), 1);
|
|
23779
|
+
path17 = __toESM(require("path"), 1);
|
|
23633
23780
|
PolicyChecker = class {
|
|
23634
23781
|
filePath = null;
|
|
23635
23782
|
lastMtime = 0;
|
|
@@ -23648,10 +23795,10 @@ var init_policy = __esm({
|
|
|
23648
23795
|
reload() {
|
|
23649
23796
|
if (!this.filePath) return;
|
|
23650
23797
|
try {
|
|
23651
|
-
const stat =
|
|
23798
|
+
const stat = fs22.statSync(this.filePath);
|
|
23652
23799
|
if (stat.mtimeMs === this.lastMtime) return;
|
|
23653
23800
|
this.lastMtime = stat.mtimeMs;
|
|
23654
|
-
const raw =
|
|
23801
|
+
const raw = fs22.readFileSync(this.filePath, "utf-8");
|
|
23655
23802
|
this.policy = JSON.parse(raw);
|
|
23656
23803
|
} catch {
|
|
23657
23804
|
}
|
|
@@ -23828,104 +23975,6 @@ var init_buffers = __esm({
|
|
|
23828
23975
|
}
|
|
23829
23976
|
});
|
|
23830
23977
|
|
|
23831
|
-
// src/app/macos/bridge.ts
|
|
23832
|
-
var bridge_exports3 = {};
|
|
23833
|
-
__export(bridge_exports3, {
|
|
23834
|
-
createMacOSBridge: () => createMacOSBridge,
|
|
23835
|
-
ensureMacOSBridge: () => ensureMacOSBridge,
|
|
23836
|
-
resolveBridgePath: () => resolveBridgePath
|
|
23837
|
-
});
|
|
23838
|
-
function resolveBridgePath() {
|
|
23839
|
-
const localBuild = path17.resolve(__dirname_bridge, "../../../browse-ax/.build/release/browse-ax");
|
|
23840
|
-
if (fs22.existsSync(localBuild)) return localBuild;
|
|
23841
|
-
const localDebug = path17.resolve(__dirname_bridge, "../../../browse-ax/.build/debug/browse-ax");
|
|
23842
|
-
if (fs22.existsSync(localDebug)) return localDebug;
|
|
23843
|
-
const installed = path17.resolve(__dirname_bridge, "../../bin/browse-ax");
|
|
23844
|
-
if (fs22.existsSync(installed)) return installed;
|
|
23845
|
-
const lazyPath = path17.join(
|
|
23846
|
-
process.env.BROWSE_LOCAL_DIR || path17.join(process.cwd(), ".browse"),
|
|
23847
|
-
"bin",
|
|
23848
|
-
"browse-ax"
|
|
23849
|
-
);
|
|
23850
|
-
if (fs22.existsSync(lazyPath)) return lazyPath;
|
|
23851
|
-
throw new Error(
|
|
23852
|
-
"browse-ax binary not found. Build it with: cd browse-ax && swift build -c release\nOr run: browse doctor --platform macos"
|
|
23853
|
-
);
|
|
23854
|
-
}
|
|
23855
|
-
async function ensureMacOSBridge() {
|
|
23856
|
-
if (process.platform !== "darwin") {
|
|
23857
|
-
throw new Error("App automation requires macOS (uses Accessibility API)");
|
|
23858
|
-
}
|
|
23859
|
-
return resolveBridgePath();
|
|
23860
|
-
}
|
|
23861
|
-
async function execBridge(bridgePath, args) {
|
|
23862
|
-
return new Promise((resolve9, reject) => {
|
|
23863
|
-
const proc = (0, import_child_process9.spawn)(bridgePath, args, {
|
|
23864
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
23865
|
-
});
|
|
23866
|
-
const chunks = [];
|
|
23867
|
-
const errChunks = [];
|
|
23868
|
-
proc.stdout.on("data", (c) => chunks.push(c));
|
|
23869
|
-
proc.stderr.on("data", (c) => errChunks.push(c));
|
|
23870
|
-
proc.on("close", (code) => {
|
|
23871
|
-
const stdout = Buffer.concat(chunks).toString("utf-8").trim();
|
|
23872
|
-
const stderr = Buffer.concat(errChunks).toString("utf-8").trim();
|
|
23873
|
-
if (code !== 0) {
|
|
23874
|
-
try {
|
|
23875
|
-
const err = JSON.parse(stderr || stdout);
|
|
23876
|
-
reject(new Error(err.error || `Bridge exited with code ${code}`));
|
|
23877
|
-
} catch {
|
|
23878
|
-
reject(new Error(stderr || `Bridge exited with code ${code}`));
|
|
23879
|
-
}
|
|
23880
|
-
return;
|
|
23881
|
-
}
|
|
23882
|
-
try {
|
|
23883
|
-
resolve9(JSON.parse(stdout));
|
|
23884
|
-
} catch {
|
|
23885
|
-
reject(new Error(`Invalid bridge output: ${stdout.slice(0, 200)}`));
|
|
23886
|
-
}
|
|
23887
|
-
});
|
|
23888
|
-
});
|
|
23889
|
-
}
|
|
23890
|
-
function createMacOSBridge(bridgePath, pid) {
|
|
23891
|
-
const base = ["--pid", String(pid)];
|
|
23892
|
-
return {
|
|
23893
|
-
async tree() {
|
|
23894
|
-
return execBridge(bridgePath, [...base, "tree"]);
|
|
23895
|
-
},
|
|
23896
|
-
async action(nodePath, actionName) {
|
|
23897
|
-
return execBridge(bridgePath, [...base, "action", JSON.stringify(nodePath), actionName]);
|
|
23898
|
-
},
|
|
23899
|
-
async setValue(nodePath, value) {
|
|
23900
|
-
return execBridge(bridgePath, [...base, "set-value", JSON.stringify(nodePath), value]);
|
|
23901
|
-
},
|
|
23902
|
-
async type(text) {
|
|
23903
|
-
return execBridge(bridgePath, [...base, "type", text]);
|
|
23904
|
-
},
|
|
23905
|
-
async press(key) {
|
|
23906
|
-
return execBridge(bridgePath, [...base, "press", key]);
|
|
23907
|
-
},
|
|
23908
|
-
async screenshot(outputPath) {
|
|
23909
|
-
return execBridge(bridgePath, [...base, "screenshot", outputPath]);
|
|
23910
|
-
},
|
|
23911
|
-
async state() {
|
|
23912
|
-
return execBridge(bridgePath, [...base, "state"]);
|
|
23913
|
-
}
|
|
23914
|
-
};
|
|
23915
|
-
}
|
|
23916
|
-
var import_child_process9, path17, fs22, import_url6, __filename_bridge2, __dirname_bridge;
|
|
23917
|
-
var init_bridge3 = __esm({
|
|
23918
|
-
"src/app/macos/bridge.ts"() {
|
|
23919
|
-
"use strict";
|
|
23920
|
-
import_child_process9 = require("child_process");
|
|
23921
|
-
path17 = __toESM(require("path"), 1);
|
|
23922
|
-
fs22 = __toESM(require("fs"), 1);
|
|
23923
|
-
import_url6 = require("url");
|
|
23924
|
-
__filename_bridge2 = (0, import_url6.fileURLToPath)(__import_meta_url);
|
|
23925
|
-
__dirname_bridge = path17.dirname(__filename_bridge2);
|
|
23926
|
-
}
|
|
23927
|
-
});
|
|
23928
|
-
|
|
23929
23978
|
// src/commands/meta/system.ts
|
|
23930
23979
|
async function handleSystemCommand(command, args, target, shutdown2, sessionManager2, currentSession, lifecycle) {
|
|
23931
23980
|
switch (command) {
|