ante-erp-cli 1.11.22 → 1.11.24

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/bin/ante-cli.js CHANGED
@@ -51,6 +51,7 @@ program
51
51
  .option('--no-interactive', 'Non-interactive mode with defaults')
52
52
  .option('--skip-checks', 'Skip system requirements check')
53
53
  .option('--force', 'Force reinstall even if already installed (dangerous)')
54
+ .option('--preserve-credentials', 'Preserve existing credentials during force reinstall')
54
55
  .option('--with-facial', 'Install Facial Recognition Web app')
55
56
  .option('--with-gate', 'Install Gate App')
56
57
  .option('--with-guardian', 'Install Guardian App')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ante-erp-cli",
3
- "version": "1.11.22",
3
+ "version": "1.11.24",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,7 @@
1
1
  import chalk from 'chalk';
2
2
  import boxen from 'boxen';
3
3
  import ora from 'ora';
4
+ import inquirer from 'inquirer';
4
5
  import { Listr } from 'listr2';
5
6
  import { execa } from 'execa';
6
7
  import { mkdirSync, writeFileSync, existsSync, renameSync, readFileSync } from 'fs';
@@ -155,6 +156,39 @@ export async function install(options) {
155
156
  console.log(chalk.yellow('\n⚠️ WARNING: Force reinstall mode\n'));
156
157
  console.log(chalk.red('This will overwrite your existing installation at: ') + chalk.white(existing));
157
158
  console.log(chalk.gray('Existing .env file will be backed up automatically\n'));
159
+
160
+ // Add strong warning about credential regeneration
161
+ if (!options.preserveCredentials) {
162
+ console.log(chalk.red('⚠️ CRITICAL WARNING:\n'));
163
+ console.log(chalk.yellow('This will generate NEW database credentials!'));
164
+ console.log(chalk.yellow('Your existing PostgreSQL, MongoDB, and Redis passwords will be replaced.'));
165
+ console.log(chalk.yellow('This will BREAK database connections if volumes persist!\n'));
166
+ console.log(chalk.cyan('To preserve existing credentials, use: ') + chalk.white('--preserve-credentials\n'));
167
+
168
+ // Require interactive confirmation
169
+ const { confirmReinstall } = await inquirer.prompt([
170
+ {
171
+ type: 'input',
172
+ name: 'confirmReinstall',
173
+ message: 'Type "reinstall-and-reset-credentials" to confirm:',
174
+ validate: (input) => {
175
+ if (input === 'reinstall-and-reset-credentials') {
176
+ return true;
177
+ }
178
+ return 'You must type the exact phrase to continue';
179
+ }
180
+ }
181
+ ]);
182
+
183
+ if (confirmReinstall !== 'reinstall-and-reset-credentials') {
184
+ console.error(chalk.red('\n✗ Installation cancelled.\n'));
185
+ process.exit(1);
186
+ }
187
+
188
+ console.log(chalk.green('\n✓ Confirmation received. Proceeding with force reinstall...\n'));
189
+ } else {
190
+ console.log(chalk.green('✓ Preserving existing credentials from .env file\n'));
191
+ }
158
192
  }
159
193
 
160
194
  // Calculate total steps
@@ -269,10 +303,53 @@ export async function install(options) {
269
303
  }
270
304
  }
271
305
 
272
- // Step: Generate credentials
273
- console.log(chalk.cyan(formatStepTitle(stepGenerateCredentials, totalSteps, 'Generating secure credentials')));
274
- const credentials = generateCredentials();
275
- console.log(chalk.green(`✓ ${formatStepTitle(stepGenerateCredentials, totalSteps, 'Secure credentials generated')}\n`));
306
+ // Step: Generate or preserve credentials
307
+ let credentials;
308
+ if (existing && options.force && options.preserveCredentials) {
309
+ // Preserve credentials from existing .env file
310
+ console.log(chalk.cyan(formatStepTitle(stepGenerateCredentials, totalSteps, 'Preserving existing credentials')));
311
+ const existingEnvPath = join(existing, '.env');
312
+ if (existsSync(existingEnvPath)) {
313
+ const envContent = readFileSync(existingEnvPath, 'utf8');
314
+
315
+ // Extract credentials from existing .env
316
+ const extractCredential = (key) => {
317
+ const match = envContent.match(new RegExp(`^${key}=(.+)$`, 'm'));
318
+ return match ? match[1].trim() : null;
319
+ };
320
+
321
+ credentials = {
322
+ dbPassword: extractCredential('DB_PASSWORD'),
323
+ redisPassword: extractCredential('REDIS_PASSWORD'),
324
+ mongoPassword: extractCredential('MONGO_PASSWORD'),
325
+ jwtSecret: extractCredential('JWT_SECRET'),
326
+ developerKey: extractCredential('DEVELOPER_KEY'),
327
+ encryptionKey: extractCredential('ENCRYPTION_KEY')
328
+ };
329
+
330
+ // Validate that all credentials were found
331
+ const missingCredentials = Object.entries(credentials)
332
+ .filter(([key, value]) => !value)
333
+ .map(([key]) => key);
334
+
335
+ if (missingCredentials.length > 0) {
336
+ console.error(chalk.red(`\n✗ Unable to preserve credentials. Missing: ${missingCredentials.join(', ')}`));
337
+ console.error(chalk.gray('Falling back to generating new credentials...\n'));
338
+ credentials = generateCredentials();
339
+ } else {
340
+ console.log(chalk.green(`✓ ${formatStepTitle(stepGenerateCredentials, totalSteps, 'Existing credentials preserved')}\n`));
341
+ }
342
+ } else {
343
+ console.error(chalk.red('\n✗ Unable to find existing .env file'));
344
+ console.error(chalk.gray('Falling back to generating new credentials...\n'));
345
+ credentials = generateCredentials();
346
+ }
347
+ } else {
348
+ // Generate new credentials
349
+ console.log(chalk.cyan(formatStepTitle(stepGenerateCredentials, totalSteps, 'Generating secure credentials')));
350
+ credentials = generateCredentials();
351
+ console.log(chalk.green(`✓ ${formatStepTitle(stepGenerateCredentials, totalSteps, 'Secure credentials generated')}\n`));
352
+ }
276
353
 
277
354
  // Installation tasks with Listr2
278
355
  const tasks = new Listr([
@@ -402,73 +402,140 @@ export async function setDomain(options) {
402
402
  if (hasPosApp) posAppUrl = buildURL(ipConfig.ip, ipConfig.posAppPort);
403
403
  } else {
404
404
  // Manual URL input (domain configuration)
405
- const domainPrompts = [
405
+
406
+ // First, ask if using ante.ph domain setup
407
+ const antePhSetup = await inquirer.prompt([
406
408
  {
407
- type: 'input',
408
- name: 'frontendUrl',
409
- message: 'Frontend URL:\n Examples: https://staging.ante.ph or http://143.198.91.153:8080\n Enter URL',
410
- default: currentFrontendUrl || 'http://localhost:8080',
411
- validate: validateUrl
409
+ type: 'confirm',
410
+ name: 'useAntePh',
411
+ message: 'Are you setting up domains on ante.ph?',
412
+ default: false
412
413
  }
413
- ];
414
+ ]);
414
415
 
415
- if (hasGateApp) {
416
- domainPrompts.push({
417
- type: 'input',
418
- name: 'gateAppUrl',
419
- message: 'Gate App URL:\n Examples: https://gate.ante.ph or http://143.198.91.153:8081\n Enter URL',
420
- default: currentGateAppUrl || 'http://localhost:8081',
421
- validate: validateUrl
422
- });
423
- }
416
+ if (antePhSetup.useAntePh) {
417
+ // ante.ph subdomain-based setup
418
+ const subdomainPrompt = await inquirer.prompt([
419
+ {
420
+ type: 'input',
421
+ name: 'subdomain',
422
+ message: 'Enter subdomain prefix (e.g., "test" for test.ante.ph):',
423
+ validate: (input) => {
424
+ if (!input || input.trim() === '') {
425
+ return 'Subdomain cannot be empty';
426
+ }
427
+ // Validate subdomain format (alphanumeric and hyphens only)
428
+ if (!/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i.test(input.trim())) {
429
+ return 'Subdomain must contain only letters, numbers, and hyphens (cannot start or end with hyphen)';
430
+ }
431
+ return true;
432
+ }
433
+ }
434
+ ]);
424
435
 
425
- if (hasGuardianApp) {
426
- domainPrompts.push({
427
- type: 'input',
428
- name: 'guardianAppUrl',
429
- message: 'Guardian App URL:\n Examples: https://guardian.ante.ph or http://143.198.91.153:8082\n Enter URL',
430
- default: currentGuardianAppUrl || 'http://localhost:8082',
431
- validate: validateUrl
432
- });
433
- }
436
+ const subdomain = subdomainPrompt.subdomain.trim();
437
+
438
+ // Auto-generate URLs with ante.ph pattern
439
+ frontendUrl = `https://${subdomain}.ante.ph`;
440
+ apiUrl = `https://${subdomain}-api.ante.ph`;
441
+ if (hasGateApp) gateAppUrl = `https://${subdomain}-gate.ante.ph`;
442
+ if (hasGuardianApp) guardianAppUrl = `https://${subdomain}-guardian.ante.ph`;
443
+ if (hasFacialWeb) facialWebUrl = `https://${subdomain}-fr.ante.ph`;
444
+ if (hasPosApp) posAppUrl = `https://${subdomain}-pos.ante.ph`;
445
+
446
+ // Display generated domains for confirmation
447
+ console.log(chalk.cyan('\n📋 Generated Domain Configuration:\n'));
448
+ console.log(chalk.gray(` Frontend: ${frontendUrl}`));
449
+ console.log(chalk.gray(` Backend API: ${apiUrl}`));
450
+ if (hasGateApp) console.log(chalk.gray(` Gate App: ${gateAppUrl}`));
451
+ if (hasGuardianApp) console.log(chalk.gray(` Guardian App: ${guardianAppUrl}`));
452
+ if (hasFacialWeb) console.log(chalk.gray(` Facial Web: ${facialWebUrl}`));
453
+ if (hasPosApp) console.log(chalk.gray(` POS App: ${posAppUrl}`));
454
+ console.log('');
455
+
456
+ const confirmDomains = await inquirer.prompt([
457
+ {
458
+ type: 'confirm',
459
+ name: 'confirmed',
460
+ message: 'Proceed with these domains?',
461
+ default: true
462
+ }
463
+ ]);
434
464
 
435
- if (hasFacialWeb) {
436
- domainPrompts.push({
437
- type: 'input',
438
- name: 'facialWebUrl',
439
- message: 'Facial Web URL:\n Examples: https://facial.ante.ph or http://143.198.91.153:8083\n Enter URL',
440
- default: currentFacialWebUrl || 'http://localhost:8083',
441
- validate: validateUrl
442
- });
443
- }
465
+ if (!confirmDomains.confirmed) {
466
+ console.log(chalk.yellow('\nDomain setup cancelled. Please run the command again.\n'));
467
+ process.exit(0);
468
+ }
469
+ } else {
470
+ // Manual URL input for custom domains
471
+ const domainPrompts = [
472
+ {
473
+ type: 'input',
474
+ name: 'frontendUrl',
475
+ message: 'Frontend URL:\n Examples: https://staging.ante.ph or http://143.198.91.153:8080\n Enter URL',
476
+ default: currentFrontendUrl || 'http://localhost:8080',
477
+ validate: validateUrl
478
+ }
479
+ ];
480
+
481
+ if (hasGateApp) {
482
+ domainPrompts.push({
483
+ type: 'input',
484
+ name: 'gateAppUrl',
485
+ message: 'Gate App URL:\n Examples: https://gate.ante.ph or http://143.198.91.153:8081\n Enter URL',
486
+ default: currentGateAppUrl || 'http://localhost:8081',
487
+ validate: validateUrl
488
+ });
489
+ }
490
+
491
+ if (hasGuardianApp) {
492
+ domainPrompts.push({
493
+ type: 'input',
494
+ name: 'guardianAppUrl',
495
+ message: 'Guardian App URL:\n Examples: https://guardian.ante.ph or http://143.198.91.153:8082\n Enter URL',
496
+ default: currentGuardianAppUrl || 'http://localhost:8082',
497
+ validate: validateUrl
498
+ });
499
+ }
500
+
501
+ if (hasFacialWeb) {
502
+ domainPrompts.push({
503
+ type: 'input',
504
+ name: 'facialWebUrl',
505
+ message: 'Facial Web URL:\n Examples: https://facial.ante.ph or http://143.198.91.153:8083\n Enter URL',
506
+ default: currentFacialWebUrl || 'http://localhost:8083',
507
+ validate: validateUrl
508
+ });
509
+ }
510
+
511
+ if (hasPosApp) {
512
+ domainPrompts.push({
513
+ type: 'input',
514
+ name: 'posAppUrl',
515
+ message: 'POS App URL:\n Examples: https://pos.ante.ph or http://143.198.91.153:8084\n Enter URL',
516
+ default: currentPosAppUrl || 'http://localhost:8084',
517
+ validate: validateUrl
518
+ });
519
+ }
444
520
 
445
- if (hasPosApp) {
446
521
  domainPrompts.push({
447
522
  type: 'input',
448
- name: 'posAppUrl',
449
- message: 'POS App URL:\n Examples: https://pos.ante.ph or http://143.198.91.153:8084\n Enter URL',
450
- default: currentPosAppUrl || 'http://localhost:8084',
523
+ name: 'apiUrl',
524
+ message: 'Backend API URL:\n Examples: https://staging-api.ante.ph or http://143.198.91.153:3001\n Enter URL',
525
+ default: currentApiUrl || 'http://localhost:3001',
451
526
  validate: validateUrl
452
527
  });
453
- }
454
528
 
455
- domainPrompts.push({
456
- type: 'input',
457
- name: 'apiUrl',
458
- message: 'Backend API URL:\n Examples: https://staging-api.ante.ph or http://143.198.91.153:3001\n Enter URL',
459
- default: currentApiUrl || 'http://localhost:3001',
460
- validate: validateUrl
461
- });
462
-
463
- const answers = await inquirer.prompt(domainPrompts);
529
+ const answers = await inquirer.prompt(domainPrompts);
464
530
 
465
- // Sanitize URLs to fix common formatting issues
466
- frontendUrl = sanitizeUrl(answers.frontendUrl);
467
- apiUrl = sanitizeUrl(answers.apiUrl);
468
- if (hasGateApp) gateAppUrl = sanitizeUrl(answers.gateAppUrl);
469
- if (hasGuardianApp) guardianAppUrl = sanitizeUrl(answers.guardianAppUrl);
470
- if (hasFacialWeb) facialWebUrl = sanitizeUrl(answers.facialWebUrl);
471
- if (hasPosApp) posAppUrl = sanitizeUrl(answers.posAppUrl);
531
+ // Sanitize URLs to fix common formatting issues
532
+ frontendUrl = sanitizeUrl(answers.frontendUrl);
533
+ apiUrl = sanitizeUrl(answers.apiUrl);
534
+ if (hasGateApp) gateAppUrl = sanitizeUrl(answers.gateAppUrl);
535
+ if (hasGuardianApp) guardianAppUrl = sanitizeUrl(answers.guardianAppUrl);
536
+ if (hasFacialWeb) facialWebUrl = sanitizeUrl(answers.facialWebUrl);
537
+ if (hasPosApp) posAppUrl = sanitizeUrl(answers.posAppUrl);
538
+ }
472
539
  }
473
540
  } else {
474
541
  // Non-interactive mode
@@ -603,7 +670,8 @@ export async function setDomain(options) {
603
670
  {
604
671
  type: 'input',
605
672
  name: 'email',
606
- message: 'Email address for certificate notifications:',
673
+ message: 'Email address for certificate notifications (default: admin@ante.ph):',
674
+ default: 'admin@ante.ph',
607
675
  validate: validateEmail
608
676
  }
609
677
  ]);