docdex 0.2.22 → 0.2.24

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/lib/uninstall.js CHANGED
@@ -2,25 +2,138 @@
2
2
  "use strict";
3
3
 
4
4
  const fs = require("node:fs");
5
+ const net = require("node:net");
5
6
  const os = require("node:os");
6
7
  const path = require("node:path");
7
8
  const { spawnSync } = require("node:child_process");
8
9
 
9
10
  const DAEMON_TASK_NAME = "Docdex Daemon";
10
11
  const STARTUP_FAILURE_MARKER = "startup_registration_failed.json";
12
+ const BIN_NAMES = ["docdex", "docdexd"];
13
+ const PACKAGE_NAME = "docdex";
14
+ const DEFAULT_HOST = "127.0.0.1";
15
+ const DEFAULT_DAEMON_PORT = 28491;
16
+ const PORT_WAIT_TIMEOUT_MS = 3000;
17
+ const PORT_WAIT_INTERVAL_MS = 200;
18
+
19
+ function docdexRootPath() {
20
+ return path.join(os.homedir(), ".docdex");
21
+ }
11
22
 
12
23
  function daemonRootPath() {
13
- return path.join(os.homedir(), ".docdex", "daemon_root");
24
+ return path.join(docdexRootPath(), "daemon_root");
14
25
  }
15
26
 
16
27
  function stateDir() {
17
- return path.join(os.homedir(), ".docdex", "state");
28
+ return path.join(docdexRootPath(), "state");
18
29
  }
19
30
 
20
- function daemonLockPath() {
31
+ function daemonLockPaths() {
32
+ const paths = [];
21
33
  const override = process.env.DOCDEX_DAEMON_LOCK_PATH;
22
- if (override && override.trim()) return override.trim();
23
- return path.join(os.homedir(), ".docdex", "daemon.lock");
34
+ if (override && override.trim()) paths.push(override.trim());
35
+ const root = docdexRootPath();
36
+ paths.push(path.join(root, "locks", "daemon.lock"));
37
+ paths.push(path.join(root, "daemon.lock"));
38
+ return Array.from(new Set(paths));
39
+ }
40
+
41
+ function isPortAvailable(port, host) {
42
+ return new Promise((resolve) => {
43
+ const server = net.createServer();
44
+ server.unref();
45
+ server.once("error", () => resolve(false));
46
+ server.once("listening", () => {
47
+ server.close(() => resolve(true));
48
+ });
49
+ server.listen(port, host);
50
+ });
51
+ }
52
+
53
+ function sleep(ms) {
54
+ return new Promise((resolve) => setTimeout(resolve, ms));
55
+ }
56
+
57
+ function sleepSync(ms) {
58
+ if (!ms || ms <= 0) return;
59
+ const buffer = new SharedArrayBuffer(4);
60
+ const view = new Int32Array(buffer);
61
+ Atomics.wait(view, 0, 0, ms);
62
+ }
63
+
64
+ function isPidRunning(pid) {
65
+ if (!pid) return false;
66
+ try {
67
+ process.kill(pid, 0);
68
+ return true;
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function readLockMetadata(lockPath) {
75
+ if (!lockPath || !fs.existsSync(lockPath)) return null;
76
+ try {
77
+ const raw = fs.readFileSync(lockPath, "utf8");
78
+ const payload = JSON.parse(raw);
79
+ if (!payload || typeof payload !== "object") return null;
80
+ const pid = typeof payload.pid === "number" ? payload.pid : null;
81
+ const port = typeof payload.port === "number" ? payload.port : null;
82
+ return { pid, port, lockPath };
83
+ } catch {
84
+ return null;
85
+ }
86
+ }
87
+
88
+ function findRunningDaemonFromLocks() {
89
+ for (const lockPath of daemonLockPaths()) {
90
+ const meta = readLockMetadata(lockPath);
91
+ if (!meta) continue;
92
+ if (isPidRunning(meta.pid)) return meta;
93
+ }
94
+ return null;
95
+ }
96
+
97
+ function manualStopInstructions() {
98
+ if (process.platform === "darwin") {
99
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
100
+ const domain = uid != null ? `gui/${uid}` : "gui/$UID";
101
+ return [
102
+ "Manual cleanup required:",
103
+ `- launchctl bootout ${domain} ~/Library/LaunchAgents/com.docdex.daemon.plist`,
104
+ "- launchctl remove com.docdex.daemon",
105
+ "- pkill -f docdexd",
106
+ "- rm -f ~/.docdex/locks/daemon.lock",
107
+ "- lsof -iTCP:28491 -sTCP:LISTEN",
108
+ ];
109
+ }
110
+ if (process.platform === "win32") {
111
+ return [
112
+ "Manual cleanup required:",
113
+ `- schtasks /End /TN "${DAEMON_TASK_NAME}"`,
114
+ "- schtasks /Delete /TN \"Docdex Daemon\" /F",
115
+ "- taskkill /IM docdexd.exe /T /F",
116
+ "- del %USERPROFILE%\\.docdex\\locks\\daemon.lock",
117
+ "- netstat -ano | findstr 28491",
118
+ ];
119
+ }
120
+ return [
121
+ "Manual cleanup required:",
122
+ "- systemctl --user stop docdexd.service",
123
+ "- systemctl --user disable --now docdexd.service",
124
+ "- pkill -f docdexd",
125
+ "- rm -f ~/.docdex/locks/daemon.lock",
126
+ "- lsof -iTCP:28491 -sTCP:LISTEN",
127
+ ];
128
+ }
129
+
130
+ async function waitForPortFree({ host = DEFAULT_HOST, port = DEFAULT_DAEMON_PORT } = {}) {
131
+ const deadline = Date.now() + PORT_WAIT_TIMEOUT_MS;
132
+ while (Date.now() < deadline) {
133
+ if (await isPortAvailable(port, host)) return true;
134
+ await sleep(PORT_WAIT_INTERVAL_MS);
135
+ }
136
+ return false;
24
137
  }
25
138
 
26
139
  function clientConfigPaths() {
@@ -376,6 +489,10 @@ function killPid(pid) {
376
489
  return true;
377
490
  }
378
491
  process.kill(pid, "SIGTERM");
492
+ sleepSync(150);
493
+ if (isPidRunning(pid)) {
494
+ process.kill(pid, "SIGKILL");
495
+ }
379
496
  return true;
380
497
  } catch {
381
498
  return false;
@@ -383,20 +500,23 @@ function killPid(pid) {
383
500
  }
384
501
 
385
502
  function stopDaemonFromLock() {
386
- const lockPath = daemonLockPath();
387
- if (!fs.existsSync(lockPath)) return false;
388
- try {
389
- const raw = fs.readFileSync(lockPath, "utf8");
390
- const payload = JSON.parse(raw);
391
- const pid = payload && typeof payload.pid === "number" ? payload.pid : null;
392
- const stopped = killPid(pid);
393
- fs.unlinkSync(lockPath);
394
- return stopped;
395
- } catch {
396
- return false;
503
+ let stopped = false;
504
+ for (const lockPath of daemonLockPaths()) {
505
+ if (!fs.existsSync(lockPath)) continue;
506
+ try {
507
+ const raw = fs.readFileSync(lockPath, "utf8");
508
+ const payload = JSON.parse(raw);
509
+ const pid = payload && typeof payload.pid === "number" ? payload.pid : null;
510
+ stopped = killPid(pid) || stopped;
511
+ fs.unlinkSync(lockPath);
512
+ } catch {
513
+ continue;
514
+ }
397
515
  }
516
+ return stopped;
398
517
  }
399
518
 
519
+
400
520
  function stopDaemonByName() {
401
521
  if (process.platform === "win32") {
402
522
  spawnSync("taskkill", ["/IM", "docdexd.exe", "/T", "/F"]);
@@ -404,40 +524,46 @@ function stopDaemonByName() {
404
524
  }
405
525
  spawnSync("pkill", ["-TERM", "-x", "docdexd"]);
406
526
  spawnSync("pkill", ["-TERM", "-f", "docdexd daemon"]);
527
+ spawnSync("pkill", ["-TERM", "-f", "docdexd serve"]);
528
+ spawnSync("pkill", ["-KILL", "-x", "docdexd"]);
529
+ spawnSync("pkill", ["-KILL", "-f", "docdexd daemon"]);
530
+ spawnSync("pkill", ["-KILL", "-f", "docdexd serve"]);
407
531
  return true;
408
532
  }
409
533
 
410
- function stopMcpByName() {
411
- if (process.platform === "win32") {
412
- spawnSync("taskkill", ["/IM", "docdex-mcp-server.exe", "/T", "/F"]);
413
- return true;
414
- }
415
- spawnSync("pkill", ["-TERM", "-x", "docdex-mcp-server"]);
416
- spawnSync("pkill", ["-TERM", "-f", "docdex-mcp-server"]);
417
- return true;
418
- }
419
534
 
420
535
  function unregisterStartup() {
421
536
  if (process.platform === "darwin") {
422
537
  const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.docdex.daemon.plist");
538
+ const uid = typeof process.getuid === "function" ? process.getuid() : null;
539
+ const domain = uid != null ? `gui/${uid}` : null;
540
+ if (domain) {
541
+ spawnSync("launchctl", ["bootout", domain, "com.docdex.daemon"]);
542
+ } else {
543
+ spawnSync("launchctl", ["bootout", "com.docdex.daemon"]);
544
+ }
545
+ spawnSync("launchctl", ["stop", "com.docdex.daemon"]);
423
546
  if (fs.existsSync(plistPath)) {
424
- const uid = typeof process.getuid === "function" ? process.getuid() : null;
425
- if (uid != null) {
426
- spawnSync("launchctl", ["bootout", `gui/${uid}`, plistPath]);
547
+ if (domain) {
548
+ spawnSync("launchctl", ["bootout", domain, plistPath]);
549
+ } else {
550
+ spawnSync("launchctl", ["bootout", plistPath]);
427
551
  }
428
552
  spawnSync("launchctl", ["unload", "-w", plistPath]);
429
- spawnSync("launchctl", ["remove", "com.docdex.daemon"]);
430
553
  try {
431
554
  fs.unlinkSync(plistPath);
432
555
  } catch {}
433
556
  }
557
+ spawnSync("launchctl", ["remove", "com.docdex.daemon"]);
434
558
  return true;
435
559
  }
436
560
 
437
561
  if (process.platform === "linux") {
438
562
  const systemdDir = path.join(os.homedir(), ".config", "systemd", "user");
439
563
  const unitPath = path.join(systemdDir, "docdexd.service");
564
+ spawnSync("systemctl", ["--user", "stop", "docdexd.service"]);
440
565
  spawnSync("systemctl", ["--user", "disable", "--now", "docdexd.service"]);
566
+ spawnSync("systemctl", ["--user", "reset-failed", "docdexd.service"]);
441
567
  if (fs.existsSync(unitPath)) {
442
568
  try {
443
569
  fs.unlinkSync(unitPath);
@@ -490,16 +616,119 @@ function removeClientConfigs() {
490
616
  }
491
617
  }
492
618
 
493
- async function main() {
494
- const stopped = stopDaemonFromLock();
495
- if (!stopped) {
496
- stopDaemonByName();
619
+ function removeDocdexRoot() {
620
+ const root = docdexRootPath();
621
+ if (!fs.existsSync(root)) return false;
622
+ const resolvedRoot = path.resolve(root);
623
+ const resolvedHome = path.resolve(os.homedir());
624
+ const expectedRoot = path.join(resolvedHome, ".docdex");
625
+ if (resolvedRoot !== expectedRoot) return false;
626
+ try {
627
+ fs.rmSync(resolvedRoot, { recursive: true, force: true });
628
+ return true;
629
+ } catch {
630
+ return false;
497
631
  }
498
- stopMcpByName();
632
+ }
633
+
634
+ function removePath(target) {
635
+ if (!target || !fs.existsSync(target)) return false;
636
+ try {
637
+ fs.rmSync(target, { recursive: true, force: true });
638
+ return true;
639
+ } catch {
640
+ return false;
641
+ }
642
+ }
643
+
644
+ function removeBinsInDir(dirPath) {
645
+ if (!dirPath || !fs.existsSync(dirPath)) return false;
646
+ let removed = false;
647
+ for (const name of BIN_NAMES) {
648
+ removed = removePath(path.join(dirPath, name)) || removed;
649
+ }
650
+ return removed;
651
+ }
652
+
653
+ function removeNodeModuleAt(dirPath) {
654
+ if (!dirPath) return false;
655
+ return removePath(path.join(dirPath, PACKAGE_NAME));
656
+ }
657
+
658
+ function removePrefixInstalls(prefix) {
659
+ if (!prefix) return;
660
+ removeBinsInDir(path.join(prefix, "bin"));
661
+ removeBinsInDir(prefix);
662
+ removeNodeModuleAt(path.join(prefix, "lib", "node_modules"));
663
+ removeNodeModuleAt(path.join(prefix, "node_modules"));
664
+ }
665
+
666
+ function removeHomebrewInstalls() {
667
+ const prefixes = new Set();
668
+ if (process.env.HOMEBREW_PREFIX) prefixes.add(process.env.HOMEBREW_PREFIX);
669
+ prefixes.add("/opt/homebrew");
670
+ prefixes.add("/usr/local");
671
+ for (const prefix of prefixes) {
672
+ removeBinsInDir(path.join(prefix, "bin"));
673
+ }
674
+ }
675
+
676
+ function removeNvmInstalls() {
677
+ const home = os.homedir();
678
+ const roots = new Set();
679
+ if (process.env.NVM_DIR) roots.add(process.env.NVM_DIR);
680
+ if (process.env.NVM_HOME) roots.add(process.env.NVM_HOME);
681
+ roots.add(path.join(home, ".nvm"));
682
+ if (process.env.NVM_BIN) {
683
+ removeBinsInDir(process.env.NVM_BIN);
684
+ }
685
+ for (const root of roots) {
686
+ if (!root || !fs.existsSync(root)) continue;
687
+ const versionsRoot = path.join(root, "versions", "node");
688
+ if (!fs.existsSync(versionsRoot)) continue;
689
+ const entries = fs.readdirSync(versionsRoot, { withFileTypes: true });
690
+ for (const entry of entries) {
691
+ if (!entry.isDirectory()) continue;
692
+ const versionDir = path.join(versionsRoot, entry.name);
693
+ removeBinsInDir(path.join(versionDir, "bin"));
694
+ removeNodeModuleAt(path.join(versionDir, "lib", "node_modules"));
695
+ removeNodeModuleAt(path.join(versionDir, "node_modules"));
696
+ }
697
+ }
698
+ }
699
+
700
+ function removeCargoInstalls() {
701
+ const home = os.homedir();
702
+ removeBinsInDir(path.join(home, ".cargo", "bin"));
703
+ }
704
+
705
+ function purgeExternalInstalls() {
706
+ const prefix = process.env.npm_config_prefix || process.env.PREFIX;
707
+ if (prefix) removePrefixInstalls(prefix);
708
+ removeHomebrewInstalls();
709
+ removeNvmInstalls();
710
+ removeCargoInstalls();
711
+ }
712
+
713
+ async function main() {
499
714
  unregisterStartup();
715
+ stopDaemonFromLock();
716
+ stopDaemonByName();
717
+ const freed = await waitForPortFree();
718
+ const running = findRunningDaemonFromLocks();
719
+ if (!freed || running) {
720
+ console.warn(
721
+ `[docdex] ${DEFAULT_HOST}:${DEFAULT_DAEMON_PORT} is still in use or a daemon PID is alive; stop the process manually before reinstalling.`
722
+ );
723
+ for (const line of manualStopInstructions()) {
724
+ console.warn(`[docdex] ${line}`);
725
+ }
726
+ }
500
727
  removeClientConfigs();
501
728
  clearStartupFailure();
502
729
  removeDaemonRootNotice();
730
+ removeDocdexRoot();
731
+ purgeExternalInstalls();
503
732
  }
504
733
 
505
734
  if (require.main === module) {
package/package.json CHANGED
@@ -1,12 +1,11 @@
1
1
  {
2
2
  "name": "docdex",
3
- "version": "0.2.22",
3
+ "version": "0.2.24",
4
4
  "mcpName": "io.github.bekirdag/docdex",
5
5
  "description": "Local-first documentation and code indexer with HTTP/MCP search, AST, and agent memory.",
6
6
  "bin": {
7
7
  "docdex": "bin/docdex.js",
8
- "docdexd": "bin/docdex.js",
9
- "docdex-mcp-server": "bin/docdex-mcp-server.js"
8
+ "docdexd": "bin/docdex.js"
10
9
  },
11
10
  "files": [
12
11
  "bin",
@@ -62,7 +61,6 @@
62
61
  "access": "public"
63
62
  },
64
63
  "dependencies": {
65
- "playwright": "^1.49.0",
66
64
  "tar": "^6.2.1"
67
65
  }
68
66
  }
@@ -1,66 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const fs = require("node:fs");
5
- const path = require("node:path");
6
- const { spawn } = require("node:child_process");
7
-
8
- const {
9
- detectPlatformKey,
10
- targetTripleForPlatformKey,
11
- assetPatternForPlatformKey,
12
- UnsupportedPlatformError
13
- } = require("../lib/platform");
14
-
15
- function run() {
16
- let platformKey;
17
- try {
18
- platformKey = detectPlatformKey();
19
- } catch (err) {
20
- if (err instanceof UnsupportedPlatformError) {
21
- const detected = `${err.details?.platform ?? process.platform}/${err.details?.arch ?? process.arch}`;
22
- const libc = err.details?.libc ? `/${err.details.libc}` : "";
23
- console.error(`[docdex] unsupported platform (${detected}${libc})`);
24
- console.error(`[docdex] error code: ${err.code}`);
25
- console.error("[docdex] No download/run was attempted for this platform.");
26
- if (Array.isArray(err.details?.supportedPlatformKeys) && err.details.supportedPlatformKeys.length) {
27
- console.error(`[docdex] Supported platforms: ${err.details.supportedPlatformKeys.join(", ")}`);
28
- }
29
- if (typeof err.details?.candidatePlatformKey === "string") {
30
- console.error(`[docdex] Asset naming pattern: ${assetPatternForPlatformKey(err.details.candidatePlatformKey)}`);
31
- }
32
- console.error("[docdex] Next steps: use a supported platform or build from source (Rust).");
33
- process.exit(err.exitCode || 3);
34
- return;
35
- }
36
- console.error(`[docdex] failed to detect platform: ${err?.message || String(err)}`);
37
- process.exit(1);
38
- return;
39
- }
40
-
41
- const basePath = path.join(__dirname, "..", "dist", platformKey);
42
- const binaryPath = path.join(
43
- basePath,
44
- process.platform === "win32" ? "docdex-mcp-server.exe" : "docdex-mcp-server"
45
- );
46
-
47
- if (!fs.existsSync(binaryPath)) {
48
- console.error(
49
- `[docdex] Missing MCP server binary for ${platformKey}. Try reinstalling or set DOCDEX_MCP_SERVER_BIN.`
50
- );
51
- try {
52
- console.error(`[docdex] Expected target triple: ${targetTripleForPlatformKey(platformKey)}`);
53
- console.error(`[docdex] Asset naming pattern: ${assetPatternForPlatformKey(platformKey)}`);
54
- } catch {}
55
- process.exit(1);
56
- }
57
-
58
- const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit" });
59
- child.on("exit", (code) => process.exit(code ?? 1));
60
- child.on("error", (err) => {
61
- console.error(`[docdex] failed to launch MCP server: ${err.message}`);
62
- process.exit(1);
63
- });
64
- }
65
-
66
- run();
@@ -1,174 +0,0 @@
1
- #!/usr/bin/env node
2
- "use strict";
3
-
4
- const { chromium, firefox, webkit } = require("playwright");
5
-
6
- const DEFAULT_TIMEOUT_MS = 15000;
7
- const DEFAULT_BROWSER = "chromium";
8
- const VIEWPORT = { width: 1920, height: 1080 };
9
- const CHROMIUM_ARGS = [
10
- "--disable-blink-features=AutomationControlled",
11
- "--disable-dev-shm-usage",
12
- "--disable-gpu",
13
- "--no-sandbox",
14
- "--no-first-run",
15
- "--no-default-browser-check"
16
- ];
17
-
18
- function normalizeBrowser(value) {
19
- const trimmed = String(value || "").trim().toLowerCase();
20
- if (trimmed === "chrome" || trimmed === "chromium" || trimmed === "chromium-browser") {
21
- return "chromium";
22
- }
23
- if (trimmed === "firefox") return "firefox";
24
- if (trimmed === "webkit") return "webkit";
25
- return DEFAULT_BROWSER;
26
- }
27
-
28
- function resolveBrowserType(name) {
29
- switch (name) {
30
- case "chromium":
31
- return chromium;
32
- case "firefox":
33
- return firefox;
34
- case "webkit":
35
- return webkit;
36
- default:
37
- return chromium;
38
- }
39
- }
40
-
41
- function parseArgs(argv) {
42
- const parsed = {
43
- url: "",
44
- browser: DEFAULT_BROWSER,
45
- timeoutMs: DEFAULT_TIMEOUT_MS,
46
- userAgent: "",
47
- headless: true,
48
- userDataDir: ""
49
- };
50
- for (let i = 0; i < argv.length; i += 1) {
51
- const value = argv[i];
52
- if (value === "--url" && argv[i + 1]) {
53
- parsed.url = argv[i + 1];
54
- i += 1;
55
- continue;
56
- }
57
- if (value === "--browser" && argv[i + 1]) {
58
- parsed.browser = argv[i + 1];
59
- i += 1;
60
- continue;
61
- }
62
- if (value === "--timeout-ms" && argv[i + 1]) {
63
- parsed.timeoutMs = Number(argv[i + 1]);
64
- i += 1;
65
- continue;
66
- }
67
- if (value === "--user-agent" && argv[i + 1]) {
68
- parsed.userAgent = argv[i + 1];
69
- i += 1;
70
- continue;
71
- }
72
- if (value === "--user-data-dir" && argv[i + 1]) {
73
- parsed.userDataDir = argv[i + 1];
74
- i += 1;
75
- continue;
76
- }
77
- if (value === "--headless") {
78
- parsed.headless = true;
79
- continue;
80
- }
81
- if (value === "--headed") {
82
- parsed.headless = false;
83
- continue;
84
- }
85
- }
86
- parsed.browser = normalizeBrowser(parsed.browser);
87
- if (!parsed.url) {
88
- throw new Error("missing --url");
89
- }
90
- if (!Number.isFinite(parsed.timeoutMs) || parsed.timeoutMs <= 0) {
91
- parsed.timeoutMs = DEFAULT_TIMEOUT_MS;
92
- }
93
- return parsed;
94
- }
95
-
96
- async function fetchWithPlaywright(options) {
97
- const browserName = normalizeBrowser(options.browser);
98
- const browserType = resolveBrowserType(browserName);
99
- const launchOptions = {
100
- headless: options.headless
101
- };
102
- if (browserName === "chromium") {
103
- launchOptions.args = CHROMIUM_ARGS;
104
- }
105
-
106
- let browser;
107
- let context;
108
- try {
109
- if (options.userDataDir) {
110
- context = await browserType.launchPersistentContext(options.userDataDir, {
111
- ...launchOptions,
112
- viewport: VIEWPORT,
113
- userAgent: options.userAgent || undefined
114
- });
115
- } else {
116
- browser = await browserType.launch(launchOptions);
117
- context = await browser.newContext({
118
- viewport: VIEWPORT,
119
- userAgent: options.userAgent || undefined
120
- });
121
- }
122
-
123
- await context.addInitScript(() => {
124
- Object.defineProperty(navigator, "webdriver", { get: () => undefined });
125
- });
126
-
127
- const page = await context.newPage();
128
- page.setDefaultTimeout(options.timeoutMs);
129
- const response = await page.goto(options.url, {
130
- waitUntil: "domcontentloaded",
131
- timeout: options.timeoutMs
132
- });
133
- const html = await page.content();
134
- const status = response ? response.status() : null;
135
- const finalUrl = page.url();
136
- await page.close();
137
-
138
- if (!html || !String(html).trim()) {
139
- throw new Error("empty HTML response");
140
- }
141
-
142
- return { html: String(html), status, final_url: finalUrl };
143
- } finally {
144
- if (context) {
145
- await context.close();
146
- }
147
- if (browser) {
148
- await browser.close();
149
- }
150
- }
151
- }
152
-
153
- async function main() {
154
- try {
155
- const options = parseArgs(process.argv.slice(2));
156
- const result = await fetchWithPlaywright(options);
157
- process.stdout.write(JSON.stringify(result) + "\n");
158
- } catch (err) {
159
- const message = err?.message || String(err);
160
- console.error(`[docdex] playwright fetch failed: ${message}`);
161
- process.exit(1);
162
- }
163
- }
164
-
165
- if (require.main === module) {
166
- main();
167
- }
168
-
169
- module.exports = {
170
- fetchWithPlaywright,
171
- normalizeBrowser,
172
- parseArgs,
173
- resolveBrowserType
174
- };