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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hedgequantx",
3
- "version": "2.9.226",
3
+ "version": "2.9.227",
4
4
  "description": "HedgeQuantX - Prop Futures Trading CLI",
5
5
  "main": "src/app.js",
6
6
  "bin": {
@@ -35,7 +35,8 @@
35
35
  'use strict';
36
36
 
37
37
  const fs = require('fs');
38
- const { spawn } = require('child_process');
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
  };