ante-erp-cli 1.11.36 → 1.11.37
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/install.js +17 -2
- package/src/commands/regenerate-compose.js +7 -3
- package/src/commands/set-domain.js +78 -4
- package/src/commands/update.js +36 -6
- package/src/templates/docker-compose.yml.js +30 -0
- package/src/templates/env.js +6 -1
- package/src/utils/nginx.js +107 -2
package/package.json
CHANGED
package/src/commands/install.js
CHANGED
|
@@ -81,6 +81,7 @@ function showSuccess(installDir, credentials, config) {
|
|
|
81
81
|
const guardianAppUrl = config.guardianAppDomain || 'http://localhost:8082';
|
|
82
82
|
const facialWebUrl = config.facialAppDomain || 'http://localhost:8083';
|
|
83
83
|
const posAppUrl = config.posAppDomain || 'http://localhost:8084';
|
|
84
|
+
const clientAppUrl = config.clientAppDomain || 'http://localhost:9005';
|
|
84
85
|
|
|
85
86
|
let accessInfo = chalk.white('Access Information:') + '\n' +
|
|
86
87
|
chalk.gray('━'.repeat(40)) + '\n' +
|
|
@@ -102,6 +103,10 @@ function showSuccess(installDir, credentials, config) {
|
|
|
102
103
|
accessInfo += chalk.cyan('POS App: ') + chalk.white(posAppUrl) + '\n';
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
if (config.installClient) {
|
|
107
|
+
accessInfo += chalk.cyan('Client App: ') + chalk.white(clientAppUrl) + '\n';
|
|
108
|
+
}
|
|
109
|
+
|
|
105
110
|
accessInfo += chalk.cyan('Backend: ') + chalk.white(apiUrl) + '\n' +
|
|
106
111
|
chalk.cyan('WebSocket: ') + chalk.white(apiUrl) + '\n\n';
|
|
107
112
|
|
|
@@ -312,6 +317,7 @@ export async function install(options) {
|
|
|
312
317
|
const guardianAppPort = 8082;
|
|
313
318
|
const facialWebPort = 8083;
|
|
314
319
|
const posAppPort = 8084;
|
|
320
|
+
const clientAppPort = 9005;
|
|
315
321
|
|
|
316
322
|
const config = {
|
|
317
323
|
installDir: options.dir || './ante-erp',
|
|
@@ -322,18 +328,21 @@ export async function install(options) {
|
|
|
322
328
|
guardianAppDomain: buildURL(host, guardianAppPort),
|
|
323
329
|
facialAppDomain: buildURL(host, facialWebPort),
|
|
324
330
|
posAppDomain: buildURL(host, posAppPort),
|
|
331
|
+
clientAppDomain: buildURL(host, clientAppPort),
|
|
325
332
|
frontendPort,
|
|
326
333
|
apiPort,
|
|
327
334
|
gateAppPort,
|
|
328
335
|
guardianAppPort,
|
|
329
336
|
facialWebPort,
|
|
330
337
|
posAppPort,
|
|
338
|
+
clientAppPort,
|
|
331
339
|
companyId: 1,
|
|
332
340
|
installGate: true, // Install all frontends by default
|
|
333
341
|
installGuardian: true,
|
|
334
342
|
installFacial: true,
|
|
335
343
|
installPos: true,
|
|
336
|
-
|
|
344
|
+
installClient: true,
|
|
345
|
+
frontends: ['main', 'gate', 'guardian', 'facial', 'pos', 'client']
|
|
337
346
|
};
|
|
338
347
|
|
|
339
348
|
console.log(chalk.green(`✓ ${formatStepTitle(stepNetworkDetect, totalSteps, `Network detected: ${host}`)}\n`));
|
|
@@ -346,6 +355,7 @@ export async function install(options) {
|
|
|
346
355
|
console.log(chalk.cyan(' Guardian App:'), chalk.white(config.guardianAppDomain));
|
|
347
356
|
console.log(chalk.cyan(' Facial Web:'), chalk.white(config.facialAppDomain));
|
|
348
357
|
console.log(chalk.cyan(' POS App:'), chalk.white(config.posAppDomain));
|
|
358
|
+
console.log(chalk.cyan(' Client App:'), chalk.white(config.clientAppDomain));
|
|
349
359
|
console.log(chalk.cyan(' API:'), chalk.white(config.apiDomain));
|
|
350
360
|
console.log();
|
|
351
361
|
|
|
@@ -426,11 +436,13 @@ export async function install(options) {
|
|
|
426
436
|
guardianAppPort: config.guardianAppPort,
|
|
427
437
|
facialWebPort: config.facialWebPort,
|
|
428
438
|
posAppPort: config.posAppPort,
|
|
439
|
+
clientAppPort: config.clientAppPort,
|
|
429
440
|
installMain: true,
|
|
430
441
|
installGate: config.installGate,
|
|
431
442
|
installGuardian: config.installGuardian,
|
|
432
443
|
installFacial: config.installFacial,
|
|
433
444
|
installPos: config.installPos,
|
|
445
|
+
installClient: config.installClient,
|
|
434
446
|
companyId: config.companyId
|
|
435
447
|
});
|
|
436
448
|
|
|
@@ -444,15 +456,18 @@ export async function install(options) {
|
|
|
444
456
|
guardianAppPort: config.guardianAppPort,
|
|
445
457
|
facialWebPort: config.facialWebPort,
|
|
446
458
|
posAppPort: config.posAppPort,
|
|
459
|
+
clientAppPort: config.clientAppPort,
|
|
447
460
|
gateAppUrl: config.gateAppDomain,
|
|
448
461
|
guardianAppUrl: config.guardianAppDomain,
|
|
449
462
|
facialWebUrl: config.facialAppDomain,
|
|
450
463
|
posAppUrl: config.posAppDomain,
|
|
464
|
+
clientAppUrl: config.clientAppDomain,
|
|
451
465
|
companyId: config.companyId,
|
|
452
466
|
installGate: config.installGate,
|
|
453
467
|
installGuardian: config.installGuardian,
|
|
454
468
|
installFacial: config.installFacial,
|
|
455
|
-
installPos: config.installPos
|
|
469
|
+
installPos: config.installPos,
|
|
470
|
+
installClient: config.installClient
|
|
456
471
|
});
|
|
457
472
|
|
|
458
473
|
writeFileSync(join(config.installDir, 'docker-compose.yml'), dockerCompose);
|
|
@@ -35,10 +35,12 @@ function detectInstalledApps(composeFile) {
|
|
|
35
35
|
hasMain: composeContent.includes('frontend:') || composeContent.includes('container_name: ante-frontend'),
|
|
36
36
|
hasGateApp: composeContent.includes('gate-app:') || composeContent.includes('container_name: ante-gate-app'),
|
|
37
37
|
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app'),
|
|
38
|
-
hasFacialWeb: composeContent.includes('facial-web:') || composeContent.includes('container_name: ante-facial-web')
|
|
38
|
+
hasFacialWeb: composeContent.includes('facial-web:') || composeContent.includes('container_name: ante-facial-web'),
|
|
39
|
+
hasPosApp: composeContent.includes('pos-app:') || composeContent.includes('container_name: ante-pos'),
|
|
40
|
+
hasClientApp: composeContent.includes('client-app:') || composeContent.includes('container_name: ante-client')
|
|
39
41
|
};
|
|
40
42
|
} catch {
|
|
41
|
-
return { hasMain: true, hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false };
|
|
43
|
+
return { hasMain: true, hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false, hasPosApp: false, hasClientApp: false };
|
|
42
44
|
}
|
|
43
45
|
}
|
|
44
46
|
|
|
@@ -74,7 +76,9 @@ export async function regenerateCompose() {
|
|
|
74
76
|
console.log(chalk.gray(` Frontend Main: ${installed.hasMain ? '✓ Installed' : '✗ Not installed'}`));
|
|
75
77
|
console.log(chalk.gray(` Gate App: ${installed.hasGateApp ? '✓ Installed' : '✗ Not installed'}`));
|
|
76
78
|
console.log(chalk.gray(` Guardian App: ${installed.hasGuardianApp ? '✓ Installed' : '✗ Not installed'}`));
|
|
77
|
-
console.log(chalk.gray(` Facial Web: ${installed.hasFacialWeb ? '✓ Installed' : '✗ Not installed'}
|
|
79
|
+
console.log(chalk.gray(` Facial Web: ${installed.hasFacialWeb ? '✓ Installed' : '✗ Not installed'}`));
|
|
80
|
+
console.log(chalk.gray(` POS App: ${installed.hasPosApp ? '✓ Installed' : '✗ Not installed'}`));
|
|
81
|
+
console.log(chalk.gray(` Client App: ${installed.hasClientApp ? '✓ Installed' : '✗ Not installed'}\n`));
|
|
78
82
|
|
|
79
83
|
// Parse .env file
|
|
80
84
|
const envConfig = parseEnvFile(envFile);
|
|
@@ -107,10 +107,11 @@ function detectInstalledApps(composeFile) {
|
|
|
107
107
|
hasGateApp: composeContent.includes('gate-app:') || composeContent.includes('container_name: ante-gate-app'),
|
|
108
108
|
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app'),
|
|
109
109
|
hasFacialWeb: composeContent.includes('facial-web:') || composeContent.includes('container_name: ante-facial-web'),
|
|
110
|
-
hasPosApp: composeContent.includes('pos-app:') || composeContent.includes('container_name: ante-pos')
|
|
110
|
+
hasPosApp: composeContent.includes('pos-app:') || composeContent.includes('container_name: ante-pos'),
|
|
111
|
+
hasClientApp: composeContent.includes('client-app:') || composeContent.includes('container_name: ante-client')
|
|
111
112
|
};
|
|
112
113
|
} catch {
|
|
113
|
-
return { hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false, hasPosApp: false };
|
|
114
|
+
return { hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false, hasPosApp: false, hasClientApp: false };
|
|
114
115
|
}
|
|
115
116
|
}
|
|
116
117
|
|
|
@@ -158,10 +159,10 @@ export async function setDomain(options) {
|
|
|
158
159
|
console.log(); // Add spacing
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
let frontendUrl, apiUrl, gateAppUrl, guardianAppUrl, facialWebUrl, posAppUrl;
|
|
162
|
+
let frontendUrl, apiUrl, gateAppUrl, guardianAppUrl, facialWebUrl, posAppUrl, clientAppUrl;
|
|
162
163
|
|
|
163
164
|
// Detect which apps are installed
|
|
164
|
-
const { hasGateApp, hasGuardianApp, hasFacialWeb, hasPosApp } = detectInstalledApps(composeFile);
|
|
165
|
+
const { hasGateApp, hasGuardianApp, hasFacialWeb, hasPosApp, hasClientApp } = detectInstalledApps(composeFile);
|
|
165
166
|
|
|
166
167
|
if (options.interactive !== false) {
|
|
167
168
|
// Show current values
|
|
@@ -171,6 +172,7 @@ export async function setDomain(options) {
|
|
|
171
172
|
const currentGuardianAppUrl = getEnvValue(envPath, 'GUARDIAN_APP_URL');
|
|
172
173
|
const currentFacialWebUrl = getEnvValue(envPath, 'FACIAL_WEB_URL');
|
|
173
174
|
const currentPosAppUrl = getEnvValue(envPath, 'POS_APP_URL');
|
|
175
|
+
const currentClientAppUrl = getEnvValue(envPath, 'CLIENT_APP_URL');
|
|
174
176
|
|
|
175
177
|
if (currentFrontendUrl) {
|
|
176
178
|
console.log(chalk.gray(`Current Frontend URL: ${currentFrontendUrl}`));
|
|
@@ -187,6 +189,9 @@ export async function setDomain(options) {
|
|
|
187
189
|
if (currentPosAppUrl) {
|
|
188
190
|
console.log(chalk.gray(`Current POS App URL: ${currentPosAppUrl}`));
|
|
189
191
|
}
|
|
192
|
+
if (currentClientAppUrl) {
|
|
193
|
+
console.log(chalk.gray(`Current Client App URL: ${currentClientAppUrl}`));
|
|
194
|
+
}
|
|
190
195
|
if (currentApiUrl) {
|
|
191
196
|
console.log(chalk.gray(`Current API URL: ${currentApiUrl}\n`));
|
|
192
197
|
}
|
|
@@ -254,6 +259,15 @@ export async function setDomain(options) {
|
|
|
254
259
|
});
|
|
255
260
|
}
|
|
256
261
|
|
|
262
|
+
if (hasClientApp) {
|
|
263
|
+
portPrompts.push({
|
|
264
|
+
type: 'number',
|
|
265
|
+
name: 'clientAppPort',
|
|
266
|
+
message: 'Client App port:',
|
|
267
|
+
default: 9005
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
|
|
257
271
|
portPrompts.push({
|
|
258
272
|
type: 'number',
|
|
259
273
|
name: 'apiPort',
|
|
@@ -269,6 +283,7 @@ export async function setDomain(options) {
|
|
|
269
283
|
if (hasGuardianApp) guardianAppUrl = buildURL(detectedIP, ports.guardianAppPort);
|
|
270
284
|
if (hasFacialWeb) facialWebUrl = buildURL(detectedIP, ports.facialWebPort);
|
|
271
285
|
if (hasPosApp) posAppUrl = buildURL(detectedIP, ports.posAppPort);
|
|
286
|
+
if (hasClientApp) clientAppUrl = buildURL(detectedIP, ports.clientAppPort);
|
|
272
287
|
} else if (configType.type === 'localhost') {
|
|
273
288
|
const portPrompts = [
|
|
274
289
|
{
|
|
@@ -315,6 +330,15 @@ export async function setDomain(options) {
|
|
|
315
330
|
});
|
|
316
331
|
}
|
|
317
332
|
|
|
333
|
+
if (hasClientApp) {
|
|
334
|
+
portPrompts.push({
|
|
335
|
+
type: 'number',
|
|
336
|
+
name: 'clientAppPort',
|
|
337
|
+
message: 'Client App port:',
|
|
338
|
+
default: 9005
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
318
342
|
portPrompts.push({
|
|
319
343
|
type: 'number',
|
|
320
344
|
name: 'apiPort',
|
|
@@ -330,6 +354,7 @@ export async function setDomain(options) {
|
|
|
330
354
|
if (hasGuardianApp) guardianAppUrl = buildURL('localhost', ports.guardianAppPort);
|
|
331
355
|
if (hasFacialWeb) facialWebUrl = buildURL('localhost', ports.facialWebPort);
|
|
332
356
|
if (hasPosApp) posAppUrl = buildURL('localhost', ports.posAppPort);
|
|
357
|
+
if (hasClientApp) clientAppUrl = buildURL('localhost', ports.clientAppPort);
|
|
333
358
|
} else if (configType.type === 'custom-ip') {
|
|
334
359
|
const ipPrompts = [
|
|
335
360
|
{
|
|
@@ -385,6 +410,15 @@ export async function setDomain(options) {
|
|
|
385
410
|
});
|
|
386
411
|
}
|
|
387
412
|
|
|
413
|
+
if (hasClientApp) {
|
|
414
|
+
ipPrompts.push({
|
|
415
|
+
type: 'number',
|
|
416
|
+
name: 'clientAppPort',
|
|
417
|
+
message: 'Client App port:',
|
|
418
|
+
default: 9005
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
388
422
|
ipPrompts.push({
|
|
389
423
|
type: 'number',
|
|
390
424
|
name: 'apiPort',
|
|
@@ -400,6 +434,7 @@ export async function setDomain(options) {
|
|
|
400
434
|
if (hasGuardianApp) guardianAppUrl = buildURL(ipConfig.ip, ipConfig.guardianAppPort);
|
|
401
435
|
if (hasFacialWeb) facialWebUrl = buildURL(ipConfig.ip, ipConfig.facialWebPort);
|
|
402
436
|
if (hasPosApp) posAppUrl = buildURL(ipConfig.ip, ipConfig.posAppPort);
|
|
437
|
+
if (hasClientApp) clientAppUrl = buildURL(ipConfig.ip, ipConfig.clientAppPort);
|
|
403
438
|
} else {
|
|
404
439
|
// Manual URL input (domain configuration)
|
|
405
440
|
|
|
@@ -442,6 +477,7 @@ export async function setDomain(options) {
|
|
|
442
477
|
if (hasGuardianApp) guardianAppUrl = `https://${subdomain}-guardian.ante.ph`;
|
|
443
478
|
if (hasFacialWeb) facialWebUrl = `https://${subdomain}-fr.ante.ph`;
|
|
444
479
|
if (hasPosApp) posAppUrl = `https://${subdomain}-pos.ante.ph`;
|
|
480
|
+
if (hasClientApp) clientAppUrl = `https://${subdomain}-client.ante.ph`;
|
|
445
481
|
|
|
446
482
|
// Display generated domains for confirmation
|
|
447
483
|
console.log(chalk.cyan('\n📋 Generated Domain Configuration:\n'));
|
|
@@ -451,6 +487,7 @@ export async function setDomain(options) {
|
|
|
451
487
|
if (hasGuardianApp) console.log(chalk.gray(` Guardian App: ${guardianAppUrl}`));
|
|
452
488
|
if (hasFacialWeb) console.log(chalk.gray(` Facial Web: ${facialWebUrl}`));
|
|
453
489
|
if (hasPosApp) console.log(chalk.gray(` POS App: ${posAppUrl}`));
|
|
490
|
+
if (hasClientApp) console.log(chalk.gray(` Client App: ${clientAppUrl}`));
|
|
454
491
|
console.log('');
|
|
455
492
|
|
|
456
493
|
const confirmDomains = await inquirer.prompt([
|
|
@@ -518,6 +555,16 @@ export async function setDomain(options) {
|
|
|
518
555
|
});
|
|
519
556
|
}
|
|
520
557
|
|
|
558
|
+
if (hasClientApp) {
|
|
559
|
+
domainPrompts.push({
|
|
560
|
+
type: 'input',
|
|
561
|
+
name: 'clientAppUrl',
|
|
562
|
+
message: 'Client App URL:\n Examples: https://client.ante.ph or http://143.198.91.153:9005\n Enter URL',
|
|
563
|
+
default: currentClientAppUrl || 'http://localhost:9005',
|
|
564
|
+
validate: validateUrl
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
521
568
|
domainPrompts.push({
|
|
522
569
|
type: 'input',
|
|
523
570
|
name: 'apiUrl',
|
|
@@ -535,6 +582,7 @@ export async function setDomain(options) {
|
|
|
535
582
|
if (hasGuardianApp) guardianAppUrl = sanitizeUrl(answers.guardianAppUrl);
|
|
536
583
|
if (hasFacialWeb) facialWebUrl = sanitizeUrl(answers.facialWebUrl);
|
|
537
584
|
if (hasPosApp) posAppUrl = sanitizeUrl(answers.posAppUrl);
|
|
585
|
+
if (hasClientApp) clientAppUrl = sanitizeUrl(answers.clientAppUrl);
|
|
538
586
|
}
|
|
539
587
|
}
|
|
540
588
|
} else {
|
|
@@ -590,6 +638,10 @@ export async function setDomain(options) {
|
|
|
590
638
|
envUpdates.POS_APP_URL = posAppUrl;
|
|
591
639
|
}
|
|
592
640
|
|
|
641
|
+
if (hasClientApp && clientAppUrl) {
|
|
642
|
+
envUpdates.CLIENT_APP_URL = clientAppUrl;
|
|
643
|
+
}
|
|
644
|
+
|
|
593
645
|
updateEnvFile(envPath, envUpdates);
|
|
594
646
|
|
|
595
647
|
console.log(chalk.green('✓ Configuration updated'));
|
|
@@ -627,6 +679,11 @@ export async function setDomain(options) {
|
|
|
627
679
|
nginxConfig.posAppPort = 8084;
|
|
628
680
|
}
|
|
629
681
|
|
|
682
|
+
if (hasClientApp && clientAppUrl) {
|
|
683
|
+
nginxConfig.clientAppDomain = clientAppUrl;
|
|
684
|
+
nginxConfig.clientAppPort = 9005;
|
|
685
|
+
}
|
|
686
|
+
|
|
630
687
|
await configureNginx(nginxConfig);
|
|
631
688
|
} catch (error) {
|
|
632
689
|
console.log(chalk.yellow('\n⚠ NGINX configuration failed:', error.message));
|
|
@@ -724,6 +781,12 @@ export async function setDomain(options) {
|
|
|
724
781
|
domains.push(posAppDomain);
|
|
725
782
|
}
|
|
726
783
|
}
|
|
784
|
+
if (hasClientApp && clientAppUrl && clientAppUrl.startsWith('https://')) {
|
|
785
|
+
const clientAppDomain = extractDomain(clientAppUrl);
|
|
786
|
+
if (clientAppDomain !== 'localhost' && !clientAppDomain.match(/^\d+\.\d+\.\d+\.\d+$/) && !domains.includes(clientAppDomain)) {
|
|
787
|
+
domains.push(clientAppDomain);
|
|
788
|
+
}
|
|
789
|
+
}
|
|
727
790
|
|
|
728
791
|
for (const domain of domains) {
|
|
729
792
|
console.log(chalk.gray(` Obtaining certificate for ${domain}...`));
|
|
@@ -766,6 +829,11 @@ export async function setDomain(options) {
|
|
|
766
829
|
sslNginxConfig.posAppPort = 8084;
|
|
767
830
|
}
|
|
768
831
|
|
|
832
|
+
if (hasClientApp && clientAppUrl) {
|
|
833
|
+
sslNginxConfig.clientAppDomain = clientAppUrl;
|
|
834
|
+
sslNginxConfig.clientAppPort = 9005;
|
|
835
|
+
}
|
|
836
|
+
|
|
769
837
|
await updateNginxForSSL(sslNginxConfig);
|
|
770
838
|
|
|
771
839
|
// Step 3: Setup auto-renewal
|
|
@@ -904,6 +972,9 @@ export async function setDomain(options) {
|
|
|
904
972
|
if (hasFacialWeb && facialWebUrl) {
|
|
905
973
|
console.log(chalk.cyan('Facial Web: '), chalk.white(facialWebUrl));
|
|
906
974
|
}
|
|
975
|
+
if (hasClientApp && clientAppUrl) {
|
|
976
|
+
console.log(chalk.cyan('Client App: '), chalk.white(clientAppUrl));
|
|
977
|
+
}
|
|
907
978
|
console.log(chalk.cyan('API: '), chalk.white(apiUrl));
|
|
908
979
|
console.log(chalk.cyan('WebSocket: '), chalk.white(apiUrl));
|
|
909
980
|
|
|
@@ -918,6 +989,9 @@ export async function setDomain(options) {
|
|
|
918
989
|
if (hasFacialWeb && facialWebUrl) {
|
|
919
990
|
console.log(chalk.white(` Facial Web: ${facialWebUrl}`));
|
|
920
991
|
}
|
|
992
|
+
if (hasClientApp && clientAppUrl) {
|
|
993
|
+
console.log(chalk.white(` Client App: ${clientAppUrl}`));
|
|
994
|
+
}
|
|
921
995
|
console.log();
|
|
922
996
|
|
|
923
997
|
} catch (error) {
|
package/src/commands/update.js
CHANGED
|
@@ -21,10 +21,11 @@ function detectInstalledApps(composeFile) {
|
|
|
21
21
|
hasGateApp: composeContent.includes('gate-app:') || composeContent.includes('container_name: ante-gate-app'),
|
|
22
22
|
hasGuardianApp: composeContent.includes('guardian-app:') || composeContent.includes('container_name: ante-guardian-app'),
|
|
23
23
|
hasFacialWeb: composeContent.includes('facial-web:') || composeContent.includes('container_name: ante-facial-web'),
|
|
24
|
-
hasPosApp: composeContent.includes('pos-app:') || composeContent.includes('container_name: ante-pos')
|
|
24
|
+
hasPosApp: composeContent.includes('pos-app:') || composeContent.includes('container_name: ante-pos'),
|
|
25
|
+
hasClientApp: composeContent.includes('client-app:') || composeContent.includes('container_name: ante-client')
|
|
25
26
|
};
|
|
26
27
|
} catch {
|
|
27
|
-
return { hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false, hasPosApp: false };
|
|
28
|
+
return { hasGateApp: false, hasGuardianApp: false, hasFacialWeb: false, hasPosApp: false, hasClientApp: false };
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -39,7 +40,8 @@ function detectMissingServices(composeFile) {
|
|
|
39
40
|
gateApp: !installed.hasGateApp,
|
|
40
41
|
guardianApp: !installed.hasGuardianApp,
|
|
41
42
|
facialWeb: !installed.hasFacialWeb,
|
|
42
|
-
posApp: !installed.hasPosApp
|
|
43
|
+
posApp: !installed.hasPosApp,
|
|
44
|
+
clientApp: !installed.hasClientApp
|
|
43
45
|
};
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -80,11 +82,13 @@ function updateDockerCompose(composeFile, envFile, newServices) {
|
|
|
80
82
|
guardianAppPort: parseInt(envConfig.GUARDIAN_APP_PORT) || 8082,
|
|
81
83
|
facialWebPort: parseInt(envConfig.FACIAL_WEB_PORT) || 8083,
|
|
82
84
|
posAppPort: parseInt(envConfig.POS_APP_PORT) || 8084,
|
|
85
|
+
clientAppPort: parseInt(envConfig.CLIENT_APP_PORT) || 9005,
|
|
83
86
|
installMain: true,
|
|
84
87
|
installGate: currentInstalled.hasGateApp || newServices.gateApp,
|
|
85
88
|
installGuardian: currentInstalled.hasGuardianApp || newServices.guardianApp,
|
|
86
89
|
installFacial: currentInstalled.hasFacialWeb || newServices.facialWeb,
|
|
87
90
|
installPos: currentInstalled.hasPosApp || newServices.posApp,
|
|
91
|
+
installClient: currentInstalled.hasClientApp || newServices.clientApp,
|
|
88
92
|
companyId: parseInt(envConfig.COMPANY_ID) || 1
|
|
89
93
|
});
|
|
90
94
|
|
|
@@ -200,8 +204,32 @@ COMPANY_ID=1
|
|
|
200
204
|
}
|
|
201
205
|
}
|
|
202
206
|
|
|
207
|
+
// Add client-app configuration if needed
|
|
208
|
+
if (newServices.clientApp && !envContent.includes('CLIENT_APP_PORT')) {
|
|
209
|
+
if (!envContent.includes('# FRONTEND APP CONFIGURATION')) {
|
|
210
|
+
envContent += `\n# ------------------------------------------------------------------------------
|
|
211
|
+
# FRONTEND APP CONFIGURATION
|
|
212
|
+
# ------------------------------------------------------------------------------
|
|
213
|
+
COMPANY_ID=1
|
|
214
|
+
`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Replace commented line or add new
|
|
218
|
+
if (envContent.includes('# CLIENT_APP_PORT=')) {
|
|
219
|
+
envContent = envContent.replace(/# CLIENT_APP_PORT=\d+/, 'CLIENT_APP_PORT=9005');
|
|
220
|
+
} else {
|
|
221
|
+
envContent += `CLIENT_APP_PORT=9005\n`;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (envContent.includes('# CLIENT_APP_URL=')) {
|
|
225
|
+
envContent = envContent.replace(/# CLIENT_APP_URL=.*/, 'CLIENT_APP_URL=http://localhost:9005');
|
|
226
|
+
} else {
|
|
227
|
+
envContent += `CLIENT_APP_URL=http://localhost:9005\n`;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
203
231
|
// Add COMPANY_ID if missing and any new service is being added
|
|
204
|
-
if ((newServices.gateApp || newServices.guardianApp || newServices.facialWeb || newServices.posApp) && !envContent.includes('COMPANY_ID=')) {
|
|
232
|
+
if ((newServices.gateApp || newServices.guardianApp || newServices.facialWeb || newServices.posApp || newServices.clientApp) && !envContent.includes('COMPANY_ID=')) {
|
|
205
233
|
envContent += `COMPANY_ID=1\n`;
|
|
206
234
|
}
|
|
207
235
|
|
|
@@ -255,10 +283,10 @@ export async function update(options) {
|
|
|
255
283
|
|
|
256
284
|
// Detect missing services that could be installed
|
|
257
285
|
const missingServices = detectMissingServices(composeFile);
|
|
258
|
-
const hasNewServices = missingServices.gateApp || missingServices.guardianApp || missingServices.facialWeb || missingServices.posApp;
|
|
286
|
+
const hasNewServices = missingServices.gateApp || missingServices.guardianApp || missingServices.facialWeb || missingServices.posApp || missingServices.clientApp;
|
|
259
287
|
|
|
260
288
|
// Track which services to install (will be set to missingServices if any found)
|
|
261
|
-
let servicesToInstall = { gateApp: false, guardianApp: false, facialWeb: false, posApp: false };
|
|
289
|
+
let servicesToInstall = { gateApp: false, guardianApp: false, facialWeb: false, posApp: false, clientApp: false };
|
|
262
290
|
|
|
263
291
|
// Calculate total steps dynamically
|
|
264
292
|
let totalSteps = 6; // Base steps: check services, pull, stop, start, backend health, migrations
|
|
@@ -329,6 +357,7 @@ export async function update(options) {
|
|
|
329
357
|
if (missingServices.guardianApp) availableServices.push('Guardian App');
|
|
330
358
|
if (missingServices.facialWeb) availableServices.push('Facial Web');
|
|
331
359
|
if (missingServices.posApp) availableServices.push('POS App');
|
|
360
|
+
if (missingServices.clientApp) availableServices.push('Client App');
|
|
332
361
|
|
|
333
362
|
task.title = formatStepTitle(stepCheckServices, totalSteps, `Found new services: ${availableServices.join(', ')}`);
|
|
334
363
|
ctx.missingServices = missingServices;
|
|
@@ -343,6 +372,7 @@ export async function update(options) {
|
|
|
343
372
|
if (servicesToInstall.guardianApp) servicesAdded.push('Guardian App');
|
|
344
373
|
if (servicesToInstall.facialWeb) servicesAdded.push('Facial Web');
|
|
345
374
|
if (servicesToInstall.posApp) servicesAdded.push('POS App');
|
|
375
|
+
if (servicesToInstall.clientApp) servicesAdded.push('Client App');
|
|
346
376
|
|
|
347
377
|
// Update docker-compose.yml with new services
|
|
348
378
|
updateDockerCompose(composeFile, envFile, servicesToInstall);
|
|
@@ -11,10 +11,12 @@ export function generateDockerCompose(options = {}) {
|
|
|
11
11
|
guardianAppPort = 8082,
|
|
12
12
|
facialWebPort = 8083,
|
|
13
13
|
posAppPort = 8084,
|
|
14
|
+
clientAppPort = 9005,
|
|
14
15
|
installGate = false,
|
|
15
16
|
installGuardian = false,
|
|
16
17
|
installFacial = false,
|
|
17
18
|
installPos = false,
|
|
19
|
+
installClient = false,
|
|
18
20
|
companyId = 1
|
|
19
21
|
} = options;
|
|
20
22
|
|
|
@@ -299,6 +301,34 @@ ${installGate ? `
|
|
|
299
301
|
options:
|
|
300
302
|
max-size: "10m"
|
|
301
303
|
max-file: "3"
|
|
304
|
+
` : ''}${installClient ? `
|
|
305
|
+
# ANTE Client App
|
|
306
|
+
client-app:
|
|
307
|
+
image: ghcr.io/gtplusnet/ante-self-hosted-ante-client:latest
|
|
308
|
+
container_name: ante-client
|
|
309
|
+
restart: unless-stopped
|
|
310
|
+
depends_on:
|
|
311
|
+
backend:
|
|
312
|
+
condition: service_healthy
|
|
313
|
+
environment:
|
|
314
|
+
- VITE_API_URL=\${API_URL:-http://localhost:${backendPort}}
|
|
315
|
+
- VITE_APP_NAME=ANTE Client Portal
|
|
316
|
+
- VITE_APP_VERSION=\${VERSION:-1.0.0}
|
|
317
|
+
ports:
|
|
318
|
+
- "${clientAppPort}:9005"
|
|
319
|
+
networks:
|
|
320
|
+
- ante-network
|
|
321
|
+
healthcheck:
|
|
322
|
+
test: ["CMD", "wget", "--no-verbose", "--tries=1", "--spider", "http://localhost:9005/"]
|
|
323
|
+
interval: 30s
|
|
324
|
+
timeout: 10s
|
|
325
|
+
retries: 3
|
|
326
|
+
start_period: 40s
|
|
327
|
+
logging:
|
|
328
|
+
driver: "json-file"
|
|
329
|
+
options:
|
|
330
|
+
max-size: "10m"
|
|
331
|
+
max-file: "3"
|
|
302
332
|
` : ''}
|
|
303
333
|
volumes:
|
|
304
334
|
postgres_data:
|
package/src/templates/env.js
CHANGED
|
@@ -15,15 +15,18 @@ export function generateEnv(credentials, options = {}) {
|
|
|
15
15
|
guardianAppPort = 8082,
|
|
16
16
|
facialWebPort = 8083,
|
|
17
17
|
posAppPort = 8084,
|
|
18
|
+
clientAppPort = 9005,
|
|
18
19
|
gateAppUrl = 'http://localhost:8081',
|
|
19
20
|
guardianAppUrl = 'http://localhost:8082',
|
|
20
21
|
facialWebUrl = 'http://localhost:8083',
|
|
21
22
|
posAppUrl = 'http://localhost:8084',
|
|
23
|
+
clientAppUrl = 'http://localhost:9005',
|
|
22
24
|
companyId = 1,
|
|
23
25
|
installGate = false,
|
|
24
26
|
installGuardian = false,
|
|
25
27
|
installFacial = false,
|
|
26
28
|
installPos = false,
|
|
29
|
+
installClient = false,
|
|
27
30
|
smtpHost = '',
|
|
28
31
|
smtpPort = 587,
|
|
29
32
|
smtpUsername = '',
|
|
@@ -68,9 +71,10 @@ ${installGate ? `GATE_APP_PORT=${gateAppPort}` : '# GATE_APP_PORT=8081'}
|
|
|
68
71
|
${installGuardian ? `GUARDIAN_APP_PORT=${guardianAppPort}` : '# GUARDIAN_APP_PORT=8082'}
|
|
69
72
|
${installFacial ? `FACIAL_WEB_PORT=${facialWebPort}` : '# FACIAL_WEB_PORT=8083'}
|
|
70
73
|
${installPos ? `POS_APP_PORT=${posAppPort}` : '# POS_APP_PORT=8084'}
|
|
74
|
+
${installClient ? `CLIENT_APP_PORT=${clientAppPort}` : '# CLIENT_APP_PORT=9005'}
|
|
71
75
|
# Note: WebSocket runs on the same port as backend (BACKEND_PORT)
|
|
72
76
|
|
|
73
|
-
${(installGate || installGuardian || installFacial || installPos) ? `# ------------------------------------------------------------------------------
|
|
77
|
+
${(installGate || installGuardian || installFacial || installPos || installClient) ? `# ------------------------------------------------------------------------------
|
|
74
78
|
# FRONTEND APP CONFIGURATION
|
|
75
79
|
# ------------------------------------------------------------------------------
|
|
76
80
|
COMPANY_ID=${companyId}
|
|
@@ -78,6 +82,7 @@ ${installGate ? `GATE_APP_URL=${gateAppUrl}` : '# GATE_APP_URL=http://localhost:
|
|
|
78
82
|
${installGuardian ? `GUARDIAN_APP_URL=${guardianAppUrl}` : '# GUARDIAN_APP_URL=http://localhost:8082'}
|
|
79
83
|
${installFacial ? `FACIAL_WEB_URL=${facialWebUrl}` : '# FACIAL_WEB_URL=http://localhost:8083'}
|
|
80
84
|
${installPos ? `POS_APP_URL=${posAppUrl}` : '# POS_APP_URL=http://localhost:8084'}
|
|
85
|
+
${installClient ? `CLIENT_APP_URL=${clientAppUrl}` : '# CLIENT_APP_URL=http://localhost:9005'}
|
|
81
86
|
|
|
82
87
|
` : ''}# ------------------------------------------------------------------------------
|
|
83
88
|
# EMAIL CONFIGURATION
|
package/src/utils/nginx.js
CHANGED
|
@@ -52,12 +52,14 @@ export async function installNginx(spinner) {
|
|
|
52
52
|
* @param {string} [config.guardianAppDomain] - Guardian app domain (optional)
|
|
53
53
|
* @param {string} [config.facialAppDomain] - Facial web app domain (optional)
|
|
54
54
|
* @param {string} [config.posAppDomain] - POS app domain (optional)
|
|
55
|
+
* @param {string} [config.clientAppDomain] - Client app domain (optional)
|
|
55
56
|
* @param {number} config.frontendPort - Frontend Docker port (default: 8080)
|
|
56
57
|
* @param {number} config.apiPort - API Docker port (default: 3001)
|
|
57
58
|
* @param {number} [config.gateAppPort] - Gate app Docker port (default: 8081)
|
|
58
59
|
* @param {number} [config.guardianAppPort] - Guardian app Docker port (default: 8082)
|
|
59
60
|
* @param {number} [config.facialWebPort] - Facial web app Docker port (default: 8083)
|
|
60
61
|
* @param {number} [config.posAppPort] - POS app Docker port (default: 8084)
|
|
62
|
+
* @param {number} [config.clientAppPort] - Client app Docker port (default: 9005)
|
|
61
63
|
* @param {boolean} config.ssl - Enable SSL configuration (default: false)
|
|
62
64
|
* @returns {string} NGINX configuration content
|
|
63
65
|
*/
|
|
@@ -69,12 +71,14 @@ export function generateNginxConfig(config) {
|
|
|
69
71
|
guardianAppDomain,
|
|
70
72
|
facialAppDomain,
|
|
71
73
|
posAppDomain,
|
|
74
|
+
clientAppDomain,
|
|
72
75
|
frontendPort = 8080,
|
|
73
76
|
apiPort = 3001,
|
|
74
77
|
gateAppPort = 8081,
|
|
75
78
|
guardianAppPort = 8082,
|
|
76
79
|
facialWebPort = 8083,
|
|
77
80
|
posAppPort = 8084,
|
|
81
|
+
clientAppPort = 9005,
|
|
78
82
|
ssl = false
|
|
79
83
|
} = config;
|
|
80
84
|
|
|
@@ -85,6 +89,7 @@ export function generateNginxConfig(config) {
|
|
|
85
89
|
const guardianAppHost = guardianAppDomain ? guardianAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
86
90
|
const facialAppHost = facialAppDomain ? facialAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
87
91
|
const posAppHost = posAppDomain ? posAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
92
|
+
const clientAppHost = clientAppDomain ? clientAppDomain.replace(/^https?:\/\//, '').replace(/:\d+$/, '') : null;
|
|
88
93
|
|
|
89
94
|
if (ssl) {
|
|
90
95
|
return generateSslNginxConfig({
|
|
@@ -94,12 +99,14 @@ export function generateNginxConfig(config) {
|
|
|
94
99
|
guardianAppHost,
|
|
95
100
|
facialAppHost,
|
|
96
101
|
posAppHost,
|
|
102
|
+
clientAppHost,
|
|
97
103
|
frontendPort,
|
|
98
104
|
apiPort,
|
|
99
105
|
gateAppPort,
|
|
100
106
|
guardianAppPort,
|
|
101
107
|
facialWebPort,
|
|
102
|
-
posAppPort
|
|
108
|
+
posAppPort,
|
|
109
|
+
clientAppPort
|
|
103
110
|
});
|
|
104
111
|
}
|
|
105
112
|
|
|
@@ -292,6 +299,37 @@ server {
|
|
|
292
299
|
proxy_read_timeout 60s;
|
|
293
300
|
}
|
|
294
301
|
}
|
|
302
|
+
` : ''}${clientAppHost ? `
|
|
303
|
+
# ANTE Client App Configuration
|
|
304
|
+
server {
|
|
305
|
+
listen 80;
|
|
306
|
+
listen [::]:80;
|
|
307
|
+
server_name ${clientAppHost};
|
|
308
|
+
|
|
309
|
+
# Increase buffer sizes for large headers
|
|
310
|
+
client_header_buffer_size 16k;
|
|
311
|
+
large_client_header_buffers 4 16k;
|
|
312
|
+
|
|
313
|
+
# Increase body size for file uploads
|
|
314
|
+
client_max_body_size 100M;
|
|
315
|
+
|
|
316
|
+
location / {
|
|
317
|
+
proxy_pass http://localhost:${clientAppPort};
|
|
318
|
+
proxy_http_version 1.1;
|
|
319
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
320
|
+
proxy_set_header Connection 'upgrade';
|
|
321
|
+
proxy_set_header Host $host;
|
|
322
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
323
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
324
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
325
|
+
proxy_cache_bypass $http_upgrade;
|
|
326
|
+
|
|
327
|
+
# Timeout settings
|
|
328
|
+
proxy_connect_timeout 60s;
|
|
329
|
+
proxy_send_timeout 60s;
|
|
330
|
+
proxy_read_timeout 60s;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
295
333
|
` : ''}`;
|
|
296
334
|
}
|
|
297
335
|
|
|
@@ -304,12 +342,14 @@ server {
|
|
|
304
342
|
* @param {string} [config.guardianAppHost] - Guardian app hostname (optional)
|
|
305
343
|
* @param {string} [config.facialAppHost] - Facial web app hostname (optional)
|
|
306
344
|
* @param {string} [config.posAppHost] - POS app hostname (optional)
|
|
345
|
+
* @param {string} [config.clientAppHost] - Client app hostname (optional)
|
|
307
346
|
* @param {number} config.frontendPort - Frontend port
|
|
308
347
|
* @param {number} config.apiPort - API port
|
|
309
348
|
* @param {number} [config.gateAppPort] - Gate app port (default: 8081)
|
|
310
349
|
* @param {number} [config.guardianAppPort] - Guardian app port (default: 8082)
|
|
311
350
|
* @param {number} [config.facialWebPort] - Facial web app port (default: 8083)
|
|
312
351
|
* @param {number} [config.posAppPort] - POS app port (default: 8084)
|
|
352
|
+
* @param {number} [config.clientAppPort] - Client app port (default: 9005)
|
|
313
353
|
* @returns {string} NGINX configuration with SSL
|
|
314
354
|
*/
|
|
315
355
|
function generateSslNginxConfig(config) {
|
|
@@ -320,12 +360,14 @@ function generateSslNginxConfig(config) {
|
|
|
320
360
|
guardianAppHost,
|
|
321
361
|
facialAppHost,
|
|
322
362
|
posAppHost,
|
|
363
|
+
clientAppHost,
|
|
323
364
|
frontendPort,
|
|
324
365
|
apiPort,
|
|
325
366
|
gateAppPort = 8081,
|
|
326
367
|
guardianAppPort = 8082,
|
|
327
368
|
facialWebPort = 8083,
|
|
328
|
-
posAppPort = 8084
|
|
369
|
+
posAppPort = 8084,
|
|
370
|
+
clientAppPort = 9005
|
|
329
371
|
} = config;
|
|
330
372
|
|
|
331
373
|
return `# ANTE Frontend Configuration (HTTPS)
|
|
@@ -683,6 +725,63 @@ server {
|
|
|
683
725
|
server_name ${posAppHost};
|
|
684
726
|
return 301 https://$server_name$request_uri;
|
|
685
727
|
}
|
|
728
|
+
` : ''}${clientAppHost ? `
|
|
729
|
+
# ANTE Client App Configuration (HTTPS)
|
|
730
|
+
server {
|
|
731
|
+
listen 443 ssl;
|
|
732
|
+
listen [::]:443 ssl;
|
|
733
|
+
http2 on;
|
|
734
|
+
server_name ${clientAppHost};
|
|
735
|
+
|
|
736
|
+
# SSL Certificate paths
|
|
737
|
+
ssl_certificate /etc/letsencrypt/live/${clientAppHost}/fullchain.pem;
|
|
738
|
+
ssl_certificate_key /etc/letsencrypt/live/${clientAppHost}/privkey.pem;
|
|
739
|
+
|
|
740
|
+
# SSL Configuration
|
|
741
|
+
ssl_protocols TLSv1.2 TLSv1.3;
|
|
742
|
+
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
743
|
+
ssl_prefer_server_ciphers on;
|
|
744
|
+
ssl_session_cache shared:SSL:10m;
|
|
745
|
+
ssl_session_timeout 10m;
|
|
746
|
+
|
|
747
|
+
# Security headers
|
|
748
|
+
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
|
|
749
|
+
add_header X-Frame-Options SAMEORIGIN always;
|
|
750
|
+
add_header X-Content-Type-Options nosniff always;
|
|
751
|
+
add_header X-XSS-Protection "1; mode=block" always;
|
|
752
|
+
|
|
753
|
+
# Increase buffer sizes for large headers
|
|
754
|
+
client_header_buffer_size 16k;
|
|
755
|
+
large_client_header_buffers 4 16k;
|
|
756
|
+
|
|
757
|
+
# Increase body size for file uploads
|
|
758
|
+
client_max_body_size 100M;
|
|
759
|
+
|
|
760
|
+
location / {
|
|
761
|
+
proxy_pass http://localhost:${clientAppPort};
|
|
762
|
+
proxy_http_version 1.1;
|
|
763
|
+
proxy_set_header Upgrade $http_upgrade;
|
|
764
|
+
proxy_set_header Connection 'upgrade';
|
|
765
|
+
proxy_set_header Host $host;
|
|
766
|
+
proxy_set_header X-Real-IP $remote_addr;
|
|
767
|
+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
768
|
+
proxy_set_header X-Forwarded-Proto $scheme;
|
|
769
|
+
proxy_cache_bypass $http_upgrade;
|
|
770
|
+
|
|
771
|
+
# Timeout settings
|
|
772
|
+
proxy_connect_timeout 60s;
|
|
773
|
+
proxy_send_timeout 60s;
|
|
774
|
+
proxy_read_timeout 60s;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
# HTTP to HTTPS redirect for ${clientAppHost}
|
|
779
|
+
server {
|
|
780
|
+
listen 80;
|
|
781
|
+
listen [::]:80;
|
|
782
|
+
server_name ${clientAppHost};
|
|
783
|
+
return 301 https://$server_name$request_uri;
|
|
784
|
+
}
|
|
686
785
|
` : ''}`;
|
|
687
786
|
}
|
|
688
787
|
|
|
@@ -762,6 +861,12 @@ export async function configureNginx(config) {
|
|
|
762
861
|
if (config.facialAppDomain) {
|
|
763
862
|
console.log(chalk.gray(` Facial Web: ${config.facialAppDomain} → localhost:${config.facialWebPort || 8083}`));
|
|
764
863
|
}
|
|
864
|
+
if (config.posAppDomain) {
|
|
865
|
+
console.log(chalk.gray(` POS App: ${config.posAppDomain} → localhost:${config.posAppPort || 8084}`));
|
|
866
|
+
}
|
|
867
|
+
if (config.clientAppDomain) {
|
|
868
|
+
console.log(chalk.gray(` Client App: ${config.clientAppDomain} → localhost:${config.clientAppPort || 9005}`));
|
|
869
|
+
}
|
|
765
870
|
console.log(chalk.gray(` API: ${config.apiDomain} → localhost:${config.apiPort || 3001}`));
|
|
766
871
|
|
|
767
872
|
} catch (error) {
|