ante-erp-cli 1.9.7 → 1.9.9

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
@@ -32,6 +32,8 @@ import { doctor } from '../src/commands/doctor.js';
32
32
  import { uninstall } from '../src/commands/uninstall.js';
33
33
  import { migrate, seed, shell, optimize, reset as dbReset, info } from '../src/commands/database.js';
34
34
  import { cloneDb } from '../src/commands/clone-db.js';
35
+ import { cloneMongodb } from '../src/commands/clone-mongodb.js';
36
+ import { exposeDbPorts, secureDbPorts, dbPortStatus } from '../src/commands/database-ports.js';
35
37
  import { setDomain } from '../src/commands/set-domain.js';
36
38
  import { sslEnable, sslStatus } from '../src/commands/ssl-enable.js';
37
39
  import { regenerateCompose } from '../src/commands/regenerate-compose.js';
@@ -47,6 +49,10 @@ program
47
49
  .option('--preset <type>', 'Installation preset (minimal, standard, enterprise)', 'standard')
48
50
  .option('--no-interactive', 'Non-interactive mode with defaults')
49
51
  .option('--skip-checks', 'Skip system requirements check')
52
+ .option('--with-facial', 'Install Facial Recognition Web app')
53
+ .option('--with-gate', 'Install Gate App')
54
+ .option('--with-guardian', 'Install Guardian App')
55
+ .option('--with-all-frontends', 'Install all frontend applications')
50
56
  .action(install);
51
57
 
52
58
  // Update & Maintenance
@@ -152,6 +158,37 @@ dbCmd
152
158
  .option('--no-prisma', 'Skip Prisma operations')
153
159
  .action(cloneDb);
154
160
 
161
+ dbCmd
162
+ .command('clone-mongodb <mongodb-url>')
163
+ .description('Clone remote MongoDB database to local')
164
+ .option('--skip-dump', 'Use existing backup directory')
165
+ .option('--backup-dir <path>', 'Path to existing backup directory')
166
+ .option('--force', 'Skip confirmation prompts')
167
+ .action(cloneMongodb);
168
+
169
+ dbCmd
170
+ .command('expose')
171
+ .description('Expose database ports for external access (DEVELOPMENT ONLY)')
172
+ .option('--postgres', 'Expose only PostgreSQL (5432)')
173
+ .option('--redis', 'Expose only Redis (6379)')
174
+ .option('--mongodb', 'Expose only MongoDB (27017)')
175
+ .option('--all', 'Expose all databases (default)', true)
176
+ .option('--force', 'Skip confirmation prompts')
177
+ .option('--no-restart', 'Don\'t restart services automatically')
178
+ .action(exposeDbPorts);
179
+
180
+ dbCmd
181
+ .command('secure')
182
+ .description('Remove database port bindings (secure databases)')
183
+ .option('--force', 'Skip confirmation prompts')
184
+ .option('--no-restart', 'Don\'t restart services automatically')
185
+ .action(secureDbPorts);
186
+
187
+ dbCmd
188
+ .command('port-status')
189
+ .description('Show database port exposure status')
190
+ .action(dbPortStatus);
191
+
155
192
  // Status & Monitoring
156
193
  program
157
194
  .command('status')
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ante-erp-cli",
3
- "version": "1.9.7",
3
+ "version": "1.9.9",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,25 +42,25 @@
42
42
  "node": ">=24.0.0"
43
43
  },
44
44
  "dependencies": {
45
- "commander": "^12.0.0",
46
- "inquirer": "^9.2.0",
47
- "chalk": "^5.3.0",
48
- "ora": "^8.0.0",
49
45
  "boxen": "^7.1.0",
50
- "execa": "^8.0.0",
51
- "listr2": "^8.0.0",
46
+ "chalk": "^5.3.0",
47
+ "cli-table3": "^0.6.3",
48
+ "commander": "^12.0.0",
52
49
  "conf": "^12.0.0",
53
50
  "dotenv": "^16.4.0",
54
- "js-yaml": "^4.1.0",
55
- "semver": "^7.6.0",
56
- "update-notifier": "^7.0.0",
51
+ "execa": "^8.0.0",
52
+ "inquirer": "^9.2.0",
53
+ "js-yaml": "^4.1.1",
54
+ "listr2": "^8.0.0",
57
55
  "node-fetch": "^3.3.0",
58
- "cli-table3": "^0.6.3",
59
- "prompts": "^2.4.2"
56
+ "ora": "^8.0.0",
57
+ "prompts": "^2.4.2",
58
+ "semver": "^7.6.0",
59
+ "update-notifier": "^7.0.0"
60
60
  },
61
61
  "devDependencies": {
62
62
  "eslint": "^8.57.0",
63
- "prettier": "^3.2.0",
64
- "jest": "^29.7.0"
63
+ "jest": "^29.7.0",
64
+ "prettier": "^3.2.0"
65
65
  }
66
66
  }
@@ -0,0 +1,290 @@
1
+ import chalk from 'chalk';
2
+ import ora from 'ora';
3
+ import inquirer from 'inquirer';
4
+ import { join } from 'path';
5
+ import { existsSync, readFileSync, readdirSync } from 'fs';
6
+ import { stat } from 'fs/promises';
7
+ import { getInstallDir } from '../utils/config.js';
8
+ import {
9
+ parseMongoUrl,
10
+ buildMongoUrl,
11
+ testConnection,
12
+ dumpDatabase,
13
+ restoreDatabase,
14
+ verifyDataRestored
15
+ } from '../utils/mongodb.js';
16
+
17
+ /**
18
+ * Build MONGODB_URI for installed ANTE instance
19
+ * @param {string} installDir - Installation directory
20
+ * @returns {string} MONGODB_URI for local MongoDB
21
+ */
22
+ function readMongoDbUri(installDir) {
23
+ const envFile = join(installDir, '.env');
24
+
25
+ if (!existsSync(envFile)) {
26
+ throw new Error(
27
+ '.env file not found. This command is for installed ANTE instances only.\n' +
28
+ 'For development environments, use the appropriate script.'
29
+ );
30
+ }
31
+
32
+ const envContent = readFileSync(envFile, 'utf-8');
33
+
34
+ // Try to find MONGODB_URI or MONGODB_URL
35
+ let match = envContent.match(/^MONGODB_URI=(.+)$/m);
36
+ if (!match) {
37
+ match = envContent.match(/^MONGODB_URL=(.+)$/m);
38
+ }
39
+
40
+ if (!match) {
41
+ throw new Error('MONGODB_URI or MONGODB_URL not found in .env file');
42
+ }
43
+
44
+ const mongoUri = match[1].trim();
45
+
46
+ // Validate it looks like a MongoDB URI
47
+ if (!mongoUri.startsWith('mongodb://') && !mongoUri.startsWith('mongodb+srv://')) {
48
+ throw new Error('Invalid MongoDB URI format in .env file');
49
+ }
50
+
51
+ return mongoUri;
52
+ }
53
+
54
+ /**
55
+ * Clone remote MongoDB database to local database
56
+ * @param {string} sourceUrl - Source MongoDB URL
57
+ * @param {Object} options - Command options
58
+ */
59
+ export async function cloneMongodb(sourceUrl, options = {}) {
60
+ try {
61
+ console.log('');
62
+ console.log(chalk.blue('╔════════════════════════════════════════════════════════════╗'));
63
+ console.log(chalk.blue('║ ANTE CLI - Clone MongoDB Database ║'));
64
+ console.log(chalk.blue('╚════════════════════════════════════════════════════════════╝'));
65
+ console.log('');
66
+
67
+ // Get ANTE installation directory
68
+ const installDir = getInstallDir();
69
+ const backupDir = join(installDir, 'backups', 'mongodb');
70
+ const composeFile = join(installDir, 'docker-compose.yml');
71
+
72
+ // Step 1: Parse source database URL
73
+ console.log(chalk.yellow('Step 1/5: Parsing source database URL...'));
74
+ let sourceInfo;
75
+ try {
76
+ sourceInfo = parseMongoUrl(sourceUrl);
77
+ console.log(chalk.green('✓ Source URL parsed successfully'));
78
+ console.log(chalk.gray(` Host: ${sourceInfo.host}${sourceInfo.port ? ':' + sourceInfo.port : ' (SRV)'}`));
79
+ console.log(chalk.gray(` Database: ${sourceInfo.database}`));
80
+ console.log(chalk.gray(` User: ${sourceInfo.user || '(none)'}`));
81
+ if (sourceInfo.authDatabase && sourceInfo.authDatabase !== sourceInfo.database) {
82
+ console.log(chalk.gray(` Auth Database: ${sourceInfo.authDatabase}`));
83
+ }
84
+ console.log('');
85
+ } catch (error) {
86
+ throw new Error(`Failed to parse source URL: ${error.message}`);
87
+ }
88
+
89
+ // Step 2: Parse local database URL
90
+ console.log(chalk.yellow('Step 2/5: Reading local database configuration...'));
91
+ const localMongoUri = readMongoDbUri(installDir);
92
+ if (!localMongoUri) {
93
+ throw new Error('.env file not found or MONGODB_URI not set');
94
+ }
95
+
96
+ let localInfo;
97
+ try {
98
+ localInfo = parseMongoUrl(localMongoUri);
99
+ console.log(chalk.green('✓ Local database configuration loaded'));
100
+ console.log(chalk.gray(` Host: ${localInfo.host}${localInfo.port ? ':' + localInfo.port : ' (SRV)'}`));
101
+ console.log(chalk.gray(` Database: ${localInfo.database}`));
102
+ console.log(chalk.gray(` User: ${localInfo.user || '(none)'}`));
103
+ console.log('');
104
+ } catch (error) {
105
+ throw new Error(`Failed to parse local MONGODB_URI: ${error.message}`);
106
+ }
107
+
108
+ // Step 3: Test connections
109
+ console.log(chalk.yellow('Step 3/5: Testing database connections...'));
110
+
111
+ // Test source connection
112
+ const sourceSpinner = ora('Testing source database connection...').start();
113
+ const sourceTest = await testConnection(sourceInfo);
114
+ if (!sourceTest.success) {
115
+ sourceSpinner.fail(chalk.red('Source database connection failed'));
116
+ throw new Error(`Cannot connect to source database: ${sourceTest.error}`);
117
+ }
118
+ sourceSpinner.succeed(chalk.green('Source database connection successful'));
119
+
120
+ // Test local connection (use Docker Compose for installed ANTE)
121
+ const localSpinner = ora('Testing local database connection...').start();
122
+ const localTest = await testConnection(localInfo, composeFile);
123
+ if (!localTest.success) {
124
+ localSpinner.fail(chalk.red('Local database connection failed'));
125
+ throw new Error(`Cannot connect to local database: ${localTest.error}`);
126
+ }
127
+ localSpinner.succeed(chalk.green('Local database connection successful'));
128
+ console.log('');
129
+
130
+ // Step 4: Confirmation
131
+ if (!options.force) {
132
+ console.log(chalk.yellow('⚠️ WARNING: This will DROP and REPLACE your local database collections!'));
133
+ console.log(chalk.yellow('⚠️ All existing local data will be LOST!'));
134
+ console.log('');
135
+ console.log(chalk.blue('Configuration Summary:'));
136
+ console.log(chalk.gray('┌─ Source Database:'));
137
+ console.log(chalk.gray(`│ Host: ${sourceInfo.host}${sourceInfo.port ? ':' + sourceInfo.port : ' (SRV)'}`));
138
+ console.log(chalk.gray(`│ Database: ${sourceInfo.database}`));
139
+ console.log(chalk.gray('│'));
140
+ console.log(chalk.gray('└─ Target Database (Local):'));
141
+ console.log(chalk.gray(` Host: ${localInfo.host}${localInfo.port ? ':' + localInfo.port : ' (SRV)'}`));
142
+ console.log(chalk.gray(` Database: ${localInfo.database}`));
143
+ console.log('');
144
+
145
+ const { confirm } = await inquirer.prompt([
146
+ {
147
+ type: 'confirm',
148
+ name: 'confirm',
149
+ message: 'Do you want to continue?',
150
+ default: false
151
+ }
152
+ ]);
153
+
154
+ if (!confirm) {
155
+ console.log(chalk.gray('\nClone cancelled by user.'));
156
+ return;
157
+ }
158
+ console.log('');
159
+ }
160
+
161
+ // Step 5: Dump source database (if not skipped)
162
+ let dumpDirectory;
163
+ if (options.skipDump && options.backupDir) {
164
+ console.log(chalk.yellow('Step 4/5: Using existing backup directory...'));
165
+ dumpDirectory = options.backupDir;
166
+
167
+ if (!existsSync(dumpDirectory)) {
168
+ throw new Error(`Backup directory not found: ${dumpDirectory}`);
169
+ }
170
+
171
+ // Verify backup contains database folder with BSON files
172
+ const dbDumpDir = join(dumpDirectory, sourceInfo.database);
173
+ if (!existsSync(dbDumpDir)) {
174
+ throw new Error(`Database dump folder not found: ${dbDumpDir}`);
175
+ }
176
+
177
+ const files = readdirSync(dbDumpDir);
178
+ const bsonFiles = files.filter(f => f.endsWith('.bson'));
179
+
180
+ if (bsonFiles.length === 0) {
181
+ throw new Error(`No BSON files found in backup directory: ${dbDumpDir}`);
182
+ }
183
+
184
+ // Calculate total size
185
+ let totalSize = 0;
186
+ for (const file of files) {
187
+ const filePath = join(dbDumpDir, file);
188
+ const stats = await stat(filePath);
189
+ totalSize += stats.size;
190
+ }
191
+
192
+ const sizeInMB = (totalSize / (1024 * 1024)).toFixed(2);
193
+ const size = sizeInMB >= 1 ? `${sizeInMB} MB` : `${(totalSize / 1024).toFixed(2)} KB`;
194
+
195
+ console.log(chalk.green('✓ Using existing backup'));
196
+ console.log(chalk.gray(` Directory: ${dumpDirectory}`));
197
+ console.log(chalk.gray(` Collections: ${bsonFiles.length}`));
198
+ console.log(chalk.gray(` Size: ${size}`));
199
+ console.log('');
200
+ } else {
201
+ console.log(chalk.yellow('Step 4/5: Dumping source database...'));
202
+ console.log(chalk.gray('This may take several minutes depending on database size...'));
203
+ console.log('');
204
+
205
+ const timestamp = new Date().toISOString().split('.')[0].replace(/:/g, '-').replace('T', '_');
206
+ dumpDirectory = join(backupDir, `clone-backup-${timestamp}`);
207
+
208
+ const dumpSpinner = ora('Creating database dump...').start();
209
+ const dumpResult = await dumpDatabase(sourceInfo, dumpDirectory);
210
+
211
+ if (!dumpResult.success) {
212
+ dumpSpinner.fail(chalk.red('Database dump failed'));
213
+ throw new Error(dumpResult.error);
214
+ }
215
+
216
+ dumpSpinner.succeed(chalk.green('Database dump completed'));
217
+ console.log(chalk.gray(` Directory: ${dumpResult.dir}`));
218
+ console.log(chalk.gray(` Collections: ${dumpResult.collections}`));
219
+ console.log(chalk.gray(` Size: ${dumpResult.size}`));
220
+ console.log('');
221
+ }
222
+
223
+ // Step 6: Restore to local database
224
+ console.log(chalk.yellow('Step 5/5: Restoring to local database...'));
225
+ console.log(chalk.gray('This may take several minutes...'));
226
+ console.log('');
227
+
228
+ const restoreSpinner = ora('Restoring database (collections will be dropped and recreated)...').start();
229
+ const restoreResult = await restoreDatabase(dumpDirectory, localInfo, true, composeFile);
230
+
231
+ if (!restoreResult.success) {
232
+ restoreSpinner.fail(chalk.red('Database restore failed'));
233
+ throw new Error(restoreResult.error);
234
+ }
235
+
236
+ restoreSpinner.succeed(chalk.green('Database restore completed'));
237
+ console.log('');
238
+
239
+ // Step 6A: Verify data was restored
240
+ const verifySpinner = ora('Verifying data restoration...').start();
241
+ const verifyResult = await verifyDataRestored(localInfo, composeFile);
242
+
243
+ if (!verifyResult.success) {
244
+ verifySpinner.fail(chalk.red('Data verification failed'));
245
+ console.log(chalk.yellow(' Warning: Could not verify data restoration'));
246
+ } else {
247
+ verifySpinner.succeed(chalk.green(`Verified: ${verifyResult.collectionCount} collections, ${verifyResult.totalDocuments.toLocaleString()} documents restored`));
248
+
249
+ // Warn if no data was restored
250
+ if (verifyResult.totalDocuments === 0) {
251
+ console.log(chalk.red('\n⚠️ WARNING: No documents were restored!'));
252
+ console.log(chalk.yellow(' Possible causes:'));
253
+ console.log(chalk.gray(' - Source database was empty'));
254
+ console.log(chalk.gray(' - Authentication issues prevented data restore'));
255
+ console.log(chalk.gray(' - Permission issues with target database'));
256
+ console.log(chalk.gray(' - Data restore errors were silently ignored\n'));
257
+ }
258
+ }
259
+ console.log('');
260
+
261
+ // Final summary
262
+ console.log(chalk.green('╔════════════════════════════════════════════════════════════╗'));
263
+ console.log(chalk.green('║ Database Clone Completed Successfully! ║'));
264
+ console.log(chalk.green('╚════════════════════════════════════════════════════════════╝'));
265
+ console.log('');
266
+ console.log(chalk.blue('Summary:'));
267
+ console.log(chalk.gray(` ✓ Source database dumped to: ${dumpDirectory}`));
268
+ console.log(chalk.gray(` ✓ Restored to local database: ${localInfo.database}`));
269
+
270
+ // Show data restoration summary
271
+ if (verifyResult && verifyResult.success) {
272
+ if (verifyResult.totalDocuments > 0) {
273
+ console.log(chalk.green(` ✓ Data restored: ${verifyResult.totalDocuments.toLocaleString()} documents across ${verifyResult.collectionCount} collections`));
274
+ } else {
275
+ console.log(chalk.red(` ✗ No data restored (${verifyResult.collectionCount} collections created but empty)`));
276
+ }
277
+ }
278
+
279
+ console.log('');
280
+ console.log(chalk.cyan('Your local MongoDB database is now cloned from the source!'));
281
+ console.log(chalk.gray('You can start your services with: ante start'));
282
+ console.log('');
283
+
284
+ } catch (error) {
285
+ console.log('');
286
+ console.error(chalk.red('✗ MongoDB clone failed:'), error.message);
287
+ console.log('');
288
+ process.exit(1);
289
+ }
290
+ }