docdex 0.2.19 → 0.2.20
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/CHANGELOG.md +5 -0
- package/bin/docdex.js +22 -2
- package/lib/postinstall_setup.js +28 -21
- package/lib/update_check.js +218 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.2.20
|
|
4
|
+
- Prompt for npm updates at CLI start (TTY-only, opt-out via `DOCDEX_UPDATE_CHECK=0`).
|
|
5
|
+
- Export bundled Playwright fetcher for daemon startup (launchd/systemd/schtasks + immediate spawn).
|
|
6
|
+
- Pass `DOCDEX_PLAYWRIGHT_FETCHER` in the npm wrapper when launching the daemon.
|
|
7
|
+
|
|
3
8
|
## 0.2.19
|
|
4
9
|
- Playwright issue fix
|
|
5
10
|
- Agents md adding command manually
|
package/bin/docdex.js
CHANGED
|
@@ -5,6 +5,7 @@ const fs = require("node:fs");
|
|
|
5
5
|
const path = require("node:path");
|
|
6
6
|
const { spawn } = require("node:child_process");
|
|
7
7
|
|
|
8
|
+
const pkg = require("../package.json");
|
|
8
9
|
const {
|
|
9
10
|
artifactName,
|
|
10
11
|
detectLibcFromRuntime,
|
|
@@ -13,6 +14,7 @@ const {
|
|
|
13
14
|
assetPatternForPlatformKey,
|
|
14
15
|
UnsupportedPlatformError
|
|
15
16
|
} = require("../lib/platform");
|
|
17
|
+
const { checkForUpdateOnce } = require("../lib/update_check");
|
|
16
18
|
|
|
17
19
|
function isDoctorCommand(argv) {
|
|
18
20
|
const sub = argv[0];
|
|
@@ -139,7 +141,7 @@ function runDoctor() {
|
|
|
139
141
|
process.exit(report.exitCode);
|
|
140
142
|
}
|
|
141
143
|
|
|
142
|
-
function run() {
|
|
144
|
+
async function run() {
|
|
143
145
|
const argv = process.argv.slice(2);
|
|
144
146
|
if (isDoctorCommand(argv)) {
|
|
145
147
|
runDoctor();
|
|
@@ -164,9 +166,11 @@ function run() {
|
|
|
164
166
|
}
|
|
165
167
|
console.error("[docdex] Next steps: use a supported platform or build from source (Rust).");
|
|
166
168
|
process.exit(err.exitCode || 3);
|
|
169
|
+
return;
|
|
167
170
|
}
|
|
168
171
|
console.error(`[docdex] failed to detect platform: ${err?.message || String(err)}`);
|
|
169
172
|
process.exit(1);
|
|
173
|
+
return;
|
|
170
174
|
}
|
|
171
175
|
|
|
172
176
|
const basePath = path.join(__dirname, "..", "dist", platformKey);
|
|
@@ -186,12 +190,25 @@ function run() {
|
|
|
186
190
|
console.error(`[docdex] Asset naming pattern: ${assetPatternForPlatformKey(platformKey)}`);
|
|
187
191
|
} catch {}
|
|
188
192
|
process.exit(1);
|
|
193
|
+
return;
|
|
189
194
|
}
|
|
190
195
|
|
|
196
|
+
await checkForUpdateOnce({
|
|
197
|
+
currentVersion: pkg.version,
|
|
198
|
+
env: process.env,
|
|
199
|
+
stdout: process.stdout,
|
|
200
|
+
stderr: process.stderr,
|
|
201
|
+
logger: console
|
|
202
|
+
});
|
|
203
|
+
|
|
191
204
|
const env = { ...process.env };
|
|
192
205
|
if (!env.DOCDEX_MCP_SERVER_BIN && fs.existsSync(mcpBinaryPath)) {
|
|
193
206
|
env.DOCDEX_MCP_SERVER_BIN = mcpBinaryPath;
|
|
194
207
|
}
|
|
208
|
+
const fetcherPath = path.join(__dirname, "..", "lib", "playwright_fetch.js");
|
|
209
|
+
if (!env.DOCDEX_PLAYWRIGHT_FETCHER && fs.existsSync(fetcherPath)) {
|
|
210
|
+
env.DOCDEX_PLAYWRIGHT_FETCHER = fetcherPath;
|
|
211
|
+
}
|
|
195
212
|
const child = spawn(binaryPath, process.argv.slice(2), { stdio: "inherit", env });
|
|
196
213
|
child.on("exit", (code) => process.exit(code ?? 1));
|
|
197
214
|
child.on("error", (err) => {
|
|
@@ -200,4 +217,7 @@ function run() {
|
|
|
200
217
|
});
|
|
201
218
|
}
|
|
202
219
|
|
|
203
|
-
run()
|
|
220
|
+
run().catch((err) => {
|
|
221
|
+
console.error(`[docdex] unexpected error: ${err?.message || String(err)}`);
|
|
222
|
+
process.exit(1);
|
|
223
|
+
});
|
package/lib/postinstall_setup.js
CHANGED
|
@@ -1464,8 +1464,26 @@ async function maybePromptOllamaModel({
|
|
|
1464
1464
|
return { status: "skipped", reason: "invalid_selection" };
|
|
1465
1465
|
}
|
|
1466
1466
|
|
|
1467
|
+
function resolvePlaywrightFetcherPath() {
|
|
1468
|
+
const candidate = path.join(__dirname, "playwright_fetch.js");
|
|
1469
|
+
return fs.existsSync(candidate) ? candidate : null;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
function buildDaemonEnvPairs({ mcpBinaryPath } = {}) {
|
|
1473
|
+
const pairs = [["DOCDEX_BROWSER_AUTO_INSTALL", "0"]];
|
|
1474
|
+
if (mcpBinaryPath) pairs.push(["DOCDEX_MCP_SERVER_BIN", mcpBinaryPath]);
|
|
1475
|
+
const fetcher = resolvePlaywrightFetcherPath();
|
|
1476
|
+
if (fetcher) pairs.push(["DOCDEX_PLAYWRIGHT_FETCHER", fetcher]);
|
|
1477
|
+
return pairs;
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
function buildDaemonEnv({ mcpBinaryPath } = {}) {
|
|
1481
|
+
return Object.fromEntries(buildDaemonEnvPairs({ mcpBinaryPath }));
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1467
1484
|
function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger }) {
|
|
1468
1485
|
if (!binaryPath) return { ok: false, reason: "missing_binary" };
|
|
1486
|
+
const envPairs = buildDaemonEnvPairs({ mcpBinaryPath });
|
|
1469
1487
|
const args = [
|
|
1470
1488
|
"daemon",
|
|
1471
1489
|
"--repo",
|
|
@@ -1478,21 +1496,16 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
|
|
|
1478
1496
|
"warn",
|
|
1479
1497
|
"--secure-mode=false"
|
|
1480
1498
|
];
|
|
1481
|
-
const envMcpBin = mcpBinaryPath ? `DOCDEX_MCP_SERVER_BIN=${mcpBinaryPath}` : null;
|
|
1482
1499
|
|
|
1483
1500
|
if (process.platform === "darwin") {
|
|
1484
1501
|
const plistPath = path.join(os.homedir(), "Library", "LaunchAgents", "com.docdex.daemon.plist");
|
|
1485
1502
|
const logDir = path.join(os.homedir(), ".docdex", "logs");
|
|
1486
1503
|
fs.mkdirSync(logDir, { recursive: true });
|
|
1487
1504
|
const programArgs = [binaryPath, ...args];
|
|
1488
|
-
const envVars = [
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
];
|
|
1492
|
-
if (mcpBinaryPath) {
|
|
1493
|
-
envVars.push(" <key>DOCDEX_MCP_SERVER_BIN</key>\n");
|
|
1494
|
-
envVars.push(` <string>${mcpBinaryPath}</string>\n`);
|
|
1495
|
-
}
|
|
1505
|
+
const envVars = envPairs.flatMap(([key, value]) => [
|
|
1506
|
+
` <key>${key}</key>\n`,
|
|
1507
|
+
` <string>${value}</string>\n`
|
|
1508
|
+
]);
|
|
1496
1509
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>\n` +
|
|
1497
1510
|
`<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n` +
|
|
1498
1511
|
`<plist version="1.0">\n` +
|
|
@@ -1534,6 +1547,7 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
|
|
|
1534
1547
|
const systemdDir = path.join(os.homedir(), ".config", "systemd", "user");
|
|
1535
1548
|
const unitPath = path.join(systemdDir, "docdexd.service");
|
|
1536
1549
|
fs.mkdirSync(systemdDir, { recursive: true });
|
|
1550
|
+
const envLines = envPairs.map(([key, value]) => `Environment=${key}=${value}`);
|
|
1537
1551
|
const unit = [
|
|
1538
1552
|
"[Unit]",
|
|
1539
1553
|
"Description=Docdex daemon",
|
|
@@ -1541,8 +1555,7 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
|
|
|
1541
1555
|
"",
|
|
1542
1556
|
"[Service]",
|
|
1543
1557
|
`ExecStart=${binaryPath} ${args.join(" ")}`,
|
|
1544
|
-
|
|
1545
|
-
envMcpBin ? `Environment=${envMcpBin}` : null,
|
|
1558
|
+
...envLines,
|
|
1546
1559
|
"Restart=always",
|
|
1547
1560
|
"RestartSec=2",
|
|
1548
1561
|
"",
|
|
@@ -1561,10 +1574,7 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
|
|
|
1561
1574
|
if (process.platform === "win32") {
|
|
1562
1575
|
const taskName = "Docdex Daemon";
|
|
1563
1576
|
const joinedArgs = args.map((arg) => `"${arg}"`).join(" ");
|
|
1564
|
-
const envParts = [
|
|
1565
|
-
if (mcpBinaryPath) {
|
|
1566
|
-
envParts.push(`set "DOCDEX_MCP_SERVER_BIN=${mcpBinaryPath}"`);
|
|
1567
|
-
}
|
|
1577
|
+
const envParts = envPairs.map(([key, value]) => `set "${key}=${value}"`);
|
|
1568
1578
|
const taskArgs =
|
|
1569
1579
|
`"cmd.exe" /c "${envParts.join(" && ")} && \"${binaryPath}\" ${joinedArgs}"`;
|
|
1570
1580
|
const create = spawnSync("schtasks", [
|
|
@@ -1592,10 +1602,7 @@ function registerStartup({ binaryPath, mcpBinaryPath, port, repoRoot, logger })
|
|
|
1592
1602
|
|
|
1593
1603
|
function startDaemonNow({ binaryPath, mcpBinaryPath, port, repoRoot }) {
|
|
1594
1604
|
if (!binaryPath) return false;
|
|
1595
|
-
const extraEnv = {};
|
|
1596
|
-
if (mcpBinaryPath) {
|
|
1597
|
-
extraEnv.DOCDEX_MCP_SERVER_BIN = mcpBinaryPath;
|
|
1598
|
-
}
|
|
1605
|
+
const extraEnv = buildDaemonEnv({ mcpBinaryPath });
|
|
1599
1606
|
const child = spawn(
|
|
1600
1607
|
binaryPath,
|
|
1601
1608
|
[
|
|
@@ -1615,7 +1622,6 @@ function startDaemonNow({ binaryPath, mcpBinaryPath, port, repoRoot }) {
|
|
|
1615
1622
|
detached: true,
|
|
1616
1623
|
env: {
|
|
1617
1624
|
...process.env,
|
|
1618
|
-
DOCDEX_BROWSER_AUTO_INSTALL: "0",
|
|
1619
1625
|
...extraEnv
|
|
1620
1626
|
}
|
|
1621
1627
|
}
|
|
@@ -1847,5 +1853,6 @@ module.exports = {
|
|
|
1847
1853
|
canPromptWithTty,
|
|
1848
1854
|
shouldSkipSetup,
|
|
1849
1855
|
launchSetupWizard,
|
|
1850
|
-
applyAgentInstructions
|
|
1856
|
+
applyAgentInstructions,
|
|
1857
|
+
buildDaemonEnv
|
|
1851
1858
|
};
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const https = require("node:https");
|
|
4
|
+
|
|
5
|
+
const UPDATE_CHECK_ENV = "DOCDEX_UPDATE_CHECK";
|
|
6
|
+
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org/docdex/latest";
|
|
7
|
+
const DEFAULT_TIMEOUT_MS = 1500;
|
|
8
|
+
const MAX_RESPONSE_BYTES = 128 * 1024;
|
|
9
|
+
|
|
10
|
+
let hasChecked = false;
|
|
11
|
+
|
|
12
|
+
function normalizeVersion(value) {
|
|
13
|
+
if (typeof value !== "string") return "";
|
|
14
|
+
return value.trim().replace(/^v/i, "");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function parseSemver(value) {
|
|
18
|
+
const normalized = normalizeVersion(value);
|
|
19
|
+
const match = normalized.match(
|
|
20
|
+
/^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/u
|
|
21
|
+
);
|
|
22
|
+
if (!match) return null;
|
|
23
|
+
return {
|
|
24
|
+
major: Number(match[1]),
|
|
25
|
+
minor: Number(match[2]),
|
|
26
|
+
patch: Number(match[3]),
|
|
27
|
+
prerelease: match[4] ? match[4].split(".") : null
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function compareIdentifiers(left, right) {
|
|
32
|
+
const leftNum = /^[0-9]+$/.test(left) ? Number(left) : null;
|
|
33
|
+
const rightNum = /^[0-9]+$/.test(right) ? Number(right) : null;
|
|
34
|
+
|
|
35
|
+
if (leftNum != null && rightNum != null) {
|
|
36
|
+
if (leftNum === rightNum) return 0;
|
|
37
|
+
return leftNum > rightNum ? 1 : -1;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (leftNum != null) return -1;
|
|
41
|
+
if (rightNum != null) return 1;
|
|
42
|
+
if (left === right) return 0;
|
|
43
|
+
return left > right ? 1 : -1;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function comparePrerelease(left, right) {
|
|
47
|
+
if (!left && !right) return 0;
|
|
48
|
+
if (!left) return 1;
|
|
49
|
+
if (!right) return -1;
|
|
50
|
+
|
|
51
|
+
const length = Math.max(left.length, right.length);
|
|
52
|
+
for (let i = 0; i < length; i += 1) {
|
|
53
|
+
const leftId = left[i];
|
|
54
|
+
const rightId = right[i];
|
|
55
|
+
if (leftId == null) return -1;
|
|
56
|
+
if (rightId == null) return 1;
|
|
57
|
+
const result = compareIdentifiers(leftId, rightId);
|
|
58
|
+
if (result !== 0) return result;
|
|
59
|
+
}
|
|
60
|
+
return 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function compareSemver(left, right) {
|
|
64
|
+
if (!left || !right) return null;
|
|
65
|
+
if (left.major !== right.major) return left.major > right.major ? 1 : -1;
|
|
66
|
+
if (left.minor !== right.minor) return left.minor > right.minor ? 1 : -1;
|
|
67
|
+
if (left.patch !== right.patch) return left.patch > right.patch ? 1 : -1;
|
|
68
|
+
return comparePrerelease(left.prerelease, right.prerelease);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function isDisabledEnv(value) {
|
|
72
|
+
if (value == null) return false;
|
|
73
|
+
const normalized = String(value).trim().toLowerCase();
|
|
74
|
+
return normalized === "0" || normalized === "false" || normalized === "off" || normalized === "no";
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isEnabledEnv(value) {
|
|
78
|
+
if (value == null) return false;
|
|
79
|
+
const normalized = String(value).trim().toLowerCase();
|
|
80
|
+
return normalized === "1" || normalized === "true" || normalized === "on" || normalized === "yes";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function isInteractive({ stdout, stderr } = {}) {
|
|
84
|
+
return Boolean(stdout?.isTTY || stderr?.isTTY);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function shouldCheckForUpdate({ env, stdout, stderr } = {}) {
|
|
88
|
+
const envValue = env?.[UPDATE_CHECK_ENV];
|
|
89
|
+
if (isDisabledEnv(envValue)) return false;
|
|
90
|
+
if (env?.CI && !isEnabledEnv(envValue)) return false;
|
|
91
|
+
if (!isInteractive({ stdout, stderr }) && !isEnabledEnv(envValue)) return false;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function fetchLatestVersion({
|
|
96
|
+
httpsModule = https,
|
|
97
|
+
registryUrl = DEFAULT_REGISTRY_URL,
|
|
98
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
99
|
+
maxBytes = MAX_RESPONSE_BYTES
|
|
100
|
+
} = {}) {
|
|
101
|
+
if (!httpsModule || typeof httpsModule.request !== "function") {
|
|
102
|
+
return Promise.resolve(null);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return new Promise((resolve) => {
|
|
106
|
+
let resolved = false;
|
|
107
|
+
const finish = (value) => {
|
|
108
|
+
if (resolved) return;
|
|
109
|
+
resolved = true;
|
|
110
|
+
resolve(value);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const req = httpsModule.request(
|
|
114
|
+
registryUrl,
|
|
115
|
+
{
|
|
116
|
+
method: "GET",
|
|
117
|
+
headers: {
|
|
118
|
+
"User-Agent": "docdex-update-check",
|
|
119
|
+
Accept: "application/json"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
(res) => {
|
|
123
|
+
if (!res || res.statusCode !== 200) {
|
|
124
|
+
res?.resume?.();
|
|
125
|
+
finish(null);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
res.setEncoding?.("utf8");
|
|
129
|
+
let body = "";
|
|
130
|
+
res.on("data", (chunk) => {
|
|
131
|
+
body += chunk;
|
|
132
|
+
if (body.length > maxBytes) {
|
|
133
|
+
req.destroy?.();
|
|
134
|
+
finish(null);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
res.on("end", () => {
|
|
138
|
+
if (!body) {
|
|
139
|
+
finish(null);
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
try {
|
|
143
|
+
const parsed = JSON.parse(body);
|
|
144
|
+
const version = typeof parsed?.version === "string" ? parsed.version : null;
|
|
145
|
+
finish(version);
|
|
146
|
+
} catch {
|
|
147
|
+
finish(null);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
req.on("error", () => finish(null));
|
|
154
|
+
if (typeof req.setTimeout === "function") {
|
|
155
|
+
req.setTimeout(timeoutMs, () => {
|
|
156
|
+
req.destroy?.();
|
|
157
|
+
finish(null);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
if (typeof req.end === "function") req.end();
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function checkForUpdate({
|
|
165
|
+
currentVersion,
|
|
166
|
+
env = process.env,
|
|
167
|
+
stdout = process.stdout,
|
|
168
|
+
stderr = process.stderr,
|
|
169
|
+
logger = console,
|
|
170
|
+
httpsModule = https,
|
|
171
|
+
registryUrl = DEFAULT_REGISTRY_URL,
|
|
172
|
+
timeoutMs = DEFAULT_TIMEOUT_MS,
|
|
173
|
+
maxBytes = MAX_RESPONSE_BYTES
|
|
174
|
+
} = {}) {
|
|
175
|
+
if (!shouldCheckForUpdate({ env, stdout, stderr })) {
|
|
176
|
+
return { checked: false, updateAvailable: false };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const current = normalizeVersion(currentVersion);
|
|
180
|
+
if (!current) {
|
|
181
|
+
return { checked: true, updateAvailable: false };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const latest = normalizeVersion(
|
|
185
|
+
await fetchLatestVersion({ httpsModule, registryUrl, timeoutMs, maxBytes })
|
|
186
|
+
);
|
|
187
|
+
if (!latest) {
|
|
188
|
+
return { checked: true, updateAvailable: false };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const comparison = compareSemver(parseSemver(current), parseSemver(latest));
|
|
192
|
+
if (comparison == null || comparison >= 0) {
|
|
193
|
+
return { checked: true, updateAvailable: false, latestVersion: latest };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
logger?.log?.(`[docdex] Update available: v${current} -> v${latest}`);
|
|
197
|
+
logger?.log?.("[docdex] Run: npm i -g docdex@latest");
|
|
198
|
+
logger?.log?.(`[docdex] Disable update checks with ${UPDATE_CHECK_ENV}=0`);
|
|
199
|
+
|
|
200
|
+
return { checked: true, updateAvailable: true, latestVersion: latest };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async function checkForUpdateOnce(options) {
|
|
204
|
+
if (hasChecked) return { checked: false, updateAvailable: false };
|
|
205
|
+
hasChecked = true;
|
|
206
|
+
return checkForUpdate(options);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = {
|
|
210
|
+
DEFAULT_REGISTRY_URL,
|
|
211
|
+
DEFAULT_TIMEOUT_MS,
|
|
212
|
+
MAX_RESPONSE_BYTES,
|
|
213
|
+
checkForUpdate,
|
|
214
|
+
checkForUpdateOnce,
|
|
215
|
+
compareSemver,
|
|
216
|
+
parseSemver,
|
|
217
|
+
shouldCheckForUpdate
|
|
218
|
+
};
|