dexbot 1.4.15 → 1.4.16
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/README.md +21 -15
- package/claw/data/positions.json +2035 -1
- package/claw/modules/chain_broadcast.ts +4 -5
- package/claw/modules/credit_runtime_adapter.ts +6 -7
- package/claw/modules/dexbot_profiles.ts +4 -1
- package/claw/modules/launcher_paths.ts +4 -1
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/credential-daemon.js +7 -7
- package/dist/credential-daemon.js.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.d.ts.map +1 -1
- package/dist/market_adapter/core/market_adapter_service.js +7 -1
- package/dist/market_adapter/core/market_adapter_service.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1 -2
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +2 -3
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/market_adapter.d.ts.map +1 -1
- package/dist/market_adapter/market_adapter.js +1 -0
- package/dist/market_adapter/market_adapter.js.map +1 -1
- package/dist/market_adapter/utils/data_discovery.js +2 -2
- package/dist/market_adapter/utils/data_discovery.js.map +1 -1
- package/dist/modules/chain_keys.d.ts.map +1 -1
- package/dist/modules/chain_keys.js +7 -1
- package/dist/modules/chain_keys.js.map +1 -1
- package/dist/modules/credential_policy.js +1 -1
- package/dist/modules/credential_policy.js.map +1 -1
- package/dist/modules/credential_runtime.d.ts +0 -3
- package/dist/modules/credential_runtime.d.ts.map +1 -1
- package/dist/modules/credential_runtime.js +5 -27
- package/dist/modules/credential_runtime.js.map +1 -1
- package/dist/modules/key_store.js +1 -1
- package/dist/modules/key_store.js.map +1 -1
- package/dist/modules/launcher/credential_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/credential_daemon.js +3 -3
- package/dist/modules/launcher/credential_daemon.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +2 -2
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +11 -1
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/paths.d.ts +42 -13
- package/dist/modules/paths.d.ts.map +1 -1
- package/dist/modules/paths.js +140 -30
- package/dist/modules/paths.js.map +1 -1
- package/dist/pm2.js +3 -3
- package/dist/pm2.js.map +1 -1
- package/dist/scripts/update.d.ts +14 -5
- package/dist/scripts/update.d.ts.map +1 -1
- package/dist/scripts/update.js +335 -172
- package/dist/scripts/update.js.map +1 -1
- package/package.json +3 -3
- package/scripts/lib/dexbot-paths.sh +71 -20
package/dist/scripts/update.js
CHANGED
|
@@ -2,11 +2,20 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* DEXBot2 Auto-Update Script
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* Supports two installation layouts:
|
|
6
|
+
*
|
|
7
|
+
* 1. Git checkout:
|
|
8
|
+
* - Fetches from configured remote repository
|
|
9
|
+
* - Detects if updates are available
|
|
10
|
+
* - Handles branch switching if needed
|
|
11
|
+
* - Reinstalls npm dependencies and rebuilds TypeScript sources
|
|
12
|
+
*
|
|
13
|
+
* 2. npm package install (e.g. `npm install -g dexbot`):
|
|
14
|
+
* - Compares the installed version against the npm registry
|
|
15
|
+
* - Runs `npm install -g <pkg>@<latest>` to fetch the newest release
|
|
16
|
+
* - Skips the git and local build steps (published packages are pre-built)
|
|
17
|
+
*
|
|
18
|
+
* Shared tail for both layouts:
|
|
10
19
|
* - Selectively restarts active runtime processes
|
|
11
20
|
* - Gracefully handles missing files or PM2
|
|
12
21
|
*
|
|
@@ -23,11 +32,12 @@
|
|
|
23
32
|
import { execSync } from 'node:child_process';
|
|
24
33
|
import path from 'node:path';
|
|
25
34
|
import fs from 'node:fs';
|
|
35
|
+
import { homedir } from 'node:os';
|
|
26
36
|
import { sendControlCommand } from '../modules/launcher/supervisor_control.js';
|
|
27
37
|
// Import update configuration from constants
|
|
28
38
|
// Contains: REPOSITORY_URL, BRANCH, BUILD_DIR settings
|
|
29
39
|
import { UPDATER, BUILD_DIR } from '../modules/constants.js';
|
|
30
|
-
import { PATHS } from '../modules/paths.js';
|
|
40
|
+
import { PATHS, isGlobalNpmPackageDir } from '../modules/paths.js';
|
|
31
41
|
import { Config } from '../modules/config.js';
|
|
32
42
|
import { getStorage } from '../modules/storage/index.js';
|
|
33
43
|
const { readJSON } = getStorage();
|
|
@@ -60,24 +70,101 @@ function updateError(msg) {
|
|
|
60
70
|
return colorUpdateOutput(msg, UPDATE_COLORS.error, process.stderr);
|
|
61
71
|
}
|
|
62
72
|
/**
|
|
63
|
-
*
|
|
73
|
+
* runIn: Execute shell command in a given working directory with error handling
|
|
64
74
|
*
|
|
65
75
|
* Runs command with inherited stdio so user sees full output.
|
|
66
76
|
* Throws error if command fails, breaking update process.
|
|
67
77
|
*
|
|
78
|
+
* @param {string} cwd - Working directory for the command
|
|
68
79
|
* @param {string} cmd - Shell command to execute
|
|
69
80
|
* @throws {Error} If command exits with non-zero status
|
|
70
81
|
*/
|
|
71
|
-
function
|
|
82
|
+
function runIn(cwd, cmd) {
|
|
72
83
|
log(`Executing: ${cmd}`);
|
|
73
84
|
try {
|
|
74
|
-
execSync(cmd, { stdio: 'inherit', cwd
|
|
85
|
+
execSync(cmd, { stdio: 'inherit', cwd });
|
|
75
86
|
}
|
|
76
87
|
catch (err) {
|
|
77
88
|
console.error(updateError(`[ERROR] Command failed: ${cmd}`));
|
|
78
89
|
throw err;
|
|
79
90
|
}
|
|
80
91
|
}
|
|
92
|
+
/**
|
|
93
|
+
* run: Execute shell command in the project root with error handling
|
|
94
|
+
*
|
|
95
|
+
* Runs command with inherited stdio so user sees full output.
|
|
96
|
+
* Throws error if command fails, breaking update process.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} cmd - Shell command to execute
|
|
99
|
+
* @throws {Error} If command exits with non-zero status
|
|
100
|
+
*/
|
|
101
|
+
function run(cmd) {
|
|
102
|
+
runIn(PATHS.PROJECT_ROOT, cmd);
|
|
103
|
+
}
|
|
104
|
+
// ── npm-install helpers ──────────────────────────────────────────────
|
|
105
|
+
function readPackageJson(root) {
|
|
106
|
+
try {
|
|
107
|
+
return readJSON(path.join(root, 'package.json'));
|
|
108
|
+
}
|
|
109
|
+
catch (_) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function readPackageVersion(root) {
|
|
114
|
+
const pkg = readPackageJson(root);
|
|
115
|
+
return typeof pkg?.version === 'string' ? pkg.version : '';
|
|
116
|
+
}
|
|
117
|
+
function readPackageName(root) {
|
|
118
|
+
const pkg = readPackageJson(root);
|
|
119
|
+
return typeof pkg?.name === 'string' && pkg.name ? pkg.name : 'dexbot';
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* compareVersions: Minimal semver comparison for major.minor.patch strings.
|
|
123
|
+
*
|
|
124
|
+
* Returns a negative number when a < b, zero when equal, positive when a > b.
|
|
125
|
+
* Prerelease/build suffixes are ignored (sufficient for update detection).
|
|
126
|
+
*/
|
|
127
|
+
function compareVersions(a, b) {
|
|
128
|
+
const na = a.split('.').map((n) => parseInt(n, 10) || 0);
|
|
129
|
+
const nb = b.split('.').map((n) => parseInt(n, 10) || 0);
|
|
130
|
+
const len = Math.max(na.length, nb.length);
|
|
131
|
+
for (let i = 0; i < len; i++) {
|
|
132
|
+
const va = na[i] ?? 0;
|
|
133
|
+
const vb = nb[i] ?? 0;
|
|
134
|
+
if (va !== vb)
|
|
135
|
+
return va < vb ? -1 : 1;
|
|
136
|
+
}
|
|
137
|
+
return 0;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* getNpmLatestVersion: Query the npm registry for the latest published version.
|
|
141
|
+
*
|
|
142
|
+
* Throws when the registry is unreachable so the update is treated as failed
|
|
143
|
+
* (exit 1) and the scheduler does not restart the daemon for a no-op.
|
|
144
|
+
*/
|
|
145
|
+
function getNpmLatestVersion(pkgName) {
|
|
146
|
+
try {
|
|
147
|
+
const version = execSync(`npm view ${pkgName} version`, { stdio: 'pipe' }).toString().trim();
|
|
148
|
+
if (!version)
|
|
149
|
+
throw new Error('npm returned an empty version.');
|
|
150
|
+
return version;
|
|
151
|
+
}
|
|
152
|
+
catch (err) {
|
|
153
|
+
throw new Error(`Could not reach the npm registry to check for updates (${getErrorMessage(err)}).`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* getGlobalNpmRoot: Resolve the npm global install root (e.g. <prefix>/lib/node_modules).
|
|
158
|
+
* Returns an empty string when npm is unavailable.
|
|
159
|
+
*/
|
|
160
|
+
function getGlobalNpmRoot() {
|
|
161
|
+
try {
|
|
162
|
+
return execSync('npm root -g', { stdio: 'pipe' }).toString().trim();
|
|
163
|
+
}
|
|
164
|
+
catch (_) {
|
|
165
|
+
return '';
|
|
166
|
+
}
|
|
167
|
+
}
|
|
81
168
|
function readLivePidFile(filePath) {
|
|
82
169
|
if (!fs.existsSync(filePath))
|
|
83
170
|
return 0;
|
|
@@ -236,21 +323,244 @@ async function restartActiveIsolatedProcesses() {
|
|
|
236
323
|
await sendControlCommand({ cmd: 'restart-running' });
|
|
237
324
|
return true;
|
|
238
325
|
}
|
|
326
|
+
// ── Shared update tail (both git and npm layouts) ────────────────────
|
|
327
|
+
/**
|
|
328
|
+
* Snapshot pre-update runtime state BEFORE touching code (git pull or
|
|
329
|
+
* `npm install -g`). The daemon may shut down during the operation (e.g.
|
|
330
|
+
* a prepare hook or build), erasing its PID file. By capturing state up
|
|
331
|
+
* front, we can still restart it after the update completes.
|
|
332
|
+
*/
|
|
333
|
+
function snapshotMonolithicState() {
|
|
334
|
+
const monolithicWasRunning = !!detectMonolithicRuntime();
|
|
335
|
+
const hadMonolithicFiles = detectAnyMonolithicFiles();
|
|
336
|
+
if (monolithicWasRunning) {
|
|
337
|
+
log('Monolithic daemon detected running before update. Will restart after update.');
|
|
338
|
+
}
|
|
339
|
+
else if (hadMonolithicFiles) {
|
|
340
|
+
log('Monolithic PID/file artifacts found but daemon is not alive. Will attempt restart after update.');
|
|
341
|
+
}
|
|
342
|
+
return { monolithicWasRunning, hadMonolithicFiles };
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Regenerate the PM2 ecosystem config so profiles/ecosystem.config.cjs
|
|
346
|
+
* reflects the current bots.json state (including dexbot-adapter and
|
|
347
|
+
* dexbot-update service apps). Uses the freshly compiled dist/pm2.js.
|
|
348
|
+
*/
|
|
349
|
+
async function regenerateEcosystemConfig() {
|
|
350
|
+
log('Regenerating PM2 ecosystem config...');
|
|
351
|
+
try {
|
|
352
|
+
// Try loading from compiled dist/ first, then fall back to source dir
|
|
353
|
+
const distPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
|
|
354
|
+
const pm2Module = fs.existsSync(distPath)
|
|
355
|
+
? await import(distPath)
|
|
356
|
+
: await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
|
|
357
|
+
pm2Module.generateEcosystemConfig({ clawOnly: false, exitOnError: false });
|
|
358
|
+
log('Ecosystem config regenerated successfully.');
|
|
359
|
+
}
|
|
360
|
+
catch (err) {
|
|
361
|
+
log(`Warning: Ecosystem config regeneration failed (${getErrorMessage(err)}). Continuing with existing config.`);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Restart active runtime processes after an update so the new code is picked
|
|
366
|
+
* up. Handles, in order of preference:
|
|
367
|
+
* - Monolithic daemon (SIGUSR2 restart)
|
|
368
|
+
* - Isolated supervisor (control-command restart)
|
|
369
|
+
* - PM2 selective restart from bots.json
|
|
370
|
+
* - Fallback auto-start of a monolithic daemon that died during the update
|
|
371
|
+
* Never touches dexbot-cred through bulk PM2 actions.
|
|
372
|
+
*/
|
|
373
|
+
async function restartActiveRuntimes({ monolithicWasRunning, hadMonolithicFiles }) {
|
|
374
|
+
let restarted = false;
|
|
375
|
+
log('Restarting active runtime processes...');
|
|
376
|
+
try {
|
|
377
|
+
if (Config.DEXBOT_UPDATE_SKIP_RELOAD) {
|
|
378
|
+
log('Restart skipped (managed by launcher).');
|
|
379
|
+
restarted = true;
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
const monolithic = detectMonolithicRuntime();
|
|
383
|
+
if (monolithic) {
|
|
384
|
+
restartMonolithicRuntime(monolithic);
|
|
385
|
+
restarted = true;
|
|
386
|
+
}
|
|
387
|
+
else if (await restartActiveIsolatedProcesses()) {
|
|
388
|
+
log('Isolated supervisor runtime restarted.');
|
|
389
|
+
restarted = true;
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const BOTS_FILE = PATHS.PROFILES.BOTS_JSON;
|
|
393
|
+
if (fs.existsSync(BOTS_FILE)) {
|
|
394
|
+
const raw = fs.readFileSync(BOTS_FILE, 'utf8');
|
|
395
|
+
const stripped = raw.replace(/\/\*(?:.|[\r\n])*?\*\//g, '').replace(/(^|\s*)\/\/.*$/gm, '');
|
|
396
|
+
const config = JSON.parse(stripped);
|
|
397
|
+
const activeInConfig = (config.bots || [])
|
|
398
|
+
.filter((b) => b.active !== false)
|
|
399
|
+
.map((b) => b.name)
|
|
400
|
+
.filter((name) => !!name);
|
|
401
|
+
if (activeInConfig.length > 0) {
|
|
402
|
+
let runningProcesses = [];
|
|
403
|
+
try {
|
|
404
|
+
const output = execSync('pm2 jlist').toString().trim();
|
|
405
|
+
const jsonStart = output.indexOf('[');
|
|
406
|
+
if (jsonStart !== -1) {
|
|
407
|
+
const jsonPart = output.substring(jsonStart);
|
|
408
|
+
const parsed = JSON.parse(jsonPart);
|
|
409
|
+
runningProcesses = parsed.map((p) => p.name);
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
log('Warning: PM2 jlist output did not contain JSON array.');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
catch (e) {
|
|
416
|
+
log('Warning: Could not fetch PM2 process list. Falling back to config-only detection.');
|
|
417
|
+
runningProcesses = activeInConfig;
|
|
418
|
+
}
|
|
419
|
+
const botsToRestart = activeInConfig.filter((name) => runningProcesses.includes(name));
|
|
420
|
+
const activeBots = (config.bots || []).filter((b) => b.active !== false);
|
|
421
|
+
const runningActiveBots = activeBots.filter((b) => runningProcesses.includes(b.name));
|
|
422
|
+
const maPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
|
|
423
|
+
const pm2Module = fs.existsSync(maPath)
|
|
424
|
+
? await import(maPath)
|
|
425
|
+
: await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
|
|
426
|
+
const marketAdapterRequired = pm2Module.needsMarketAdapter(runningActiveBots);
|
|
427
|
+
const serviceAppsToRestart = marketAdapterRequired ? ['dexbot-adapter'] : [];
|
|
428
|
+
const servicesToRestart = serviceAppsToRestart.filter((name) => runningProcesses.includes(name));
|
|
429
|
+
const allToRestart = [...botsToRestart, ...servicesToRestart];
|
|
430
|
+
if (allToRestart.length > 0) {
|
|
431
|
+
log(`Active processes detected: ${allToRestart.join(', ')}`);
|
|
432
|
+
for (const name of allToRestart) {
|
|
433
|
+
try {
|
|
434
|
+
run(`pm2 restart "${name}"`);
|
|
435
|
+
}
|
|
436
|
+
catch (e) {
|
|
437
|
+
log(`Warning: Failed to restart process "${name}" (it might not be running).`);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
restarted = true;
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
log('No active processes currently running in PM2. Skipping restart.');
|
|
444
|
+
}
|
|
445
|
+
if (marketAdapterRequired && !runningProcesses.includes('dexbot-adapter')) {
|
|
446
|
+
log('dexbot-adapter is required by an AMA-grid bot but not running. Starting from ecosystem...');
|
|
447
|
+
try {
|
|
448
|
+
run(`pm2 start "${PATHS.PROFILES.ECOSYSTEM_CONFIG_JS}" --only dexbot-adapter`);
|
|
449
|
+
}
|
|
450
|
+
catch (e) {
|
|
451
|
+
log('Warning: Failed to start dexbot-adapter from ecosystem config.');
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
log('No active bots found in config.');
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
log('Warning: profiles/bots.json not found, skipping selective restart.');
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (err) {
|
|
466
|
+
log(`Warning: runtime restart logic failed (${getErrorMessage(err)}). Skipping bulk restart to avoid touching dexbot-cred.`);
|
|
467
|
+
}
|
|
468
|
+
// Fallback restart for monolithic daemon that died during the update
|
|
469
|
+
if (!restarted && !Config.DEXBOT_UPDATE_SKIP_RELOAD) {
|
|
470
|
+
if (monolithicWasRunning || hadMonolithicFiles) {
|
|
471
|
+
log('Monolithic daemon was detected before update but is no longer running. Auto-starting...');
|
|
472
|
+
if (startMonolithicRuntime()) {
|
|
473
|
+
restarted = true;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
log('Auto-start did not succeed; the manual-start instructions below apply.');
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (!restarted) {
|
|
481
|
+
console.log(colorUpdateOutput('\n⚠️ No active runtime was restarted.\n' +
|
|
482
|
+
' If the daemon was running before, start it manually from a terminal:\n' +
|
|
483
|
+
' dexbot start\n' +
|
|
484
|
+
' (will prompt for master password; add --foreground for interactive mode)\n' +
|
|
485
|
+
' For non-interactive automation:\n' +
|
|
486
|
+
' dexbot start --headless --password-file <path>\n', UPDATE_COLORS.warn));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
// ── npm-install update flow ──────────────────────────────────────────
|
|
490
|
+
/**
|
|
491
|
+
* Update a globally installed npm package to the latest published version.
|
|
492
|
+
*
|
|
493
|
+
* Steps:
|
|
494
|
+
* 1. Compare installed package.json version against the npm registry.
|
|
495
|
+
* 2. Exit cleanly (0) when already up to date — no restart needed.
|
|
496
|
+
* 3. Verify this is a global install (only that layout can be replaced in place).
|
|
497
|
+
* 4. Snapshot runtime state, then run `npm install -g <pkg>@<latest>` from a
|
|
498
|
+
* neutral cwd (home dir) so npm can freely swap the package directory the
|
|
499
|
+
* currently running process executes from.
|
|
500
|
+
* 5. Regenerate the ecosystem config and restart active runtimes using the
|
|
501
|
+
* new code — the published package ships a pre-built dist/, so there is
|
|
502
|
+
* no local TypeScript build step.
|
|
503
|
+
*/
|
|
504
|
+
async function runNpmUpdateFlow() {
|
|
505
|
+
log('Detected npm package installation. Checking npm registry for updates...');
|
|
506
|
+
const pkgName = readPackageName(PATHS.PROJECT_ROOT);
|
|
507
|
+
const currentVersion = readPackageVersion(PATHS.PROJECT_ROOT);
|
|
508
|
+
if (!currentVersion) {
|
|
509
|
+
throw new Error(`Could not read a version from package.json at ${PATHS.PROJECT_ROOT}.`);
|
|
510
|
+
}
|
|
511
|
+
log(`Current installed version: ${currentVersion}`);
|
|
512
|
+
const latestVersion = getNpmLatestVersion(pkgName);
|
|
513
|
+
log(`Latest published version: ${latestVersion}`);
|
|
514
|
+
if (compareVersions(currentVersion, latestVersion) >= 0) {
|
|
515
|
+
log('DEXBot2 is already up to date (installed version matches or exceeds the latest published version).');
|
|
516
|
+
process.exit(0);
|
|
517
|
+
}
|
|
518
|
+
const globalRoot = getGlobalNpmRoot();
|
|
519
|
+
if (!globalRoot || !PATHS.PROJECT_ROOT.startsWith(globalRoot)) {
|
|
520
|
+
throw new Error('DEXBot2 is installed as a local (non-global) npm dependency; auto-update cannot replace the ' +
|
|
521
|
+
'package in place. Run `npm update ' + pkgName + '` in the parent project directory and restart manually.');
|
|
522
|
+
}
|
|
523
|
+
log(`${currentVersion} -> ${latestVersion} update available. Proceeding with npm update...`);
|
|
524
|
+
// Snapshot pre-update runtime state BEFORE npm replaces the package files.
|
|
525
|
+
const snapshot = snapshotMonolithicState();
|
|
526
|
+
// Run from a neutral cwd: npm's reify swaps the package directory this
|
|
527
|
+
// process is executing from, so the working directory may briefly not exist.
|
|
528
|
+
runIn(homedir(), `npm install -g ${pkgName}@${latestVersion}`);
|
|
529
|
+
const installedAfter = readPackageVersion(PATHS.PROJECT_ROOT);
|
|
530
|
+
log(`Installed version after update: ${installedAfter || 'unknown'}`);
|
|
531
|
+
await regenerateEcosystemConfig();
|
|
532
|
+
await restartActiveRuntimes(snapshot);
|
|
533
|
+
}
|
|
239
534
|
(async () => {
|
|
240
535
|
try {
|
|
241
536
|
// Change to project root for all git operations
|
|
242
537
|
process.chdir(PATHS.PROJECT_ROOT);
|
|
243
538
|
log('Starting DEXBot2 update process...');
|
|
244
|
-
// Get configured repository URL and target branch
|
|
245
|
-
const repoUrl = UPDATER.REPOSITORY_URL;
|
|
246
|
-
let branch = UPDATER.BRANCH;
|
|
247
539
|
/**
|
|
248
|
-
* STEP
|
|
249
|
-
*
|
|
540
|
+
* STEP 0: Detect Installation Layout
|
|
541
|
+
*
|
|
542
|
+
* Two supported layouts:
|
|
543
|
+
* 1. Git checkout (has a .git dir) -> git pull + npm install + build
|
|
544
|
+
* 2. Global npm package install (path under a node_modules tree) ->
|
|
545
|
+
* `npm install -g <pkg>@<latest>` (pre-built dist, no local build)
|
|
546
|
+
*
|
|
547
|
+
* A git checkout wins over the npm path so a repo that was npm-linked is
|
|
548
|
+
* still updated through git. Neither layout -> fail with a clear message.
|
|
250
549
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
550
|
+
const isGitRepo = fs.existsSync(path.join(PATHS.PROJECT_ROOT, '.git'));
|
|
551
|
+
const isNpmInstall = isGlobalNpmPackageDir(PATHS.PROJECT_ROOT);
|
|
552
|
+
if (!isGitRepo && isNpmInstall) {
|
|
553
|
+
await runNpmUpdateFlow();
|
|
554
|
+
logSuccess('DEXBot2 update completed successfully.');
|
|
555
|
+
process.exit(0);
|
|
253
556
|
}
|
|
557
|
+
if (!isGitRepo) {
|
|
558
|
+
throw new Error('DEXBot2 is neither a git checkout nor an npm package install. ' +
|
|
559
|
+
'Auto-update is not available for this installation.');
|
|
560
|
+
}
|
|
561
|
+
// Get configured repository URL and target branch
|
|
562
|
+
const repoUrl = UPDATER.REPOSITORY_URL;
|
|
563
|
+
let branch = UPDATER.BRANCH;
|
|
254
564
|
/**
|
|
255
565
|
* STEP 2: Detect Current Branch
|
|
256
566
|
* Gets the current checked-out branch name
|
|
@@ -350,14 +660,9 @@ async function restartActiveIsolatedProcesses() {
|
|
|
350
660
|
* hook or build), erasing its PID file. By capturing state up front,
|
|
351
661
|
* we can still restart it after the build completes.
|
|
352
662
|
*/
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
log('Monolithic daemon detected running before update. Will restart after build.');
|
|
357
|
-
}
|
|
358
|
-
else if (hadMonolithicFiles) {
|
|
359
|
-
log('Monolithic PID/file artifacts found but daemon is not alive. Will attempt restart after build.');
|
|
360
|
-
}
|
|
663
|
+
const snapshot = snapshotMonolithicState();
|
|
664
|
+
const monolithicWasRunning = snapshot.monolithicWasRunning;
|
|
665
|
+
const hadMonolithicFiles = snapshot.hadMonolithicFiles;
|
|
361
666
|
/**
|
|
362
667
|
* STEP 6b: Prepare Working Directory
|
|
363
668
|
* Stashes local changes to ensure a clean pull.
|
|
@@ -489,157 +794,15 @@ async function restartActiveIsolatedProcesses() {
|
|
|
489
794
|
* state, including service apps like dexbot-adapter and dexbot-update.
|
|
490
795
|
* Uses the compiled dist/pm2.js (after TS build) for correct dist/ paths.
|
|
491
796
|
*/
|
|
492
|
-
|
|
493
|
-
try {
|
|
494
|
-
// Try loading from compiled dist/ first, then fall back to source dir
|
|
495
|
-
const distPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
|
|
496
|
-
const pm2Module = fs.existsSync(distPath)
|
|
497
|
-
? await import(distPath)
|
|
498
|
-
: await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
|
|
499
|
-
pm2Module.generateEcosystemConfig({ clawOnly: false, exitOnError: false });
|
|
500
|
-
log('Ecosystem config regenerated successfully.');
|
|
501
|
-
}
|
|
502
|
-
catch (err) {
|
|
503
|
-
log(`Warning: Ecosystem config regeneration failed (${getErrorMessage(err)}). Continuing with existing config.`);
|
|
504
|
-
}
|
|
797
|
+
await regenerateEcosystemConfig();
|
|
505
798
|
/**
|
|
506
|
-
* STEP 9: Restart Active Runtime Processes
|
|
799
|
+
* STEP 9/9b: Restart Active Runtime Processes
|
|
507
800
|
* Intelligently restarts only the bots that were active before update
|
|
508
|
-
*
|
|
509
|
-
* -
|
|
510
|
-
*
|
|
511
|
-
* - Handles missing bots.json gracefully
|
|
512
|
-
* - Never restarts dexbot-cred through bulk PM2 actions
|
|
513
|
-
*
|
|
514
|
-
* Uses the pre-update snapshot (monolithicWasRunning) as a fallback:
|
|
515
|
-
* if the daemon shut down during git/npm ops and its PID file is gone,
|
|
516
|
-
* we still attempt to restart it.
|
|
801
|
+
* (monolithic, isolated supervisor, or PM2 selective restart), with a
|
|
802
|
+
* fallback auto-start for a monolithic daemon that died during the update.
|
|
803
|
+
* Never restarts dexbot-cred through bulk PM2 actions.
|
|
517
804
|
*/
|
|
518
|
-
|
|
519
|
-
log('Restarting active runtime processes...');
|
|
520
|
-
try {
|
|
521
|
-
if (Config.DEXBOT_UPDATE_SKIP_RELOAD) {
|
|
522
|
-
log('Restart skipped (managed by launcher).');
|
|
523
|
-
restarted = true;
|
|
524
|
-
}
|
|
525
|
-
else {
|
|
526
|
-
const monolithic = detectMonolithicRuntime();
|
|
527
|
-
if (monolithic) {
|
|
528
|
-
restartMonolithicRuntime(monolithic);
|
|
529
|
-
restarted = true;
|
|
530
|
-
}
|
|
531
|
-
else if (await restartActiveIsolatedProcesses()) {
|
|
532
|
-
log('Isolated supervisor runtime restarted.');
|
|
533
|
-
restarted = true;
|
|
534
|
-
}
|
|
535
|
-
else {
|
|
536
|
-
const BOTS_FILE = PATHS.PROFILES.BOTS_JSON;
|
|
537
|
-
if (fs.existsSync(BOTS_FILE)) {
|
|
538
|
-
const raw = fs.readFileSync(BOTS_FILE, 'utf8');
|
|
539
|
-
const stripped = raw.replace(/\/\*(?:.|[\r\n])*?\*\//g, '').replace(/(^|\s*)\/\/.*$/gm, '');
|
|
540
|
-
const config = JSON.parse(stripped);
|
|
541
|
-
const activeInConfig = (config.bots || [])
|
|
542
|
-
.filter((b) => b.active !== false)
|
|
543
|
-
.map((b) => b.name)
|
|
544
|
-
.filter((name) => !!name);
|
|
545
|
-
if (activeInConfig.length > 0) {
|
|
546
|
-
let runningProcesses = [];
|
|
547
|
-
try {
|
|
548
|
-
const output = execSync('pm2 jlist').toString().trim();
|
|
549
|
-
const jsonStart = output.indexOf('[');
|
|
550
|
-
if (jsonStart !== -1) {
|
|
551
|
-
const jsonPart = output.substring(jsonStart);
|
|
552
|
-
const parsed = JSON.parse(jsonPart);
|
|
553
|
-
runningProcesses = parsed.map((p) => p.name);
|
|
554
|
-
}
|
|
555
|
-
else {
|
|
556
|
-
log('Warning: PM2 jlist output did not contain JSON array.');
|
|
557
|
-
}
|
|
558
|
-
}
|
|
559
|
-
catch (e) {
|
|
560
|
-
log('Warning: Could not fetch PM2 process list. Falling back to config-only detection.');
|
|
561
|
-
runningProcesses = activeInConfig;
|
|
562
|
-
}
|
|
563
|
-
const botsToRestart = activeInConfig.filter((name) => runningProcesses.includes(name));
|
|
564
|
-
const activeBots = (config.bots || []).filter((b) => b.active !== false);
|
|
565
|
-
const runningActiveBots = activeBots.filter((b) => runningProcesses.includes(b.name));
|
|
566
|
-
const maPath = path.join(PATHS.PROJECT_ROOT, BUILD_DIR, 'pm2.js');
|
|
567
|
-
const pm2Module = fs.existsSync(maPath)
|
|
568
|
-
? await import(maPath)
|
|
569
|
-
: await import(path.join(PATHS.PROJECT_ROOT, 'pm2.js'));
|
|
570
|
-
const marketAdapterRequired = pm2Module.needsMarketAdapter(runningActiveBots);
|
|
571
|
-
const serviceAppsToRestart = marketAdapterRequired ? ['dexbot-adapter'] : [];
|
|
572
|
-
const servicesToRestart = serviceAppsToRestart.filter((name) => runningProcesses.includes(name));
|
|
573
|
-
const allToRestart = [...botsToRestart, ...servicesToRestart];
|
|
574
|
-
if (allToRestart.length > 0) {
|
|
575
|
-
log(`Active processes detected: ${allToRestart.join(', ')}`);
|
|
576
|
-
for (const name of allToRestart) {
|
|
577
|
-
try {
|
|
578
|
-
run(`pm2 restart "${name}"`);
|
|
579
|
-
}
|
|
580
|
-
catch (e) {
|
|
581
|
-
log(`Warning: Failed to restart process "${name}" (it might not be running).`);
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
restarted = true;
|
|
585
|
-
}
|
|
586
|
-
else {
|
|
587
|
-
log('No active processes currently running in PM2. Skipping restart.');
|
|
588
|
-
}
|
|
589
|
-
if (marketAdapterRequired && !runningProcesses.includes('dexbot-adapter')) {
|
|
590
|
-
log('dexbot-adapter is required by an AMA-grid bot but not running. Starting from ecosystem...');
|
|
591
|
-
try {
|
|
592
|
-
run('pm2 start profiles/ecosystem.config.cjs --only dexbot-adapter');
|
|
593
|
-
}
|
|
594
|
-
catch (e) {
|
|
595
|
-
log('Warning: Failed to start dexbot-adapter from ecosystem config.');
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
}
|
|
599
|
-
else {
|
|
600
|
-
log('No active bots found in config.');
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
else {
|
|
604
|
-
log('Warning: profiles/bots.json not found, skipping selective restart.');
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
catch (err) {
|
|
610
|
-
log(`Warning: runtime restart logic failed (${getErrorMessage(err)}). Skipping bulk restart to avoid touching dexbot-cred.`);
|
|
611
|
-
}
|
|
612
|
-
/**
|
|
613
|
-
* STEP 9b: Fallback restart for monolithic daemon
|
|
614
|
-
*
|
|
615
|
-
* If the monolithic daemon was running (or had state files) before the
|
|
616
|
-
* update but is no longer alive (common when the daemon is killed during
|
|
617
|
-
* git/npm operations like the prepare hook), try to auto-start it.
|
|
618
|
-
* This prevents the "bot updated but is not running" failure mode.
|
|
619
|
-
*
|
|
620
|
-
* Only mark `restarted` true when startMonolithicRuntime reports
|
|
621
|
-
* success — otherwise fall through to the manual-start instructions
|
|
622
|
-
* below so the operator knows the daemon did not actually come back.
|
|
623
|
-
*/
|
|
624
|
-
if (!restarted && !Config.DEXBOT_UPDATE_SKIP_RELOAD) {
|
|
625
|
-
if (monolithicWasRunning || hadMonolithicFiles) {
|
|
626
|
-
log('Monolithic daemon was detected before update but is no longer running. Auto-starting...');
|
|
627
|
-
if (startMonolithicRuntime()) {
|
|
628
|
-
restarted = true;
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
log('Auto-start did not succeed; the manual-start instructions below apply.');
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (!restarted) {
|
|
636
|
-
console.log(colorUpdateOutput('\n⚠️ No active runtime was restarted.\n' +
|
|
637
|
-
' If the daemon was running before, start it manually from a terminal:\n' +
|
|
638
|
-
' dexbot start\n' +
|
|
639
|
-
' (will prompt for master password; add --foreground for interactive mode)\n' +
|
|
640
|
-
' For non-interactive automation:\n' +
|
|
641
|
-
' dexbot start --headless --password-file <path>\n', UPDATE_COLORS.warn));
|
|
642
|
-
}
|
|
805
|
+
await restartActiveRuntimes({ monolithicWasRunning, hadMonolithicFiles });
|
|
643
806
|
logSuccess('DEXBot2 update completed successfully.');
|
|
644
807
|
process.exit(0);
|
|
645
808
|
}
|