prowl-tools 0.1.4 → 0.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +10 -0
- package/README.md +519 -2
- package/dist/{chunk-ZEFVTKQT.js → chunk-5KQR3IR3.js} +3321 -346
- package/dist/chunk-5KQR3IR3.js.map +1 -0
- package/dist/{chunk-MBAIGNVO.js → chunk-WHAMB4TY.js} +43 -3
- package/dist/chunk-WHAMB4TY.js.map +1 -0
- package/dist/index.cjs +3527 -481
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +325 -67
- package/dist/index.js.map +1 -1
- package/dist/lib.cjs +3444 -325
- package/dist/lib.cjs.map +1 -1
- package/dist/lib.d.cts +1236 -11
- package/dist/lib.d.ts +1236 -11
- package/dist/lib.js +194 -2
- package/dist/{loader-XMTDB6OL.js → loader-PBBYV3U7.js} +2 -2
- package/package.json +5 -1
- package/dist/chunk-MBAIGNVO.js.map +0 -1
- package/dist/chunk-ZEFVTKQT.js.map +0 -1
- /package/dist/{loader-XMTDB6OL.js.map → loader-PBBYV3U7.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DEFAULT_FLAKY_THRESHOLD,
|
|
4
|
+
analyzeAndroidApp,
|
|
5
|
+
analyzeIosApp,
|
|
6
|
+
analyzeMacApp,
|
|
4
7
|
analyzePage,
|
|
8
|
+
assertAndroidAppAllowed,
|
|
9
|
+
assertIosAppAllowed,
|
|
10
|
+
assertTargetAppAllowed,
|
|
5
11
|
closeBrowser,
|
|
6
12
|
createPlaywrightDriver,
|
|
7
13
|
generateHunt,
|
|
14
|
+
launchAndroidSession,
|
|
8
15
|
launchBrowser,
|
|
16
|
+
launchIosSession,
|
|
17
|
+
launchMacSession,
|
|
9
18
|
parseBrowserEngine,
|
|
10
19
|
printCiSummary,
|
|
11
20
|
rankFlaky,
|
|
@@ -14,15 +23,16 @@ import {
|
|
|
14
23
|
runSuite,
|
|
15
24
|
saveStorageState,
|
|
16
25
|
updateBacklogFromSuite
|
|
17
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-5KQR3IR3.js";
|
|
18
27
|
import {
|
|
19
28
|
CONFIG_DIR,
|
|
29
|
+
findConfigPath,
|
|
20
30
|
listHunts,
|
|
21
31
|
loadConfig,
|
|
22
32
|
loadHuntMeta,
|
|
23
33
|
loadHuntTags,
|
|
24
34
|
resolveViewport
|
|
25
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-WHAMB4TY.js";
|
|
26
36
|
|
|
27
37
|
// src/cli/program.ts
|
|
28
38
|
import { Command as Command13 } from "commander";
|
|
@@ -30,7 +40,7 @@ import { Command as Command13 } from "commander";
|
|
|
30
40
|
// package.json
|
|
31
41
|
var package_default = {
|
|
32
42
|
name: "prowl-tools",
|
|
33
|
-
version: "0.1.
|
|
43
|
+
version: "0.1.6",
|
|
34
44
|
description: "CLI-first QA testing tool for deterministic Playwright flows.",
|
|
35
45
|
type: "module",
|
|
36
46
|
license: "Apache-2.0",
|
|
@@ -100,6 +110,10 @@ var package_default = {
|
|
|
100
110
|
yaml: "^2.6.1",
|
|
101
111
|
zod: "^3.23.8"
|
|
102
112
|
},
|
|
113
|
+
optionalDependencies: {
|
|
114
|
+
"appium-uiautomator2-server": "10.6.2",
|
|
115
|
+
"appium-webdriveragent": "16.4.0"
|
|
116
|
+
},
|
|
103
117
|
devDependencies: {
|
|
104
118
|
"@types/node": "^22.13.1",
|
|
105
119
|
"@types/pngjs": "^6.0.5",
|
|
@@ -190,6 +204,7 @@ function describeStep(step) {
|
|
|
190
204
|
const th = step.assertScreenshot.threshold !== void 0 ? ` (threshold: ${step.assertScreenshot.threshold})` : "";
|
|
191
205
|
return `assertScreenshot "${step.assertScreenshot.name}"${th}`;
|
|
192
206
|
}
|
|
207
|
+
if ("assertWithAI" in step) return `assertWithAI "${truncate(step.assertWithAI, 60)}"`;
|
|
193
208
|
return "unknown step";
|
|
194
209
|
}
|
|
195
210
|
function truncate(text, max) {
|
|
@@ -205,6 +220,9 @@ function printStepResult(result, step, _index) {
|
|
|
205
220
|
const duration = chalk.gray(`(${result.durationMs}ms)`);
|
|
206
221
|
if (result.status === "pass") {
|
|
207
222
|
console.log(` ${chalk.green("\u2713")} ${label} ${duration}`);
|
|
223
|
+
} else if (result.status === "warn") {
|
|
224
|
+
const note = result.value ? chalk.gray(` \u2014 ${result.value}`) : "";
|
|
225
|
+
console.log(` ${chalk.yellow("\u25CB")} ${label} ${duration}${note}`);
|
|
208
226
|
} else {
|
|
209
227
|
const error = result.error ? chalk.gray(` \u2014 ${result.error}`) : "";
|
|
210
228
|
console.log(` ${chalk.red("\u2717")} ${label} ${duration}${error}`);
|
|
@@ -422,7 +440,7 @@ function buildLoginCommand() {
|
|
|
422
440
|
let session = null;
|
|
423
441
|
try {
|
|
424
442
|
const { config, configDir } = loadConfig(options.config);
|
|
425
|
-
if (config.target.type
|
|
443
|
+
if (config.target.type !== "web") {
|
|
426
444
|
throw new Error("`prowl login` captures browser auth state and only applies to web targets.");
|
|
427
445
|
}
|
|
428
446
|
const targetUrl = options.url ?? config.target.url;
|
|
@@ -779,73 +797,313 @@ function parseViewportFlag(value) {
|
|
|
779
797
|
}
|
|
780
798
|
return value;
|
|
781
799
|
}
|
|
782
|
-
function
|
|
783
|
-
const
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
800
|
+
function tryLoadConfig(configPath) {
|
|
801
|
+
const configLocation = configPath !== void 0 ? configPath : findConfigPath(process.cwd());
|
|
802
|
+
if (configLocation === null) {
|
|
803
|
+
return null;
|
|
804
|
+
}
|
|
805
|
+
try {
|
|
806
|
+
return loadConfig(configLocation).config;
|
|
807
|
+
} catch (error) {
|
|
808
|
+
const message = error instanceof Error ? error.message : "Unknown configuration error";
|
|
809
|
+
throw new Error(`Failed to load config at ${configLocation}: ${message}`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function looksLikeApk(app) {
|
|
813
|
+
return /\.apk$/i.test(app.trim());
|
|
814
|
+
}
|
|
815
|
+
function pickNativePlatform(platformFlag, config, app) {
|
|
816
|
+
if (platformFlag !== void 0) {
|
|
817
|
+
const normalized = platformFlag.trim().toLowerCase();
|
|
818
|
+
if (normalized === "macos" || normalized === "android" || normalized === "ios") {
|
|
819
|
+
return normalized;
|
|
820
|
+
}
|
|
821
|
+
throw new Error(`Unknown --platform "${platformFlag}". Use one of: macos, android, ios.`);
|
|
822
|
+
}
|
|
823
|
+
const configType = config?.target.type;
|
|
824
|
+
if (configType === "macos" || configType === "android" || configType === "ios") {
|
|
825
|
+
return configType;
|
|
826
|
+
}
|
|
827
|
+
return looksLikeApk(app) ? "android" : "macos";
|
|
828
|
+
}
|
|
829
|
+
async function runWebAnalyze(url, options) {
|
|
830
|
+
const engine = parseBrowserEngine(options.browser);
|
|
831
|
+
const channel = options.channel;
|
|
832
|
+
const viewport = options.viewport ? resolveViewport(parseViewportFlag(options.viewport)) : { width: 1280, height: 720 };
|
|
833
|
+
const session = await launchBrowser({
|
|
834
|
+
headless: !options.headed,
|
|
835
|
+
slowMo: 0,
|
|
836
|
+
timeout: 3e4,
|
|
837
|
+
trace: false,
|
|
838
|
+
recordHar: false,
|
|
839
|
+
runDir: process.cwd(),
|
|
840
|
+
engine,
|
|
841
|
+
channel,
|
|
842
|
+
viewport
|
|
843
|
+
});
|
|
844
|
+
const driver = createPlaywrightDriver(session.page);
|
|
845
|
+
try {
|
|
846
|
+
await driver.goto(url, { waitUntil: "networkidle" });
|
|
847
|
+
const result = await analyzePage(driver);
|
|
848
|
+
if (options.json) {
|
|
849
|
+
console.log(JSON.stringify(result, null, 2));
|
|
850
|
+
} else {
|
|
851
|
+
console.log(`
|
|
807
852
|
${chalk10.bold("Page Analysis:")} ${result.title}`);
|
|
808
|
-
|
|
853
|
+
console.log(` ${chalk10.gray("URL:")} ${result.url}
|
|
809
854
|
`);
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
855
|
+
if (result.forms.length > 0) {
|
|
856
|
+
console.log(chalk10.bold(" Forms:"));
|
|
857
|
+
for (const form of result.forms) {
|
|
858
|
+
const method = form.method ? chalk10.cyan(form.method) : "";
|
|
859
|
+
const action = form.action ? chalk10.gray(form.action) : "";
|
|
860
|
+
console.log(` [${form.index}] ${method} ${action} (${form.fieldCount} fields)`);
|
|
861
|
+
}
|
|
862
|
+
console.log();
|
|
863
|
+
}
|
|
864
|
+
if (result.elements.length > 0) {
|
|
865
|
+
console.log(chalk10.bold(" Interactive Elements:"));
|
|
866
|
+
for (const el of result.elements) {
|
|
867
|
+
const tag = chalk10.cyan(el.tag);
|
|
868
|
+
const type = el.type ? chalk10.gray(`[${el.type}]`) : "";
|
|
869
|
+
const bestSelector = el.selectors.testId ?? el.selectors.label ?? el.selectors.ariaLabel ?? el.selectors.css ?? el.selectors.name ?? "";
|
|
870
|
+
const selectorStr = bestSelector ? chalk10.yellow(bestSelector) : chalk10.gray("(no selector)");
|
|
871
|
+
const req = el.required ? chalk10.red(" *") : "";
|
|
872
|
+
const form = el.formGroup !== void 0 ? chalk10.gray(` form[${el.formGroup}]`) : "";
|
|
873
|
+
console.log(` ${tag}${type} ${selectorStr}${req}${form}`);
|
|
874
|
+
}
|
|
875
|
+
console.log();
|
|
876
|
+
}
|
|
877
|
+
if (result.links.length > 0) {
|
|
878
|
+
console.log(chalk10.bold(" Links:"));
|
|
879
|
+
for (const link of result.links.slice(0, 20)) {
|
|
880
|
+
const text = link.text || chalk10.gray("(no text)");
|
|
881
|
+
console.log(` ${text} ${chalk10.gray("\u2192")} ${chalk10.blue(link.href)}`);
|
|
882
|
+
}
|
|
883
|
+
if (result.links.length > 20) {
|
|
884
|
+
console.log(chalk10.gray(` ... and ${result.links.length - 20} more`));
|
|
885
|
+
}
|
|
886
|
+
console.log();
|
|
887
|
+
}
|
|
888
|
+
console.log(chalk10.gray(` ${result.elements.length} elements, ${result.forms.length} forms, ${result.links.length} links
|
|
844
889
|
`));
|
|
890
|
+
}
|
|
891
|
+
} finally {
|
|
892
|
+
await closeBrowser(session);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function printMacElement(el) {
|
|
896
|
+
const role = chalk10.cyan(el.role);
|
|
897
|
+
const [best] = el.selectors;
|
|
898
|
+
const selectorStr = best ? chalk10.yellow(best) : chalk10.gray("(no selector)");
|
|
899
|
+
const label = el.title ?? el.description ?? el.value;
|
|
900
|
+
const labelStr = label ? ` ${chalk10.gray(`"${label}"`)}` : "";
|
|
901
|
+
const disabled = el.enabled === false ? chalk10.gray(" (disabled)") : "";
|
|
902
|
+
console.log(` ${role} ${selectorStr}${labelStr}${disabled}`);
|
|
903
|
+
}
|
|
904
|
+
async function runMacAnalyze(app, config, options) {
|
|
905
|
+
const allowedApps = config?.guardrails.allowedApps ?? [];
|
|
906
|
+
assertTargetAppAllowed(allowedApps, app);
|
|
907
|
+
const session = await launchMacSession({ app, timeoutMs: config?.browser.timeout });
|
|
908
|
+
try {
|
|
909
|
+
const result = await analyzeMacApp(session.client, { app: session.bundleId });
|
|
910
|
+
if (options.json) {
|
|
911
|
+
console.log(JSON.stringify(result, null, 2));
|
|
912
|
+
} else {
|
|
913
|
+
console.log(`
|
|
914
|
+
${chalk10.bold("App Analysis:")} ${result.app}
|
|
915
|
+
`);
|
|
916
|
+
if (result.windows.length > 0) {
|
|
917
|
+
console.log(chalk10.bold(" Windows:"));
|
|
918
|
+
for (const win of result.windows) {
|
|
919
|
+
const title = win.title ? chalk10.gray(`"${win.title}"`) : chalk10.gray("(untitled)");
|
|
920
|
+
console.log(` ${title} ${chalk10.yellow(win.selector)}`);
|
|
845
921
|
}
|
|
846
|
-
|
|
847
|
-
|
|
922
|
+
console.log();
|
|
923
|
+
}
|
|
924
|
+
if (result.elements.length > 0) {
|
|
925
|
+
console.log(chalk10.bold(" Interactive Elements:"));
|
|
926
|
+
for (const el of result.elements) {
|
|
927
|
+
printMacElement(el);
|
|
928
|
+
}
|
|
929
|
+
console.log();
|
|
930
|
+
}
|
|
931
|
+
if (result.menuItems.length > 0) {
|
|
932
|
+
console.log(chalk10.bold(" Menu Bar:"));
|
|
933
|
+
for (const el of result.menuItems) {
|
|
934
|
+
printMacElement(el);
|
|
935
|
+
}
|
|
936
|
+
console.log();
|
|
937
|
+
}
|
|
938
|
+
console.log(
|
|
939
|
+
chalk10.gray(
|
|
940
|
+
` ${result.elements.length} elements, ${result.windows.length} windows, ${result.menuItems.length} menu items
|
|
941
|
+
`
|
|
942
|
+
)
|
|
943
|
+
);
|
|
944
|
+
}
|
|
945
|
+
} finally {
|
|
946
|
+
await session.client.close();
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function printAndroidElement(el) {
|
|
950
|
+
const role = chalk10.cyan(el.className);
|
|
951
|
+
const [best] = el.selectors;
|
|
952
|
+
const selectorStr = best ? chalk10.yellow(best) : chalk10.gray("(no selector)");
|
|
953
|
+
const label = el.contentDesc ?? el.text;
|
|
954
|
+
const labelStr = label ? ` ${chalk10.gray(`"${label}"`)}` : "";
|
|
955
|
+
const disabled = el.enabled === false ? chalk10.gray(" (disabled)") : "";
|
|
956
|
+
console.log(` ${role} ${selectorStr}${labelStr}${disabled}`);
|
|
957
|
+
}
|
|
958
|
+
async function runAndroidAnalyze(app, config, options, deviceSerial) {
|
|
959
|
+
const allowedApps = config?.guardrails.allowedApps ?? [];
|
|
960
|
+
if (!looksLikeApk(app)) {
|
|
961
|
+
assertAndroidAppAllowed(allowedApps, app);
|
|
962
|
+
}
|
|
963
|
+
const session = await launchAndroidSession({
|
|
964
|
+
app,
|
|
965
|
+
...deviceSerial ? { deviceSerial } : {},
|
|
966
|
+
timeoutMs: config?.browser.timeout,
|
|
967
|
+
allowedApps
|
|
968
|
+
});
|
|
969
|
+
try {
|
|
970
|
+
const sourceFn = session.client.source;
|
|
971
|
+
if (typeof sourceFn !== "function") {
|
|
972
|
+
throw new Error(
|
|
973
|
+
"The Android agent client does not expose source(); a live uiautomator2 session is required to analyze."
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
const result = await analyzeAndroidApp(
|
|
977
|
+
{ source: () => sourceFn.call(session.client) },
|
|
978
|
+
{ app: session.package }
|
|
979
|
+
);
|
|
980
|
+
if (options.json) {
|
|
981
|
+
console.log(JSON.stringify(result, null, 2));
|
|
982
|
+
} else {
|
|
983
|
+
console.log(`
|
|
984
|
+
${chalk10.bold("App Analysis:")} ${result.app}
|
|
985
|
+
`);
|
|
986
|
+
if (result.elements.length > 0) {
|
|
987
|
+
console.log(chalk10.bold(" Interactive Elements:"));
|
|
988
|
+
for (const el of result.elements) {
|
|
989
|
+
printAndroidElement(el);
|
|
990
|
+
}
|
|
991
|
+
console.log();
|
|
848
992
|
}
|
|
993
|
+
console.log(chalk10.gray(` ${result.elements.length} elements
|
|
994
|
+
`));
|
|
995
|
+
}
|
|
996
|
+
} finally {
|
|
997
|
+
await session.teardown();
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
function printIosElement(el) {
|
|
1001
|
+
const role = chalk10.cyan(el.type);
|
|
1002
|
+
const [best] = el.selectors;
|
|
1003
|
+
const selectorStr = best ? chalk10.yellow(best) : chalk10.gray("(no selector)");
|
|
1004
|
+
const label = el.label ?? el.value ?? el.name;
|
|
1005
|
+
const labelStr = label ? ` ${chalk10.gray(`"${label}"`)}` : "";
|
|
1006
|
+
const disabled = el.enabled === false ? chalk10.gray(" (disabled)") : "";
|
|
1007
|
+
console.log(` ${role} ${selectorStr}${labelStr}${disabled}`);
|
|
1008
|
+
}
|
|
1009
|
+
async function runIosAnalyze(app, config, options, udid) {
|
|
1010
|
+
const allowedApps = config?.guardrails.allowedApps ?? [];
|
|
1011
|
+
if (!/\//.test(app) && !/\.app$/i.test(app.trim())) {
|
|
1012
|
+
assertIosAppAllowed(allowedApps, app);
|
|
1013
|
+
}
|
|
1014
|
+
const session = await launchIosSession({
|
|
1015
|
+
app,
|
|
1016
|
+
...udid ? { udid } : {},
|
|
1017
|
+
timeoutMs: config?.browser.timeout,
|
|
1018
|
+
allowedApps
|
|
1019
|
+
});
|
|
1020
|
+
try {
|
|
1021
|
+
const sourceFn = session.client.source;
|
|
1022
|
+
if (typeof sourceFn !== "function") {
|
|
1023
|
+
throw new Error(
|
|
1024
|
+
"The iOS agent client does not expose source(); a live WebDriverAgent session is required to analyze."
|
|
1025
|
+
);
|
|
1026
|
+
}
|
|
1027
|
+
const result = await analyzeIosApp(
|
|
1028
|
+
{ source: () => sourceFn.call(session.client) },
|
|
1029
|
+
{ app: session.bundleId }
|
|
1030
|
+
);
|
|
1031
|
+
if (options.json) {
|
|
1032
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1033
|
+
} else {
|
|
1034
|
+
console.log(`
|
|
1035
|
+
${chalk10.bold("App Analysis:")} ${result.app}
|
|
1036
|
+
`);
|
|
1037
|
+
if (result.windows.length > 0) {
|
|
1038
|
+
console.log(chalk10.bold(" Windows:"));
|
|
1039
|
+
for (const win of result.windows) {
|
|
1040
|
+
const title = win.label ?? win.name;
|
|
1041
|
+
const titleStr = title ? chalk10.gray(`"${title}"`) : chalk10.gray("(untitled)");
|
|
1042
|
+
console.log(` ${titleStr} ${chalk10.yellow(win.selector)}`);
|
|
1043
|
+
}
|
|
1044
|
+
console.log();
|
|
1045
|
+
}
|
|
1046
|
+
if (result.elements.length > 0) {
|
|
1047
|
+
console.log(chalk10.bold(" Interactive Elements:"));
|
|
1048
|
+
for (const el of result.elements) {
|
|
1049
|
+
printIosElement(el);
|
|
1050
|
+
}
|
|
1051
|
+
console.log();
|
|
1052
|
+
}
|
|
1053
|
+
console.log(chalk10.gray(` ${result.elements.length} elements, ${result.windows.length} windows
|
|
1054
|
+
`));
|
|
1055
|
+
}
|
|
1056
|
+
} finally {
|
|
1057
|
+
await session.teardown();
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function runNativeAnalyze(platform, app, config, options, device) {
|
|
1061
|
+
if (platform === "android") {
|
|
1062
|
+
await runAndroidAnalyze(app, config, options, device.deviceSerial);
|
|
1063
|
+
return;
|
|
1064
|
+
}
|
|
1065
|
+
if (platform === "ios") {
|
|
1066
|
+
await runIosAnalyze(app, config, options, device.udid);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
await runMacAnalyze(app, config, options);
|
|
1070
|
+
}
|
|
1071
|
+
function buildAnalyzeCommand() {
|
|
1072
|
+
const command = new Command8("analyze").argument("[url]", "URL to analyze (web target)").description("Analyze a page or native app to discover interactive elements and selectors").option("--json", "Output as JSON").option("--app <app>", "Native app to analyze: bundle id / package / .app / .apk (macOS, Android, or iOS)").option("--platform <name>", "Native platform for --app: macos, android, or ios (else inferred)").option("--device <serial>", "Android adb device serial (native Android target)").option("--udid <udid>", "iOS simulator UDID (native iOS target)").option("--browser <engine>", "Browser engine: chromium, firefox, or webkit").option("--channel <name>", "Browser channel: chrome, msedge, etc.").option("--viewport <size>", "Viewport size: WxH or preset (mobile, tablet, desktop)").option("--headed", "Show browser window").option("--config <path>", "Custom config path").action(async (url, options) => {
|
|
1073
|
+
try {
|
|
1074
|
+
if (options.app && url) {
|
|
1075
|
+
throw new Error("Pass either a URL argument (web) or --app (native), not both.");
|
|
1076
|
+
}
|
|
1077
|
+
if (options.app) {
|
|
1078
|
+
const config2 = tryLoadConfig(options.config);
|
|
1079
|
+
const platform = pickNativePlatform(options.platform, config2, options.app);
|
|
1080
|
+
await runNativeAnalyze(platform, options.app, config2, options, {
|
|
1081
|
+
deviceSerial: options.device,
|
|
1082
|
+
udid: options.udid
|
|
1083
|
+
});
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (url) {
|
|
1087
|
+
await runWebAnalyze(url, options);
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
const config = tryLoadConfig(options.config);
|
|
1091
|
+
const target = config?.target;
|
|
1092
|
+
if (target?.type === "macos") {
|
|
1093
|
+
await runMacAnalyze(target.app, config, options);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
if (target?.type === "android") {
|
|
1097
|
+
await runAndroidAnalyze(target.app, config, options, options.device ?? target.deviceSerial);
|
|
1098
|
+
return;
|
|
1099
|
+
}
|
|
1100
|
+
if (target?.type === "ios") {
|
|
1101
|
+
await runIosAnalyze(target.app, config, options, options.udid ?? target.udid);
|
|
1102
|
+
return;
|
|
1103
|
+
}
|
|
1104
|
+
throw new Error(
|
|
1105
|
+
"analyze needs a target: pass a URL for the web target, or use --app <bundle-id|package|.app|.apk> (optionally with --platform), or set a native target.type in .prowl/config.yml."
|
|
1106
|
+
);
|
|
849
1107
|
} catch (error) {
|
|
850
1108
|
const message = error instanceof Error ? error.message : "Analysis failed";
|
|
851
1109
|
if (options.json) {
|
|
@@ -919,7 +1177,7 @@ function buildGenerateCommand() {
|
|
|
919
1177
|
} else if (options.output) {
|
|
920
1178
|
let configDir;
|
|
921
1179
|
try {
|
|
922
|
-
const { loadConfig: loadConfig2 } = await import("./loader-
|
|
1180
|
+
const { loadConfig: loadConfig2 } = await import("./loader-PBBYV3U7.js");
|
|
923
1181
|
const result = loadConfig2(options.config);
|
|
924
1182
|
configDir = result.configDir;
|
|
925
1183
|
} catch {
|