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 +1 -1
- package/src/commands/clone-db.js +4 -4
- package/src/utils/postgres.js +102 -4
package/package.json
CHANGED
package/src/commands/clone-db.js
CHANGED
|
@@ -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();
|
package/src/utils/postgres.js
CHANGED
|
@@ -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
|
-
//
|
|
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('/',
|
|
317
|
+
`/backups/${join('/', dumpFileName)}`
|
|
220
318
|
);
|
|
221
319
|
|
|
222
320
|
try {
|