doc-detective 4.41.6 → 4.42.1
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 +3 -4
- package/bin/doc-detective-lsp.js +2 -0
- package/dist/agents/adapters/claude-code.js +1 -1
- package/dist/agents/adapters/claude-code.js.map +1 -1
- package/dist/agents/adapters/copilot-cli.js +2 -2
- package/dist/agents/adapters/copilot-cli.js.map +1 -1
- package/dist/agents/adapters/gemini-cli.js +1 -1
- package/dist/agents/adapters/gemini-cli.js.map +1 -1
- package/dist/agents/adapters/qwen-code.js +1 -1
- package/dist/agents/adapters/qwen-code.js.map +1 -1
- package/dist/cli.js +27 -6
- package/dist/cli.js.map +1 -1
- package/dist/common/src/schemas/schemas.json +2 -2
- package/dist/common/src/schemas/schemasReferenced.json +448 -294
- package/dist/common/src/schemas/validators.cjs +140 -124
- package/dist/common/src/schemas/validatorsCheck.cjs +1 -1
- package/dist/common/src/types/generated/annotate_v3.d.ts +12 -12
- package/dist/common/src/types/generated/annotate_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/annotation_v3.d.ts +6 -6
- package/dist/common/src/types/generated/annotation_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/checkLink_v3.d.ts +4 -0
- package/dist/common/src/types/generated/checkLink_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/click_v3.d.ts +2 -2
- package/dist/common/src/types/generated/click_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/config_v3.d.ts +260 -108
- package/dist/common/src/types/generated/config_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/context_v3.d.ts +10 -2
- package/dist/common/src/types/generated/context_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/dragAndDrop_v3.d.ts +2 -2
- package/dist/common/src/types/generated/dragAndDrop_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/find_v3.d.ts +6 -6
- package/dist/common/src/types/generated/find_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/goTo_v3.d.ts +1 -1
- package/dist/common/src/types/generated/goTo_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/httpRequest_v3.d.ts +2 -2
- package/dist/common/src/types/generated/httpRequest_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/record_v3.d.ts +5 -5
- package/dist/common/src/types/generated/record_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/report_v3.d.ts +529 -207
- package/dist/common/src/types/generated/report_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts +754 -314
- package/dist/common/src/types/generated/resolvedTests_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/runCode_v3.d.ts +11 -2
- package/dist/common/src/types/generated/runCode_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/runShell_v3.d.ts +11 -2
- package/dist/common/src/types/generated/runShell_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/screenshot_v3.d.ts +12 -12
- package/dist/common/src/types/generated/screenshot_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/spec_v3.d.ts +494 -206
- package/dist/common/src/types/generated/spec_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/startSurface_v3.d.ts +24 -6
- package/dist/common/src/types/generated/startSurface_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/step_v3.d.ts +223 -100
- package/dist/common/src/types/generated/step_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/test_v3.d.ts +482 -202
- package/dist/common/src/types/generated/test_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/type_v3.d.ts +3 -3
- package/dist/common/src/types/generated/type_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/waitUntil_v3.d.ts +2 -2
- package/dist/common/src/types/generated/waitUntil_v3.d.ts.map +1 -1
- package/dist/common/src/types/generated/wait_v3.d.ts +2 -3
- package/dist/common/src/types/generated/wait_v3.d.ts.map +1 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +6 -4
- package/dist/core/config.js.map +1 -1
- package/dist/core/files.d.ts +18 -3
- package/dist/core/files.d.ts.map +1 -1
- package/dist/core/files.js +58 -18
- package/dist/core/files.js.map +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +16 -0
- package/dist/core/index.js.map +1 -1
- package/dist/core/integrations/index.d.ts +2 -1
- package/dist/core/integrations/index.d.ts.map +1 -1
- package/dist/core/integrations/index.js +15 -5
- package/dist/core/integrations/index.js.map +1 -1
- package/dist/core/openapi.d.ts.map +1 -1
- package/dist/core/openapi.js +130 -6
- package/dist/core/openapi.js.map +1 -1
- package/dist/core/ptyWatchdog.d.ts.map +1 -1
- package/dist/core/ptyWatchdog.js +6 -3
- package/dist/core/ptyWatchdog.js.map +1 -1
- package/dist/core/resolveTests.d.ts.map +1 -1
- package/dist/core/resolveTests.js +5 -3
- package/dist/core/resolveTests.js.map +1 -1
- package/dist/core/telem.d.ts.map +1 -1
- package/dist/core/telem.js +5 -7
- package/dist/core/telem.js.map +1 -1
- package/dist/core/tests/checkLink.d.ts.map +1 -1
- package/dist/core/tests/checkLink.js +12 -7
- package/dist/core/tests/checkLink.js.map +1 -1
- package/dist/core/tests/click.d.ts.map +1 -1
- package/dist/core/tests/click.js +7 -0
- package/dist/core/tests/click.js.map +1 -1
- package/dist/core/tests/findElement.d.ts.map +1 -1
- package/dist/core/tests/findElement.js +3 -2
- package/dist/core/tests/findElement.js.map +1 -1
- package/dist/core/tests/goTo.d.ts.map +1 -1
- package/dist/core/tests/goTo.js +2 -1
- package/dist/core/tests/goTo.js.map +1 -1
- package/dist/core/tests/httpRequest.d.ts.map +1 -1
- package/dist/core/tests/httpRequest.js +4 -5
- package/dist/core/tests/httpRequest.js.map +1 -1
- package/dist/core/tests/processSurface.d.ts +4 -1
- package/dist/core/tests/processSurface.d.ts.map +1 -1
- package/dist/core/tests/processSurface.js +3 -0
- package/dist/core/tests/processSurface.js.map +1 -1
- package/dist/core/tests/saveScreenshot.d.ts.map +1 -1
- package/dist/core/tests/saveScreenshot.js +8 -3
- package/dist/core/tests/saveScreenshot.js.map +1 -1
- package/dist/core/tests/startRecording.d.ts.map +1 -1
- package/dist/core/tests/startRecording.js +2 -1
- package/dist/core/tests/startRecording.js.map +1 -1
- package/dist/core/tests/stopRecording.d.ts.map +1 -1
- package/dist/core/tests/stopRecording.js +2 -1
- package/dist/core/tests/stopRecording.js.map +1 -1
- package/dist/core/tests/timing.d.ts +4 -0
- package/dist/core/tests/timing.d.ts.map +1 -0
- package/dist/core/tests/timing.js +8 -0
- package/dist/core/tests/timing.js.map +1 -0
- package/dist/core/tests.d.ts +15 -0
- package/dist/core/tests.d.ts.map +1 -1
- package/dist/core/tests.js +77 -30
- package/dist/core/tests.js.map +1 -1
- package/dist/core/utils.d.ts +42 -2
- package/dist/core/utils.d.ts.map +1 -1
- package/dist/core/utils.js +284 -90
- package/dist/core/utils.js.map +1 -1
- package/dist/debug/index.d.ts.map +1 -1
- package/dist/debug/index.js +8 -36
- package/dist/debug/index.js.map +1 -1
- package/dist/hints/hints.js +2 -2
- package/dist/hints/hints.js.map +1 -1
- package/dist/index.cjs +689 -235
- package/dist/reporters/htmlReporter.js +1 -1
- package/dist/runtime/cacheDir.d.ts.map +1 -1
- package/dist/runtime/cacheDir.js +6 -11
- package/dist/runtime/cacheDir.js.map +1 -1
- package/dist/runtime/heavyDeps.d.ts +12 -0
- package/dist/runtime/heavyDeps.d.ts.map +1 -1
- package/dist/runtime/heavyDeps.js +31 -7
- package/dist/runtime/heavyDeps.js.map +1 -1
- package/dist/runtime/selfUpdate.d.ts +3 -3
- package/dist/runtime/selfUpdate.d.ts.map +1 -1
- package/dist/runtime/selfUpdate.js +34 -5
- package/dist/runtime/selfUpdate.js.map +1 -1
- package/dist/upgradeNotice.d.ts +41 -0
- package/dist/upgradeNotice.d.ts.map +1 -0
- package/dist/upgradeNotice.js +356 -0
- package/dist/upgradeNotice.js.map +1 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +17 -8
- package/dist/utils.js.map +1 -1
- package/package.json +2 -2
- package/scripts/postinstall.js +1 -1
package/dist/index.cjs
CHANGED
|
@@ -518,12 +518,27 @@ function resolveManagedDepNames(pkg) {
|
|
|
518
518
|
]);
|
|
519
519
|
return withPeerCompanions([...names]);
|
|
520
520
|
}
|
|
521
|
+
function packageRoot() {
|
|
522
|
+
for (let dir = __dirname; !cachedRoot; dir = import_node_path.default.dirname(dir)) {
|
|
523
|
+
try {
|
|
524
|
+
const pkg = JSON.parse(import_node_fs.default.readFileSync(import_node_path.default.join(dir, "package.json"), "utf8"));
|
|
525
|
+
if (pkg.name === "doc-detective") {
|
|
526
|
+
cachedRoot = dir;
|
|
527
|
+
cachedPkg = pkg;
|
|
528
|
+
}
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
if (import_node_path.default.dirname(dir) === dir)
|
|
532
|
+
break;
|
|
533
|
+
}
|
|
534
|
+
return cachedRoot;
|
|
535
|
+
}
|
|
536
|
+
function ownVersion() {
|
|
537
|
+
return packageRoot() && cachedPkg.version;
|
|
538
|
+
}
|
|
521
539
|
function readShimPackageJson() {
|
|
522
|
-
if (
|
|
523
|
-
|
|
524
|
-
const pkgPath = import_node_path.default.resolve(__dirname, "../../package.json");
|
|
525
|
-
const raw = import_node_fs.default.readFileSync(pkgPath, "utf8");
|
|
526
|
-
cachedPkg = JSON.parse(raw);
|
|
540
|
+
if (!packageRoot())
|
|
541
|
+
throw new Error("doc-detective's package.json not found");
|
|
527
542
|
return cachedPkg;
|
|
528
543
|
}
|
|
529
544
|
function getDeclaredVersion(name) {
|
|
@@ -583,7 +598,7 @@ function compareTuple(a, b) {
|
|
|
583
598
|
}
|
|
584
599
|
return 0;
|
|
585
600
|
}
|
|
586
|
-
var import_node_fs, import_node_path, import_node_url, __dirname, HEAVY_NPM_DEPS, RUNTIME_PEER_COMPANIONS, cachedPkg;
|
|
601
|
+
var import_node_fs, import_node_path, import_node_url, __dirname, HEAVY_NPM_DEPS, RUNTIME_PEER_COMPANIONS, cachedRoot, cachedPkg;
|
|
587
602
|
var init_heavyDeps = __esm({
|
|
588
603
|
"dist/runtime/heavyDeps.js"() {
|
|
589
604
|
"use strict";
|
|
@@ -675,9 +690,9 @@ function getBrowsersDir(ctx = {}) {
|
|
|
675
690
|
}
|
|
676
691
|
function legacyBrowserSnapshotCandidates() {
|
|
677
692
|
const out = [import_node_path2.default.resolve("browser-snapshots")];
|
|
678
|
-
const shimRoot =
|
|
679
|
-
const fromShim = import_node_path2.default.join(shimRoot, "browser-snapshots");
|
|
680
|
-
if (fromShim !== out[0])
|
|
693
|
+
const shimRoot = packageRoot();
|
|
694
|
+
const fromShim = shimRoot && import_node_path2.default.join(shimRoot, "browser-snapshots");
|
|
695
|
+
if (fromShim && fromShim !== out[0])
|
|
681
696
|
out.push(fromShim);
|
|
682
697
|
return out;
|
|
683
698
|
}
|
|
@@ -734,15 +749,14 @@ function writeInstalledRecord(record, ctx = {}) {
|
|
|
734
749
|
}
|
|
735
750
|
}
|
|
736
751
|
}
|
|
737
|
-
var import_node_fs2, import_node_os, import_node_path2,
|
|
752
|
+
var import_node_fs2, import_node_os, import_node_path2, SHELL_METAS;
|
|
738
753
|
var init_cacheDir = __esm({
|
|
739
754
|
"dist/runtime/cacheDir.js"() {
|
|
740
755
|
"use strict";
|
|
741
756
|
import_node_fs2 = __toESM(require("node:fs"), 1);
|
|
742
757
|
import_node_os = __toESM(require("node:os"), 1);
|
|
743
758
|
import_node_path2 = __toESM(require("node:path"), 1);
|
|
744
|
-
|
|
745
|
-
__dirname2 = import_node_path2.default.dirname((0, import_node_url2.fileURLToPath)(importMetaUrl));
|
|
759
|
+
init_heavyDeps();
|
|
746
760
|
SHELL_METAS = /[;&|`$<>"'\n\r%^!]/;
|
|
747
761
|
}
|
|
748
762
|
});
|
|
@@ -810,7 +824,7 @@ function tryResolveFromCache(name, ctx = {}) {
|
|
|
810
824
|
const pkgJsonAnchor = import_node_path3.default.join(runtimeDir, "package.json");
|
|
811
825
|
if (!import_node_fs3.default.existsSync(pkgJsonAnchor))
|
|
812
826
|
return null;
|
|
813
|
-
const requireFromCache = (0, import_node_module.createRequire)((0,
|
|
827
|
+
const requireFromCache = (0, import_node_module.createRequire)((0, import_node_url2.pathToFileURL)(pkgJsonAnchor).href);
|
|
814
828
|
return resolveEntry(requireFromCache, name);
|
|
815
829
|
} catch (err) {
|
|
816
830
|
const detail = err instanceof Error ? err.stack ?? `${err.name}: ${err.message}` : (0, import_node_util.inspect)(err);
|
|
@@ -870,7 +884,7 @@ async function loadHeavyDep(name, options = {}) {
|
|
|
870
884
|
throw new Error(`Failed to resolve heavy dep '${name}' even after install. Inspect <cacheDir>/runtime/node_modules/${name}.`);
|
|
871
885
|
}
|
|
872
886
|
}
|
|
873
|
-
return await import((0,
|
|
887
|
+
return await import((0, import_node_url2.pathToFileURL)(resolved).href);
|
|
874
888
|
}
|
|
875
889
|
function heavyDepPrefetchTarget(name, ctx = {}) {
|
|
876
890
|
const entry = resolveHeavyDepPath(name, ctx);
|
|
@@ -883,7 +897,7 @@ function prefetchHeavyDep(name, ctx = {}) {
|
|
|
883
897
|
try {
|
|
884
898
|
const target = heavyDepPrefetchTarget(name, ctx);
|
|
885
899
|
if (target)
|
|
886
|
-
import((0,
|
|
900
|
+
import((0, import_node_url2.pathToFileURL)(target).href).catch(() => {
|
|
887
901
|
});
|
|
888
902
|
} catch {
|
|
889
903
|
}
|
|
@@ -1099,14 +1113,14 @@ async function ensureRuntimeInstalled(packages, options = {}) {
|
|
|
1099
1113
|
}
|
|
1100
1114
|
recordRuntimeDependencies(runtimeDir, ctx);
|
|
1101
1115
|
}
|
|
1102
|
-
var import_node_path3, import_node_fs3, import_node_child_process,
|
|
1116
|
+
var import_node_path3, import_node_fs3, import_node_child_process, import_node_url2, import_node_module, import_node_util, RUNTIME_DEBUG, defaultLogger, requireFromShim, PREFETCH_SIBLING, DEFAULT_INSTALL_TIMEOUT_MS, RUNTIME_PACKAGE_JSON_CONTENTS;
|
|
1103
1117
|
var init_loader = __esm({
|
|
1104
1118
|
"dist/runtime/loader.js"() {
|
|
1105
1119
|
"use strict";
|
|
1106
1120
|
import_node_path3 = __toESM(require("node:path"), 1);
|
|
1107
1121
|
import_node_fs3 = __toESM(require("node:fs"), 1);
|
|
1108
1122
|
import_node_child_process = require("node:child_process");
|
|
1109
|
-
|
|
1123
|
+
import_node_url2 = require("node:url");
|
|
1110
1124
|
import_node_module = require("node:module");
|
|
1111
1125
|
import_node_util = require("node:util");
|
|
1112
1126
|
init_heavyDeps();
|
|
@@ -1532,8 +1546,8 @@ var init_windowsBash = __esm({
|
|
|
1532
1546
|
|
|
1533
1547
|
// dist/core/ptyWatchdog.js
|
|
1534
1548
|
function defaultCreateWorker(data) {
|
|
1535
|
-
const
|
|
1536
|
-
return new import_node_worker_threads.Worker(
|
|
1549
|
+
const worker = import_node_path5.default.join(packageRoot(), "dist", "core", "ptyProbeWorker.js");
|
|
1550
|
+
return new import_node_worker_threads.Worker(worker, { workerData: data });
|
|
1537
1551
|
}
|
|
1538
1552
|
function probePtyAllocation(opts) {
|
|
1539
1553
|
const timeoutMs = opts.timeoutMs ?? PTY_PROBE_TIMEOUT_MS;
|
|
@@ -1615,12 +1629,14 @@ async function assertConptyAllocatable(opts) {
|
|
|
1615
1629
|
throw err;
|
|
1616
1630
|
}
|
|
1617
1631
|
}
|
|
1618
|
-
var import_node_fs6, import_node_worker_threads, PTY_PROBE_TIMEOUT_MS;
|
|
1632
|
+
var import_node_fs6, import_node_path5, import_node_worker_threads, PTY_PROBE_TIMEOUT_MS;
|
|
1619
1633
|
var init_ptyWatchdog = __esm({
|
|
1620
1634
|
"dist/core/ptyWatchdog.js"() {
|
|
1621
1635
|
"use strict";
|
|
1622
1636
|
import_node_fs6 = __toESM(require("node:fs"), 1);
|
|
1637
|
+
import_node_path5 = __toESM(require("node:path"), 1);
|
|
1623
1638
|
import_node_worker_threads = require("node:worker_threads");
|
|
1639
|
+
init_heavyDeps();
|
|
1624
1640
|
PTY_PROBE_TIMEOUT_MS = Number(process.env.DOC_DETECTIVE_PTY_PROBE_TIMEOUT_MS) || 15e3;
|
|
1625
1641
|
}
|
|
1626
1642
|
});
|
|
@@ -2388,6 +2404,24 @@ var init_verification = __esm({
|
|
|
2388
2404
|
function loadAxios() {
|
|
2389
2405
|
return axiosModule ??= import("axios").then((m) => m.default);
|
|
2390
2406
|
}
|
|
2407
|
+
function sessionVersions({ capabilities, app }) {
|
|
2408
|
+
const result = {};
|
|
2409
|
+
const str = (value) => typeof value === "string" && value ? value : void 0;
|
|
2410
|
+
const caps = capabilities ?? {};
|
|
2411
|
+
const browserVersion = str(caps.browserVersion) ?? str(app?.version)?.replace(/^[a-z]+_/, "");
|
|
2412
|
+
if (browserVersion)
|
|
2413
|
+
result.browserVersion = browserVersion;
|
|
2414
|
+
const chromedriver = str(caps.chrome?.chromedriverVersion);
|
|
2415
|
+
if (chromedriver) {
|
|
2416
|
+
result.driver = { name: "chromedriver", version: chromedriver.split(" ")[0] };
|
|
2417
|
+
} else if (app?.name === "firefox") {
|
|
2418
|
+
const version = str(caps["moz:geckodriverVersion"]) ?? /geckodriver-(\d+(?:\.\d+)*)/i.exec(import_node_path6.default.basename(str(app.driver) ?? ""))?.[1];
|
|
2419
|
+
result.driver = { name: "geckodriver", ...version && { version } };
|
|
2420
|
+
} else if (app?.name === "safari") {
|
|
2421
|
+
result.driver = { name: "safaridriver" };
|
|
2422
|
+
}
|
|
2423
|
+
return result;
|
|
2424
|
+
}
|
|
2391
2425
|
function createAppiumPool(ports) {
|
|
2392
2426
|
const available = [...ports];
|
|
2393
2427
|
const waiters = [];
|
|
@@ -2603,11 +2637,9 @@ async function spawnPtyBackgroundCommand(cmd, args = [], options = {}) {
|
|
|
2603
2637
|
try {
|
|
2604
2638
|
ptyProcess = pty.spawn(shell, shellArgs, {
|
|
2605
2639
|
name: "xterm-color",
|
|
2606
|
-
//
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
cols: 120,
|
|
2610
|
-
rows: 30,
|
|
2640
|
+
// `tty: {cols, rows}`; Ajv skips defaults inside the schema's anyOf.
|
|
2641
|
+
cols: options.cols ?? 120,
|
|
2642
|
+
rows: options.rows ?? 30,
|
|
2611
2643
|
cwd: options.cwd || process.cwd(),
|
|
2612
2644
|
env: shellSpawnEnv(options.shell) ?? process.env
|
|
2613
2645
|
});
|
|
@@ -2969,13 +3001,13 @@ function appendQueryParams(url, params) {
|
|
|
2969
3001
|
return pathAndAuthority + (query ? "?" + query : "") + fragment;
|
|
2970
3002
|
}
|
|
2971
3003
|
function cleanTemp() {
|
|
2972
|
-
const tempDir =
|
|
3004
|
+
const tempDir = import_node_path6.default.join(import_node_os2.default.tmpdir(), "doc-detective");
|
|
2973
3005
|
if (!import_node_fs7.default.existsSync(tempDir))
|
|
2974
3006
|
return;
|
|
2975
3007
|
for (const entry of import_node_fs7.default.readdirSync(tempDir)) {
|
|
2976
3008
|
if (PRESERVED_TEMP_ENTRIES.has(entry))
|
|
2977
3009
|
continue;
|
|
2978
|
-
const curPath =
|
|
3010
|
+
const curPath = import_node_path6.default.join(tempDir, entry);
|
|
2979
3011
|
try {
|
|
2980
3012
|
const stat = import_node_fs7.default.statSync(curPath);
|
|
2981
3013
|
if (stat.isDirectory()) {
|
|
@@ -3003,7 +3035,7 @@ function safeFilenameFromUrl(fileURL, fallback) {
|
|
|
3003
3035
|
raw = fileURL;
|
|
3004
3036
|
}
|
|
3005
3037
|
raw = raw.split("?")[0].split("#")[0];
|
|
3006
|
-
const base =
|
|
3038
|
+
const base = import_node_path6.default.basename(raw.replace(/\\/g, "/"));
|
|
3007
3039
|
return sanitizeFilesystemName(base, fallback);
|
|
3008
3040
|
}
|
|
3009
3041
|
function redactUrlForOutput(value) {
|
|
@@ -3041,36 +3073,56 @@ function isPrivateOrLoopbackAddress(ip) {
|
|
|
3041
3073
|
return false;
|
|
3042
3074
|
}
|
|
3043
3075
|
if (import_node_net.default.isIPv6(ip)) {
|
|
3044
|
-
const
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
if (
|
|
3076
|
+
const g = ipv6Groups(ip);
|
|
3077
|
+
const zero = (from, to) => g.slice(from, to).every((x) => x === 0);
|
|
3078
|
+
const v4 = `${g[6] >> 8}.${g[6] & 255}.${g[7] >> 8}.${g[7] & 255}`;
|
|
3079
|
+
if ((g[0] & 65024) === 64512)
|
|
3048
3080
|
return true;
|
|
3049
|
-
if (
|
|
3081
|
+
if ((g[0] & 65472) === 65152)
|
|
3050
3082
|
return true;
|
|
3051
|
-
if (
|
|
3083
|
+
if ((g[0] & 65472) === 65216)
|
|
3052
3084
|
return true;
|
|
3053
|
-
if (
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3060
|
-
|
|
3061
|
-
|
|
3062
|
-
|
|
3063
|
-
|
|
3064
|
-
}
|
|
3065
|
-
return false;
|
|
3085
|
+
if (g[0] === 8194) {
|
|
3086
|
+
return isPrivateOrLoopbackAddress(`${g[1] >> 8}.${g[1] & 255}.${g[2] >> 8}.${g[2] & 255}`);
|
|
3087
|
+
}
|
|
3088
|
+
if (zero(0, 4) && g[4] === 65535 && g[5] === 0)
|
|
3089
|
+
return isPrivateOrLoopbackAddress(v4);
|
|
3090
|
+
if (zero(0, 6))
|
|
3091
|
+
return isPrivateOrLoopbackAddress(v4);
|
|
3092
|
+
if (zero(0, 5) && g[5] === 65535)
|
|
3093
|
+
return isPrivateOrLoopbackAddress(v4);
|
|
3094
|
+
if (g[0] === 100 && g[1] === 65435 && zero(2, 6)) {
|
|
3095
|
+
return isPrivateOrLoopbackAddress(v4);
|
|
3066
3096
|
}
|
|
3067
3097
|
return false;
|
|
3068
3098
|
}
|
|
3069
3099
|
return false;
|
|
3070
3100
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3101
|
+
function ipv6Groups(ip) {
|
|
3102
|
+
let text = ip.toLowerCase().split("%")[0];
|
|
3103
|
+
const dotted = text.match(/(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
|
|
3104
|
+
if (dotted) {
|
|
3105
|
+
const [a, b, c, d] = dotted.slice(1).map(Number);
|
|
3106
|
+
text = text.slice(0, dotted.index) + `${(a << 8 | b).toString(16)}:${(c << 8 | d).toString(16)}`;
|
|
3107
|
+
}
|
|
3108
|
+
const [head, tail] = text.split("::");
|
|
3109
|
+
const left = head ? head.split(":") : [];
|
|
3110
|
+
const right = tail ? tail.split(":") : [];
|
|
3111
|
+
const groups = tail === void 0 ? left : [...left, ...Array(8 - left.length - right.length).fill("0"), ...right];
|
|
3112
|
+
return groups.map((group) => parseInt(group, 16));
|
|
3113
|
+
}
|
|
3114
|
+
function envFlag(name) {
|
|
3115
|
+
return /^(1|true|yes|on)$/i.test((process.env[name] ?? "").trim());
|
|
3116
|
+
}
|
|
3117
|
+
function localUrlPolicy() {
|
|
3118
|
+
const allow = envFlag("DOC_DETECTIVE_ALLOW_LOCAL_URLS");
|
|
3119
|
+
const block = envFlag("DOC_DETECTIVE_BLOCK_LOCAL_URLS");
|
|
3120
|
+
if (allow && block) {
|
|
3121
|
+
throw new UrlGuardError("DOC_DETECTIVE_ALLOW_LOCAL_URLS and DOC_DETECTIVE_BLOCK_LOCAL_URLS can't both be set.");
|
|
3122
|
+
}
|
|
3123
|
+
return allow ? "allow" : block ? "block" : "hops";
|
|
3124
|
+
}
|
|
3125
|
+
async function privateAddressOf(fileURL, { every = false } = {}) {
|
|
3074
3126
|
let parsed;
|
|
3075
3127
|
try {
|
|
3076
3128
|
parsed = new URL(fileURL);
|
|
@@ -3082,33 +3134,117 @@ async function assertUrlHostIsPublic(fileURL) {
|
|
|
3082
3134
|
}
|
|
3083
3135
|
const host = parsed.hostname.replace(/^\[|\]$/g, "");
|
|
3084
3136
|
if (import_node_net.default.isIP(host)) {
|
|
3085
|
-
|
|
3086
|
-
throw new Error(`Refusing to fetch private/loopback address (${host}). Set DOC_DETECTIVE_ALLOW_LOCAL_URLS=true to allow.`);
|
|
3087
|
-
}
|
|
3088
|
-
return;
|
|
3137
|
+
return isPrivateOrLoopbackAddress(host) ? host : void 0;
|
|
3089
3138
|
}
|
|
3090
3139
|
const lower = host.toLowerCase();
|
|
3091
3140
|
if (lower === "localhost" || lower.endsWith(".localhost")) {
|
|
3092
|
-
|
|
3141
|
+
return host;
|
|
3093
3142
|
}
|
|
3094
3143
|
let addresses;
|
|
3095
3144
|
try {
|
|
3096
3145
|
addresses = await import_promises.default.lookup(host, { all: true });
|
|
3097
3146
|
} catch (error) {
|
|
3098
|
-
throw new Error(`Couldn't resolve host ${host}
|
|
3147
|
+
throw Object.assign(new Error(`Couldn't resolve host ${host}: ${error.message}`), { host, code: error.code ?? error.message });
|
|
3099
3148
|
}
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3149
|
+
const privates = addresses.filter(({ address }) => isPrivateOrLoopbackAddress(address));
|
|
3150
|
+
const isPrivate = every ? privates.length === addresses.length : privates.length > 0;
|
|
3151
|
+
return isPrivate ? privates[0]?.address : void 0;
|
|
3152
|
+
}
|
|
3153
|
+
function strictModeError(url) {
|
|
3154
|
+
return new UrlGuardError(`Couldn't fetch ${redactUrlForOutput(url)}: DOC_DETECTIVE_BLOCK_LOCAL_URLS refuses private and loopback addresses.
|
|
3155
|
+
Hint: Unset DOC_DETECTIVE_BLOCK_LOCAL_URLS to allow local URLs.`);
|
|
3156
|
+
}
|
|
3157
|
+
function redirectError(from, address) {
|
|
3158
|
+
return new UrlGuardError(`Couldn't follow the redirect from ${redactUrlForOutput(from)}: it leads to ${address}, a private address, from a public one.
|
|
3159
|
+
Hint: If you trust this redirect, set DOC_DETECTIVE_ALLOW_LOCAL_URLS=1.`);
|
|
3160
|
+
}
|
|
3161
|
+
function hostKey(hostname) {
|
|
3162
|
+
return hostname.replace(/^\[|\]$/g, "").replace(/\.$/, "").toLowerCase();
|
|
3163
|
+
}
|
|
3164
|
+
function proxyConfigured() {
|
|
3165
|
+
return ["http_proxy", "https_proxy", "all_proxy"].some((key) => process.env[key] || process.env[key.toUpperCase()]);
|
|
3166
|
+
}
|
|
3167
|
+
async function getGuarded(url, opts = FETCH_BINARY_DEFAULTS, refuseFirst, refuseRedirect = redirectError) {
|
|
3168
|
+
const policy = localUrlPolicy();
|
|
3169
|
+
const axios = await loadAxios();
|
|
3170
|
+
const [{ default: http3 }, { default: https4 }] = await Promise.all([
|
|
3171
|
+
import("node:http"),
|
|
3172
|
+
import("node:https")
|
|
3173
|
+
]);
|
|
3174
|
+
let current = url;
|
|
3175
|
+
let refuse = policy === "hops" ? refuseFirst : void 0;
|
|
3176
|
+
let reachedPublic = false;
|
|
3177
|
+
for (let hops = 0; ; hops++) {
|
|
3178
|
+
if (policy === "block") {
|
|
3179
|
+
if (await privateAddressOf(current))
|
|
3180
|
+
throw strictModeError(current);
|
|
3181
|
+
const hopUrl = current;
|
|
3182
|
+
refuse = () => strictModeError(hopUrl);
|
|
3183
|
+
}
|
|
3184
|
+
const seen = {};
|
|
3185
|
+
const refuseAtConnect = refuse;
|
|
3186
|
+
const target = hostKey(new URL(current).hostname);
|
|
3187
|
+
const lookup = (hostname, options, callback) => {
|
|
3188
|
+
if (proxyConfigured() && hostKey(hostname) !== target) {
|
|
3189
|
+
return import_node_dns.default.lookup(hostname, options, callback);
|
|
3190
|
+
}
|
|
3191
|
+
return import_node_dns.default.lookup(hostname, options, (error, address, family) => {
|
|
3192
|
+
if (error)
|
|
3193
|
+
return callback(error, address, family);
|
|
3194
|
+
const answers = Array.isArray(address) ? address : [{ address }];
|
|
3195
|
+
seen.resolved = true;
|
|
3196
|
+
seen.private = answers.find((a) => isPrivateOrLoopbackAddress(a.address))?.address;
|
|
3197
|
+
seen.public = answers.some((a) => !isPrivateOrLoopbackAddress(a.address));
|
|
3198
|
+
if (seen.private && refuseAtConnect)
|
|
3199
|
+
return callback(refuseAtConnect(seen.private));
|
|
3200
|
+
callback(null, address, family);
|
|
3201
|
+
});
|
|
3202
|
+
};
|
|
3203
|
+
const response = await axios.get(current, {
|
|
3204
|
+
...opts,
|
|
3205
|
+
maxRedirects: 0,
|
|
3206
|
+
httpAgent: new http3.Agent({ lookup }),
|
|
3207
|
+
httpsAgent: new https4.Agent({ lookup }),
|
|
3208
|
+
validateStatus: (status) => status >= 200 && status < 300 || REDIRECT_STATUSES.includes(status)
|
|
3209
|
+
}).catch((error) => {
|
|
3210
|
+
throw error?.cause instanceof UrlGuardError ? error.cause : error;
|
|
3211
|
+
});
|
|
3212
|
+
let hopPublic = seen.resolved ? seen.public : import_node_net.default.isIP(target) ? !isPrivateOrLoopbackAddress(target) : void 0;
|
|
3213
|
+
let fresh;
|
|
3214
|
+
const freshPublic = () => fresh ??= privateAddressOf(current, { every: true }).then((a) => !a, () => true);
|
|
3215
|
+
if (hopPublic === void 0 && policy !== "allow")
|
|
3216
|
+
hopPublic = await freshPublic();
|
|
3217
|
+
if (hopPublic)
|
|
3218
|
+
reachedPublic = true;
|
|
3219
|
+
if (!REDIRECT_STATUSES.includes(response.status)) {
|
|
3220
|
+
return { data: response.data, url: current, reachedPublic };
|
|
3221
|
+
}
|
|
3222
|
+
const location = response.headers?.location;
|
|
3223
|
+
if (!location || hops >= 5) {
|
|
3224
|
+
throw new UrlGuardError(`Couldn't fetch ${redactUrlForOutput(url)}: ${hops >= 5 ? "it needs more than 5 redirects" : `the ${response.status} redirect has no Location header`}.`);
|
|
3225
|
+
}
|
|
3226
|
+
const next = new URL(location, current).toString();
|
|
3227
|
+
const scheme = new URL(next).protocol;
|
|
3228
|
+
if (scheme !== "http:" && scheme !== "https:") {
|
|
3229
|
+
throw new UrlGuardError(`Couldn't fetch ${redactUrlForOutput(current)}: the redirect leads to an unsupported scheme (\`${scheme.slice(0, -1)}\`).`);
|
|
3230
|
+
}
|
|
3231
|
+
if (policy === "hops") {
|
|
3232
|
+
const fromPublic = hopPublic || await freshPublic();
|
|
3233
|
+
refuse = void 0;
|
|
3234
|
+
if (fromPublic) {
|
|
3235
|
+
const address = await privateAddressOf(next);
|
|
3236
|
+
if (address)
|
|
3237
|
+
throw refuseRedirect(current, address);
|
|
3238
|
+
const from = current;
|
|
3239
|
+
refuse = (connectAddress) => refuseRedirect(from, connectAddress);
|
|
3240
|
+
}
|
|
3241
|
+
}
|
|
3242
|
+
current = next;
|
|
3104
3243
|
}
|
|
3105
3244
|
}
|
|
3106
3245
|
async function fetchFile(fileURL, opts = {}) {
|
|
3107
3246
|
try {
|
|
3108
|
-
|
|
3109
|
-
await assertUrlHostIsPublic(fileURL);
|
|
3110
|
-
}
|
|
3111
|
-
const response = await (await loadAxios()).get(fileURL, opts.binary ? FETCH_BINARY_DEFAULTS : void 0);
|
|
3247
|
+
const response = opts.binary ? await getGuarded(fileURL) : await (await loadAxios()).get(fileURL);
|
|
3112
3248
|
let data;
|
|
3113
3249
|
if (opts.binary) {
|
|
3114
3250
|
data = Buffer.from(response.data);
|
|
@@ -3119,11 +3255,11 @@ async function fetchFile(fileURL, opts = {}) {
|
|
|
3119
3255
|
}
|
|
3120
3256
|
const fileName = safeFilenameFromUrl(fileURL, "fetched_file");
|
|
3121
3257
|
const hash = import_node_crypto3.default.createHash("md5").update(data).digest("hex");
|
|
3122
|
-
const ddTempDir =
|
|
3123
|
-
const filePath =
|
|
3124
|
-
const resolvedDir =
|
|
3125
|
-
const resolvedFile =
|
|
3126
|
-
if (!resolvedFile.startsWith(resolvedDir +
|
|
3258
|
+
const ddTempDir = import_node_path6.default.join(import_node_os2.default.tmpdir(), "doc-detective");
|
|
3259
|
+
const filePath = import_node_path6.default.join(ddTempDir, `${hash}_${fileName}`);
|
|
3260
|
+
const resolvedDir = import_node_path6.default.resolve(ddTempDir);
|
|
3261
|
+
const resolvedFile = import_node_path6.default.resolve(filePath);
|
|
3262
|
+
if (!resolvedFile.startsWith(resolvedDir + import_node_path6.default.sep)) {
|
|
3127
3263
|
return {
|
|
3128
3264
|
result: "error",
|
|
3129
3265
|
message: new Error(`Refusing to write outside temp dir: ${resolvedFile}`)
|
|
@@ -3302,10 +3438,10 @@ function expandRequirementPath(entry, env) {
|
|
|
3302
3438
|
function commandOnPath(command, env, existsSync) {
|
|
3303
3439
|
const hasSeparator = command.includes("/") || command.includes("\\");
|
|
3304
3440
|
const pathValue = env.PATH ?? env.Path ?? "";
|
|
3305
|
-
const directories = hasSeparator ? [""] : pathValue.split(
|
|
3441
|
+
const directories = hasSeparator ? [""] : pathValue.split(import_node_path6.default.delimiter).filter(Boolean);
|
|
3306
3442
|
const extensions = process.platform === "win32" ? ["", ...(env.PATHEXT ?? ".COM;.EXE;.BAT;.CMD").split(";").filter(Boolean)] : [""];
|
|
3307
3443
|
for (const directory of directories) {
|
|
3308
|
-
const base = hasSeparator ? command :
|
|
3444
|
+
const base = hasSeparator ? command : import_node_path6.default.join(directory, command);
|
|
3309
3445
|
for (const extension of extensions) {
|
|
3310
3446
|
if (existsSync(base + extension))
|
|
3311
3447
|
return true;
|
|
@@ -3396,7 +3532,7 @@ function shieldsBadge(format, date, { label = "Last verified", color = "brightgr
|
|
|
3396
3532
|
}
|
|
3397
3533
|
function writeFileAtomic(file, content) {
|
|
3398
3534
|
const target = import_node_fs7.default.realpathSync(file);
|
|
3399
|
-
const tmp =
|
|
3535
|
+
const tmp = import_node_path6.default.join(import_node_path6.default.dirname(target), `.${import_node_path6.default.basename(target)}.${import_node_crypto3.default.randomBytes(6).toString("hex")}.tmp`);
|
|
3400
3536
|
const fd = import_node_fs7.default.openSync(tmp, "wx", 384);
|
|
3401
3537
|
let keepTmp = false;
|
|
3402
3538
|
try {
|
|
@@ -3462,7 +3598,7 @@ function applyVerifiedMarkers({ config, results, files: detectedFiles = [], init
|
|
|
3462
3598
|
}
|
|
3463
3599
|
}
|
|
3464
3600
|
const today = verifiedDate();
|
|
3465
|
-
const version = (
|
|
3601
|
+
const version = ownVersion() ?? "unknown";
|
|
3466
3602
|
for (const file of files) {
|
|
3467
3603
|
try {
|
|
3468
3604
|
if (!import_node_fs7.default.existsSync(file))
|
|
@@ -3481,7 +3617,7 @@ function applyVerifiedMarkers({ config, results, files: detectedFiles = [], init
|
|
|
3481
3617
|
version,
|
|
3482
3618
|
resolve: (check) => resolveVerifiedId(results, check),
|
|
3483
3619
|
onUnknown: (check) => log(config, "warning", `verified-by check '${check}' in ${file} matches no test or spec; skipping.`),
|
|
3484
|
-
onSkipped: (line,
|
|
3620
|
+
onSkipped: (line, reason2) => log(config, "warning", `Skipping the meta statement at ${file}:${line}: ${reason2}.`)
|
|
3485
3621
|
});
|
|
3486
3622
|
if (content !== original)
|
|
3487
3623
|
writeFileAtomic(file, content);
|
|
@@ -3641,11 +3777,11 @@ function getRunOutputDir(config, { create = true } = {}) {
|
|
|
3641
3777
|
let base = String(config?.output || ".");
|
|
3642
3778
|
const reportFileExtensions = [".json", ".html", ".htm", ".xml", ".md"];
|
|
3643
3779
|
if (reportFileExtensions.some((ext) => base.toLowerCase().endsWith(ext))) {
|
|
3644
|
-
base =
|
|
3780
|
+
base = import_node_path6.default.dirname(base);
|
|
3645
3781
|
}
|
|
3646
|
-
const runsRoot =
|
|
3782
|
+
const runsRoot = import_node_path6.default.resolve(base, ".doc-detective", "runs");
|
|
3647
3783
|
const runId = getOrInitRunTimestamp(config);
|
|
3648
|
-
let dir =
|
|
3784
|
+
let dir = import_node_path6.default.join(runsRoot, runId);
|
|
3649
3785
|
if (create) {
|
|
3650
3786
|
import_node_fs7.default.mkdirSync(runsRoot, { recursive: true });
|
|
3651
3787
|
let suffix = 2;
|
|
@@ -3656,7 +3792,7 @@ function getRunOutputDir(config, { create = true } = {}) {
|
|
|
3656
3792
|
} catch (error) {
|
|
3657
3793
|
if (error?.code !== "EEXIST")
|
|
3658
3794
|
throw error;
|
|
3659
|
-
dir =
|
|
3795
|
+
dir = import_node_path6.default.join(runsRoot, `${runId}-${suffix++}`);
|
|
3660
3796
|
}
|
|
3661
3797
|
}
|
|
3662
3798
|
}
|
|
@@ -3733,15 +3869,15 @@ function shellSpawnEnv(shellExecutable, deps = {}) {
|
|
|
3733
3869
|
return void 0;
|
|
3734
3870
|
if (!/bash(\.exe)?$/i.test(shellExecutable))
|
|
3735
3871
|
return void 0;
|
|
3736
|
-
const dir =
|
|
3737
|
-
if (!
|
|
3872
|
+
const dir = import_node_path6.default.win32.dirname(shellExecutable);
|
|
3873
|
+
if (!import_node_path6.default.win32.isAbsolute(dir))
|
|
3738
3874
|
return void 0;
|
|
3739
3875
|
const pathKey = Object.keys(env).find((k) => k.toUpperCase() === "PATH") ?? "Path";
|
|
3740
3876
|
const existing = env[pathKey];
|
|
3741
3877
|
return {
|
|
3742
3878
|
...env,
|
|
3743
3879
|
// No trailing delimiter (an empty PATH segment) when PATH was unset.
|
|
3744
|
-
[pathKey]: existing ? `${dir}${
|
|
3880
|
+
[pathKey]: existing ? `${dir}${import_node_path6.default.win32.delimiter}${existing}` : dir
|
|
3745
3881
|
};
|
|
3746
3882
|
}
|
|
3747
3883
|
async function spawnCommand(cmd, args = [], options = {}) {
|
|
@@ -3842,22 +3978,30 @@ function llevenshteinDistance(s, t) {
|
|
|
3842
3978
|
}
|
|
3843
3979
|
return arr[t.length][s.length];
|
|
3844
3980
|
}
|
|
3845
|
-
var import_node_fs7, import_node_os2,
|
|
3981
|
+
var import_node_fs7, import_node_os2, import_node_path6, import_node_crypto3, import_promises, import_node_dns, import_node_net, import_node_child_process4, axiosModule, FETCH_BINARY_DEFAULTS, FETCH_TEXT_DEFAULTS, BACKGROUND_BUFFER_LIMIT, PTY_PACKAGE, READY_POLL_INTERVAL_MS, TRANSIENT_SESSION_ERROR, SESSION_TIMEOUT_ABORT, BROWSER_ERROR_PAGE, INITIAL_BLANK_DOCUMENT, TRANSIENT_PROCESS_INIT_EXIT_CODES, SETTLE_CEILING_MAX_MS, PRESERVED_TEMP_ENTRIES, UrlGuardError, REDIRECT_STATUSES, VIEWPORT_TOLERANCE_PX, VIEWPORT_SETTLE_MS, DEFAULT_VIEWPORT, ENV_VAR_REGEX, VERIFIED_FORMAT_BY_EXT, VERIFIED_IMG_BODY, posixBashProbe;
|
|
3846
3982
|
var init_utils = __esm({
|
|
3847
3983
|
"dist/core/utils.js"() {
|
|
3848
3984
|
"use strict";
|
|
3849
3985
|
import_node_fs7 = __toESM(require("node:fs"), 1);
|
|
3850
3986
|
import_node_os2 = __toESM(require("node:os"), 1);
|
|
3851
|
-
|
|
3987
|
+
import_node_path6 = __toESM(require("node:path"), 1);
|
|
3852
3988
|
import_node_crypto3 = __toESM(require("node:crypto"), 1);
|
|
3853
3989
|
import_promises = __toESM(require("node:dns/promises"), 1);
|
|
3990
|
+
import_node_dns = __toESM(require("node:dns"), 1);
|
|
3854
3991
|
import_node_net = __toESM(require("node:net"), 1);
|
|
3855
3992
|
import_node_child_process4 = require("node:child_process");
|
|
3856
3993
|
init_loader();
|
|
3857
3994
|
init_windowsBash();
|
|
3995
|
+
init_heavyDeps();
|
|
3858
3996
|
init_ptyWatchdog();
|
|
3859
|
-
import_node_module3 = require("node:module");
|
|
3860
3997
|
init_verification();
|
|
3998
|
+
FETCH_BINARY_DEFAULTS = {
|
|
3999
|
+
responseType: "arraybuffer",
|
|
4000
|
+
timeout: 3e4,
|
|
4001
|
+
maxContentLength: 50 * 1024 * 1024,
|
|
4002
|
+
maxBodyLength: 50 * 1024 * 1024
|
|
4003
|
+
};
|
|
4004
|
+
FETCH_TEXT_DEFAULTS = { ...FETCH_BINARY_DEFAULTS, responseType: "text" };
|
|
3861
4005
|
BACKGROUND_BUFFER_LIMIT = 256 * 1024;
|
|
3862
4006
|
PTY_PACKAGE = "@homebridge/node-pty-prebuilt-multiarch";
|
|
3863
4007
|
READY_POLL_INTERVAL_MS = 500;
|
|
@@ -3884,13 +4028,9 @@ var init_utils = __esm({
|
|
|
3884
4028
|
"jre",
|
|
3885
4029
|
"ios"
|
|
3886
4030
|
]);
|
|
3887
|
-
|
|
3888
|
-
responseType: "arraybuffer",
|
|
3889
|
-
timeout: 3e4,
|
|
3890
|
-
maxContentLength: 50 * 1024 * 1024,
|
|
3891
|
-
maxBodyLength: 50 * 1024 * 1024,
|
|
3892
|
-
maxRedirects: 5
|
|
4031
|
+
UrlGuardError = class extends Error {
|
|
3893
4032
|
};
|
|
4033
|
+
REDIRECT_STATUSES = [301, 302, 303, 307, 308];
|
|
3894
4034
|
VIEWPORT_TOLERANCE_PX = 16;
|
|
3895
4035
|
VIEWPORT_SETTLE_MS = 2e3;
|
|
3896
4036
|
DEFAULT_VIEWPORT = { width: 1200, height: 800 };
|
|
@@ -3932,7 +4072,7 @@ __export(browsers_exports, {
|
|
|
3932
4072
|
verifyDriverBinary: () => verifyDriverBinary
|
|
3933
4073
|
});
|
|
3934
4074
|
function isAllowedDriverPath(binaryPath) {
|
|
3935
|
-
return typeof binaryPath === "string" &&
|
|
4075
|
+
return typeof binaryPath === "string" && import_node_path7.default.isAbsolute(binaryPath) && ALLOWED_DRIVER_PATH.test(binaryPath);
|
|
3936
4076
|
}
|
|
3937
4077
|
function parseDriverVersion(output) {
|
|
3938
4078
|
const m = output.match(/(\d+\.\d+(?:\.\d+)*)/);
|
|
@@ -4104,12 +4244,12 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4104
4244
|
}
|
|
4105
4245
|
if (!force && existing && isStillFresh(existing.latestCheckedAt, now)) {
|
|
4106
4246
|
if (existing.latestKnownVersion === existing.installedVersion) {
|
|
4107
|
-
const
|
|
4108
|
-
return { path:
|
|
4247
|
+
const path34 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
|
|
4248
|
+
return { path: path34, version: existing.installedVersion, outdated: false };
|
|
4109
4249
|
}
|
|
4110
|
-
const
|
|
4250
|
+
const path33 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
|
|
4111
4251
|
logger(`${name} ${existing.installedVersion} installed in ${cacheDir}; "${BROWSER_CHANNELS[name]}" channel is now ${existing.latestKnownVersion}. Run \`doc-detective install browsers ${name} --force\` to update.`, "warn");
|
|
4112
|
-
return { path:
|
|
4252
|
+
return { path: path33, version: existing.installedVersion, outdated: true };
|
|
4113
4253
|
}
|
|
4114
4254
|
let latest;
|
|
4115
4255
|
try {
|
|
@@ -4117,8 +4257,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4117
4257
|
} catch (err) {
|
|
4118
4258
|
logger(`Channel resolution for ${name} skipped: ${String(err)}`, "debug");
|
|
4119
4259
|
if (existing) {
|
|
4120
|
-
const
|
|
4121
|
-
return { path:
|
|
4260
|
+
const path33 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
|
|
4261
|
+
return { path: path33, version: existing.installedVersion, outdated: false };
|
|
4122
4262
|
}
|
|
4123
4263
|
throw err;
|
|
4124
4264
|
}
|
|
@@ -4129,8 +4269,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4129
4269
|
latestCheckedAt: now.toISOString()
|
|
4130
4270
|
};
|
|
4131
4271
|
writeInstalledRecord(record, ctx);
|
|
4132
|
-
const
|
|
4133
|
-
return { path:
|
|
4272
|
+
const path33 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
|
|
4273
|
+
return { path: path33, version: latest, outdated: false };
|
|
4134
4274
|
}
|
|
4135
4275
|
if (!force && existing) {
|
|
4136
4276
|
record.browsers[name] = {
|
|
@@ -4140,8 +4280,8 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4140
4280
|
};
|
|
4141
4281
|
writeInstalledRecord(record, ctx);
|
|
4142
4282
|
logger(`${name} ${existing.installedVersion} installed in ${cacheDir}; "${BROWSER_CHANNELS[name]}" channel is now ${latest}. Run \`doc-detective install browsers ${name} --force\` to update.`, "warn");
|
|
4143
|
-
const
|
|
4144
|
-
return { path:
|
|
4283
|
+
const path33 = await locateExecutable(browsersModule, name, existing.installedVersion, cacheDir, platform);
|
|
4284
|
+
return { path: path33, version: existing.installedVersion, outdated: true };
|
|
4145
4285
|
}
|
|
4146
4286
|
logger(`Installing ${name} ${latest} into ${cacheDir}`, "info");
|
|
4147
4287
|
await browsersModule.install({
|
|
@@ -4149,9 +4289,9 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4149
4289
|
buildId: latest,
|
|
4150
4290
|
cacheDir
|
|
4151
4291
|
});
|
|
4152
|
-
let
|
|
4153
|
-
if (name === "chromedriver" && isAllowedDriverPath(
|
|
4154
|
-
let verify = await verifyDriverBinary(name,
|
|
4292
|
+
let path32 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
|
|
4293
|
+
if (name === "chromedriver" && isAllowedDriverPath(path32)) {
|
|
4294
|
+
let verify = await verifyDriverBinary(name, path32, { exec: deps.verifyExec });
|
|
4155
4295
|
if (!verify.ok) {
|
|
4156
4296
|
logger(`Installed ${name} ${latest} failed validation (${verify.error}); reinstalling once.`, "warn");
|
|
4157
4297
|
try {
|
|
@@ -4160,11 +4300,11 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4160
4300
|
logger(`Failed to prune broken ${name} ${latest}: ${String(err)}`, "debug");
|
|
4161
4301
|
}
|
|
4162
4302
|
await browsersModule.install({ browser: name, buildId: latest, cacheDir });
|
|
4163
|
-
|
|
4164
|
-
verify = await verifyDriverBinary(name,
|
|
4303
|
+
path32 = await locateExecutable(browsersModule, name, latest, cacheDir, platform);
|
|
4304
|
+
verify = await verifyDriverBinary(name, path32, { exec: deps.verifyExec });
|
|
4165
4305
|
}
|
|
4166
4306
|
if (!verify.ok) {
|
|
4167
|
-
throw new Error(`${name} ${latest} is present but non-functional after a reinstall (${verify.error}). It may be a partial or corrupt download; delete ${
|
|
4307
|
+
throw new Error(`${name} ${latest} is present but non-functional after a reinstall (${verify.error}). It may be a partial or corrupt download; delete ${path32} or reinstall.`);
|
|
4168
4308
|
}
|
|
4169
4309
|
}
|
|
4170
4310
|
if (existing && existing.installedVersion !== latest) {
|
|
@@ -4185,7 +4325,7 @@ async function ensureBrowserInstalledImpl(name, options = {}) {
|
|
|
4185
4325
|
latestCheckedAt: now.toISOString()
|
|
4186
4326
|
};
|
|
4187
4327
|
writeInstalledRecord(record, ctx);
|
|
4188
|
-
return { path:
|
|
4328
|
+
return { path: path32, version: latest, outdated: false };
|
|
4189
4329
|
}
|
|
4190
4330
|
async function locateExecutable(browsersModule, name, buildId, cacheDir, platform) {
|
|
4191
4331
|
if (typeof browsersModule.computeExecutablePath === "function") {
|
|
@@ -4218,9 +4358,9 @@ async function withGeckodriverCacheDir(cacheDir, fn) {
|
|
|
4218
4358
|
}
|
|
4219
4359
|
}
|
|
4220
4360
|
function isPathInside(child, dir) {
|
|
4221
|
-
const resolvedDir =
|
|
4222
|
-
const resolvedChild =
|
|
4223
|
-
return resolvedChild === resolvedDir || resolvedChild.startsWith(resolvedDir +
|
|
4361
|
+
const resolvedDir = import_node_path7.default.resolve(dir);
|
|
4362
|
+
const resolvedChild = import_node_path7.default.resolve(child);
|
|
4363
|
+
return resolvedChild === resolvedDir || resolvedChild.startsWith(resolvedDir + import_node_path7.default.sep);
|
|
4224
4364
|
}
|
|
4225
4365
|
function compareGeckodriverVersionsDesc(a, b) {
|
|
4226
4366
|
const pa = a.split(".");
|
|
@@ -4239,7 +4379,7 @@ function isRegularFileEntry(dir, entry) {
|
|
|
4239
4379
|
if (!entry.isSymbolicLink())
|
|
4240
4380
|
return false;
|
|
4241
4381
|
try {
|
|
4242
|
-
return import_node_fs8.default.statSync(
|
|
4382
|
+
return import_node_fs8.default.statSync(import_node_path7.default.join(dir, entry.name)).isFile();
|
|
4243
4383
|
} catch {
|
|
4244
4384
|
return false;
|
|
4245
4385
|
}
|
|
@@ -4253,7 +4393,7 @@ function readDirEntries(dir) {
|
|
|
4253
4393
|
}
|
|
4254
4394
|
function bareBinaryIn(dir, binName, entries) {
|
|
4255
4395
|
const match = entries.find((entry) => entry.name === binName && isRegularFileEntry(dir, entry));
|
|
4256
|
-
return match ?
|
|
4396
|
+
return match ? import_node_path7.default.join(dir, binName) : void 0;
|
|
4257
4397
|
}
|
|
4258
4398
|
function newestVersionedIn(dir, entries) {
|
|
4259
4399
|
const wantExe = process.platform === "win32";
|
|
@@ -4267,7 +4407,7 @@ function newestVersionedIn(dir, entries) {
|
|
|
4267
4407
|
if (wantExe !== Boolean(match[2]))
|
|
4268
4408
|
continue;
|
|
4269
4409
|
if (!best || compareGeckodriverVersionsDesc(match[1], best.version) < 0) {
|
|
4270
|
-
best = { version: match[1], file:
|
|
4410
|
+
best = { version: match[1], file: import_node_path7.default.join(dir, entry.name) };
|
|
4271
4411
|
}
|
|
4272
4412
|
}
|
|
4273
4413
|
return best;
|
|
@@ -4280,7 +4420,7 @@ function geckodriverBinaryInCache(cacheDir) {
|
|
|
4280
4420
|
for (const entry of rootEntries) {
|
|
4281
4421
|
if (!entry.isDirectory())
|
|
4282
4422
|
continue;
|
|
4283
|
-
const dir =
|
|
4423
|
+
const dir = import_node_path7.default.join(cacheDir, entry.name);
|
|
4284
4424
|
const nestedEntries = readDirEntries(dir);
|
|
4285
4425
|
const nestedVersioned = newestVersionedIn(dir, nestedEntries);
|
|
4286
4426
|
if (nestedVersioned && (!bestVersioned || compareGeckodriverVersionsDesc(nestedVersioned.version, bestVersioned.version) < 0)) {
|
|
@@ -4360,14 +4500,14 @@ function getInstalledBrowsers(ctx = {}) {
|
|
|
4360
4500
|
latestKnownVersion: entry.latestKnownVersion
|
|
4361
4501
|
}));
|
|
4362
4502
|
}
|
|
4363
|
-
var import_node_child_process5, import_node_fs8, import_node_os3,
|
|
4503
|
+
var import_node_child_process5, import_node_fs8, import_node_os3, import_node_path7, DRIVER_VERSION_ARGS, DRIVER_VERIFY_TIMEOUT_MS, ALLOWED_DRIVER_PATH, EXECUTABLE_HEADER_BYTES, ELF_MACHINE_BY_NODE_ARCH, ELF_MACHINE_LABELS, defaultDriverExec, BROWSER_CHANNELS, FRESHNESS_TTL_MS, RESOLVE_TIMEOUT_MS, RUNTIME_DEBUG2, defaultLogger2, inFlightInstalls2, geckodriverEnvQueue, GECKODRIVER_VERSIONED_NAME;
|
|
4364
4504
|
var init_browsers = __esm({
|
|
4365
4505
|
"dist/runtime/browsers.js"() {
|
|
4366
4506
|
"use strict";
|
|
4367
4507
|
import_node_child_process5 = require("node:child_process");
|
|
4368
4508
|
import_node_fs8 = __toESM(require("node:fs"), 1);
|
|
4369
4509
|
import_node_os3 = __toESM(require("node:os"), 1);
|
|
4370
|
-
|
|
4510
|
+
import_node_path7 = __toESM(require("node:path"), 1);
|
|
4371
4511
|
init_cacheDir();
|
|
4372
4512
|
init_loader();
|
|
4373
4513
|
DRIVER_VERSION_ARGS = {
|
|
@@ -4429,7 +4569,7 @@ var init_browsers = __esm({
|
|
|
4429
4569
|
function useDriversArgs(appiumHome, packages) {
|
|
4430
4570
|
let manifestText;
|
|
4431
4571
|
try {
|
|
4432
|
-
manifestText = import_node_fs9.default.readFileSync(
|
|
4572
|
+
manifestText = import_node_fs9.default.readFileSync(import_node_path8.default.join(appiumHome, "node_modules", ".cache", "appium", "extensions.yaml"), "utf8");
|
|
4433
4573
|
} catch {
|
|
4434
4574
|
}
|
|
4435
4575
|
let registered;
|
|
@@ -4442,7 +4582,7 @@ function useDriversArgs(appiumHome, packages) {
|
|
|
4442
4582
|
}
|
|
4443
4583
|
const names = packages.flatMap((pkg) => {
|
|
4444
4584
|
try {
|
|
4445
|
-
const name = JSON.parse(import_node_fs9.default.readFileSync(
|
|
4585
|
+
const name = JSON.parse(import_node_fs9.default.readFileSync(import_node_path8.default.join(appiumHome, "node_modules", pkg, "package.json"), "utf8"))?.appium?.driverName;
|
|
4446
4586
|
return typeof name === "string" && name ? [{ name, pkg }] : [];
|
|
4447
4587
|
} catch {
|
|
4448
4588
|
return [];
|
|
@@ -4461,26 +4601,26 @@ function browserPoolDriverPackages(platform) {
|
|
|
4461
4601
|
];
|
|
4462
4602
|
}
|
|
4463
4603
|
function appiumHomeForDriverPath(driverEntry) {
|
|
4464
|
-
const marker = `${
|
|
4604
|
+
const marker = `${import_node_path8.default.sep}node_modules${import_node_path8.default.sep}`;
|
|
4465
4605
|
const idx = driverEntry.lastIndexOf(marker);
|
|
4466
4606
|
return idx === -1 ? null : driverEntry.slice(0, idx);
|
|
4467
4607
|
}
|
|
4468
4608
|
function setAppiumHome(ctx = {}) {
|
|
4469
4609
|
process.env.APPIUM_HOME = getRuntimeDir({ cacheDir: ctx.cacheDir });
|
|
4470
4610
|
}
|
|
4471
|
-
var import_node_fs9,
|
|
4611
|
+
var import_node_fs9, import_node_path8;
|
|
4472
4612
|
var init_appium = __esm({
|
|
4473
4613
|
"dist/core/appium.js"() {
|
|
4474
4614
|
"use strict";
|
|
4475
4615
|
import_node_fs9 = __toESM(require("node:fs"), 1);
|
|
4476
|
-
|
|
4616
|
+
import_node_path8 = __toESM(require("node:path"), 1);
|
|
4477
4617
|
init_cacheDir();
|
|
4478
4618
|
init_lazyYaml();
|
|
4479
4619
|
}
|
|
4480
4620
|
});
|
|
4481
4621
|
|
|
4482
4622
|
// dist/core/files.js
|
|
4483
|
-
async function readFile({ fileURLOrPath }) {
|
|
4623
|
+
async function readFile({ fileURLOrPath, throwOnError = false }) {
|
|
4484
4624
|
if (!fileURLOrPath) {
|
|
4485
4625
|
throw new Error("fileURLOrPath is required");
|
|
4486
4626
|
}
|
|
@@ -4497,40 +4637,55 @@ async function readFile({ fileURLOrPath }) {
|
|
|
4497
4637
|
isRemote = parsedURL.protocol === "http:" || parsedURL.protocol === "https:";
|
|
4498
4638
|
} catch (error) {
|
|
4499
4639
|
}
|
|
4640
|
+
const fail = (message) => {
|
|
4641
|
+
if (throwOnError)
|
|
4642
|
+
throw new Error(message);
|
|
4643
|
+
log({}, "warning", message);
|
|
4644
|
+
return null;
|
|
4645
|
+
};
|
|
4500
4646
|
if (isRemote) {
|
|
4501
4647
|
try {
|
|
4502
4648
|
const response = await (await import("axios")).default.get(fileURLOrPath);
|
|
4503
4649
|
content = response.data;
|
|
4504
4650
|
} catch (error) {
|
|
4505
|
-
|
|
4506
|
-
return null;
|
|
4651
|
+
return fail(`Couldn't read ${redactUrlForOutput(fileURLOrPath)}${reason(error.message)}`);
|
|
4507
4652
|
}
|
|
4508
4653
|
} else {
|
|
4509
4654
|
try {
|
|
4510
4655
|
content = await import_node_fs10.default.promises.readFile(fileURLOrPath, "utf8");
|
|
4511
4656
|
} catch (error) {
|
|
4512
|
-
|
|
4513
|
-
console.warn(`File not found: ${fileURLOrPath}`);
|
|
4514
|
-
} else {
|
|
4515
|
-
console.warn(`Error reading file: ${error.message}`);
|
|
4516
|
-
}
|
|
4517
|
-
return null;
|
|
4657
|
+
return fail(`Couldn't read ${import_node_path9.default.resolve(fileURLOrPath)}${error.code === "ENOENT" ? ": the file doesn't exist." : reason(error.message)}`);
|
|
4518
4658
|
}
|
|
4519
4659
|
}
|
|
4520
|
-
|
|
4660
|
+
return parseFileContent(content, fileURLOrPath, { throwOnError });
|
|
4661
|
+
}
|
|
4662
|
+
function locationOf(fileURLOrPath) {
|
|
4663
|
+
return /^https?:\/\//i.test(fileURLOrPath) ? redactUrlForOutput(fileURLOrPath) : import_node_path9.default.resolve(fileURLOrPath);
|
|
4664
|
+
}
|
|
4665
|
+
function parseFileContent(content, fileURLOrPath, { throwOnError = false } = {}) {
|
|
4666
|
+
if (typeof content !== "string")
|
|
4667
|
+
return content;
|
|
4668
|
+
const name = /^https?:\/\//i.test(fileURLOrPath) ? new URL(fileURLOrPath).pathname : fileURLOrPath;
|
|
4669
|
+
const ext = name.split(".").pop()?.toLowerCase();
|
|
4670
|
+
const failed = (format, error) => {
|
|
4671
|
+
if (throwOnError) {
|
|
4672
|
+
const cause = String(error.message).split("\n")[0].replace(/:$/, "");
|
|
4673
|
+
throw new Error(`Couldn't read ${locationOf(fileURLOrPath)}: it isn't valid ${format} (${cause}).`);
|
|
4674
|
+
}
|
|
4675
|
+
log({}, "warning", `Failed to parse ${format}: ${error.message}`);
|
|
4676
|
+
return content;
|
|
4677
|
+
};
|
|
4521
4678
|
if (ext === "json") {
|
|
4522
4679
|
try {
|
|
4523
4680
|
return JSON.parse(content);
|
|
4524
4681
|
} catch (error) {
|
|
4525
|
-
|
|
4526
|
-
return content;
|
|
4682
|
+
return failed("JSON", error);
|
|
4527
4683
|
}
|
|
4528
4684
|
} else if (ext === "yaml" || ext === "yml") {
|
|
4529
4685
|
try {
|
|
4530
4686
|
return requireYaml().parse(content);
|
|
4531
4687
|
} catch (error) {
|
|
4532
|
-
|
|
4533
|
-
return content;
|
|
4688
|
+
return failed("YAML", error);
|
|
4534
4689
|
}
|
|
4535
4690
|
} else {
|
|
4536
4691
|
return content;
|
|
@@ -4579,13 +4734,13 @@ async function resolvePaths({ config, object, filePath, nested = false, objectTy
|
|
|
4579
4734
|
if (relativePath.startsWith("https://") || relativePath.startsWith("http://") || relativePath.startsWith("heretto:")) {
|
|
4580
4735
|
return relativePath;
|
|
4581
4736
|
}
|
|
4582
|
-
if (
|
|
4737
|
+
if (import_node_path9.default.isAbsolute(relativePath)) {
|
|
4583
4738
|
return relativePath;
|
|
4584
4739
|
}
|
|
4585
4740
|
const fileExists = import_node_fs10.default.existsSync(filePath2);
|
|
4586
|
-
const isFile = fileExists ? import_node_fs10.default.lstatSync(filePath2).isFile() :
|
|
4587
|
-
const basePath = isFile ?
|
|
4588
|
-
return baseType === "file" ?
|
|
4741
|
+
const isFile = fileExists ? import_node_fs10.default.lstatSync(filePath2).isFile() : import_node_path9.default.parse(filePath2).ext !== "";
|
|
4742
|
+
const basePath = isFile ? import_node_path9.default.dirname(filePath2) : filePath2;
|
|
4743
|
+
return baseType === "file" ? import_node_path9.default.resolve(basePath, relativePath) : import_node_path9.default.resolve(relativePath);
|
|
4589
4744
|
}
|
|
4590
4745
|
const relativePathBase = config.relativePathBase;
|
|
4591
4746
|
let pathProperties;
|
|
@@ -4635,7 +4790,7 @@ async function resolvePaths({ config, object, filePath, nested = false, objectTy
|
|
|
4635
4790
|
isStep: property === "steps"
|
|
4636
4791
|
});
|
|
4637
4792
|
} else if (typeof item === "string" && pathProperties.includes(property)) {
|
|
4638
|
-
const resolved = property === "path" && object.directory &&
|
|
4793
|
+
const resolved = property === "path" && object.directory && import_node_path9.default.isAbsolute(object.directory) ? resolve(relativePathBase, item, object.directory) : resolve(relativePathBase, item, filePath);
|
|
4639
4794
|
object[property][i] = resolved;
|
|
4640
4795
|
}
|
|
4641
4796
|
}
|
|
@@ -4653,7 +4808,7 @@ async function resolvePaths({ config, object, filePath, nested = false, objectTy
|
|
|
4653
4808
|
}
|
|
4654
4809
|
if (pathProperties.includes(property) || isStep && stepShorthandPathProperties.includes(property)) {
|
|
4655
4810
|
if (property === "path" && object.directory) {
|
|
4656
|
-
const directory =
|
|
4811
|
+
const directory = import_node_path9.default.isAbsolute(object.directory) ? object.directory : resolve(relativePathBase, object.directory, filePath);
|
|
4657
4812
|
object[property] = resolve(relativePathBase, object[property], directory);
|
|
4658
4813
|
} else {
|
|
4659
4814
|
object[property] = resolve(relativePathBase, object[property], filePath);
|
|
@@ -4663,14 +4818,19 @@ async function resolvePaths({ config, object, filePath, nested = false, objectTy
|
|
|
4663
4818
|
}
|
|
4664
4819
|
return object;
|
|
4665
4820
|
}
|
|
4666
|
-
var import_node_fs10,
|
|
4821
|
+
var import_node_fs10, import_node_path9, reason;
|
|
4667
4822
|
var init_files = __esm({
|
|
4668
4823
|
"dist/core/files.js"() {
|
|
4669
4824
|
"use strict";
|
|
4670
4825
|
import_node_fs10 = __toESM(require("node:fs"), 1);
|
|
4671
|
-
|
|
4826
|
+
import_node_path9 = __toESM(require("node:path"), 1);
|
|
4672
4827
|
init_validate();
|
|
4673
4828
|
init_lazyYaml();
|
|
4829
|
+
init_utils();
|
|
4830
|
+
reason = (text) => {
|
|
4831
|
+
const trimmed2 = text?.trim();
|
|
4832
|
+
return !trimmed2 ? "." : `: ${/[.!?]$/.test(trimmed2) ? trimmed2 : `${trimmed2}.`}`;
|
|
4833
|
+
};
|
|
4674
4834
|
}
|
|
4675
4835
|
});
|
|
4676
4836
|
|
|
@@ -4685,9 +4845,80 @@ async function loadDescription(descriptionPath = "") {
|
|
|
4685
4845
|
if (!descriptionPath) {
|
|
4686
4846
|
throw new Error("Description is required.");
|
|
4687
4847
|
}
|
|
4688
|
-
const
|
|
4689
|
-
|
|
4690
|
-
const
|
|
4848
|
+
const guard = localUrlPolicy() !== "allow";
|
|
4849
|
+
let lastPublic;
|
|
4850
|
+
const docName = (given, final) => {
|
|
4851
|
+
const [a, b] = [given, final].map(redactUrlForOutput);
|
|
4852
|
+
return a === b ? a : `${a} (redirected to ${b})`;
|
|
4853
|
+
};
|
|
4854
|
+
let definition;
|
|
4855
|
+
if (guard && /^https?:\/\//i.test(descriptionPath)) {
|
|
4856
|
+
const root = await getGuarded(descriptionPath, FETCH_TEXT_DEFAULTS);
|
|
4857
|
+
const text = root.data;
|
|
4858
|
+
let content = text;
|
|
4859
|
+
try {
|
|
4860
|
+
content = JSON.parse(text);
|
|
4861
|
+
} catch {
|
|
4862
|
+
}
|
|
4863
|
+
definition = parseFileContent(content, descriptionPath, { throwOnError: true });
|
|
4864
|
+
if (root.reachedPublic)
|
|
4865
|
+
lastPublic = docName(descriptionPath, root.url);
|
|
4866
|
+
} else {
|
|
4867
|
+
definition = await readFile({ fileURLOrPath: descriptionPath, throwOnError: true });
|
|
4868
|
+
}
|
|
4869
|
+
if (typeof definition !== "object" || definition === null || Array.isArray(definition)) {
|
|
4870
|
+
throw new Error(`Couldn't read ${locationOf(descriptionPath)}: it's empty or isn't a JSON or YAML object.`);
|
|
4871
|
+
}
|
|
4872
|
+
const refused = (ref, doc, target) => new UrlGuardError(`Couldn't resolve $ref ${ref} in ${doc}: a public document can't refer to ${target}.
|
|
4873
|
+
Hint: If you trust this document, set DOC_DETECTIVE_ALLOW_LOCAL_URLS=1, or download it and point descriptionPath at the local copy.`);
|
|
4874
|
+
const { default: parser, FileResolver, getJsonSchemaRefParserDefaultOptions } = await import("@apidevtools/json-schema-ref-parser");
|
|
4875
|
+
if (typeof FileResolver.read !== "function") {
|
|
4876
|
+
throw new Error("Couldn't load the OpenAPI description: json-schema-ref-parser's FileResolver.read isn't a function.");
|
|
4877
|
+
}
|
|
4878
|
+
const httpDefaults = getJsonSchemaRefParserDefaultOptions?.()?.resolve?.http;
|
|
4879
|
+
if (typeof httpDefaults?.read !== "function" || !("safeUrlResolver" in httpDefaults)) {
|
|
4880
|
+
throw new Error("Couldn't load the OpenAPI description: json-schema-ref-parser's http resolver has no read function or safeUrlResolver option, so Doc Detective can't guard its $refs.");
|
|
4881
|
+
}
|
|
4882
|
+
const readLocalFile = FileResolver.read.bind(FileResolver);
|
|
4883
|
+
const dereferencedDefinition = await parser.dereference(definition, {
|
|
4884
|
+
resolve: {
|
|
4885
|
+
file: {
|
|
4886
|
+
async read(file) {
|
|
4887
|
+
if (lastPublic) {
|
|
4888
|
+
throw refused(redactUrlForOutput(file.url.split("#")[0]), lastPublic, "a local file");
|
|
4889
|
+
}
|
|
4890
|
+
return readLocalFile(file);
|
|
4891
|
+
}
|
|
4892
|
+
},
|
|
4893
|
+
http: {
|
|
4894
|
+
// The parser's own guard refuses every private address, even from a
|
|
4895
|
+
// local description. Doc Detective's read() below enforces
|
|
4896
|
+
// localUrlPolicy() in its place, so don't remove this.
|
|
4897
|
+
safeUrlResolver: false,
|
|
4898
|
+
async read(file) {
|
|
4899
|
+
const ref = redactUrlForOutput(file.url);
|
|
4900
|
+
const doc = lastPublic;
|
|
4901
|
+
if (doc) {
|
|
4902
|
+
let address;
|
|
4903
|
+
try {
|
|
4904
|
+
address = await privateAddressOf(file.url);
|
|
4905
|
+
} catch (error) {
|
|
4906
|
+
if (!error.host)
|
|
4907
|
+
throw error;
|
|
4908
|
+
throw new Error(`Couldn't resolve $ref ${ref} in ${doc}: couldn't resolve host ${error.host} (${error.code}).`);
|
|
4909
|
+
}
|
|
4910
|
+
if (address)
|
|
4911
|
+
throw refused(ref, doc, `${address}, a private address`);
|
|
4912
|
+
}
|
|
4913
|
+
const refuseRef = doc ? (privateAddress) => refused(ref, doc, `${privateAddress}, a private address`) : void 0;
|
|
4914
|
+
const { data, url, reachedPublic } = await getGuarded(file.url, FETCH_TEXT_DEFAULTS, refuseRef, refuseRef && ((_from, privateAddress) => refuseRef(privateAddress)));
|
|
4915
|
+
if (guard && reachedPublic)
|
|
4916
|
+
lastPublic = docName(file.url, url);
|
|
4917
|
+
return data;
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
}
|
|
4921
|
+
});
|
|
4691
4922
|
return dereferencedDefinition;
|
|
4692
4923
|
}
|
|
4693
4924
|
async function getOperation(definition = {}, operationId = "", responseCode = "", exampleKey = "", server = "") {
|
|
@@ -4697,10 +4928,10 @@ async function getOperation(definition = {}, operationId = "", responseCode = ""
|
|
|
4697
4928
|
if (!operationId) {
|
|
4698
4929
|
throw new Error("OperationId is required.");
|
|
4699
4930
|
}
|
|
4700
|
-
for (const
|
|
4701
|
-
for (const method in definition.paths[
|
|
4702
|
-
if (definition.paths[
|
|
4703
|
-
const operation = definition.paths[
|
|
4931
|
+
for (const path32 in definition.paths) {
|
|
4932
|
+
for (const method in definition.paths[path32]) {
|
|
4933
|
+
if (definition.paths[path32][method].operationId === operationId) {
|
|
4934
|
+
const operation = definition.paths[path32][method];
|
|
4704
4935
|
if (!server) {
|
|
4705
4936
|
if (definition.servers && definition.servers.length > 0) {
|
|
4706
4937
|
server = definition.servers[0].url;
|
|
@@ -4708,9 +4939,9 @@ async function getOperation(definition = {}, operationId = "", responseCode = ""
|
|
|
4708
4939
|
throw new Error("No server URL provided and no servers defined in the OpenAPI definition.");
|
|
4709
4940
|
}
|
|
4710
4941
|
}
|
|
4711
|
-
const example = await compileExample(operation, server +
|
|
4942
|
+
const example = await compileExample(operation, server + path32, responseCode, exampleKey);
|
|
4712
4943
|
const schemas = getSchemas(operation, responseCode);
|
|
4713
|
-
return { path:
|
|
4944
|
+
return { path: path32, method, definition: operation, schemas, example };
|
|
4714
4945
|
}
|
|
4715
4946
|
}
|
|
4716
4947
|
}
|
|
@@ -4731,15 +4962,15 @@ function getSchemas(definition = {}, responseCode = "") {
|
|
|
4731
4962
|
schemas.response = definition.responses[responseCode].content[Object.keys(definition.responses[responseCode].content)[0]].schema;
|
|
4732
4963
|
return schemas;
|
|
4733
4964
|
}
|
|
4734
|
-
async function compileExample(operation = {},
|
|
4965
|
+
async function compileExample(operation = {}, path32 = "", responseCode = "", exampleKey = "") {
|
|
4735
4966
|
if (!operation) {
|
|
4736
4967
|
throw new Error("Operation is required.");
|
|
4737
4968
|
}
|
|
4738
|
-
if (!
|
|
4969
|
+
if (!path32) {
|
|
4739
4970
|
throw new Error("Path is required.");
|
|
4740
4971
|
}
|
|
4741
4972
|
let example = {
|
|
4742
|
-
url:
|
|
4973
|
+
url: path32,
|
|
4743
4974
|
request: { parameters: {}, headers: {}, body: {} },
|
|
4744
4975
|
response: { headers: {}, body: {} }
|
|
4745
4976
|
};
|
|
@@ -5104,6 +5335,9 @@ function getEnvironment() {
|
|
|
5104
5335
|
const environment = {};
|
|
5105
5336
|
environment.arch = import_node_os4.default.arch();
|
|
5106
5337
|
environment.platform = platformMap[process.platform];
|
|
5338
|
+
environment.docDetectiveVersion = ownVersion();
|
|
5339
|
+
environment.nodeVersion = process.versions.node;
|
|
5340
|
+
environment.osRelease = import_node_os4.default.release();
|
|
5107
5341
|
return environment;
|
|
5108
5342
|
}
|
|
5109
5343
|
function cacheKeyFor(config) {
|
|
@@ -5177,7 +5411,7 @@ function detectInstalledBrowserDrivers(config, isInstalled = (name) => Boolean(r
|
|
|
5177
5411
|
async function probeBrowserEnvironment({ config, browsersDir }) {
|
|
5178
5412
|
setAppiumHome({ cacheDir: config?.cacheDir });
|
|
5179
5413
|
const cwd = process.cwd();
|
|
5180
|
-
process.chdir(
|
|
5414
|
+
process.chdir(packageRoot() ?? cwd);
|
|
5181
5415
|
let installedBrowsers = [];
|
|
5182
5416
|
let browserDetectionFailed = false;
|
|
5183
5417
|
let installedDrivers = { chromium: false, gecko: false, safari: false };
|
|
@@ -5393,7 +5627,7 @@ async function getBrowserDiagnostics({ config }) {
|
|
|
5393
5627
|
});
|
|
5394
5628
|
return { browsers, detectionFailed };
|
|
5395
5629
|
}
|
|
5396
|
-
var import_node_os4, import_node_fs11,
|
|
5630
|
+
var import_node_os4, import_node_fs11, import_node_child_process6, platformMap, defaultFileTypes, cachedAppsByDir;
|
|
5397
5631
|
var init_config = __esm({
|
|
5398
5632
|
"dist/core/config.js"() {
|
|
5399
5633
|
"use strict";
|
|
@@ -5401,15 +5635,13 @@ var init_config = __esm({
|
|
|
5401
5635
|
import_node_fs11 = __toESM(require("node:fs"), 1);
|
|
5402
5636
|
init_validate();
|
|
5403
5637
|
init_utils();
|
|
5404
|
-
import_node_path9 = __toESM(require("node:path"), 1);
|
|
5405
5638
|
import_node_child_process6 = require("node:child_process");
|
|
5406
5639
|
init_loader();
|
|
5407
5640
|
init_cacheDir();
|
|
5641
|
+
init_heavyDeps();
|
|
5408
5642
|
init_browsers();
|
|
5409
5643
|
init_appium();
|
|
5410
5644
|
init_openapi();
|
|
5411
|
-
import_node_url4 = require("node:url");
|
|
5412
|
-
__dirname3 = import_node_path9.default.dirname((0, import_node_url4.fileURLToPath)(importMetaUrl));
|
|
5413
5645
|
platformMap = {
|
|
5414
5646
|
darwin: "mac",
|
|
5415
5647
|
linux: "linux",
|
|
@@ -6684,10 +6916,9 @@ function sendTelemetry(config, command, results) {
|
|
|
6684
6916
|
if (config?.telemetry?.send === false)
|
|
6685
6917
|
return;
|
|
6686
6918
|
const telemetryData = process.env["DOC_DETECTIVE_META"] !== void 0 ? JSON.parse(process.env["DOC_DETECTIVE_META"]) : {};
|
|
6687
|
-
const pkg = require2("../../package.json");
|
|
6688
6919
|
telemetryData.distribution = telemetryData.distribution || "doc-detective";
|
|
6689
6920
|
telemetryData.dist_interface = telemetryData.dist_interface || "package";
|
|
6690
|
-
telemetryData.core_version =
|
|
6921
|
+
telemetryData.core_version = ownVersion() ?? "unknown";
|
|
6691
6922
|
telemetryData.dist_version = telemetryData.dist_version || telemetryData.core_version;
|
|
6692
6923
|
telemetryData.core_platform = platformMap2[import_node_os14.default.platform()] || import_node_os14.default.platform();
|
|
6693
6924
|
telemetryData.dist_platform = telemetryData.dist_platform || telemetryData.core_platform;
|
|
@@ -6706,14 +6937,14 @@ function sendTelemetry(config, command, results) {
|
|
|
6706
6937
|
Object.entries(value).forEach(([key, value2]) => {
|
|
6707
6938
|
if (typeof value2 === "object") {
|
|
6708
6939
|
Object.entries(value2).forEach(([key2, value22]) => {
|
|
6709
|
-
telemetryData[`${parentKey.
|
|
6940
|
+
telemetryData[`${parentKey.replaceAll(" ", "_")}_${key.replaceAll(" ", "_")}_${key2.replaceAll(" ", "_")}`] = value22;
|
|
6710
6941
|
});
|
|
6711
6942
|
} else {
|
|
6712
|
-
telemetryData[`${parentKey.
|
|
6943
|
+
telemetryData[`${parentKey.replaceAll(" ", "_")}_${key.replaceAll(" ", "_")}`] = value2;
|
|
6713
6944
|
}
|
|
6714
6945
|
});
|
|
6715
6946
|
} else {
|
|
6716
|
-
telemetryData[parentKey.
|
|
6947
|
+
telemetryData[parentKey.replaceAll(" ", "_")] = value;
|
|
6717
6948
|
}
|
|
6718
6949
|
});
|
|
6719
6950
|
}
|
|
@@ -6728,15 +6959,14 @@ async function awaitTelemetryFlush() {
|
|
|
6728
6959
|
if (pending)
|
|
6729
6960
|
await pending;
|
|
6730
6961
|
}
|
|
6731
|
-
var import_node_os14, import_posthog_node,
|
|
6962
|
+
var import_node_os14, import_posthog_node, platformMap2, TELEMETRY_FLUSH_TIMEOUT_MS, pendingTelemetryFlush;
|
|
6732
6963
|
var init_telem = __esm({
|
|
6733
6964
|
"dist/core/telem.js"() {
|
|
6734
6965
|
"use strict";
|
|
6735
6966
|
import_node_os14 = __toESM(require("node:os"), 1);
|
|
6736
6967
|
import_posthog_node = require("posthog-node");
|
|
6737
|
-
import_node_module5 = require("node:module");
|
|
6738
6968
|
init_telemLite();
|
|
6739
|
-
|
|
6969
|
+
init_heavyDeps();
|
|
6740
6970
|
platformMap2 = {
|
|
6741
6971
|
win32: "windows",
|
|
6742
6972
|
darwin: "mac",
|
|
@@ -6992,7 +7222,7 @@ var import_node_fs12 = __toESM(require("node:fs"), 1);
|
|
|
6992
7222
|
var import_node_path10 = __toESM(require("node:path"), 1);
|
|
6993
7223
|
var import_node_https2 = __toESM(require("node:https"), 1);
|
|
6994
7224
|
var import_node_http = __toESM(require("node:http"), 1);
|
|
6995
|
-
var
|
|
7225
|
+
var import_node_module3 = require("node:module");
|
|
6996
7226
|
var import_node_os5 = __toESM(require("node:os"), 1);
|
|
6997
7227
|
var import_node_crypto4 = __toESM(require("node:crypto"), 1);
|
|
6998
7228
|
var import_adm_zip = __toESM(require("adm-zip"), 1);
|
|
@@ -7773,7 +8003,7 @@ var HerettoUploader = class {
|
|
|
7773
8003
|
return contentTypes[ext] || "application/octet-stream";
|
|
7774
8004
|
}
|
|
7775
8005
|
};
|
|
7776
|
-
var requireFromHere2 = (0,
|
|
8006
|
+
var requireFromHere2 = (0, import_node_module3.createRequire)(importMetaUrl);
|
|
7777
8007
|
var requireAxios = () => requireFromHere2("axios");
|
|
7778
8008
|
var POLLING_INTERVAL_MS = 5e3;
|
|
7779
8009
|
var POLLING_TIMEOUT_MS = 3e5;
|
|
@@ -8745,7 +8975,8 @@ function resolveContexts({ contexts, test, config }) {
|
|
|
8745
8975
|
if (platform !== void 0)
|
|
8746
8976
|
staticContext.platform = platform;
|
|
8747
8977
|
if (browser !== void 0) {
|
|
8748
|
-
|
|
8978
|
+
const { version: _authored, ...authoredBrowser } = browser;
|
|
8979
|
+
staticContext.browser = browser.name === "safari" && !isMobileTargetPlatform(platform) ? { ...authoredBrowser, name: "webkit" } : authoredBrowser;
|
|
8749
8980
|
}
|
|
8750
8981
|
staticContexts.push(staticContext);
|
|
8751
8982
|
});
|
|
@@ -14816,11 +15047,11 @@ function hasUnresolvedMetaReference(expression, context) {
|
|
|
14816
15047
|
}
|
|
14817
15048
|
return false;
|
|
14818
15049
|
}
|
|
14819
|
-
function getMetaValue(
|
|
15050
|
+
function getMetaValue(path32, context) {
|
|
14820
15051
|
if (!context) {
|
|
14821
15052
|
return void 0;
|
|
14822
15053
|
}
|
|
14823
|
-
const [basePath, jsonPointer] =
|
|
15054
|
+
const [basePath, jsonPointer] = path32.split("#");
|
|
14824
15055
|
const resolvedPath = resolvePathTemplateVariables(basePath, context);
|
|
14825
15056
|
let value = getNestedProperty(context, resolvedPath);
|
|
14826
15057
|
if (jsonPointer && value) {
|
|
@@ -14837,9 +15068,9 @@ function getMetaValue(path31, context) {
|
|
|
14837
15068
|
}
|
|
14838
15069
|
return value;
|
|
14839
15070
|
}
|
|
14840
|
-
function resolvePathTemplateVariables(
|
|
15071
|
+
function resolvePathTemplateVariables(path32, context) {
|
|
14841
15072
|
const templateRegex = /\{\{(\w+)\}\}/g;
|
|
14842
|
-
return
|
|
15073
|
+
return path32.replace(templateRegex, (match, varName) => {
|
|
14843
15074
|
if (context && context.id && varName === "id") {
|
|
14844
15075
|
return context.id;
|
|
14845
15076
|
}
|
|
@@ -14876,10 +15107,10 @@ async function resolveEmbeddedExpressions(str, context) {
|
|
|
14876
15107
|
parts.push(str.slice(lastIdx));
|
|
14877
15108
|
return parts.join("");
|
|
14878
15109
|
}
|
|
14879
|
-
function getNestedProperty(obj,
|
|
14880
|
-
if (!obj || !
|
|
15110
|
+
function getNestedProperty(obj, path32) {
|
|
15111
|
+
if (!obj || !path32)
|
|
14881
15112
|
return void 0;
|
|
14882
|
-
const parts =
|
|
15113
|
+
const parts = path32.split(".");
|
|
14883
15114
|
let current = obj;
|
|
14884
15115
|
for (const part of parts) {
|
|
14885
15116
|
if (current === null || current === void 0)
|
|
@@ -16050,6 +16281,11 @@ async function wait({ config, step, driver }) {
|
|
|
16050
16281
|
return result;
|
|
16051
16282
|
}
|
|
16052
16283
|
|
|
16284
|
+
// dist/core/tests/timing.js
|
|
16285
|
+
var RECORDING_PACE_MS = 2e3;
|
|
16286
|
+
var RENDER_SETTLE_MS = 100;
|
|
16287
|
+
var RECORDER_SETTLE_MS = 500;
|
|
16288
|
+
|
|
16053
16289
|
// dist/core/tests/findElement.js
|
|
16054
16290
|
async function finalizeFound({ result }) {
|
|
16055
16291
|
const specs = [
|
|
@@ -16222,7 +16458,7 @@ async function findElement({ config, step, driver, click, appSession, processReg
|
|
|
16222
16458
|
}
|
|
16223
16459
|
}
|
|
16224
16460
|
if (isRecordingActive(driver)) {
|
|
16225
|
-
await wait({ config, step: { wait:
|
|
16461
|
+
await wait({ config, step: { wait: RECORDING_PACE_MS }, driver });
|
|
16226
16462
|
}
|
|
16227
16463
|
return result;
|
|
16228
16464
|
} else {
|
|
@@ -16325,7 +16561,7 @@ async function findElement({ config, step, driver, click, appSession, processReg
|
|
|
16325
16561
|
}
|
|
16326
16562
|
}
|
|
16327
16563
|
if (isRecordingActive(driver)) {
|
|
16328
|
-
await wait({ config, step: { wait:
|
|
16564
|
+
await wait({ config, step: { wait: RECORDING_PACE_MS }, driver });
|
|
16329
16565
|
}
|
|
16330
16566
|
return result;
|
|
16331
16567
|
}
|
|
@@ -16960,7 +17196,7 @@ async function goTo({ config, step, driver }) {
|
|
|
16960
17196
|
}, { timeout: waitTimeout });
|
|
16961
17197
|
waitResults.documentReady.passed = true;
|
|
16962
17198
|
waitResults.documentReady.message = "Document ready";
|
|
16963
|
-
await driver.pause(
|
|
17199
|
+
await driver.pause(RENDER_SETTLE_MS);
|
|
16964
17200
|
const elapsedTime = Date.now() - waitStartTime;
|
|
16965
17201
|
const remainingTimeout = waitTimeout - elapsedTime;
|
|
16966
17202
|
if (remainingTimeout <= 0) {
|
|
@@ -17185,10 +17421,13 @@ async function startBackgroundProcessSurface({ config, descriptor, processRegist
|
|
|
17185
17421
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
17186
17422
|
let bg;
|
|
17187
17423
|
if (descriptor.tty) {
|
|
17424
|
+
const size = typeof descriptor.tty === "object" ? descriptor.tty : {};
|
|
17188
17425
|
try {
|
|
17189
17426
|
bg = await d.spawnPtyBackgroundCommand(descriptor.command, args, {
|
|
17190
17427
|
...bgOptions,
|
|
17191
|
-
cacheDir: config?.cacheDir
|
|
17428
|
+
cacheDir: config?.cacheDir,
|
|
17429
|
+
cols: size.cols,
|
|
17430
|
+
rows: size.rows
|
|
17192
17431
|
});
|
|
17193
17432
|
} catch (error) {
|
|
17194
17433
|
if (error?.code !== "NODE_PTY_UNAVAILABLE") {
|
|
@@ -17478,13 +17717,13 @@ function shouldRetry(statusCode) {
|
|
|
17478
17717
|
return false;
|
|
17479
17718
|
return statusCode === 429 || statusCode >= 500;
|
|
17480
17719
|
}
|
|
17481
|
-
async function attemptRequest(method, url, headers) {
|
|
17720
|
+
async function attemptRequest(method, url, headers, timeout) {
|
|
17482
17721
|
try {
|
|
17483
17722
|
const res = await (await loadAxios()).request({
|
|
17484
17723
|
method,
|
|
17485
17724
|
url,
|
|
17486
17725
|
headers,
|
|
17487
|
-
timeout
|
|
17726
|
+
timeout,
|
|
17488
17727
|
maxRedirects: 5,
|
|
17489
17728
|
validateStatus: () => true
|
|
17490
17729
|
});
|
|
@@ -17497,7 +17736,7 @@ async function attemptRequest(method, url, headers) {
|
|
|
17497
17736
|
retryAfter: parseRetryAfter(error.response.headers?.["retry-after"] ?? error.response.headers?.["Retry-After"])
|
|
17498
17737
|
};
|
|
17499
17738
|
}
|
|
17500
|
-
return { statusCode: null, retryAfter: null };
|
|
17739
|
+
return { statusCode: null, retryAfter: null, errorCode: error?.code };
|
|
17501
17740
|
}
|
|
17502
17741
|
}
|
|
17503
17742
|
async function checkLink({ config, step }) {
|
|
@@ -17538,11 +17777,12 @@ async function checkLink({ config, step }) {
|
|
|
17538
17777
|
}
|
|
17539
17778
|
const headers = mergeHeaders(step.checkLink.headers);
|
|
17540
17779
|
const url = step.checkLink.url;
|
|
17780
|
+
const timeout = step.checkLink.timeout ?? 1e4;
|
|
17541
17781
|
const acceptedCodes = step.checkLink.statusCodes;
|
|
17542
17782
|
const isAccepted = (code) => code !== null && acceptedCodes.indexOf(code) >= 0;
|
|
17543
17783
|
let last = { statusCode: null, retryAfter: null };
|
|
17544
17784
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
|
|
17545
|
-
last = await attemptRequest("get", url, headers);
|
|
17785
|
+
last = await attemptRequest("get", url, headers, timeout);
|
|
17546
17786
|
if (isAccepted(last.statusCode))
|
|
17547
17787
|
break;
|
|
17548
17788
|
if (!shouldRetry(last.statusCode))
|
|
@@ -17553,14 +17793,14 @@ async function checkLink({ config, step }) {
|
|
|
17553
17793
|
}
|
|
17554
17794
|
}
|
|
17555
17795
|
if (!isAccepted(last.statusCode) && (last.statusCode === 429 || last.statusCode === 403)) {
|
|
17556
|
-
const headResult = await attemptRequest("head", url, headers);
|
|
17796
|
+
const headResult = await attemptRequest("head", url, headers, timeout);
|
|
17557
17797
|
if (isAccepted(headResult.statusCode)) {
|
|
17558
17798
|
last = headResult;
|
|
17559
17799
|
}
|
|
17560
17800
|
}
|
|
17561
17801
|
if (last.statusCode === null) {
|
|
17562
17802
|
result.status = "FAIL";
|
|
17563
|
-
result.description = `Invalid or unresolvable URL: ${url}`;
|
|
17803
|
+
result.description = last.errorCode === "ECONNABORTED" ? `Couldn't reach ${redactUrlForOutput(url)}: no response within ${timeout} ms (ECONNABORTED).` : `Invalid or unresolvable URL: ${redactUrlForOutput(url)}`;
|
|
17564
17804
|
return result;
|
|
17565
17805
|
}
|
|
17566
17806
|
result.outputs = { statusCode: last.statusCode };
|
|
@@ -18048,7 +18288,8 @@ async function captureScreenshot({ config, step, driver, appSession, processRegi
|
|
|
18048
18288
|
const fetched = await fetchFile(originalUrlPath, { binary: true });
|
|
18049
18289
|
if (fetched.result !== "success") {
|
|
18050
18290
|
result.status = "FAIL";
|
|
18051
|
-
|
|
18291
|
+
const msg = fetched.message?.message ?? String(fetched.message);
|
|
18292
|
+
result.description = fetched.message instanceof UrlGuardError ? msg : `Couldn't fetch remote reference image (${redactedUrl}): ${msg}`;
|
|
18052
18293
|
return result;
|
|
18053
18294
|
}
|
|
18054
18295
|
existFilePath = fetched.path;
|
|
@@ -18171,7 +18412,7 @@ async function captureScreenshot({ config, step, driver, appSession, processRegi
|
|
|
18171
18412
|
});
|
|
18172
18413
|
window.scrollBy(-pad.left, -pad.top);
|
|
18173
18414
|
}, element, padding);
|
|
18174
|
-
await driver.pause(
|
|
18415
|
+
await driver.pause(RENDER_SETTLE_MS);
|
|
18175
18416
|
}
|
|
18176
18417
|
const recordingActive = !isAppCapture && isRecordingActive(driver);
|
|
18177
18418
|
let captureBuffer;
|
|
@@ -19103,7 +19344,7 @@ async function startRecording({ config, context, step, driver, recordingHost, ap
|
|
|
19103
19344
|
proc.on("error", (err) => {
|
|
19104
19345
|
spawnError = err;
|
|
19105
19346
|
});
|
|
19106
|
-
await new Promise((r) => setTimeout(r,
|
|
19347
|
+
await new Promise((r) => setTimeout(r, RECORDER_SETTLE_MS));
|
|
19107
19348
|
if (spawnError || proc.exitCode !== null) {
|
|
19108
19349
|
result.status = "FAIL";
|
|
19109
19350
|
result.description = `Couldn't start ffmpeg recording.${spawnError ? ` ${spawnError}` : ` ffmpeg exited with code ${proc.exitCode}.`} On macOS, grant screen recording permission; on Linux, ensure a display (or Xvfb) is available.${stderrTail ? ` ffmpeg: ${stderrTail.trim().slice(-500)}` : ""}`;
|
|
@@ -19665,7 +19906,7 @@ async function waitForStableFile(filePath, maxSeconds) {
|
|
|
19665
19906
|
stableReads = 0;
|
|
19666
19907
|
}
|
|
19667
19908
|
lastSize = size;
|
|
19668
|
-
await new Promise((r) => setTimeout(r,
|
|
19909
|
+
await new Promise((r) => setTimeout(r, RECORDER_SETTLE_MS));
|
|
19669
19910
|
}
|
|
19670
19911
|
return false;
|
|
19671
19912
|
}
|
|
@@ -20066,9 +20307,9 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
|
|
|
20066
20307
|
delete step.httpRequest.openApi.definition;
|
|
20067
20308
|
} else if (openApiDefinitions.length > 0) {
|
|
20068
20309
|
findOperation: for (const openApiConfig of openApiDefinitions) {
|
|
20069
|
-
for (const
|
|
20070
|
-
for (const method in openApiConfig.definition.paths[
|
|
20071
|
-
if (openApiConfig.definition.paths[
|
|
20310
|
+
for (const path32 in openApiConfig.definition.paths) {
|
|
20311
|
+
for (const method in openApiConfig.definition.paths[path32]) {
|
|
20312
|
+
if (openApiConfig.definition.paths[path32][method].operationId === step.httpRequest.openApi.operationId) {
|
|
20072
20313
|
openApiDefinition = openApiConfig.definition;
|
|
20073
20314
|
step.httpRequest.openApi = {
|
|
20074
20315
|
...openApiConfig,
|
|
@@ -20138,10 +20379,7 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
|
|
|
20138
20379
|
};
|
|
20139
20380
|
}
|
|
20140
20381
|
if (step.httpRequest.openApi.statusCode) {
|
|
20141
|
-
step.httpRequest.statusCodes = [
|
|
20142
|
-
step.httpRequest.openApi.statusCode,
|
|
20143
|
-
...step.httpRequest.statusCodes || []
|
|
20144
|
-
];
|
|
20382
|
+
step.httpRequest.statusCodes = [step.httpRequest.openApi.statusCode].concat(step.httpRequest.statusCodes || []);
|
|
20145
20383
|
} else if (!step.httpRequest.statusCodes) {
|
|
20146
20384
|
step.httpRequest.statusCodes = Object.keys(operation.definition.responses).filter((code) => code.startsWith("2")).map((code) => Number(code));
|
|
20147
20385
|
}
|
|
@@ -20162,7 +20400,8 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
|
|
|
20162
20400
|
step.httpRequest = {
|
|
20163
20401
|
...step.httpRequest,
|
|
20164
20402
|
method: step.httpRequest.method || "get",
|
|
20165
|
-
|
|
20403
|
+
// A scalar statusCodes is one accepted code (as in checkLink).
|
|
20404
|
+
statusCodes: [].concat(step.httpRequest.statusCodes || [200, 201]),
|
|
20166
20405
|
request: step.httpRequest.request || {
|
|
20167
20406
|
params: {},
|
|
20168
20407
|
headers: {},
|
|
@@ -20426,8 +20665,8 @@ async function httpRequest({ config, step, openApiDefinitions = [] }) {
|
|
|
20426
20665
|
result.description = descriptions.join(" ").trim();
|
|
20427
20666
|
return result;
|
|
20428
20667
|
}
|
|
20429
|
-
function fieldExistsAtPath(obj,
|
|
20430
|
-
const segments =
|
|
20668
|
+
function fieldExistsAtPath(obj, path32) {
|
|
20669
|
+
const segments = path32.match(/[^.[\]]+/g);
|
|
20431
20670
|
if (!segments) {
|
|
20432
20671
|
return false;
|
|
20433
20672
|
}
|
|
@@ -20585,6 +20824,11 @@ async function clickElement({ config, step, driver, appSession, processRegistry,
|
|
|
20585
20824
|
return result;
|
|
20586
20825
|
}
|
|
20587
20826
|
step = isValidStep.object;
|
|
20827
|
+
if (typeof step.click === "boolean") {
|
|
20828
|
+
result.status = "FAIL";
|
|
20829
|
+
result.description = `\`click\` needs an element, not ${step.click}. To click an element whose text is "${step.click}", quote it: "click": "${step.click}".`;
|
|
20830
|
+
return result;
|
|
20831
|
+
}
|
|
20588
20832
|
let findStep;
|
|
20589
20833
|
if (typeof step.click === "string") {
|
|
20590
20834
|
findStep = { find: step.click };
|
|
@@ -21619,6 +21863,7 @@ function raceBootInitiation({ startAcquire, onError }) {
|
|
|
21619
21863
|
|
|
21620
21864
|
// dist/core/tests.js
|
|
21621
21865
|
init_cacheDir();
|
|
21866
|
+
init_heavyDeps();
|
|
21622
21867
|
|
|
21623
21868
|
// dist/core/tests/runBrowserScript.js
|
|
21624
21869
|
init_validate();
|
|
@@ -22141,26 +22386,35 @@ async function uploadChangedFiles({ config, report, log: log3 }) {
|
|
|
22141
22386
|
for (const file of changedFiles) {
|
|
22142
22387
|
const uploader = getUploader(file.sourceIntegration);
|
|
22143
22388
|
if (!uploader) {
|
|
22144
|
-
|
|
22389
|
+
const why = `no uploader handles integration type \`${file.sourceIntegration.type}\`.`;
|
|
22390
|
+
log3(config, "warning", `Couldn't upload ${file.localPath}: ${why}`);
|
|
22145
22391
|
results.skipped++;
|
|
22146
22392
|
results.details.push({
|
|
22147
22393
|
localPath: file.localPath,
|
|
22148
22394
|
status: "SKIPPED",
|
|
22149
|
-
reason: `
|
|
22395
|
+
reason: `Skipped: ${why}`
|
|
22150
22396
|
});
|
|
22151
22397
|
continue;
|
|
22152
22398
|
}
|
|
22153
22399
|
const integrationConfig = getIntegrationConfig(config, file.sourceIntegration);
|
|
22154
22400
|
if (!integrationConfig) {
|
|
22155
|
-
|
|
22401
|
+
const why = `no \`${file.sourceIntegration.integrationName}\` integration is configured.`;
|
|
22402
|
+
log3(config, "warning", `Couldn't upload ${file.localPath}: ${why}`);
|
|
22156
22403
|
results.skipped++;
|
|
22157
22404
|
results.details.push({
|
|
22158
22405
|
localPath: file.localPath,
|
|
22159
22406
|
status: "SKIPPED",
|
|
22160
|
-
reason: `
|
|
22407
|
+
reason: `Skipped: ${why}`
|
|
22161
22408
|
});
|
|
22162
22409
|
continue;
|
|
22163
22410
|
}
|
|
22411
|
+
if (integrationConfig.uploadOnChange !== true) {
|
|
22412
|
+
const reason2 = `Skipped: \`uploadOnChange\` isn't enabled for integration ${file.sourceIntegration.integrationName}.`;
|
|
22413
|
+
log3(config, "debug", `${file.localPath}: ${reason2}`);
|
|
22414
|
+
results.skipped++;
|
|
22415
|
+
results.details.push({ localPath: file.localPath, status: "SKIPPED", reason: reason2 });
|
|
22416
|
+
continue;
|
|
22417
|
+
}
|
|
22164
22418
|
uploadTasks.push({
|
|
22165
22419
|
file,
|
|
22166
22420
|
uploader,
|
|
@@ -22232,8 +22486,6 @@ function getIntegrationConfig(config, sourceIntegration) {
|
|
|
22232
22486
|
// dist/core/tests.js
|
|
22233
22487
|
var import_node_http2 = __toESM(require("node:http"), 1);
|
|
22234
22488
|
var import_node_https3 = __toESM(require("node:https"), 1);
|
|
22235
|
-
var import_node_url5 = require("node:url");
|
|
22236
|
-
var __dirname4 = import_node_path30.default.dirname((0, import_node_url5.fileURLToPath)(importMetaUrl));
|
|
22237
22489
|
var lastReuseConfig;
|
|
22238
22490
|
var appiumServerCache = createServerCache({
|
|
22239
22491
|
kill: (server) => killTree(server.process?.pid),
|
|
@@ -22794,6 +23046,8 @@ async function runViaApi({ resolvedTests, apiKey, config = {} }) {
|
|
|
22794
23046
|
steps: { pass: 0, fail: 0, warning: 0, skipped: 0 }
|
|
22795
23047
|
},
|
|
22796
23048
|
// Nothing ran, but the shape stays parity with runSpecs' short-circuit.
|
|
23049
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
23050
|
+
environment: getEnvironment(),
|
|
22797
23051
|
durationMs: 0,
|
|
22798
23052
|
specs: []
|
|
22799
23053
|
};
|
|
@@ -22891,6 +23145,8 @@ async function runSpecs({ resolvedTests }) {
|
|
|
22891
23145
|
contexts: { pass: 0, fail: 0, warning: 0, skipped: 0 },
|
|
22892
23146
|
steps: { pass: 0, fail: 0, warning: 0, skipped: 0 }
|
|
22893
23147
|
},
|
|
23148
|
+
startedAt: new Date(runStart).toISOString(),
|
|
23149
|
+
environment: getEnvironment(),
|
|
22894
23150
|
durationMs: Date.now() - runStart,
|
|
22895
23151
|
specs: []
|
|
22896
23152
|
};
|
|
@@ -22917,6 +23173,10 @@ async function runSpecs({ resolvedTests }) {
|
|
|
22917
23173
|
const report = {
|
|
22918
23174
|
runId,
|
|
22919
23175
|
runDir,
|
|
23176
|
+
// Run metadata (ADR 01163): when execution started and the host that ran
|
|
23177
|
+
// it, so a consumer can tell product drift from environment drift.
|
|
23178
|
+
startedAt: new Date(runStart).toISOString(),
|
|
23179
|
+
environment: runnerDetails.environment,
|
|
22920
23180
|
summary: {
|
|
22921
23181
|
specs: {
|
|
22922
23182
|
pass: 0,
|
|
@@ -23391,30 +23651,31 @@ async function runSpecs({ resolvedTests }) {
|
|
|
23391
23651
|
process.off("SIGINT", onSignal);
|
|
23392
23652
|
process.off("SIGTERM", onSignal);
|
|
23393
23653
|
}
|
|
23394
|
-
const
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
// Default to true if not explicitly set to false
|
|
23398
|
-
);
|
|
23399
|
-
const globalUploadOnChange = config?.uploadOnChange ?? true;
|
|
23400
|
-
if (globalUploadOnChange && hasUploadEnabledIntegration && herettoConfigs.length > 0) {
|
|
23401
|
-
try {
|
|
23402
|
-
const uploadResults = await uploadChangedFiles({ config, report, log });
|
|
23403
|
-
report.uploadResults = uploadResults;
|
|
23404
|
-
} catch (error) {
|
|
23405
|
-
log(config, "warning", `Failed to upload changed files: ${error.message}`);
|
|
23406
|
-
report.uploadResults = {
|
|
23407
|
-
total: 0,
|
|
23408
|
-
successful: 0,
|
|
23409
|
-
failed: 0,
|
|
23410
|
-
skipped: 0,
|
|
23411
|
-
error: error.message
|
|
23412
|
-
};
|
|
23413
|
-
}
|
|
23414
|
-
}
|
|
23654
|
+
const uploadResults = await uploadChangedFilesIfEnabled({ config, report, log });
|
|
23655
|
+
if (uploadResults)
|
|
23656
|
+
report.uploadResults = uploadResults;
|
|
23415
23657
|
report.durationMs = Date.now() - runStart;
|
|
23416
23658
|
return report;
|
|
23417
23659
|
}
|
|
23660
|
+
async function uploadChangedFilesIfEnabled({ config, report, log: logFn, upload = uploadChangedFiles }) {
|
|
23661
|
+
const integrationEntries = Object.values(config?.integrations ?? {}).flatMap((entries) => Array.isArray(entries) ? entries : []);
|
|
23662
|
+
if (!integrationEntries.some((entry) => entry?.uploadOnChange === true)) {
|
|
23663
|
+
return void 0;
|
|
23664
|
+
}
|
|
23665
|
+
try {
|
|
23666
|
+
return await upload({ config, report, log: logFn });
|
|
23667
|
+
} catch (error) {
|
|
23668
|
+
logFn(config, "warning", `Failed to upload changed files: ${error.message}`);
|
|
23669
|
+
return {
|
|
23670
|
+
total: 0,
|
|
23671
|
+
successful: 0,
|
|
23672
|
+
failed: 0,
|
|
23673
|
+
skipped: 0,
|
|
23674
|
+
details: [],
|
|
23675
|
+
error: error.message
|
|
23676
|
+
};
|
|
23677
|
+
}
|
|
23678
|
+
}
|
|
23418
23679
|
async function runRoutedSpec({ spec, specReport, specGuardSkip, config, runnerDetails, appiumPool, portToDisplay, metaValues, installAttempts, warmUpResults, processRegistry, deviceRegistry, simulatorRegistry, appServerPool, platform, markAutoRecord, limit, resourceRegistry, exclusivityCtx }) {
|
|
23419
23680
|
let stopRest = false;
|
|
23420
23681
|
let warnedRunStop = false;
|
|
@@ -24437,6 +24698,14 @@ ${JSON.stringify(context, null, 2)}`);
|
|
|
24437
24698
|
engine: String(mobileWebBrowserName).toLowerCase(),
|
|
24438
24699
|
driver
|
|
24439
24700
|
});
|
|
24701
|
+
const versions = sessionVersions({ capabilities: driver?.capabilities });
|
|
24702
|
+
if (versions.browserVersion)
|
|
24703
|
+
contextReport.browser = {
|
|
24704
|
+
...contextReport.browser,
|
|
24705
|
+
version: versions.browserVersion
|
|
24706
|
+
};
|
|
24707
|
+
if (versions.driver)
|
|
24708
|
+
contextReport.driver = versions.driver;
|
|
24440
24709
|
} else if (driverRequired) {
|
|
24441
24710
|
appiumPort = await appiumPool.acquire();
|
|
24442
24711
|
if (portToDisplay) {
|
|
@@ -24566,6 +24835,17 @@ ${JSON.stringify(context, null, 2)}`);
|
|
|
24566
24835
|
};
|
|
24567
24836
|
clog("warning", fellBackNote);
|
|
24568
24837
|
}
|
|
24838
|
+
const versions = sessionVersions({
|
|
24839
|
+
capabilities: driver?.capabilities,
|
|
24840
|
+
app: availableApps.find((app) => app.name === normalizeBrowserName(startedName))
|
|
24841
|
+
});
|
|
24842
|
+
if (versions.browserVersion)
|
|
24843
|
+
contextReport.browser = {
|
|
24844
|
+
...contextReport.browser,
|
|
24845
|
+
version: versions.browserVersion
|
|
24846
|
+
};
|
|
24847
|
+
if (versions.driver)
|
|
24848
|
+
contextReport.driver = versions.driver;
|
|
24569
24849
|
if (driver) {
|
|
24570
24850
|
browserSessions = createSessionRegistry({
|
|
24571
24851
|
open: async (engine, overrides) => {
|
|
@@ -24748,7 +25028,12 @@ ${JSON.stringify(r, null, 2)}`);
|
|
|
24748
25028
|
r.resultDescription = r.description;
|
|
24749
25029
|
delete r.status;
|
|
24750
25030
|
delete r.description;
|
|
24751
|
-
return {
|
|
25031
|
+
return {
|
|
25032
|
+
...step,
|
|
25033
|
+
...r,
|
|
25034
|
+
startedAt: new Date(stepStart).toISOString(),
|
|
25035
|
+
durationMs: Date.now() - stepStart
|
|
25036
|
+
};
|
|
24752
25037
|
};
|
|
24753
25038
|
let stepReport = await runStepOnce();
|
|
24754
25039
|
let attempts = 1;
|
|
@@ -24952,6 +25237,7 @@ async function runContextWithRetries(args, runContextFn = runContext, delayMs =
|
|
|
24952
25237
|
const start = Date.now();
|
|
24953
25238
|
const report2 = await runContextFn(a);
|
|
24954
25239
|
if (report2 && typeof report2 === "object") {
|
|
25240
|
+
report2.startedAt = new Date(start).toISOString();
|
|
24955
25241
|
report2.durationMs = Date.now() - start;
|
|
24956
25242
|
}
|
|
24957
25243
|
return report2;
|
|
@@ -25024,6 +25310,7 @@ async function stopAllRecordings({ config, context, driver, contextReport }) {
|
|
|
25024
25310
|
contextReport.steps.push({
|
|
25025
25311
|
...stopRecordStep,
|
|
25026
25312
|
...stepResult,
|
|
25313
|
+
startedAt: new Date(stopStart).toISOString(),
|
|
25027
25314
|
durationMs: Date.now() - stopStart
|
|
25028
25315
|
});
|
|
25029
25316
|
} catch (error) {
|
|
@@ -25033,6 +25320,7 @@ async function stopAllRecordings({ config, context, driver, contextReport }) {
|
|
|
25033
25320
|
...stopRecordStep,
|
|
25034
25321
|
result: "FAIL",
|
|
25035
25322
|
resultDescription: `Couldn't stop recording. ${error?.message ?? error}`,
|
|
25323
|
+
startedAt: new Date(stopStart).toISOString(),
|
|
25036
25324
|
durationMs: Date.now() - stopStart
|
|
25037
25325
|
});
|
|
25038
25326
|
}
|
|
@@ -25314,7 +25602,7 @@ async function spawnAppiumServer(appiumEntry, config, display, extraEnv, extraAr
|
|
|
25314
25602
|
...extraArgs ?? []
|
|
25315
25603
|
], {
|
|
25316
25604
|
windowsHide: true,
|
|
25317
|
-
cwd:
|
|
25605
|
+
cwd: packageRoot(),
|
|
25318
25606
|
env
|
|
25319
25607
|
});
|
|
25320
25608
|
proc.on("error", (err) => {
|
|
@@ -25399,8 +25687,8 @@ async function driverStart(capabilities, port, maxAttempts = 4, ctx = {}) {
|
|
|
25399
25687
|
if (!isRetryableSessionError(String(err && err.message), startupCeiling))
|
|
25400
25688
|
throw err;
|
|
25401
25689
|
if (attempt < maxAttempts) {
|
|
25402
|
-
const
|
|
25403
|
-
ctx.log?.(`Session start attempt ${attempt} of ${maxAttempts} failed (${
|
|
25690
|
+
const reason2 = String(err?.message ?? err).split("\n")[0];
|
|
25691
|
+
ctx.log?.(`Session start attempt ${attempt} of ${maxAttempts} failed (${reason2}); retrying.`);
|
|
25404
25692
|
await new Promise((r) => setTimeout(r, 500 * attempt));
|
|
25405
25693
|
}
|
|
25406
25694
|
}
|
|
@@ -25495,7 +25783,7 @@ async function getRunner(options = {}) {
|
|
|
25495
25783
|
...useDriversArgs(process.env.APPIUM_HOME, browserPoolDriverPackages(process.platform))
|
|
25496
25784
|
], {
|
|
25497
25785
|
windowsHide: true,
|
|
25498
|
-
cwd:
|
|
25786
|
+
cwd: packageRoot()
|
|
25499
25787
|
});
|
|
25500
25788
|
appium.on("error", (err) => {
|
|
25501
25789
|
log(config, "warning", `Appium process error: ${err?.stack ?? err?.message ?? String(err)}`);
|
|
@@ -25560,6 +25848,165 @@ async function getRunner(options = {}) {
|
|
|
25560
25848
|
// dist/core/index.js
|
|
25561
25849
|
init_telemLite();
|
|
25562
25850
|
init_files();
|
|
25851
|
+
|
|
25852
|
+
// dist/upgradeNotice.js
|
|
25853
|
+
var import_node_path31 = __toESM(require("node:path"), 1);
|
|
25854
|
+
var UPGRADE_GUIDE_URL = "https://docs.doc-detective.com/docs/ci/upgrade-to-v5";
|
|
25855
|
+
var warned = /* @__PURE__ */ new Set();
|
|
25856
|
+
function isAction() {
|
|
25857
|
+
try {
|
|
25858
|
+
return JSON.parse(process.env.DOC_DETECTIVE_META || "{}").dist_interface === "github-actions";
|
|
25859
|
+
} catch {
|
|
25860
|
+
return false;
|
|
25861
|
+
}
|
|
25862
|
+
}
|
|
25863
|
+
function warnUpgrade(messages, logLevel) {
|
|
25864
|
+
if (logLevel === "silent" || isAction())
|
|
25865
|
+
return;
|
|
25866
|
+
for (const message of messages) {
|
|
25867
|
+
if (warned.has(message))
|
|
25868
|
+
continue;
|
|
25869
|
+
warned.add(message);
|
|
25870
|
+
console.error(`Warning: ${message} See ${UPGRADE_GUIDE_URL}`);
|
|
25871
|
+
}
|
|
25872
|
+
}
|
|
25873
|
+
function legacyEnv(env) {
|
|
25874
|
+
try {
|
|
25875
|
+
const parsed = JSON.parse(env.DOC_DETECTIVE || "{}");
|
|
25876
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
25877
|
+
} catch {
|
|
25878
|
+
return {};
|
|
25879
|
+
}
|
|
25880
|
+
}
|
|
25881
|
+
var ROUTING = ["onPass", "onFail", "onWarning", "onSkip"];
|
|
25882
|
+
var LOCATORS = ["selector", "elementText", "elementId", "elementTestId", "elementClass", "elementAttribute", "elementAria"];
|
|
25883
|
+
var REJECTS = "Doc Detective 5.0 rejects with exit 2";
|
|
25884
|
+
var EXIT_2 = `which ${REJECTS}`;
|
|
25885
|
+
function routes(owner) {
|
|
25886
|
+
return ROUTING.flatMap((key) => Array.isArray(owner[key]) ? owner[key] : []);
|
|
25887
|
+
}
|
|
25888
|
+
function testWarnings(test, where) {
|
|
25889
|
+
return routes(test).flatMap((route) => [
|
|
25890
|
+
...route.stop === "run" ? [`A test in ${where} has \`stop: "run"\` in its routing, ${EXIT_2}. Use \`stop: "spec"\`, which is what 4.x does.`] : [],
|
|
25891
|
+
...["retry", "goToStep"].filter((key) => key in route).map((key) => `A test in ${where} has \`${key}\` in its routing, which 4.x ignores and ${REJECTS}. Remove it, or move it to a step.`)
|
|
25892
|
+
]);
|
|
25893
|
+
}
|
|
25894
|
+
var FREE_FORM = /* @__PURE__ */ new Set([
|
|
25895
|
+
"request",
|
|
25896
|
+
"response",
|
|
25897
|
+
"headers",
|
|
25898
|
+
"body",
|
|
25899
|
+
"params",
|
|
25900
|
+
"parameters",
|
|
25901
|
+
"elementAttribute",
|
|
25902
|
+
"variables",
|
|
25903
|
+
"outputs",
|
|
25904
|
+
"env",
|
|
25905
|
+
"driverOptions",
|
|
25906
|
+
"openApi"
|
|
25907
|
+
]);
|
|
25908
|
+
function negatives(value, found = /* @__PURE__ */ new Set()) {
|
|
25909
|
+
if (value && typeof value === "object") {
|
|
25910
|
+
for (const [key, child] of Object.entries(value)) {
|
|
25911
|
+
if (FREE_FORM.has(key))
|
|
25912
|
+
continue;
|
|
25913
|
+
if (typeof child === "number" && child < 0 && (key === "wait" || /(timeout|duration|delay)$/i.test(key)))
|
|
25914
|
+
found.add(key);
|
|
25915
|
+
negatives(child, found);
|
|
25916
|
+
}
|
|
25917
|
+
}
|
|
25918
|
+
return found;
|
|
25919
|
+
}
|
|
25920
|
+
function offend(offenders, row, value, message) {
|
|
25921
|
+
if (!offenders.has(row))
|
|
25922
|
+
offenders.set(row, { values: /* @__PURE__ */ new Set(), message });
|
|
25923
|
+
offenders.get(row).values.add(value);
|
|
25924
|
+
}
|
|
25925
|
+
function stepWarnings(step, where, offenders) {
|
|
25926
|
+
const messages = [];
|
|
25927
|
+
for (const route of routes(step)) {
|
|
25928
|
+
if (route.stop === "spec" || route.stop === "run") {
|
|
25929
|
+
messages.push(`A step in ${where} has \`stop: "${route.stop}"\` in its routing, ${EXIT_2}. Use \`stop: "test"\` on the step and \`stop: "spec"\` on the test, which works today.`);
|
|
25930
|
+
}
|
|
25931
|
+
if ("goToTest" in route) {
|
|
25932
|
+
messages.push(`A step in ${where} has \`goToTest\` in its routing, which 4.x ignores and ${REJECTS}. Move it to the test's routing, which works today.`);
|
|
25933
|
+
}
|
|
25934
|
+
}
|
|
25935
|
+
if (typeof step.click === "boolean") {
|
|
25936
|
+
messages.push(`A \`click\` step in ${where} is \`${step.click}\`, which 4.x reads as text to click and ${REJECTS}. To click an element whose text is '${step.click}', quote it as \`"click": "${step.click}"\`, which works today. If you don't want a click, remove the step.`);
|
|
25937
|
+
}
|
|
25938
|
+
const objects = (value) => [value].flat().filter((s) => s && typeof s === "object");
|
|
25939
|
+
if (objects(step.startSurface).concat(objects(step.closeSurface)).some((s) => s.browser === "edge")) {
|
|
25940
|
+
messages.push(`A surface in ${where} uses \`browser: "edge"\`, which Doc Detective 5.0 removes. Use another \`browser\`, such as \`chrome\`, which works today.`);
|
|
25941
|
+
}
|
|
25942
|
+
const starts = objects(step.startSurface);
|
|
25943
|
+
for (const surface of starts) {
|
|
25944
|
+
const device = surface.device && typeof surface.device === "object" ? surface.device : {};
|
|
25945
|
+
const removed = ["orientation", "udid", "provider"].filter((key) => key in device);
|
|
25946
|
+
if (removed.length) {
|
|
25947
|
+
messages.push(`A \`startSurface\` device in ${where} sets ${removed.map((key) => `\`${key}\``).join(", ")}, which Doc Detective 5.0 removes. Remove ${removed.length > 1 ? "them" : "it"}, which works today.`);
|
|
25948
|
+
}
|
|
25949
|
+
if (surface.app != null && surface.workingDirectory === ".") {
|
|
25950
|
+
messages.push(`An app in ${where} sets \`workingDirectory: "."\`, which 4.x tolerates and Doc Detective 5.0 fails. Remove it, which works today.`);
|
|
25951
|
+
}
|
|
25952
|
+
}
|
|
25953
|
+
const zeroTimeouts = [
|
|
25954
|
+
...["runShell", "runCode", "goTo", "httpRequest"].filter((action) => step[action]?.timeout === 0),
|
|
25955
|
+
...starts.some((s) => s.timeout === 0) ? ["startSurface"] : []
|
|
25956
|
+
];
|
|
25957
|
+
for (const action of zeroTimeouts) {
|
|
25958
|
+
messages.push(`A \`${action}\` step in ${where} sets \`timeout: 0\`, ${EXIT_2} on \`runShell\`, \`runCode\`, \`goTo\`, \`httpRequest\`, and \`startSurface\` processes and apps. Remove \`timeout\` for the default, or set it to 1 or more, which works today. Locator and wait timeouts can still be 0.`);
|
|
25959
|
+
}
|
|
25960
|
+
for (const key of negatives(step)) {
|
|
25961
|
+
messages.push(`A step in ${where} has a negative \`${key}\`, ${EXIT_2}. Use a positive value, which works today.`);
|
|
25962
|
+
}
|
|
25963
|
+
if (step.screenshot === false) {
|
|
25964
|
+
messages.push(`A \`screenshot: false\` step in ${where} takes a screenshot today, and Doc Detective 5.0 skips it. To keep the screenshot, use \`screenshot: true\`, which works today.`);
|
|
25965
|
+
}
|
|
25966
|
+
const find = step.find && typeof step.find === "object" ? step.find : {};
|
|
25967
|
+
const click = find.click;
|
|
25968
|
+
if (typeof click === "string" && !["left", "right", "middle"].includes(click) || click && typeof click === "object" && Object.keys(click).some((key) => key !== "button" && key !== "duration")) {
|
|
25969
|
+
messages.push(`A \`find.click\` in ${where} isn't \`true\`, \`false\`, a button, or \`{button, duration}\`, ${EXIT_2}. Use \`click: true\` to click the found element, or a separate \`click\` step, which works today.`);
|
|
25970
|
+
}
|
|
25971
|
+
if (find.type && typeof find.type === "object" && LOCATORS.some((key) => key in find.type)) {
|
|
25972
|
+
messages.push(`A \`find.type\` in ${where} sets its own locator, ${EXIT_2}. Move it to a separate \`type\` step, which works today.`);
|
|
25973
|
+
}
|
|
25974
|
+
for (const action of ["goTo", "httpRequest"]) {
|
|
25975
|
+
const url = typeof step[action] === "string" ? step[action] : step[action]?.url;
|
|
25976
|
+
if (typeof url === "string" && !/^(https?:\/\/|\/)/.test(url) && !/^\$[A-Za-z0-9_]+$/.test(url)) {
|
|
25977
|
+
offend(offenders, `${action} URL`, url, (first) => `A \`${action}\` URL in ${where} (${first}) doesn't start with \`http://\`, \`https://\`, or \`/\`, and isn't a whole \`$VAR\`, ${EXIT_2}. Put the whole URL in one variable, which works today.`);
|
|
25978
|
+
}
|
|
25979
|
+
}
|
|
25980
|
+
for (const action of ["saveCookie", "loadCookie"]) {
|
|
25981
|
+
const value = step[action];
|
|
25982
|
+
if (typeof value === "string" && value.includes("/") && !value.endsWith(".txt")) {
|
|
25983
|
+
offend(offenders, action, value, (first) => `A \`${action}\` in ${where} (${first}) has a \`/\` but doesn't end in \`.txt\`, ${EXIT_2}. End a file path in \`.txt\`, which works today.`);
|
|
25984
|
+
}
|
|
25985
|
+
}
|
|
25986
|
+
return messages;
|
|
25987
|
+
}
|
|
25988
|
+
function specWarnings(resolvedTests) {
|
|
25989
|
+
const messages = [];
|
|
25990
|
+
for (const spec of resolvedTests?.specs ?? []) {
|
|
25991
|
+
const file = spec.contentPath;
|
|
25992
|
+
const where = typeof file === "string" ? `'${import_node_path31.default.isAbsolute(file) ? import_node_path31.default.relative(process.cwd(), file) : file}'` : "a spec";
|
|
25993
|
+
const offenders = /* @__PURE__ */ new Map();
|
|
25994
|
+
for (const test of spec.tests ?? []) {
|
|
25995
|
+
messages.push(...testWarnings(test, where));
|
|
25996
|
+
for (const context of test.contexts ?? []) {
|
|
25997
|
+
for (const step of context.steps ?? [])
|
|
25998
|
+
messages.push(...stepWarnings(step, where, offenders));
|
|
25999
|
+
}
|
|
26000
|
+
}
|
|
26001
|
+
for (const { values, message } of offenders.values()) {
|
|
26002
|
+
const [first] = values;
|
|
26003
|
+
messages.push(message(values.size > 1 ? `\`${first}\` and ${values.size - 1} more` : `\`${first}\``));
|
|
26004
|
+
}
|
|
26005
|
+
}
|
|
26006
|
+
return [...new Set(messages)];
|
|
26007
|
+
}
|
|
26008
|
+
|
|
26009
|
+
// dist/core/index.js
|
|
25563
26010
|
var supportMessage = `
|
|
25564
26011
|
##########################################################################
|
|
25565
26012
|
# Thanks for using Doc Detective! If this project was helpful to you, #
|
|
@@ -25604,6 +26051,7 @@ async function runTestsAndReport(config, options) {
|
|
|
25604
26051
|
config = { ...resolvedTests.config || {}, ...config || {} };
|
|
25605
26052
|
resolvedTests.config = config;
|
|
25606
26053
|
}
|
|
26054
|
+
const callerLogLevel = config?.logLevel;
|
|
25607
26055
|
if (config.dryRun) {
|
|
25608
26056
|
config.logLevel = "silent";
|
|
25609
26057
|
if (resolvedTests)
|
|
@@ -25635,6 +26083,12 @@ async function runTestsAndReport(config, options) {
|
|
|
25635
26083
|
log(config, "warning", "Couldn't resolve any tests.");
|
|
25636
26084
|
return null;
|
|
25637
26085
|
}
|
|
26086
|
+
const upgradeLogLevel = config.dryRun ? legacyEnv(process.env).config?.logLevel ?? callerLogLevel : effectiveConfig.logLevel;
|
|
26087
|
+
try {
|
|
26088
|
+
warnUpgrade(specWarnings(resolvedTests), upgradeLogLevel);
|
|
26089
|
+
} catch (error) {
|
|
26090
|
+
log(effectiveConfig, "debug", `Couldn't check specs for Doc Detective 5.0 changes: ${error?.message || error}`);
|
|
26091
|
+
}
|
|
25638
26092
|
const resolvedConfig = resolvedTests.config;
|
|
25639
26093
|
if (!resolvedConfig.dryRun && (willRunViaApi || runNeedsAvailableApps(resolvedTests.specs))) {
|
|
25640
26094
|
resolvedConfig.environment.apps = await getAvailableApps({ config: resolvedConfig });
|