ante-erp-cli 1.11.25 → 1.11.27

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.25",
3
+ "version": "1.11.27",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -4,6 +4,7 @@ import inquirer from 'inquirer';
4
4
  import { join } from 'path';
5
5
  import { existsSync, readFileSync, readdirSync } from 'fs';
6
6
  import { stat } from 'fs/promises';
7
+ import { execa } from 'execa';
7
8
  import { getInstallDir } from '../utils/config.js';
8
9
  import {
9
10
  parseMongoUrl,
@@ -89,6 +90,22 @@ export async function cloneMongodb(sourceUrl, options = {}) {
89
90
  const backupDir = join(installDir, 'backups', 'mongodb');
90
91
  const composeFile = join(installDir, 'docker-compose.yml');
91
92
 
93
+ // Step 0: Ensure Docker image is available
94
+ console.log(chalk.yellow('Preparing Docker environment...'));
95
+ const imageSpinner = ora('Pulling MongoDB Docker image (this may take a few minutes on first run)...').start();
96
+
97
+ try {
98
+ await execa('docker', ['pull', 'mongo:latest'], {
99
+ timeout: 300000 // 5 minutes for image pull
100
+ });
101
+ imageSpinner.succeed(chalk.green('Docker image ready'));
102
+ console.log('');
103
+ } catch (imageError) {
104
+ // Non-fatal: Image might already exist
105
+ imageSpinner.info(chalk.gray('Using existing Docker image'));
106
+ console.log('');
107
+ }
108
+
92
109
  // Step 1: Parse source database URL
93
110
  console.log(chalk.yellow('Step 1/5: Parsing source database URL...'));
94
111
  let sourceInfo;
@@ -358,14 +358,24 @@ export async function runMigrations(composeFile) {
358
358
  'deploy'
359
359
  ]);
360
360
 
361
+ // Combine stdout and stderr for complete output
362
+ const combinedOutput = [stdout, stderr].filter(Boolean).join('\n').trim();
363
+
361
364
  return {
362
365
  success: true,
363
- output: stdout || stderr
366
+ output: combinedOutput || 'Migration completed'
364
367
  };
365
368
  } catch (error) {
369
+ // Merge both stdout and stderr to show complete error messages
370
+ // This ensures error codes (like P3005) and resolution links are visible
371
+ const combinedError = [error.stdout, error.stderr]
372
+ .filter(Boolean)
373
+ .join('\n')
374
+ .trim();
375
+
366
376
  return {
367
377
  success: false,
368
- output: error.stdout || error.stderr || error.message
378
+ output: combinedError || error.message
369
379
  };
370
380
  }
371
381
  }
@@ -68,71 +68,87 @@ export function buildMongoUrl(connectionInfo) {
68
68
  }
69
69
 
70
70
  /**
71
- * Test MongoDB connection using Docker
71
+ * Test MongoDB connection using Docker (with retry logic)
72
72
  * @param {Object} connectionInfo - Connection details from parseMongoUrl
73
73
  * @param {string} composeFile - Optional path to docker-compose.yml for Docker Compose network access
74
+ * @param {number} maxRetries - Maximum retry attempts (default: 2)
74
75
  * @returns {Promise<{success: boolean, error?: string}>}
75
76
  */
76
- export async function testConnection(connectionInfo, composeFile = null) {
77
- try {
78
- const mongoUrl = buildMongoUrl(connectionInfo);
77
+ export async function testConnection(connectionInfo, composeFile = null, maxRetries = 2) {
78
+ let lastError = null;
79
79
 
80
- // If composeFile provided, use Docker Compose exec (for local database in Docker network)
81
- if (composeFile) {
82
- await execa('docker', [
83
- 'compose',
84
- '-f', composeFile,
85
- 'exec',
86
- '-T',
87
- 'mongodb',
80
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
81
+ try {
82
+ const mongoUrl = buildMongoUrl(connectionInfo);
83
+
84
+ // If composeFile provided, use Docker Compose exec (for local database in Docker network)
85
+ if (composeFile) {
86
+ await execa('docker', [
87
+ 'compose',
88
+ '-f', composeFile,
89
+ 'exec',
90
+ '-T',
91
+ 'mongodb',
92
+ 'mongosh',
93
+ mongoUrl,
94
+ '--quiet',
95
+ '--eval', 'db.adminCommand("ping")'
96
+ ], {
97
+ timeout: 120000 // 120 second timeout (2 minutes) - allows for Docker image pull
98
+ });
99
+
100
+ return { success: true };
101
+ }
102
+
103
+ // Standalone Docker for remote databases
104
+ let testUrl = mongoUrl;
105
+ const dockerArgs = ['run', '--rm'];
106
+
107
+ // Handle localhost connections
108
+ if (connectionInfo.host === 'localhost' || connectionInfo.host === '127.0.0.1') {
109
+ const protocol = connectionInfo.isSrv ? 'mongodb+srv://' : 'mongodb://';
110
+ const auth = connectionInfo.user && connectionInfo.password ?
111
+ `${connectionInfo.user}:${connectionInfo.password}@` : '';
112
+ const portPart = connectionInfo.port && !connectionInfo.isSrv ? `:${connectionInfo.port}` : '';
113
+ const authDbPart = connectionInfo.authDatabase && connectionInfo.authDatabase !== connectionInfo.database ?
114
+ `?authSource=${connectionInfo.authDatabase}` : '';
115
+
116
+ testUrl = `${protocol}${auth}host.docker.internal${portPart}/${connectionInfo.database}${authDbPart}`;
117
+ dockerArgs.push('--add-host=host.docker.internal:host-gateway');
118
+ }
119
+
120
+ dockerArgs.push(
121
+ 'mongo:latest',
88
122
  'mongosh',
89
- mongoUrl,
123
+ testUrl,
90
124
  '--quiet',
91
125
  '--eval', 'db.adminCommand("ping")'
92
- ], {
93
- timeout: 10000 // 10 second timeout
126
+ );
127
+
128
+ // Use Docker to test connection
129
+ await execa('docker', dockerArgs, {
130
+ timeout: 120000 // 120 second timeout (2 minutes) - allows for Docker image pull
94
131
  });
95
132
 
96
133
  return { success: true };
97
- }
98
-
99
- // Standalone Docker for remote databases
100
- let testUrl = mongoUrl;
101
- const dockerArgs = ['run', '--rm'];
102
-
103
- // Handle localhost connections
104
- if (connectionInfo.host === 'localhost' || connectionInfo.host === '127.0.0.1') {
105
- const protocol = connectionInfo.isSrv ? 'mongodb+srv://' : 'mongodb://';
106
- const auth = connectionInfo.user && connectionInfo.password ?
107
- `${connectionInfo.user}:${connectionInfo.password}@` : '';
108
- const portPart = connectionInfo.port && !connectionInfo.isSrv ? `:${connectionInfo.port}` : '';
109
- const authDbPart = connectionInfo.authDatabase && connectionInfo.authDatabase !== connectionInfo.database ?
110
- `?authSource=${connectionInfo.authDatabase}` : '';
111
-
112
- testUrl = `${protocol}${auth}host.docker.internal${portPart}/${connectionInfo.database}${authDbPart}`;
113
- dockerArgs.push('--add-host=host.docker.internal:host-gateway');
114
- }
134
+ } catch (error) {
135
+ lastError = error;
115
136
 
116
- dockerArgs.push(
117
- 'mongo:latest',
118
- 'mongosh',
119
- testUrl,
120
- '--quiet',
121
- '--eval', 'db.adminCommand("ping")'
122
- );
123
-
124
- // Use Docker to test connection
125
- await execa('docker', dockerArgs, {
126
- timeout: 10000 // 10 second timeout
127
- });
137
+ // If timeout and not last attempt, retry
138
+ if (error.timedOut && attempt < maxRetries) {
139
+ await new Promise(resolve => setTimeout(resolve, 2000)); // Wait 2s before retry
140
+ continue;
141
+ }
128
142
 
129
- return { success: true };
130
- } catch (error) {
131
- return {
132
- success: false,
133
- error: error.message || 'Connection failed'
134
- };
143
+ // If it's the last attempt or non-timeout error, break
144
+ break;
145
+ }
135
146
  }
147
+
148
+ return {
149
+ success: false,
150
+ error: lastError?.message || 'Connection failed'
151
+ };
136
152
  }
137
153
 
138
154
  /**