hedgequantx 2.9.226 → 2.9.227
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/package.json +1 -1
- package/src/services/daemon/index.js +212 -5
package/package.json
CHANGED
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
'use strict';
|
|
36
36
|
|
|
37
37
|
const fs = require('fs');
|
|
38
|
-
const
|
|
38
|
+
const path = require('path');
|
|
39
|
+
const { spawn, execSync } = require('child_process');
|
|
39
40
|
const { SOCKET_PATH, PID_FILE, SOCKET_DIR } = require('./constants');
|
|
40
41
|
const { DaemonServer } = require('./server');
|
|
41
42
|
const { DaemonClient, getDaemonClient } = require('./client');
|
|
@@ -43,6 +44,11 @@ const { logger } = require('../../utils/logger');
|
|
|
43
44
|
|
|
44
45
|
const log = logger.scope('Daemon');
|
|
45
46
|
|
|
47
|
+
/** Isolated daemon directory - survives npm updates */
|
|
48
|
+
const DAEMON_INSTALL_DIR = path.join(SOCKET_DIR, 'daemon');
|
|
49
|
+
const DAEMON_ENTRY = path.join(DAEMON_INSTALL_DIR, 'cli-daemon.js');
|
|
50
|
+
const DAEMON_VERSION_FILE = path.join(DAEMON_INSTALL_DIR, 'version.txt');
|
|
51
|
+
|
|
46
52
|
/**
|
|
47
53
|
* Check if daemon is running
|
|
48
54
|
* @returns {boolean}
|
|
@@ -86,6 +92,150 @@ function getDaemonPid() {
|
|
|
86
92
|
}
|
|
87
93
|
}
|
|
88
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Get current package version
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function getCurrentVersion() {
|
|
100
|
+
try {
|
|
101
|
+
const pkg = require('../../../package.json');
|
|
102
|
+
return pkg.version;
|
|
103
|
+
} catch (_) {
|
|
104
|
+
return '0.0.0';
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get installed daemon version
|
|
110
|
+
* @returns {string|null}
|
|
111
|
+
*/
|
|
112
|
+
function getInstalledDaemonVersion() {
|
|
113
|
+
try {
|
|
114
|
+
if (fs.existsSync(DAEMON_VERSION_FILE)) {
|
|
115
|
+
return fs.readFileSync(DAEMON_VERSION_FILE, 'utf8').trim();
|
|
116
|
+
}
|
|
117
|
+
} catch (_) {}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Install daemon to isolated directory
|
|
123
|
+
* This copies all necessary files so daemon survives npm updates
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
function installDaemon() {
|
|
127
|
+
const currentVersion = getCurrentVersion();
|
|
128
|
+
const installedVersion = getInstalledDaemonVersion();
|
|
129
|
+
|
|
130
|
+
// Skip if already installed with same version
|
|
131
|
+
if (installedVersion === currentVersion && fs.existsSync(DAEMON_ENTRY)) {
|
|
132
|
+
log.debug('Daemon already installed', { version: currentVersion });
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
log.info('Installing daemon to isolated directory', { version: currentVersion });
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// Create daemon directory
|
|
140
|
+
if (!fs.existsSync(DAEMON_INSTALL_DIR)) {
|
|
141
|
+
fs.mkdirSync(DAEMON_INSTALL_DIR, { recursive: true, mode: 0o700 });
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Get source directory (where this package is installed)
|
|
145
|
+
const srcDir = path.resolve(__dirname, '../../..');
|
|
146
|
+
|
|
147
|
+
// Files/directories to copy for daemon to work
|
|
148
|
+
const filesToCopy = [
|
|
149
|
+
'src/cli-daemon.js',
|
|
150
|
+
'src/services/daemon',
|
|
151
|
+
'src/services/rithmic',
|
|
152
|
+
'src/services/session.js',
|
|
153
|
+
'src/services/index.js',
|
|
154
|
+
'src/config',
|
|
155
|
+
'src/security',
|
|
156
|
+
'src/utils',
|
|
157
|
+
'package.json',
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
// Copy each file/directory
|
|
161
|
+
for (const file of filesToCopy) {
|
|
162
|
+
const srcPath = path.join(srcDir, file);
|
|
163
|
+
const destPath = path.join(DAEMON_INSTALL_DIR, file);
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(srcPath)) {
|
|
166
|
+
log.warn('Source not found', { file });
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create parent directory
|
|
171
|
+
const destDir = path.dirname(destPath);
|
|
172
|
+
if (!fs.existsSync(destDir)) {
|
|
173
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Copy file or directory
|
|
177
|
+
if (fs.statSync(srcPath).isDirectory()) {
|
|
178
|
+
copyDirSync(srcPath, destPath);
|
|
179
|
+
} else {
|
|
180
|
+
fs.copyFileSync(srcPath, destPath);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Copy node_modules that daemon needs
|
|
185
|
+
const nodeModulesDir = path.join(srcDir, 'node_modules');
|
|
186
|
+
const destNodeModules = path.join(DAEMON_INSTALL_DIR, 'node_modules');
|
|
187
|
+
|
|
188
|
+
// Essential dependencies for daemon
|
|
189
|
+
const deps = ['protobufjs', 'ws', 'long', 'signale', 'chalk', 'figures'];
|
|
190
|
+
|
|
191
|
+
if (!fs.existsSync(destNodeModules)) {
|
|
192
|
+
fs.mkdirSync(destNodeModules, { recursive: true });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (const dep of deps) {
|
|
196
|
+
const srcDep = path.join(nodeModulesDir, dep);
|
|
197
|
+
const destDep = path.join(destNodeModules, dep);
|
|
198
|
+
|
|
199
|
+
if (fs.existsSync(srcDep) && !fs.existsSync(destDep)) {
|
|
200
|
+
copyDirSync(srcDep, destDep);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Write version file
|
|
205
|
+
fs.writeFileSync(DAEMON_VERSION_FILE, currentVersion);
|
|
206
|
+
|
|
207
|
+
log.info('Daemon installed successfully');
|
|
208
|
+
return true;
|
|
209
|
+
} catch (err) {
|
|
210
|
+
log.error('Failed to install daemon', { error: err.message });
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Recursively copy directory
|
|
217
|
+
* @param {string} src
|
|
218
|
+
* @param {string} dest
|
|
219
|
+
*/
|
|
220
|
+
function copyDirSync(src, dest) {
|
|
221
|
+
if (!fs.existsSync(dest)) {
|
|
222
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
226
|
+
|
|
227
|
+
for (const entry of entries) {
|
|
228
|
+
const srcPath = path.join(src, entry.name);
|
|
229
|
+
const destPath = path.join(dest, entry.name);
|
|
230
|
+
|
|
231
|
+
if (entry.isDirectory()) {
|
|
232
|
+
copyDirSync(srcPath, destPath);
|
|
233
|
+
} else {
|
|
234
|
+
fs.copyFileSync(srcPath, destPath);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
89
239
|
/**
|
|
90
240
|
* Start daemon in foreground (blocking)
|
|
91
241
|
* Used when running `hqx --daemon`
|
|
@@ -147,7 +297,7 @@ async function startDaemonForeground() {
|
|
|
147
297
|
}
|
|
148
298
|
|
|
149
299
|
/**
|
|
150
|
-
* Start daemon in background
|
|
300
|
+
* Start daemon in background (from isolated directory)
|
|
151
301
|
* @returns {Promise<boolean>}
|
|
152
302
|
*/
|
|
153
303
|
async function startDaemonBackground() {
|
|
@@ -156,8 +306,60 @@ async function startDaemonBackground() {
|
|
|
156
306
|
return true;
|
|
157
307
|
}
|
|
158
308
|
|
|
309
|
+
// Install daemon to isolated directory first
|
|
310
|
+
const installed = installDaemon();
|
|
311
|
+
if (!installed) {
|
|
312
|
+
log.error('Failed to install daemon');
|
|
313
|
+
return false;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Check if isolated daemon entry exists
|
|
317
|
+
if (!fs.existsSync(DAEMON_ENTRY)) {
|
|
318
|
+
log.error('Daemon entry not found', { path: DAEMON_ENTRY });
|
|
319
|
+
// Fallback to direct execution
|
|
320
|
+
return startDaemonDirect();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return new Promise((resolve) => {
|
|
324
|
+
// Spawn daemon from isolated directory (survives npm updates)
|
|
325
|
+
const daemon = spawn(process.execPath, [DAEMON_ENTRY], {
|
|
326
|
+
detached: true,
|
|
327
|
+
stdio: 'ignore',
|
|
328
|
+
cwd: DAEMON_INSTALL_DIR,
|
|
329
|
+
env: {
|
|
330
|
+
...process.env,
|
|
331
|
+
NODE_PATH: path.join(DAEMON_INSTALL_DIR, 'node_modules'),
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
daemon.unref();
|
|
336
|
+
|
|
337
|
+
// Wait for daemon to start
|
|
338
|
+
let attempts = 0;
|
|
339
|
+
const maxAttempts = 30;
|
|
340
|
+
|
|
341
|
+
const check = setInterval(() => {
|
|
342
|
+
attempts++;
|
|
343
|
+
|
|
344
|
+
if (isDaemonRunning()) {
|
|
345
|
+
clearInterval(check);
|
|
346
|
+
log.debug('Daemon started from isolated directory');
|
|
347
|
+
resolve(true);
|
|
348
|
+
} else if (attempts >= maxAttempts) {
|
|
349
|
+
clearInterval(check);
|
|
350
|
+
log.error('Daemon failed to start');
|
|
351
|
+
resolve(false);
|
|
352
|
+
}
|
|
353
|
+
}, 100);
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Start daemon directly (fallback if isolation fails)
|
|
359
|
+
* @returns {Promise<boolean>}
|
|
360
|
+
*/
|
|
361
|
+
function startDaemonDirect() {
|
|
159
362
|
return new Promise((resolve) => {
|
|
160
|
-
// Spawn daemon process
|
|
161
363
|
const daemon = spawn(process.execPath, [
|
|
162
364
|
require.resolve('../../cli-daemon'),
|
|
163
365
|
], {
|
|
@@ -167,7 +369,6 @@ async function startDaemonBackground() {
|
|
|
167
369
|
|
|
168
370
|
daemon.unref();
|
|
169
371
|
|
|
170
|
-
// Wait for daemon to start
|
|
171
372
|
let attempts = 0;
|
|
172
373
|
const maxAttempts = 20;
|
|
173
374
|
|
|
@@ -176,7 +377,7 @@ async function startDaemonBackground() {
|
|
|
176
377
|
|
|
177
378
|
if (isDaemonRunning()) {
|
|
178
379
|
clearInterval(check);
|
|
179
|
-
log.debug('Daemon started');
|
|
380
|
+
log.debug('Daemon started (direct mode)');
|
|
180
381
|
resolve(true);
|
|
181
382
|
} else if (attempts >= maxAttempts) {
|
|
182
383
|
clearInterval(check);
|
|
@@ -248,6 +449,11 @@ module.exports = {
|
|
|
248
449
|
getDaemonClient,
|
|
249
450
|
ensureDaemon,
|
|
250
451
|
|
|
452
|
+
// Installation
|
|
453
|
+
installDaemon,
|
|
454
|
+
getCurrentVersion,
|
|
455
|
+
getInstalledDaemonVersion,
|
|
456
|
+
|
|
251
457
|
// Utilities
|
|
252
458
|
isDaemonRunning,
|
|
253
459
|
getDaemonPid,
|
|
@@ -255,4 +461,5 @@ module.exports = {
|
|
|
255
461
|
// Constants
|
|
256
462
|
SOCKET_PATH,
|
|
257
463
|
PID_FILE,
|
|
464
|
+
DAEMON_INSTALL_DIR,
|
|
258
465
|
};
|