grepmax 0.16.6 → 0.16.10
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/dist/commands/watch.js +35 -6
- package/dist/lib/daemon/daemon.js +29 -26
- package/dist/lib/utils/daemon-client.js +19 -2
- package/dist/lib/utils/watcher-store.js +0 -25
- package/dist/lib/workers/embeddings/mlx-client.js +0 -8
- package/package.json +23 -22
- package/plugins/grepmax/.claude-plugin/plugin.json +1 -1
- package/dist/lib/utils/watcher-registry.js +0 -121
package/dist/commands/watch.js
CHANGED
|
@@ -119,7 +119,6 @@ exports.watch = new commander_1.Command("watch")
|
|
|
119
119
|
process.exit(0);
|
|
120
120
|
}
|
|
121
121
|
// Daemon foreground
|
|
122
|
-
(0, watcher_store_1.migrateFromJson)();
|
|
123
122
|
const { Daemon } = yield Promise.resolve().then(() => __importStar(require("../lib/daemon/daemon")));
|
|
124
123
|
const daemon = new Daemon();
|
|
125
124
|
try {
|
|
@@ -176,8 +175,6 @@ exports.watch = new commander_1.Command("watch")
|
|
|
176
175
|
process.exit(0);
|
|
177
176
|
}
|
|
178
177
|
// --- Per-project foreground mode ---
|
|
179
|
-
// Migrate legacy watchers.json to LMDB on first use
|
|
180
|
-
(0, watcher_store_1.migrateFromJson)();
|
|
181
178
|
// Watcher requires project to be registered
|
|
182
179
|
if (!(0, project_registry_1.getProject)(projectRoot)) {
|
|
183
180
|
console.error(`[watch:${projectName}] Project not registered. Run: gmax add ${projectRoot}`);
|
|
@@ -309,14 +306,28 @@ exports.watch
|
|
|
309
306
|
var _a;
|
|
310
307
|
const { isDaemonRunning, sendDaemonCommand } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
311
308
|
let stoppedDaemon = false;
|
|
309
|
+
let daemonPid;
|
|
312
310
|
// Try shutting down daemon first
|
|
313
311
|
if (yield isDaemonRunning()) {
|
|
312
|
+
// Capture PID before IPC shutdown so the --all loop below can skip the
|
|
313
|
+
// daemon's own watcher-store entries (it registers itself under every
|
|
314
|
+
// watched project's PID — racing killProcess against the in-flight
|
|
315
|
+
// graceful shutdown is what produced "did not exit after SIGKILL"
|
|
316
|
+
// storms and the orphaned pid/sock/lock files this code path used to
|
|
317
|
+
// leave behind).
|
|
318
|
+
try {
|
|
319
|
+
const pidRaw = yield fs.promises.readFile(config_1.PATHS.daemonPidFile, "utf-8");
|
|
320
|
+
const parsed = parseInt(pidRaw.trim(), 10);
|
|
321
|
+
if (Number.isFinite(parsed) && parsed > 0)
|
|
322
|
+
daemonPid = parsed;
|
|
323
|
+
}
|
|
324
|
+
catch (_b) { }
|
|
314
325
|
let parentCmd = "?";
|
|
315
326
|
try {
|
|
316
327
|
const { execSync } = yield Promise.resolve().then(() => __importStar(require("node:child_process")));
|
|
317
328
|
parentCmd = execSync(`ps -o command= -p ${process.ppid}`, { encoding: "utf8" }).trim();
|
|
318
329
|
}
|
|
319
|
-
catch (
|
|
330
|
+
catch (_c) { }
|
|
320
331
|
yield sendDaemonCommand({
|
|
321
332
|
cmd: "shutdown",
|
|
322
333
|
reason: "gmax-watch-stop",
|
|
@@ -325,20 +336,38 @@ exports.watch
|
|
|
325
336
|
from_argv: process.argv.slice(0, 4),
|
|
326
337
|
from_parent_cmd: parentCmd,
|
|
327
338
|
});
|
|
339
|
+
// Wait for the daemon to actually exit before we start killing watcher
|
|
340
|
+
// entries — many of those entries share the daemon's PID. Poll for up
|
|
341
|
+
// to 10s; shutdown work can take a few seconds on large indexes.
|
|
342
|
+
if (daemonPid) {
|
|
343
|
+
for (let i = 0; i < 100; i++) {
|
|
344
|
+
if (!(0, watcher_store_1.isProcessRunning)(daemonPid))
|
|
345
|
+
break;
|
|
346
|
+
yield new Promise((r) => setTimeout(r, 100));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
328
349
|
console.log("Daemon stopped.");
|
|
329
350
|
stoppedDaemon = true;
|
|
330
351
|
}
|
|
331
352
|
if (options.all) {
|
|
332
353
|
const watchers = (0, watcher_store_1.listWatchers)();
|
|
354
|
+
const seenPids = new Set();
|
|
355
|
+
let projectStops = 0;
|
|
333
356
|
for (const w of watchers) {
|
|
357
|
+
if (daemonPid && w.pid === daemonPid)
|
|
358
|
+
continue;
|
|
359
|
+
if (seenPids.has(w.pid))
|
|
360
|
+
continue;
|
|
361
|
+
seenPids.add(w.pid);
|
|
334
362
|
const killed = yield (0, process_1.killProcess)(w.pid);
|
|
335
363
|
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
364
|
+
projectStops++;
|
|
336
365
|
if (!killed) {
|
|
337
366
|
console.warn(`Warning: PID ${w.pid} did not exit after SIGKILL`);
|
|
338
367
|
}
|
|
339
368
|
}
|
|
340
|
-
if (
|
|
341
|
-
console.log(`Stopped ${
|
|
369
|
+
if (projectStops > 0) {
|
|
370
|
+
console.log(`Stopped ${projectStops} per-project watcher(s).`);
|
|
342
371
|
}
|
|
343
372
|
else if (!stoppedDaemon) {
|
|
344
373
|
console.log("No running watchers.");
|
|
@@ -398,11 +398,11 @@ class Daemon {
|
|
|
398
398
|
});
|
|
399
399
|
this.processors.set(root, processor);
|
|
400
400
|
// Subscribe with @parcel/watcher — native backend, no polling.
|
|
401
|
-
// If the kernel refuses (e.g. FSEvents slots stuck after a prior kill -9
|
|
402
|
-
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
//
|
|
401
|
+
// If the kernel refuses (e.g. FSEvents slots stuck after a prior kill -9),
|
|
402
|
+
// fall straight through to poll mode. The retry/backoff path inside
|
|
403
|
+
// recoverWatcher is for transient overflows, not hard kernel-level
|
|
404
|
+
// subscribe failures, so we skip it on startup by priming failCount past
|
|
405
|
+
// MAX before invoking it.
|
|
406
406
|
try {
|
|
407
407
|
yield this.subscribeWatcher(root, processor);
|
|
408
408
|
}
|
|
@@ -1310,6 +1310,24 @@ class Daemon {
|
|
|
1310
1310
|
return;
|
|
1311
1311
|
this.shuttingDown = true;
|
|
1312
1312
|
console.log("[daemon] Shutting down...");
|
|
1313
|
+
// Drop external liveness markers FIRST so the next daemon start isn't
|
|
1314
|
+
// fooled by leftover state if the long cleanup below is interrupted
|
|
1315
|
+
// (uncaught exception, second SIGTERM, OOM kill mid-shutdown). The
|
|
1316
|
+
// fresh-lock check in isDaemonHeartbeatFresh keyed on these — orphans
|
|
1317
|
+
// here used to cause silent no-op spawns for up to 150s.
|
|
1318
|
+
try {
|
|
1319
|
+
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
1320
|
+
}
|
|
1321
|
+
catch (_e) { }
|
|
1322
|
+
try {
|
|
1323
|
+
fs.unlinkSync(config_1.PATHS.daemonPidFile);
|
|
1324
|
+
}
|
|
1325
|
+
catch (_f) { }
|
|
1326
|
+
if (this.releaseLock) {
|
|
1327
|
+
const release = this.releaseLock;
|
|
1328
|
+
this.releaseLock = null;
|
|
1329
|
+
release().catch(() => { });
|
|
1330
|
+
}
|
|
1313
1331
|
if (this.heartbeatInterval)
|
|
1314
1332
|
clearInterval(this.heartbeatInterval);
|
|
1315
1333
|
if (this.idleInterval)
|
|
@@ -1332,7 +1350,7 @@ class Daemon {
|
|
|
1332
1350
|
try {
|
|
1333
1351
|
yield ((_a = this.llmServer) === null || _a === void 0 ? void 0 : _a.stop());
|
|
1334
1352
|
}
|
|
1335
|
-
catch (
|
|
1353
|
+
catch (_g) { }
|
|
1336
1354
|
// Stop MLX embed server if we started it
|
|
1337
1355
|
this.stopMlxServer();
|
|
1338
1356
|
// Destroy worker pool to prevent orphaned child processes
|
|
@@ -1340,7 +1358,7 @@ class Daemon {
|
|
|
1340
1358
|
try {
|
|
1341
1359
|
yield (0, pool_1.destroyWorkerPool)();
|
|
1342
1360
|
}
|
|
1343
|
-
catch (
|
|
1361
|
+
catch (_h) { }
|
|
1344
1362
|
}
|
|
1345
1363
|
// Stop poll intervals + their FSEvents recovery probes
|
|
1346
1364
|
for (const interval of this.pollIntervals.values()) {
|
|
@@ -1356,26 +1374,11 @@ class Daemon {
|
|
|
1356
1374
|
try {
|
|
1357
1375
|
yield sub.unsubscribe();
|
|
1358
1376
|
}
|
|
1359
|
-
catch (
|
|
1377
|
+
catch (_j) { }
|
|
1360
1378
|
}
|
|
1361
1379
|
this.subscriptions.clear();
|
|
1362
|
-
// Close server
|
|
1380
|
+
// Close server (socket/pid/lock already dropped at the top of shutdown)
|
|
1363
1381
|
(_b = this.server) === null || _b === void 0 ? void 0 : _b.close();
|
|
1364
|
-
try {
|
|
1365
|
-
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
1366
|
-
}
|
|
1367
|
-
catch (_h) { }
|
|
1368
|
-
try {
|
|
1369
|
-
fs.unlinkSync(config_1.PATHS.daemonPidFile);
|
|
1370
|
-
}
|
|
1371
|
-
catch (_j) { }
|
|
1372
|
-
if (this.releaseLock) {
|
|
1373
|
-
try {
|
|
1374
|
-
yield this.releaseLock();
|
|
1375
|
-
}
|
|
1376
|
-
catch (_k) { }
|
|
1377
|
-
this.releaseLock = null;
|
|
1378
|
-
}
|
|
1379
1382
|
// Unregister all
|
|
1380
1383
|
for (const root of this.processors.keys()) {
|
|
1381
1384
|
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
@@ -1386,11 +1389,11 @@ class Daemon {
|
|
|
1386
1389
|
try {
|
|
1387
1390
|
yield ((_c = this.metaCache) === null || _c === void 0 ? void 0 : _c.close());
|
|
1388
1391
|
}
|
|
1389
|
-
catch (
|
|
1392
|
+
catch (_k) { }
|
|
1390
1393
|
try {
|
|
1391
1394
|
yield ((_d = this.vectorDb) === null || _d === void 0 ? void 0 : _d.close());
|
|
1392
1395
|
}
|
|
1393
|
-
catch (
|
|
1396
|
+
catch (_l) { }
|
|
1394
1397
|
console.log("[daemon] Shutdown complete");
|
|
1395
1398
|
});
|
|
1396
1399
|
}
|
|
@@ -122,13 +122,30 @@ function isDaemonRunning(opts) {
|
|
|
122
122
|
* Lock-file-based liveness probe. A running daemon refreshes daemon.lock's
|
|
123
123
|
* mtime every 60s via its heartbeat loop; a fresh mtime means the daemon is
|
|
124
124
|
* alive even if its socket ping times out under load.
|
|
125
|
+
*
|
|
126
|
+
* A fresh mtime alone is not proof of life — a SIGKILL'd (or OOM-killed,
|
|
127
|
+
* panicked, power-lost) daemon leaves the lock file behind with its last
|
|
128
|
+
* heartbeat mtime, fooling this check for up to HEARTBEAT_FRESH_THRESHOLD_MS.
|
|
129
|
+
* Cross-check that the PID file points at an actually-running process.
|
|
125
130
|
*/
|
|
126
131
|
function isDaemonHeartbeatFresh() {
|
|
127
132
|
try {
|
|
128
133
|
const stats = fs.statSync(config_1.PATHS.daemonLockFile);
|
|
129
|
-
|
|
134
|
+
if (Date.now() - stats.mtimeMs >= HEARTBEAT_FRESH_THRESHOLD_MS)
|
|
135
|
+
return false;
|
|
136
|
+
const pidRaw = fs.readFileSync(config_1.PATHS.daemonPidFile, "utf-8").trim();
|
|
137
|
+
const pid = parseInt(pidRaw, 10);
|
|
138
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
139
|
+
return false;
|
|
140
|
+
try {
|
|
141
|
+
process.kill(pid, 0);
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
catch (_a) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
130
147
|
}
|
|
131
|
-
catch (
|
|
148
|
+
catch (_b) {
|
|
132
149
|
return false;
|
|
133
150
|
}
|
|
134
151
|
}
|
|
@@ -49,7 +49,6 @@ exports.unregisterWatcher = unregisterWatcher;
|
|
|
49
49
|
exports.getWatcherForProject = getWatcherForProject;
|
|
50
50
|
exports.getWatcherCoveringPath = getWatcherCoveringPath;
|
|
51
51
|
exports.listWatchers = listWatchers;
|
|
52
|
-
exports.migrateFromJson = migrateFromJson;
|
|
53
52
|
exports.registerDaemon = registerDaemon;
|
|
54
53
|
exports.unregisterDaemon = unregisterDaemon;
|
|
55
54
|
exports.getDaemonInfo = getDaemonInfo;
|
|
@@ -173,30 +172,6 @@ function listWatchers() {
|
|
|
173
172
|
}
|
|
174
173
|
return alive;
|
|
175
174
|
}
|
|
176
|
-
/**
|
|
177
|
-
* Migrate from legacy watchers.json if it exists.
|
|
178
|
-
* Call once on startup.
|
|
179
|
-
*/
|
|
180
|
-
function migrateFromJson() {
|
|
181
|
-
const jsonPath = path.join(config_1.PATHS.globalRoot, "watchers.json");
|
|
182
|
-
if (!fs.existsSync(jsonPath))
|
|
183
|
-
return;
|
|
184
|
-
try {
|
|
185
|
-
const raw = fs.readFileSync(jsonPath, "utf-8");
|
|
186
|
-
const entries = JSON.parse(raw);
|
|
187
|
-
const db = getDb();
|
|
188
|
-
for (const entry of entries) {
|
|
189
|
-
if (entry.projectRoot && isProcessRunning(entry.pid)) {
|
|
190
|
-
db.put(entry.projectRoot, Object.assign(Object.assign({}, entry), { lastHeartbeat: Date.now() }));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
// Remove legacy file
|
|
194
|
-
fs.unlinkSync(jsonPath);
|
|
195
|
-
}
|
|
196
|
-
catch (_a) {
|
|
197
|
-
// Best effort — ignore
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
175
|
// --- Daemon registry ---
|
|
201
176
|
exports.DAEMON_KEY = "__daemon__";
|
|
202
177
|
function registerDaemon(pid) {
|
|
@@ -49,7 +49,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
49
49
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
50
50
|
exports.isMlxUp = isMlxUp;
|
|
51
51
|
exports.mlxEmbed = mlxEmbed;
|
|
52
|
-
exports.resetMlxCache = resetMlxCache;
|
|
53
52
|
const http = __importStar(require("node:http"));
|
|
54
53
|
const logger_1 = require("../../utils/logger");
|
|
55
54
|
const MLX_PORT = parseInt(process.env.MLX_EMBED_PORT || "8100", 10);
|
|
@@ -192,10 +191,3 @@ function mlxEmbed(texts) {
|
|
|
192
191
|
return data.vectors.map((v) => new Float32Array(v));
|
|
193
192
|
});
|
|
194
193
|
}
|
|
195
|
-
/**
|
|
196
|
-
* Reset availability cache (e.g., after starting the server).
|
|
197
|
-
*/
|
|
198
|
-
function resetMlxCache() {
|
|
199
|
-
mlxAvailable = null;
|
|
200
|
-
lastCheck = 0;
|
|
201
|
-
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.10",
|
|
4
4
|
"author": "Robert Owens <78518764+reowens@users.noreply.github.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -14,6 +14,27 @@
|
|
|
14
14
|
"bin": {
|
|
15
15
|
"gmax": "dist/index.js"
|
|
16
16
|
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node scripts/postinstall.js",
|
|
19
|
+
"prebuild": "mkdir -p dist",
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"postbuild": "chmod +x dist/index.js",
|
|
22
|
+
"dev": "npx tsc && node dist/index.js",
|
|
23
|
+
"test": "vitest run",
|
|
24
|
+
"test:watch": "vitest",
|
|
25
|
+
"benchmark": "./run-benchmark.sh",
|
|
26
|
+
"benchmark:index": "./run-benchmark.sh $HOME/gmax-benchmarks --index",
|
|
27
|
+
"benchmark:agent": "npx tsx src/bench/benchmark-agent.ts",
|
|
28
|
+
"benchmark:chart": "npx tsx src/bench/generate-benchmark-chart.ts",
|
|
29
|
+
"format": "biome check --write .",
|
|
30
|
+
"format:check": "biome check .",
|
|
31
|
+
"lint": "biome lint .",
|
|
32
|
+
"typecheck": "tsc --noEmit",
|
|
33
|
+
"prepublishOnly": "pnpm build",
|
|
34
|
+
"preversion": "pnpm test && pnpm typecheck",
|
|
35
|
+
"version": "bash scripts/sync-versions.sh && git add -A",
|
|
36
|
+
"postversion": "git push origin main && git push origin v$npm_package_version && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --branch v$npm_package_version --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
|
|
37
|
+
},
|
|
17
38
|
"keywords": [
|
|
18
39
|
"grepmax",
|
|
19
40
|
"grep",
|
|
@@ -65,25 +86,5 @@
|
|
|
65
86
|
"typescript": "^6.0.2",
|
|
66
87
|
"vite": "^8.0.3",
|
|
67
88
|
"vitest": "^4.1.2"
|
|
68
|
-
},
|
|
69
|
-
"scripts": {
|
|
70
|
-
"postinstall": "node scripts/postinstall.js",
|
|
71
|
-
"prebuild": "mkdir -p dist",
|
|
72
|
-
"build": "tsc",
|
|
73
|
-
"postbuild": "chmod +x dist/index.js",
|
|
74
|
-
"dev": "npx tsc && node dist/index.js",
|
|
75
|
-
"test": "vitest run",
|
|
76
|
-
"test:watch": "vitest",
|
|
77
|
-
"benchmark": "./run-benchmark.sh",
|
|
78
|
-
"benchmark:index": "./run-benchmark.sh $HOME/gmax-benchmarks --index",
|
|
79
|
-
"benchmark:agent": "npx tsx src/bench/benchmark-agent.ts",
|
|
80
|
-
"benchmark:chart": "npx tsx src/bench/generate-benchmark-chart.ts",
|
|
81
|
-
"format": "biome check --write .",
|
|
82
|
-
"format:check": "biome check .",
|
|
83
|
-
"lint": "biome lint .",
|
|
84
|
-
"typecheck": "tsc --noEmit",
|
|
85
|
-
"preversion": "pnpm test && pnpm typecheck",
|
|
86
|
-
"version": "bash scripts/sync-versions.sh && git add -A",
|
|
87
|
-
"postversion": "git push origin main && git push origin v$npm_package_version && gh release create v$npm_package_version --generate-notes --title v$npm_package_version && sleep 5 && gh run watch $(gh run list --workflow=release.yml --branch v$npm_package_version --limit 1 --json databaseId --jq '.[0].databaseId') --exit-status && sleep 30 && npm cache clean --force && npm install -g grepmax@$npm_package_version"
|
|
88
89
|
}
|
|
89
|
-
}
|
|
90
|
+
}
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Watcher registry — tracks background watcher processes per project.
|
|
4
|
-
* Ensures only one watcher runs per project root.
|
|
5
|
-
*
|
|
6
|
-
* Stored in ~/.gmax/watchers.json
|
|
7
|
-
*/
|
|
8
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
-
if (k2 === undefined) k2 = k;
|
|
10
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
-
}
|
|
14
|
-
Object.defineProperty(o, k2, desc);
|
|
15
|
-
}) : (function(o, m, k, k2) {
|
|
16
|
-
if (k2 === undefined) k2 = k;
|
|
17
|
-
o[k2] = m[k];
|
|
18
|
-
}));
|
|
19
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
-
}) : function(o, v) {
|
|
22
|
-
o["default"] = v;
|
|
23
|
-
});
|
|
24
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
-
var ownKeys = function(o) {
|
|
26
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
-
var ar = [];
|
|
28
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
-
return ar;
|
|
30
|
-
};
|
|
31
|
-
return ownKeys(o);
|
|
32
|
-
};
|
|
33
|
-
return function (mod) {
|
|
34
|
-
if (mod && mod.__esModule) return mod;
|
|
35
|
-
var result = {};
|
|
36
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
-
__setModuleDefault(result, mod);
|
|
38
|
-
return result;
|
|
39
|
-
};
|
|
40
|
-
})();
|
|
41
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.isProcessRunning = isProcessRunning;
|
|
43
|
-
exports.registerWatcher = registerWatcher;
|
|
44
|
-
exports.updateWatcherStatus = updateWatcherStatus;
|
|
45
|
-
exports.unregisterWatcher = unregisterWatcher;
|
|
46
|
-
exports.getWatcherForProject = getWatcherForProject;
|
|
47
|
-
exports.getWatcherCoveringPath = getWatcherCoveringPath;
|
|
48
|
-
exports.listWatchers = listWatchers;
|
|
49
|
-
const fs = __importStar(require("node:fs"));
|
|
50
|
-
const path = __importStar(require("node:path"));
|
|
51
|
-
const config_1 = require("../../config");
|
|
52
|
-
const REGISTRY_PATH = path.join(config_1.PATHS.globalRoot, "watchers.json");
|
|
53
|
-
function loadRegistry() {
|
|
54
|
-
try {
|
|
55
|
-
const raw = fs.readFileSync(REGISTRY_PATH, "utf-8");
|
|
56
|
-
return JSON.parse(raw);
|
|
57
|
-
}
|
|
58
|
-
catch (_a) {
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
function saveRegistry(entries) {
|
|
63
|
-
fs.mkdirSync(path.dirname(REGISTRY_PATH), { recursive: true });
|
|
64
|
-
fs.writeFileSync(REGISTRY_PATH, `${JSON.stringify(entries, null, 2)}\n`);
|
|
65
|
-
}
|
|
66
|
-
function isProcessRunning(pid) {
|
|
67
|
-
try {
|
|
68
|
-
process.kill(pid, 0);
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
catch (_a) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
function registerWatcher(info) {
|
|
76
|
-
const entries = loadRegistry().filter((e) => e.projectRoot !== info.projectRoot);
|
|
77
|
-
entries.push(info);
|
|
78
|
-
saveRegistry(entries);
|
|
79
|
-
}
|
|
80
|
-
function updateWatcherStatus(pid, status, lastReindex) {
|
|
81
|
-
const entries = loadRegistry();
|
|
82
|
-
const match = entries.find((e) => e.pid === pid);
|
|
83
|
-
if (match) {
|
|
84
|
-
match.status = status;
|
|
85
|
-
if (lastReindex)
|
|
86
|
-
match.lastReindex = lastReindex;
|
|
87
|
-
saveRegistry(entries);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function unregisterWatcher(pid) {
|
|
91
|
-
const entries = loadRegistry().filter((e) => e.pid !== pid);
|
|
92
|
-
saveRegistry(entries);
|
|
93
|
-
}
|
|
94
|
-
function getWatcherForProject(projectRoot) {
|
|
95
|
-
const entries = loadRegistry();
|
|
96
|
-
const match = entries.find((e) => e.projectRoot === projectRoot);
|
|
97
|
-
if (match && isProcessRunning(match.pid))
|
|
98
|
-
return match;
|
|
99
|
-
// Clean stale entry
|
|
100
|
-
if (match) {
|
|
101
|
-
saveRegistry(entries.filter((e) => e.pid !== match.pid));
|
|
102
|
-
}
|
|
103
|
-
return undefined;
|
|
104
|
-
}
|
|
105
|
-
function getWatcherCoveringPath(dir) {
|
|
106
|
-
const resolved = path.resolve(dir);
|
|
107
|
-
const entries = loadRegistry();
|
|
108
|
-
for (const e of entries) {
|
|
109
|
-
if (resolved.startsWith(e.projectRoot) && isProcessRunning(e.pid))
|
|
110
|
-
return e;
|
|
111
|
-
}
|
|
112
|
-
return undefined;
|
|
113
|
-
}
|
|
114
|
-
function listWatchers() {
|
|
115
|
-
const entries = loadRegistry();
|
|
116
|
-
const active = entries.filter((e) => isProcessRunning(e.pid));
|
|
117
|
-
if (active.length !== entries.length) {
|
|
118
|
-
saveRegistry(active);
|
|
119
|
-
}
|
|
120
|
-
return active;
|
|
121
|
-
}
|