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 +1 -0
- package/package.json +1 -1
- package/src/commands/install.js +81 -4
- package/src/commands/set-domain.js +124 -56
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
package/src/commands/install.js
CHANGED
|
@@ -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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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
|
-
|
|
405
|
+
|
|
406
|
+
// First, ask if using ante.ph domain setup
|
|
407
|
+
const antePhSetup = await inquirer.prompt([
|
|
406
408
|
{
|
|
407
|
-
type: '
|
|
408
|
-
name: '
|
|
409
|
-
message: '
|
|
410
|
-
default:
|
|
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 (
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
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
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
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: '
|
|
449
|
-
message: '
|
|
450
|
-
default:
|
|
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
|
-
|
|
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
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
]);
|