ante-erp-cli 1.6.9 → 1.6.10

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.6.9",
3
+ "version": "1.6.10",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -57,6 +57,7 @@ export async function cloneDb(sourceUrl, options = {}) {
57
57
  // Get ANTE installation directory
58
58
  const installDir = getInstallDir();
59
59
  const backupDir = join(installDir, 'backups');
60
+ const composeFile = join(installDir, 'docker-compose.yml');
60
61
 
61
62
  // Step 1: Parse source database URL
62
63
  console.log(chalk.yellow('Step 1/6: Parsing source database URL...'));
@@ -103,9 +104,9 @@ export async function cloneDb(sourceUrl, options = {}) {
103
104
  }
104
105
  sourceSpinner.succeed(chalk.green('Source database connection successful'));
105
106
 
106
- // Test local connection
107
+ // Test local connection (use Docker Compose for installed ANTE)
107
108
  const localSpinner = ora('Testing local database connection...').start();
108
- const localTest = await testConnection(localInfo);
109
+ const localTest = await testConnection(localInfo, composeFile);
109
110
  if (!localTest.success) {
110
111
  localSpinner.fail(chalk.red('Local database connection failed'));
111
112
  throw new Error(`Cannot connect to local database: ${localTest.error}`);
@@ -192,7 +193,7 @@ export async function cloneDb(sourceUrl, options = {}) {
192
193
  console.log('');
193
194
 
194
195
  const restoreSpinner = ora('Restoring database...').start();
195
- const restoreResult = await restoreDatabase(dumpFile, localInfo, true);
196
+ const restoreResult = await restoreDatabase(dumpFile, localInfo, true, composeFile);
196
197
 
197
198
  if (!restoreResult.success) {
198
199
  restoreSpinner.fail(chalk.red('Database restore failed'));
@@ -205,7 +206,6 @@ export async function cloneDb(sourceUrl, options = {}) {
205
206
  // Step 7: Run Prisma operations (if not skipped)
206
207
  if (!options.noPrisma) {
207
208
  console.log(chalk.yellow('Step 6/6: Running Prisma operations...'));
208
- const composeFile = join(installDir, 'docker-compose.yml');
209
209
 
210
210
  // Generate Prisma client
211
211
  const generateSpinner = ora('Generating Prisma client...').start();
@@ -50,13 +50,34 @@ export function parsePostgresUrl(url) {
50
50
  /**
51
51
  * Test PostgreSQL connection using Docker
52
52
  * @param {Object} connectionInfo - Connection details from parsePostgresUrl
53
+ * @param {string} composeFile - Optional path to docker-compose.yml for Docker Compose network access
53
54
  * @returns {Promise<{success: boolean, error?: string}>}
54
55
  */
55
- export async function testConnection(connectionInfo) {
56
+ export async function testConnection(connectionInfo, composeFile = null) {
56
57
  try {
57
58
  const { user, password, host, port, database } = connectionInfo;
58
59
 
59
- // Determine Docker host (use host.docker.internal for localhost)
60
+ // If composeFile provided, use Docker Compose exec (for local database in Docker network)
61
+ if (composeFile) {
62
+ await execa('docker', [
63
+ 'compose',
64
+ '-f', composeFile,
65
+ 'exec',
66
+ '-T',
67
+ '-e', `PGPASSWORD=${password}`,
68
+ 'postgres',
69
+ 'psql',
70
+ '-U', user,
71
+ '-d', database,
72
+ '-c', 'SELECT 1'
73
+ ], {
74
+ timeout: 10000 // 10 second timeout
75
+ });
76
+
77
+ return { success: true };
78
+ }
79
+
80
+ // Standalone Docker for remote databases
60
81
  let testHost = host;
61
82
  const dockerArgs = ['run', '--rm', '-e', `PGPASSWORD=${password}`];
62
83
 
@@ -159,9 +180,10 @@ export async function dumpDatabase(connectionInfo, outputFile) {
159
180
  * @param {string} dumpFile - Path to dump file
160
181
  * @param {Object} connectionInfo - Target database connection details
161
182
  * @param {boolean} dropSchema - Whether to drop and recreate schema first (default: true)
183
+ * @param {string} composeFile - Optional path to docker-compose.yml for Docker Compose network access
162
184
  * @returns {Promise<{success: boolean, error?: string}>}
163
185
  */
164
- export async function restoreDatabase(dumpFile, connectionInfo, dropSchema = true) {
186
+ export async function restoreDatabase(dumpFile, connectionInfo, dropSchema = true, composeFile = null) {
165
187
  try {
166
188
  const { user, password, host, port, database, schema } = connectionInfo;
167
189
 
@@ -170,6 +192,82 @@ export async function restoreDatabase(dumpFile, connectionInfo, dropSchema = tru
170
192
  throw new Error(`Dump file not found: ${dumpFile}`);
171
193
  }
172
194
 
195
+ const dumpFileName = dumpFile.split('/').pop();
196
+
197
+ // If using Docker Compose (for local database in Docker network)
198
+ if (composeFile) {
199
+ // Drop and recreate schema if requested
200
+ if (dropSchema) {
201
+ await execa('docker', [
202
+ 'compose',
203
+ '-f', composeFile,
204
+ 'exec',
205
+ '-T',
206
+ '-e', `PGPASSWORD=${password}`,
207
+ 'postgres',
208
+ 'psql',
209
+ '-U', user,
210
+ '-d', database,
211
+ '-c', `DROP SCHEMA IF EXISTS ${schema} CASCADE; CREATE SCHEMA ${schema};`
212
+ ], {
213
+ timeout: 30000 // 30 second timeout
214
+ });
215
+ }
216
+
217
+ // Copy dump file into postgres container
218
+ await execa('docker', [
219
+ 'cp',
220
+ dumpFile,
221
+ 'ante-postgres:/tmp/' + dumpFileName
222
+ ], {
223
+ timeout: 120000 // 2 minute timeout for large files
224
+ });
225
+
226
+ // Restore from inside the container
227
+ try {
228
+ await execa('docker', [
229
+ 'compose',
230
+ '-f', composeFile,
231
+ 'exec',
232
+ '-T',
233
+ '-e', `PGPASSWORD=${password}`,
234
+ 'postgres',
235
+ 'pg_restore',
236
+ '-U', user,
237
+ '-d', database,
238
+ '-v', // Verbose
239
+ '--no-owner', // Skip ownership commands
240
+ '--no-acl', // Skip access control lists
241
+ '/tmp/' + dumpFileName
242
+ ], {
243
+ timeout: 600000, // 10 minute timeout
244
+ reject: false // Don't reject on non-zero exit (pg_restore often exits with warnings)
245
+ });
246
+ } finally {
247
+ // Clean up: Remove dump file from container
248
+ try {
249
+ await execa('docker', [
250
+ 'compose',
251
+ '-f', composeFile,
252
+ 'exec',
253
+ '-T',
254
+ 'postgres',
255
+ 'rm',
256
+ '-f',
257
+ '/tmp/' + dumpFileName
258
+ ], {
259
+ timeout: 10000,
260
+ reject: false
261
+ });
262
+ } catch {
263
+ // Ignore cleanup errors
264
+ }
265
+ }
266
+
267
+ return { success: true };
268
+ }
269
+
270
+ // Standalone Docker for remote databases
173
271
  // Drop and recreate schema if requested
174
272
  if (dropSchema) {
175
273
  // Determine Docker host (use host.docker.internal for localhost)
@@ -216,7 +314,7 @@ export async function restoreDatabase(dumpFile, connectionInfo, dropSchema = tru
216
314
  '-v', // Verbose
217
315
  '--no-owner', // Skip ownership commands
218
316
  '--no-acl', // Skip access control lists
219
- `/backups/${join('/', dumpFile.split('/').pop())}`
317
+ `/backups/${join('/', dumpFileName)}`
220
318
  );
221
319
 
222
320
  try {