docdex 0.2.21 → 0.2.23

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/install.js CHANGED
@@ -8,7 +8,7 @@ const os = require("node:os");
8
8
  const path = require("node:path");
9
9
  const { pipeline } = require("node:stream/promises");
10
10
  const crypto = require("node:crypto");
11
- const { spawn, spawnSync } = require("node:child_process");
11
+ const { spawn } = require("node:child_process");
12
12
 
13
13
  const pkg = require("../package.json");
14
14
  const {
@@ -41,8 +41,6 @@ const DEFAULT_INTEGRITY_CONFIG = Object.freeze({
41
41
  const LOCAL_FALLBACK_ENV = "DOCDEX_LOCAL_FALLBACK";
42
42
  const LOCAL_BINARY_ENV = "DOCDEX_LOCAL_BINARY";
43
43
  const AGENTS_DOC_FILENAME = "agents.md";
44
- const PLAYWRIGHT_INSTALL_GUARD = "DOCDEX_INTERNAL_PLAYWRIGHT_INSTALL";
45
- const PLAYWRIGHT_SKIP_ENV = "DOCDEX_SKIP_PLAYWRIGHT_DEP_INSTALL";
46
44
 
47
45
  const EXIT_CODE_BY_ERROR_CODE = Object.freeze({
48
46
  DOCDEX_INSTALLER_CONFIG: 2,
@@ -232,60 +230,6 @@ function writeAgentInstructions() {
232
230
  }
233
231
  }
234
232
 
235
- function resolvePlaywrightPackage() {
236
- const baseDir = path.join(__dirname, "..");
237
- try {
238
- return require.resolve("playwright/package.json", { paths: [baseDir] });
239
- } catch {}
240
- try {
241
- return require.resolve("playwright/package.json");
242
- } catch {}
243
- return null;
244
- }
245
-
246
- function resolveNpmCommand() {
247
- const npmExec = process.env.npm_execpath;
248
- if (npmExec) {
249
- return { cmd: process.execPath, args: [npmExec] };
250
- }
251
- const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
252
- return { cmd: npmCmd, args: [] };
253
- }
254
-
255
- function ensurePlaywrightDependency({ logger = console } = {}) {
256
- if (process.env[PLAYWRIGHT_SKIP_ENV]) return;
257
- if (process.env[PLAYWRIGHT_INSTALL_GUARD]) return;
258
- if (resolvePlaywrightPackage()) return;
259
-
260
- const rootDir = path.join(__dirname, "..");
261
- const { cmd, args } = resolveNpmCommand();
262
- const installArgs = args.concat([
263
- "install",
264
- "--no-save",
265
- "--ignore-scripts",
266
- "--no-package-lock",
267
- "--no-audit",
268
- "--no-fund",
269
- "playwright"
270
- ]);
271
- const result = spawnSync(cmd, installArgs, {
272
- cwd: rootDir,
273
- stdio: "inherit",
274
- env: {
275
- ...process.env,
276
- [PLAYWRIGHT_INSTALL_GUARD]: "1"
277
- }
278
- });
279
- if (result.error || (typeof result.status === "number" && result.status !== 0)) {
280
- const message = result.error?.message || `npm exit status ${result.status}`;
281
- logger.warn?.(`[docdex] Playwright dependency install failed: ${message}`);
282
- return;
283
- }
284
- if (!resolvePlaywrightPackage()) {
285
- logger.warn?.("[docdex] Playwright dependency still missing after install attempt");
286
- }
287
- }
288
-
289
233
  function selectHttpClient(url) {
290
234
  try {
291
235
  const protocol = new URL(url).protocol;
@@ -577,13 +521,16 @@ function isLocalInstallRequest({ env, pathModule }) {
577
521
  return argv.some((arg) => isLikelyLocalInstallArg(arg, pathModule));
578
522
  }
579
523
 
580
- function shouldPreferLocalInstall({ env, localBinaryPath, pathModule }) {
524
+ function shouldPreferLocalInstall({ env, localBinaryPath, pathModule, localRepoRoot }) {
581
525
  if (!localBinaryPath) return false;
582
526
  if (parseEnvBool(env?.[LOCAL_FALLBACK_ENV]) === false) return false;
583
527
  if (env?.[LOCAL_BINARY_ENV]) return true;
584
- if (!env?.INIT_CWD) return false;
585
528
  if (env?.npm_lifecycle_event !== "postinstall") return false;
586
- return isLocalInstallRequest({ env, pathModule });
529
+ if (isLocalInstallRequest({ env, pathModule })) return true;
530
+ if (!env?.INIT_CWD || !localRepoRoot) return false;
531
+ const initCwd = pathModule.resolve(env.INIT_CWD);
532
+ const repoRoot = pathModule.resolve(localRepoRoot);
533
+ return initCwd === repoRoot || initCwd.startsWith(`${repoRoot}${pathModule.sep}`);
587
534
  }
588
535
 
589
536
  function resolveLocalBinaryCandidate({
@@ -599,35 +546,16 @@ function resolveLocalBinaryCandidate({
599
546
  if (fsModule.existsSync(resolved)) return resolved;
600
547
  }
601
548
  const isWin32 = platform === "win32";
602
- const mcpName = isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server";
603
549
  const root = repoRoot || detectLocalRepoRoot({ pathModule, fsModule });
604
550
  if (!root) return null;
605
551
  const binaryName = platform === "win32" ? "docdexd.exe" : "docdexd";
606
552
  const releasePath = pathModule.join(root, "target", "release", binaryName);
607
- if (fsModule.existsSync(releasePath)) {
608
- if (localMcpPresent({ fsModule, pathModule, binaryPath: releasePath, mcpName })) {
609
- return releasePath;
610
- }
611
- }
553
+ if (fsModule.existsSync(releasePath)) return releasePath;
612
554
  const debugPath = pathModule.join(root, "target", "debug", binaryName);
613
- if (fsModule.existsSync(debugPath)) {
614
- if (localMcpPresent({ fsModule, pathModule, binaryPath: debugPath, mcpName })) {
615
- return debugPath;
616
- }
617
- }
555
+ if (fsModule.existsSync(debugPath)) return debugPath;
618
556
  return null;
619
557
  }
620
558
 
621
- function localMcpPresent({ fsModule, pathModule, binaryPath, mcpName }) {
622
- const dir = pathModule.dirname(binaryPath);
623
- const candidates = [
624
- pathModule.join(dir, mcpName),
625
- pathModule.join(pathModule.dirname(dir), "release", mcpName),
626
- pathModule.join(pathModule.dirname(dir), "debug", mcpName)
627
- ];
628
- return candidates.some((candidate) => fsModule.existsSync(candidate));
629
- }
630
-
631
559
  async function installFromLocalBinary({
632
560
  fsModule,
633
561
  pathModule,
@@ -650,22 +578,6 @@ async function installFromLocalBinary({
650
578
  if (!isWin32) {
651
579
  await fsModule.promises.chmod(destPath, 0o755).catch(() => {});
652
580
  }
653
- const mcpName = isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server";
654
- const mcpCandidates = [
655
- pathModule.join(pathModule.dirname(binaryPath), mcpName),
656
- pathModule.join(pathModule.dirname(pathModule.dirname(binaryPath)), "release", mcpName),
657
- pathModule.join(pathModule.dirname(pathModule.dirname(binaryPath)), "debug", mcpName)
658
- ];
659
- const mcpSource = mcpCandidates.find((candidate) => fsModule.existsSync(candidate));
660
- if (mcpSource) {
661
- const mcpDest = pathModule.join(distDir, mcpName);
662
- await fsModule.promises.copyFile(mcpSource, mcpDest);
663
- if (!isWin32) {
664
- await fsModule.promises.chmod(mcpDest, 0o755).catch(() => {});
665
- }
666
- } else {
667
- logger?.warn?.(`[docdex] local MCP binary not found; expected near ${binaryPath}`);
668
- }
669
581
  const binarySha256 = await sha256FileFn(destPath);
670
582
  const metadata = {
671
583
  schemaVersion: INSTALL_METADATA_SCHEMA_VERSION,
@@ -718,7 +630,13 @@ async function maybeInstallLocalFallback({
718
630
 
719
631
  const candidate =
720
632
  localBinaryPath ||
721
- resolveLocalBinaryCandidate({ env, platform: process.platform, pathModule, fsModule, repoRoot: localRepoRoot });
633
+ resolveLocalBinaryCandidate({
634
+ env,
635
+ platform: process.platform,
636
+ pathModule,
637
+ fsModule,
638
+ repoRoot: localRepoRoot
639
+ });
722
640
  if (!candidate) return null;
723
641
 
724
642
  return installFromLocalBinary({
@@ -1074,9 +992,6 @@ function decideInstallAction({
1074
992
  integrityResult
1075
993
  }) {
1076
994
  if (!discoveredInstalledState?.binaryPresent) return { outcome: "update", reason: "binary_missing" };
1077
- if (discoveredInstalledState.mcpBinaryPresent === false) {
1078
- return { outcome: "update", reason: "mcp_binary_missing" };
1079
- }
1080
995
 
1081
996
  if (discoveredInstalledState.metadataStatus !== "valid") {
1082
997
  return {
@@ -1111,10 +1026,6 @@ function decideInstallAction({
1111
1026
 
1112
1027
  async function discoverInstalledState({ fsModule, pathModule, distDir, platformKey, isWin32 }) {
1113
1028
  const binaryPath = pathModule.join(distDir, isWin32 ? "docdexd.exe" : "docdexd");
1114
- const mcpBinaryPath = pathModule.join(
1115
- distDir,
1116
- isWin32 ? "docdex-mcp-server.exe" : "docdex-mcp-server"
1117
- );
1118
1029
  const metadataPath = installMetadataPath(distDir, pathModule);
1119
1030
 
1120
1031
  const existsSync = typeof fsModule?.existsSync === "function" ? fsModule.existsSync.bind(fsModule) : null;
@@ -1123,7 +1034,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1123
1034
  binaryPath,
1124
1035
  metadataPath,
1125
1036
  binaryPresent: false,
1126
- mcpBinaryPresent: false,
1127
1037
  installedVersion: null,
1128
1038
  metadata: null,
1129
1039
  metadataStatus: "unavailable",
@@ -1137,7 +1047,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1137
1047
  binaryPath,
1138
1048
  metadataPath,
1139
1049
  binaryPresent: false,
1140
- mcpBinaryPresent: false,
1141
1050
  installedVersion: null,
1142
1051
  metadata: null,
1143
1052
  metadataStatus: "missing",
@@ -1146,13 +1055,11 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1146
1055
  };
1147
1056
  }
1148
1057
 
1149
- const mcpBinaryPresent = existsSync(mcpBinaryPath);
1150
1058
  const metaResult = await readJsonFileIfPossible({ fsModule, filePath: metadataPath });
1151
1059
  const meta = metaResult.value;
1152
1060
  if (!isValidInstallMetadata(meta)) {
1153
1061
  return {
1154
1062
  binaryPath,
1155
- mcpBinaryPresent,
1156
1063
  metadataPath,
1157
1064
  binaryPresent: true,
1158
1065
  installedVersion: typeof meta?.version === "string" ? meta.version : null,
@@ -1175,7 +1082,6 @@ async function discoverInstalledState({ fsModule, pathModule, distDir, platformK
1175
1082
 
1176
1083
  return {
1177
1084
  binaryPath,
1178
- mcpBinaryPresent,
1179
1085
  metadataPath,
1180
1086
  binaryPresent: true,
1181
1087
  installedVersion: meta.version,
@@ -1249,7 +1155,6 @@ async function determineLocalInstallerOutcome({
1249
1155
 
1250
1156
  const shouldVerifyIntegrity =
1251
1157
  discoveredInstalledState.binaryPresent &&
1252
- discoveredInstalledState.mcpBinaryPresent !== false &&
1253
1158
  !discoveredInstalledState.platformMismatch &&
1254
1159
  discoveredInstalledState.installedVersion === expectedVersion &&
1255
1160
  (normalizeSha256Hex(expectedBinarySha256) || discoveredInstalledState.metadataStatus === "valid");
@@ -1805,6 +1710,7 @@ async function runInstaller(options) {
1805
1710
  });
1806
1711
 
1807
1712
  const priorRunnable = existsSync ? existsSync(local.binaryPath) : false;
1713
+ const preferLocal = shouldPreferLocalInstall({ env, localBinaryPath, pathModule, localRepoRoot });
1808
1714
 
1809
1715
  const forceLocalBinary = Boolean(env?.[LOCAL_BINARY_ENV]);
1810
1716
  if (forceLocalBinary && localBinaryPath) {
@@ -1825,6 +1731,24 @@ async function runInstaller(options) {
1825
1731
  return localInstall;
1826
1732
  }
1827
1733
 
1734
+ if (preferLocal) {
1735
+ const localInstall = await installFromLocalBinary({
1736
+ fsModule,
1737
+ pathModule,
1738
+ distDir,
1739
+ binaryPath: localBinaryPath,
1740
+ isWin32,
1741
+ version,
1742
+ platformKey,
1743
+ targetTriple,
1744
+ repoSlug: null,
1745
+ sha256FileFn,
1746
+ writeJsonFileAtomicFn,
1747
+ logger
1748
+ });
1749
+ return localInstall;
1750
+ }
1751
+
1828
1752
  if (local.outcome === "no-op") {
1829
1753
  logger.log("[docdex] Install outcome: no-op");
1830
1754
  await cleanupInstallArtifacts({
@@ -1853,24 +1777,6 @@ async function runInstaller(options) {
1853
1777
  };
1854
1778
  }
1855
1779
 
1856
- if (shouldPreferLocalInstall({ env, localBinaryPath, pathModule })) {
1857
- const localInstall = await installFromLocalBinary({
1858
- fsModule,
1859
- pathModule,
1860
- distDir,
1861
- binaryPath: localBinaryPath,
1862
- isWin32,
1863
- version,
1864
- platformKey,
1865
- targetTriple,
1866
- repoSlug: null,
1867
- sha256FileFn,
1868
- writeJsonFileAtomicFn,
1869
- logger
1870
- });
1871
- return localInstall;
1872
- }
1873
-
1874
1780
  let repoSlug = null;
1875
1781
  let archive;
1876
1782
  let expectedSha256;
@@ -2189,18 +2095,14 @@ async function main() {
2189
2095
  try {
2190
2096
  await runPostInstallSetup({ binaryPath: result?.binaryPath });
2191
2097
  } catch (err) {
2192
- console.warn(`[docdex] postinstall setup skipped: ${err?.message || err}`);
2098
+ console.warn(`[docdex] postinstall setup failed: ${err?.message || err}`);
2099
+ throw err;
2193
2100
  }
2194
2101
  try {
2195
2102
  writeAgentInstructions();
2196
2103
  } catch (err) {
2197
2104
  console.warn(`[docdex] agent instructions skipped: ${err?.message || err}`);
2198
2105
  }
2199
- try {
2200
- ensurePlaywrightDependency();
2201
- } catch (err) {
2202
- console.warn(`[docdex] playwright dependency check skipped: ${err?.message || err}`);
2203
- }
2204
2106
  printPostInstallBanner();
2205
2107
  }
2206
2108
 
@@ -2230,7 +2132,7 @@ function printPostInstallBanner() {
2230
2132
  "\x1b[32mDocdex installed successfully!\x1b[0m",
2231
2133
  "\x1b[41m\x1b[97m IMPORTANT \x1b[0m \x1b[33mNext step:\x1b[0m run \x1b[32m`docdex setup`\x1b[0m to complete the installation.",
2232
2134
  "\x1b[33mSetup:\x1b[0m configures Ollama/models + browser.",
2233
- "\x1b[34mTip:\x1b[0m after setup, start the daemon with \x1b[36m`docdexd serve --repo <path>`\x1b[0m"
2135
+ "\x1b[34mTip:\x1b[0m after setup, the daemon should auto-start; if not, run \x1b[36m`docdexd daemon`\x1b[0m"
2234
2136
  ];
2235
2137
  width = Math.max(72, content.reduce((max, line) => Math.max(max, stripAnsi(line).length), 0));
2236
2138
  const padLine = (text) => {