ante-erp-cli 1.11.62 → 1.11.64
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/commands/update.js +58 -2
- package/src/utils/docker.js +76 -11
package/package.json
CHANGED
package/src/commands/update.js
CHANGED
|
@@ -4,7 +4,7 @@ import { join } from 'path';
|
|
|
4
4
|
import { readFileSync, writeFileSync, renameSync } from 'fs';
|
|
5
5
|
import semver from 'semver';
|
|
6
6
|
import { getInstallDir } from '../utils/config.js';
|
|
7
|
-
import { pullImagesSilent, stopServicesSilent, startServicesSilent, waitForServiceHealthy, runMigrations, pruneDockerSilent, downServicesSilent, forceRecreateServicesSilent, removeVolumesSilent, listVolumes } from '../utils/docker.js';
|
|
7
|
+
import { pullImagesSilent, stopServicesSilent, startServicesSilent, waitForServiceHealthy, runMigrations, pruneDockerSilent, downServicesSilent, forceRecreateServicesSilent, removeVolumesSilent, listVolumes, freeUpPorts } from '../utils/docker.js';
|
|
8
8
|
import { backup } from './backup.js';
|
|
9
9
|
import { getCurrentVersion, getLatestVersion } from './update-cli.js';
|
|
10
10
|
import { generateDockerCompose } from '../templates/docker-compose.yml.js';
|
|
@@ -381,6 +381,45 @@ function formatStepTitle(step, total, description) {
|
|
|
381
381
|
return `[${step}/${total}] ${description}`;
|
|
382
382
|
}
|
|
383
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Extract all ports used by services from docker-compose.yml
|
|
386
|
+
* @param {string} composeFile - Path to docker-compose.yml
|
|
387
|
+
* @param {string} envFile - Path to .env file
|
|
388
|
+
* @returns {number[]} Array of port numbers
|
|
389
|
+
*/
|
|
390
|
+
function getRequiredPorts(composeFile, envFile) {
|
|
391
|
+
const envConfig = parseEnvFile(envFile);
|
|
392
|
+
const installed = detectInstalledApps(composeFile);
|
|
393
|
+
|
|
394
|
+
const ports = [];
|
|
395
|
+
|
|
396
|
+
// Core services - always needed
|
|
397
|
+
ports.push(parseInt(envConfig.BACKEND_PORT) || 3001); // Backend
|
|
398
|
+
ports.push(parseInt(envConfig.FRONTEND_PORT) || 8080); // Frontend
|
|
399
|
+
|
|
400
|
+
// Optional services - only add if installed
|
|
401
|
+
if (installed.hasGateApp) {
|
|
402
|
+
ports.push(parseInt(envConfig.GATE_APP_PORT) || 8081);
|
|
403
|
+
}
|
|
404
|
+
if (installed.hasGuardianApp) {
|
|
405
|
+
ports.push(parseInt(envConfig.GUARDIAN_APP_PORT) || 8082);
|
|
406
|
+
}
|
|
407
|
+
if (installed.hasFacialWeb) {
|
|
408
|
+
ports.push(parseInt(envConfig.FACIAL_WEB_PORT) || 8083);
|
|
409
|
+
}
|
|
410
|
+
if (installed.hasPosApp) {
|
|
411
|
+
ports.push(parseInt(envConfig.POS_APP_PORT) || 8084);
|
|
412
|
+
}
|
|
413
|
+
if (installed.hasMlmApp) {
|
|
414
|
+
ports.push(parseInt(envConfig.MLM_APP_PORT) || 9005);
|
|
415
|
+
}
|
|
416
|
+
if (installed.hasBackendMlm) {
|
|
417
|
+
ports.push(parseInt(envConfig.BACKEND_MLM_PORT) || 4001);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return ports;
|
|
421
|
+
}
|
|
422
|
+
|
|
384
423
|
/**
|
|
385
424
|
* Database volumes that should be preserved during rebuild
|
|
386
425
|
* These contain critical data and should never be deleted
|
|
@@ -459,7 +498,7 @@ export async function update(options) {
|
|
|
459
498
|
let servicesToInstall = { gateApp: false, guardianApp: false, facialWeb: false, posApp: false, mlmApp: false };
|
|
460
499
|
|
|
461
500
|
// Calculate total steps dynamically
|
|
462
|
-
let totalSteps =
|
|
501
|
+
let totalSteps = 7; // Base steps: check services, pull, stop, free ports, start, backend health, migrations
|
|
463
502
|
if (!options.skipBackup) totalSteps += 2; // Pre-start + backup steps
|
|
464
503
|
if (hasNewServices) totalSteps++; // Install new services step
|
|
465
504
|
if (options.rebuild) totalSteps += 2; // Rebuild steps: down containers + remove volumes
|
|
@@ -499,6 +538,7 @@ export async function update(options) {
|
|
|
499
538
|
const stepRebuildDown = options.rebuild ? ++currentStep : null;
|
|
500
539
|
const stepRebuildVolumes = options.rebuild ? ++currentStep : null;
|
|
501
540
|
const stepStop = !options.rebuild ? ++currentStep : null; // Skip if rebuild (already downed)
|
|
541
|
+
const stepFreePorts = ++currentStep; // Always free up ports before starting
|
|
502
542
|
const stepStart = ++currentStep;
|
|
503
543
|
const stepBackendHealth = ++currentStep;
|
|
504
544
|
const stepGateHealth = (hasGateApp || missingServices.gateApp) ? ++currentStep : null;
|
|
@@ -620,6 +660,22 @@ export async function update(options) {
|
|
|
620
660
|
await stopServicesSilent(composeFile);
|
|
621
661
|
}
|
|
622
662
|
},
|
|
663
|
+
{
|
|
664
|
+
title: formatStepTitle(stepFreePorts, totalSteps, 'Freeing up required ports'),
|
|
665
|
+
task: async (ctx, task) => {
|
|
666
|
+
// Get all ports that will be needed by our services
|
|
667
|
+
const requiredPorts = getRequiredPorts(composeFile, envFile);
|
|
668
|
+
|
|
669
|
+
// Kill any processes (like orphaned docker-proxy) using these ports
|
|
670
|
+
const result = await freeUpPorts(requiredPorts);
|
|
671
|
+
|
|
672
|
+
if (result.portsFreed.length > 0) {
|
|
673
|
+
task.title = formatStepTitle(stepFreePorts, totalSteps, `Freed ports: ${result.portsFreed.join(', ')} (killed ${result.totalKilled} processes)`);
|
|
674
|
+
} else {
|
|
675
|
+
task.title = formatStepTitle(stepFreePorts, totalSteps, 'All required ports are available');
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
},
|
|
623
679
|
{
|
|
624
680
|
title: formatStepTitle(stepStart, totalSteps, options.rebuild ? 'Starting with fresh containers' : 'Starting with new images'),
|
|
625
681
|
task: async () => {
|
package/src/utils/docker.js
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
import { execa } from 'execa';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Kill processes using a specific port
|
|
5
|
+
* This is useful when docker-proxy processes are left behind after a failed container stop
|
|
6
|
+
* @param {number} port - Port number to free up
|
|
7
|
+
* @returns {Promise<{killed: boolean, pids: number[]}>}
|
|
8
|
+
*/
|
|
9
|
+
export async function killProcessesOnPort(port) {
|
|
10
|
+
const pids = [];
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
// Use lsof to find processes using the port
|
|
14
|
+
const { stdout } = await execa('lsof', ['-i', `:${port}`, '-t'], {
|
|
15
|
+
stdout: 'pipe',
|
|
16
|
+
stderr: 'pipe',
|
|
17
|
+
reject: false
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
if (stdout && stdout.trim()) {
|
|
21
|
+
const foundPids = stdout.trim().split('\n').map(p => parseInt(p.trim())).filter(p => !isNaN(p));
|
|
22
|
+
|
|
23
|
+
for (const pid of foundPids) {
|
|
24
|
+
try {
|
|
25
|
+
await execa('kill', ['-9', pid.toString()], {
|
|
26
|
+
stdout: 'pipe',
|
|
27
|
+
stderr: 'pipe',
|
|
28
|
+
reject: false
|
|
29
|
+
});
|
|
30
|
+
pids.push(pid);
|
|
31
|
+
} catch {
|
|
32
|
+
// Ignore errors - process might have already exited
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return { killed: pids.length > 0, pids };
|
|
38
|
+
} catch {
|
|
39
|
+
// lsof might not be installed or port not in use
|
|
40
|
+
return { killed: false, pids: [] };
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Free up multiple ports by killing any processes using them
|
|
46
|
+
* @param {number[]} ports - Array of port numbers to free up
|
|
47
|
+
* @returns {Promise<{portsFreed: number[], totalKilled: number}>}
|
|
48
|
+
*/
|
|
49
|
+
export async function freeUpPorts(ports) {
|
|
50
|
+
const portsFreed = [];
|
|
51
|
+
let totalKilled = 0;
|
|
52
|
+
|
|
53
|
+
for (const port of ports) {
|
|
54
|
+
const result = await killProcessesOnPort(port);
|
|
55
|
+
if (result.killed) {
|
|
56
|
+
portsFreed.push(port);
|
|
57
|
+
totalKilled += result.pids.length;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return { portsFreed, totalKilled };
|
|
62
|
+
}
|
|
63
|
+
|
|
3
64
|
/**
|
|
4
65
|
* Pull Docker images
|
|
5
66
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
@@ -32,14 +93,15 @@ export async function startServices(composeFile, services = []) {
|
|
|
32
93
|
* Stop Docker services
|
|
33
94
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
34
95
|
* @param {string[]} services - Specific services to stop (optional)
|
|
96
|
+
* @param {number} timeout - Timeout in seconds before force kill (default: 30)
|
|
35
97
|
* @returns {Promise<void>}
|
|
36
98
|
*/
|
|
37
|
-
export async function stopServices(composeFile, services = []) {
|
|
38
|
-
const args = ['compose', '-f', composeFile, 'stop'];
|
|
99
|
+
export async function stopServices(composeFile, services = [], timeout = 30) {
|
|
100
|
+
const args = ['compose', '-f', composeFile, 'stop', '--timeout', timeout.toString()];
|
|
39
101
|
if (services.length > 0) {
|
|
40
102
|
args.push(...services);
|
|
41
103
|
}
|
|
42
|
-
|
|
104
|
+
|
|
43
105
|
await execa('docker', args, {
|
|
44
106
|
stdio: 'inherit'
|
|
45
107
|
});
|
|
@@ -237,15 +299,16 @@ export async function waitForHealthy(composeFile, services, timeout = 60) {
|
|
|
237
299
|
* Down all services and optionally remove volumes
|
|
238
300
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
239
301
|
* @param {boolean} removeVolumes - Remove volumes
|
|
302
|
+
* @param {number} timeout - Timeout in seconds before force kill (default: 30)
|
|
240
303
|
* @returns {Promise<void>}
|
|
241
304
|
*/
|
|
242
|
-
export async function downServices(composeFile, removeVolumes = false) {
|
|
243
|
-
const args = ['compose', '-f', composeFile, 'down'];
|
|
244
|
-
|
|
305
|
+
export async function downServices(composeFile, removeVolumes = false, timeout = 30) {
|
|
306
|
+
const args = ['compose', '-f', composeFile, 'down', '--timeout', timeout.toString()];
|
|
307
|
+
|
|
245
308
|
if (removeVolumes) {
|
|
246
309
|
args.push('-v');
|
|
247
310
|
}
|
|
248
|
-
|
|
311
|
+
|
|
249
312
|
await execa('docker', args, {
|
|
250
313
|
stdio: 'inherit'
|
|
251
314
|
});
|
|
@@ -313,10 +376,11 @@ export async function startServicesSilent(composeFile, services = []) {
|
|
|
313
376
|
* Stop Docker services (silent version - no output)
|
|
314
377
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
315
378
|
* @param {string[]} services - Specific services to stop (optional)
|
|
379
|
+
* @param {number} timeout - Timeout in seconds before force kill (default: 30)
|
|
316
380
|
* @returns {Promise<void>}
|
|
317
381
|
*/
|
|
318
|
-
export async function stopServicesSilent(composeFile, services = []) {
|
|
319
|
-
const args = ['compose', '-f', composeFile, 'stop'];
|
|
382
|
+
export async function stopServicesSilent(composeFile, services = [], timeout = 30) {
|
|
383
|
+
const args = ['compose', '-f', composeFile, 'stop', '--timeout', timeout.toString()];
|
|
320
384
|
if (services.length > 0) {
|
|
321
385
|
args.push(...services);
|
|
322
386
|
}
|
|
@@ -342,10 +406,11 @@ export async function pruneDockerSilent() {
|
|
|
342
406
|
* Down all services (silent version - no output)
|
|
343
407
|
* @param {string} composeFile - Path to docker-compose.yml
|
|
344
408
|
* @param {boolean} removeVolumes - Remove volumes
|
|
409
|
+
* @param {number} timeout - Timeout in seconds before force kill (default: 30)
|
|
345
410
|
* @returns {Promise<void>}
|
|
346
411
|
*/
|
|
347
|
-
export async function downServicesSilent(composeFile, removeVolumes = false) {
|
|
348
|
-
const args = ['compose', '-f', composeFile, 'down'];
|
|
412
|
+
export async function downServicesSilent(composeFile, removeVolumes = false, timeout = 30) {
|
|
413
|
+
const args = ['compose', '-f', composeFile, 'down', '--timeout', timeout.toString()];
|
|
349
414
|
|
|
350
415
|
if (removeVolumes) {
|
|
351
416
|
args.push('-v');
|