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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ante-erp-cli",
3
- "version": "1.11.36",
3
+ "version": "1.11.37",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -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
- frontends: ['main', 'gate', 'guardian', 'facial', 'pos']
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'}\n`));
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) {
@@ -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:
@@ -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
@@ -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) {