grepmax 0.12.10 → 0.12.12
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
CHANGED
|
@@ -44,7 +44,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
44
44
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
45
|
exports.watch = void 0;
|
|
46
46
|
const node_child_process_1 = require("node:child_process");
|
|
47
|
-
const fs = __importStar(require("node:fs"));
|
|
48
47
|
const path = __importStar(require("node:path"));
|
|
49
48
|
const commander_1 = require("commander");
|
|
50
49
|
const config_1 = require("../config");
|
|
@@ -80,16 +79,6 @@ exports.watch = new commander_1.Command("watch")
|
|
|
80
79
|
if (options.background) {
|
|
81
80
|
// Skip spawn if daemon already running — prevents process accumulation
|
|
82
81
|
// when SessionStart hook fires on every session/clear/resume
|
|
83
|
-
const pidFile = config_1.PATHS.daemonPidFile;
|
|
84
|
-
try {
|
|
85
|
-
const existingPid = Number.parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
86
|
-
if (existingPid) {
|
|
87
|
-
process.kill(existingPid, 0); // throws if dead
|
|
88
|
-
process.exit(0); // alive — skip
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
catch (_c) { }
|
|
92
|
-
// Also check socket as fallback
|
|
93
82
|
const { isDaemonRunning } = yield Promise.resolve().then(() => __importStar(require("../lib/utils/daemon-client")));
|
|
94
83
|
if (yield isDaemonRunning()) {
|
|
95
84
|
process.exit(0);
|
package/dist/config.js
CHANGED
|
@@ -96,6 +96,7 @@ exports.PATHS = {
|
|
|
96
96
|
logsDir: path.join(GLOBAL_ROOT, "logs"),
|
|
97
97
|
daemonSocket: path.join(GLOBAL_ROOT, "daemon.sock"),
|
|
98
98
|
daemonPidFile: path.join(GLOBAL_ROOT, "daemon.pid"),
|
|
99
|
+
daemonLockFile: path.join(GLOBAL_ROOT, "daemon.lock"),
|
|
99
100
|
// Centralized index storage — one database for all indexed directories
|
|
100
101
|
lancedbDir: path.join(GLOBAL_ROOT, "lancedb"),
|
|
101
102
|
cacheDir: path.join(GLOBAL_ROOT, "cache"),
|
|
@@ -41,12 +41,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
41
41
|
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
42
42
|
});
|
|
43
43
|
};
|
|
44
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
45
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
46
|
+
};
|
|
44
47
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
48
|
exports.Daemon = void 0;
|
|
46
49
|
const fs = __importStar(require("node:fs"));
|
|
47
50
|
const net = __importStar(require("node:net"));
|
|
48
51
|
const path = __importStar(require("node:path"));
|
|
49
52
|
const watcher = __importStar(require("@parcel/watcher"));
|
|
53
|
+
const proper_lockfile_1 = __importDefault(require("proper-lockfile"));
|
|
50
54
|
const config_1 = require("../../config");
|
|
51
55
|
const batch_processor_1 = require("../index/batch-processor");
|
|
52
56
|
const watcher_1 = require("../index/watcher");
|
|
@@ -65,6 +69,7 @@ class Daemon {
|
|
|
65
69
|
this.vectorDb = null;
|
|
66
70
|
this.metaCache = null;
|
|
67
71
|
this.server = null;
|
|
72
|
+
this.releaseLock = null;
|
|
68
73
|
this.lastActivity = Date.now();
|
|
69
74
|
this.startTime = Date.now();
|
|
70
75
|
this.heartbeatInterval = null;
|
|
@@ -75,35 +80,37 @@ class Daemon {
|
|
|
75
80
|
start() {
|
|
76
81
|
return __awaiter(this, void 0, void 0, function* () {
|
|
77
82
|
process.title = "gmax-daemon";
|
|
78
|
-
// 1.
|
|
83
|
+
// 1. Acquire exclusive lock — kernel-enforced, atomic, auto-released on death
|
|
84
|
+
fs.mkdirSync(path.dirname(config_1.PATHS.daemonLockFile), { recursive: true });
|
|
85
|
+
fs.writeFileSync(config_1.PATHS.daemonLockFile, "", { flag: "a" }); // ensure file exists
|
|
86
|
+
try {
|
|
87
|
+
this.releaseLock = yield proper_lockfile_1.default.lock(config_1.PATHS.daemonLockFile, {
|
|
88
|
+
retries: 0,
|
|
89
|
+
stale: 30000,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (err.code === "ELOCKED") {
|
|
94
|
+
console.error("[daemon] Another daemon is already running");
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
throw err;
|
|
98
|
+
}
|
|
99
|
+
// 2. Kill existing per-project watchers
|
|
79
100
|
const existing = (0, watcher_store_1.listWatchers)();
|
|
80
101
|
for (const w of existing) {
|
|
81
102
|
console.log(`[daemon] Taking over from per-project watcher (PID: ${w.pid}, ${path.basename(w.projectRoot)})`);
|
|
82
103
|
yield (0, process_1.killProcess)(w.pid);
|
|
83
104
|
(0, watcher_store_1.unregisterWatcher)(w.pid);
|
|
84
105
|
}
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
// Check if another daemon is alive
|
|
89
|
-
const existingPid = Number.parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
90
|
-
if (existingPid && existingPid !== process.pid) {
|
|
91
|
-
try {
|
|
92
|
-
process.kill(existingPid, 0); // throws if dead
|
|
93
|
-
console.error("[daemon] Another daemon is already running (PID:", existingPid + ")");
|
|
94
|
-
process.exit(0);
|
|
95
|
-
}
|
|
96
|
-
catch (_a) { }
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (_b) { }
|
|
100
|
-
fs.writeFileSync(pidFile, String(process.pid));
|
|
101
|
-
// 3. Stale socket cleanup
|
|
106
|
+
// 3. Write PID file (informational only — lock is the real guard)
|
|
107
|
+
fs.writeFileSync(config_1.PATHS.daemonPidFile, String(process.pid));
|
|
108
|
+
// 4. Stale socket cleanup
|
|
102
109
|
try {
|
|
103
110
|
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
104
111
|
}
|
|
105
|
-
catch (
|
|
106
|
-
//
|
|
112
|
+
catch (_a) { }
|
|
113
|
+
// 5. Open shared resources
|
|
107
114
|
try {
|
|
108
115
|
fs.mkdirSync(config_1.PATHS.cacheDir, { recursive: true });
|
|
109
116
|
fs.mkdirSync(config_1.PATHS.lancedbDir, { recursive: true });
|
|
@@ -114,9 +121,9 @@ class Daemon {
|
|
|
114
121
|
console.error("[daemon] Failed to open shared resources:", err);
|
|
115
122
|
throw err;
|
|
116
123
|
}
|
|
117
|
-
//
|
|
124
|
+
// 6. Register daemon (only after resources are open)
|
|
118
125
|
(0, watcher_store_1.registerDaemon)(process.pid);
|
|
119
|
-
//
|
|
126
|
+
// 7. Subscribe to all registered projects (skip missing directories)
|
|
120
127
|
const projects = (0, project_registry_1.listProjects)().filter((p) => p.status === "indexed");
|
|
121
128
|
for (const p of projects) {
|
|
122
129
|
if (!fs.existsSync(p.root)) {
|
|
@@ -130,18 +137,18 @@ class Daemon {
|
|
|
130
137
|
console.error(`[daemon] Failed to watch ${path.basename(p.root)}:`, err);
|
|
131
138
|
}
|
|
132
139
|
}
|
|
133
|
-
//
|
|
140
|
+
// 8. Heartbeat
|
|
134
141
|
this.heartbeatInterval = setInterval(() => {
|
|
135
142
|
(0, watcher_store_1.heartbeat)(process.pid);
|
|
136
143
|
}, HEARTBEAT_INTERVAL_MS);
|
|
137
|
-
//
|
|
144
|
+
// 9. Idle timeout
|
|
138
145
|
this.idleInterval = setInterval(() => {
|
|
139
146
|
if (Date.now() - this.lastActivity > IDLE_TIMEOUT_MS) {
|
|
140
147
|
console.log("[daemon] Idle for 30 minutes, shutting down");
|
|
141
148
|
this.shutdown();
|
|
142
149
|
}
|
|
143
150
|
}, HEARTBEAT_INTERVAL_MS);
|
|
144
|
-
//
|
|
151
|
+
// 10. Socket server
|
|
145
152
|
this.server = net.createServer((conn) => {
|
|
146
153
|
let buf = "";
|
|
147
154
|
conn.on("data", (chunk) => {
|
|
@@ -171,7 +178,7 @@ class Daemon {
|
|
|
171
178
|
this.server.on("error", (err) => {
|
|
172
179
|
const code = err.code;
|
|
173
180
|
if (code === "EADDRINUSE") {
|
|
174
|
-
console.error("[daemon]
|
|
181
|
+
console.error("[daemon] Socket already in use");
|
|
175
182
|
reject(err);
|
|
176
183
|
}
|
|
177
184
|
else if (code === "EOPNOTSUPP") {
|
|
@@ -301,7 +308,7 @@ class Daemon {
|
|
|
301
308
|
catch (_d) { }
|
|
302
309
|
}
|
|
303
310
|
this.subscriptions.clear();
|
|
304
|
-
// Close server + socket + PID file
|
|
311
|
+
// Close server + socket + PID file + lock
|
|
305
312
|
(_a = this.server) === null || _a === void 0 ? void 0 : _a.close();
|
|
306
313
|
try {
|
|
307
314
|
fs.unlinkSync(config_1.PATHS.daemonSocket);
|
|
@@ -311,6 +318,13 @@ class Daemon {
|
|
|
311
318
|
fs.unlinkSync(config_1.PATHS.daemonPidFile);
|
|
312
319
|
}
|
|
313
320
|
catch (_f) { }
|
|
321
|
+
if (this.releaseLock) {
|
|
322
|
+
try {
|
|
323
|
+
yield this.releaseLock();
|
|
324
|
+
}
|
|
325
|
+
catch (_g) { }
|
|
326
|
+
this.releaseLock = null;
|
|
327
|
+
}
|
|
314
328
|
// Unregister all
|
|
315
329
|
for (const root of this.processors.keys()) {
|
|
316
330
|
(0, watcher_store_1.unregisterWatcherByRoot)(root);
|
|
@@ -321,11 +335,11 @@ class Daemon {
|
|
|
321
335
|
try {
|
|
322
336
|
yield ((_b = this.metaCache) === null || _b === void 0 ? void 0 : _b.close());
|
|
323
337
|
}
|
|
324
|
-
catch (
|
|
338
|
+
catch (_h) { }
|
|
325
339
|
try {
|
|
326
340
|
yield ((_c = this.vectorDb) === null || _c === void 0 ? void 0 : _c.close());
|
|
327
341
|
}
|
|
328
|
-
catch (
|
|
342
|
+
catch (_j) { }
|
|
329
343
|
console.log("[daemon] Shutdown complete");
|
|
330
344
|
});
|
|
331
345
|
}
|
|
@@ -488,8 +488,12 @@ class VectorDB {
|
|
|
488
488
|
(_a = this.unregisterCleanup) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
489
489
|
this.unregisterCleanup = undefined;
|
|
490
490
|
if (this.db) {
|
|
491
|
-
if (this.db.close)
|
|
492
|
-
yield
|
|
491
|
+
if (this.db.close) {
|
|
492
|
+
yield Promise.race([
|
|
493
|
+
this.db.close(),
|
|
494
|
+
new Promise((resolve) => setTimeout(resolve, 5000)),
|
|
495
|
+
]);
|
|
496
|
+
}
|
|
493
497
|
}
|
|
494
498
|
this.db = null;
|
|
495
499
|
});
|
package/dist/lib/utils/exit.js
CHANGED
|
@@ -12,6 +12,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.gracefulExit = gracefulExit;
|
|
13
13
|
const pool_1 = require("../workers/pool");
|
|
14
14
|
const cleanup_1 = require("./cleanup");
|
|
15
|
+
const EXIT_TIMEOUT_MS = 8000;
|
|
15
16
|
function gracefulExit(code) {
|
|
16
17
|
return __awaiter(this, void 0, void 0, function* () {
|
|
17
18
|
const finalCode = typeof code === "number"
|
|
@@ -19,6 +20,12 @@ function gracefulExit(code) {
|
|
|
19
20
|
: typeof process.exitCode === "number"
|
|
20
21
|
? process.exitCode
|
|
21
22
|
: 0;
|
|
23
|
+
// Safety net: force-exit if cleanup hangs
|
|
24
|
+
const forceTimer = !process.env.VITEST && process.env.NODE_ENV !== "test"
|
|
25
|
+
? setTimeout(() => process.exit(finalCode), EXIT_TIMEOUT_MS)
|
|
26
|
+
: undefined;
|
|
27
|
+
if (forceTimer)
|
|
28
|
+
forceTimer.unref();
|
|
22
29
|
try {
|
|
23
30
|
if ((0, pool_1.isWorkerPoolInitialized)()) {
|
|
24
31
|
yield (0, pool_1.destroyWorkerPool)();
|
|
@@ -28,6 +35,8 @@ function gracefulExit(code) {
|
|
|
28
35
|
console.error("[exit] Failed to destroy worker pool:", err);
|
|
29
36
|
}
|
|
30
37
|
yield (0, cleanup_1.runCleanup)();
|
|
38
|
+
if (forceTimer)
|
|
39
|
+
clearTimeout(forceTimer);
|
|
31
40
|
// Avoid exiting the process during test runs so Vitest can report results.
|
|
32
41
|
if (process.env.VITEST || process.env.NODE_ENV === "test") {
|
|
33
42
|
process.exitCode = finalCode;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "grepmax",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.12",
|
|
4
4
|
"author": "Robert Owens <robowens@me.com>",
|
|
5
5
|
"homepage": "https://github.com/reowens/grepmax",
|
|
6
6
|
"bugs": {
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"onnxruntime-node": "1.24.3",
|
|
49
49
|
"ora": "^9.3.0",
|
|
50
50
|
"piscina": "^5.1.4",
|
|
51
|
+
"proper-lockfile": "^4.1.2",
|
|
51
52
|
"simsimd": "^6.5.5",
|
|
52
53
|
"uuid": "^13.0.0",
|
|
53
54
|
"web-tree-sitter": "^0.26.7",
|
|
@@ -57,6 +58,7 @@
|
|
|
57
58
|
"@anthropic-ai/claude-agent-sdk": "^0.2.87",
|
|
58
59
|
"@biomejs/biome": "2.4.10",
|
|
59
60
|
"@types/node": "^25.5.0",
|
|
61
|
+
"@types/proper-lockfile": "^4.1.4",
|
|
60
62
|
"node-gyp": "^12.1.0",
|
|
61
63
|
"ts-node": "^10.9.2",
|
|
62
64
|
"typescript": "^6.0.2",
|