ante-erp-cli 1.9.12 → 1.9.14

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
@@ -81,10 +81,9 @@ program
81
81
  const backupCmd = program
82
82
  .command('backup')
83
83
  .alias('dump')
84
- .description('Create backup of ANTE ERP')
84
+ .description('Create backup of ANTE ERP databases (PostgreSQL and MongoDB)')
85
85
  .option('-o, --output <path>', 'Output file path')
86
- .option('--compress', 'Compress backup', true)
87
- .option('--databases-only', 'Only backup databases')
86
+ .option('-k, --keyword <text>', 'Add keyword to backup filename for easier identification')
88
87
  .action(backup);
89
88
 
90
89
  backupCmd
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ante-erp-cli",
3
- "version": "1.9.12",
3
+ "version": "1.9.14",
4
4
  "description": "Comprehensive CLI tool for managing ANTE ERP self-hosted installations",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,32 +1,37 @@
1
1
  import chalk from 'chalk';
2
2
  import ora from 'ora';
3
3
  import { join } from 'path';
4
- import { readdirSync } from 'fs';
4
+ import { readdirSync, readFileSync } from 'fs';
5
5
  import { execa } from 'execa';
6
6
  import Table from 'cli-table3';
7
7
  import { getInstallDir } from '../utils/config.js';
8
+ import { dumpDatabase, parseMongoUrl } from '../utils/mongodb.js';
8
9
 
9
10
  /**
10
- * Create backup of ANTE ERP
11
+ * Create backup of ANTE ERP databases (PostgreSQL and MongoDB)
11
12
  */
12
13
  export async function backup(options) {
13
14
  try {
14
15
  const installDir = getInstallDir();
15
16
  const composeFile = join(installDir, 'docker-compose.yml');
16
17
  const backupDir = join(installDir, 'backups');
17
-
18
+
19
+ // Generate backup filename with optional keyword
18
20
  const timestamp = new Date().toISOString().split('.')[0].replace(/:/g, '-').replace('T', '_');
19
- const backupFile = options.output || join(backupDir, `ante-backup-${timestamp}.tar.gz`);
20
-
21
- const spinner = ora('Creating backup...').start();
22
-
21
+ const keywordPart = options.keyword
22
+ ? `-${options.keyword.replace(/[^a-zA-Z0-9-_]/g, '-')}`
23
+ : '';
24
+ const backupFile = options.output || join(backupDir, `ante-backup-${timestamp}${keywordPart}.tar.gz`);
25
+
26
+ const spinner = ora('Creating database backup...').start();
27
+
23
28
  try {
24
29
  // Create temporary backup directory
25
30
  const tempDir = join(installDir, '.backup-temp');
26
31
  await execa('mkdir', ['-p', tempDir]);
27
32
 
28
33
  // Backup PostgreSQL - dump directly to file inside container
29
- spinner.text = 'Backing up PostgreSQL...';
34
+ spinner.text = 'Backing up PostgreSQL database...';
30
35
  await execa('docker', [
31
36
  'compose',
32
37
  '-f',
@@ -50,23 +55,64 @@ export async function backup(options) {
50
55
  'ante-postgres:/tmp/postgres.dump',
51
56
  join(tempDir, 'postgres.dump')
52
57
  ]);
53
-
54
- // Copy uploaded files
55
- if (!options.databasesOnly) {
56
- spinner.text = 'Backing up uploaded files...';
57
- await execa('docker', [
58
- 'cp',
59
- 'ante-backend:/app/uploads',
60
- join(tempDir, 'uploads')
61
- ]);
58
+
59
+ // Backup MongoDB
60
+ spinner.text = 'Backing up MongoDB database...';
61
+
62
+ // Read MongoDB password from .env file
63
+ const envPath = join(installDir, '.env');
64
+ const envContent = readFileSync(envPath, 'utf8');
65
+
66
+ // Try to find MONGODB_URI first (development environment)
67
+ let mongoInfo;
68
+ const mongoUriMatch = envContent.match(/MONGODB_URI=(.+)/);
69
+
70
+ if (mongoUriMatch) {
71
+ // Development environment with full URI
72
+ const mongoUri = mongoUriMatch[1].trim();
73
+ mongoInfo = parseMongoUrl(mongoUri);
74
+ } else {
75
+ // CLI installation environment - construct URI from components
76
+ const mongoPasswordMatch = envContent.match(/MONGO_PASSWORD=(.+)/);
77
+
78
+ if (!mongoPasswordMatch) {
79
+ throw new Error('MONGO_PASSWORD not found in .env file');
80
+ }
81
+
82
+ const mongoPassword = mongoPasswordMatch[1].trim();
83
+
84
+ // Construct MongoDB connection info for CLI installation
85
+ mongoInfo = {
86
+ user: 'ante',
87
+ password: mongoPassword,
88
+ host: 'localhost',
89
+ port: '27017',
90
+ database: 'ante',
91
+ authDatabase: 'admin',
92
+ isSrv: false
93
+ };
94
+ }
95
+
96
+ // Create MongoDB dump in temporary directory
97
+ const mongoDumpTempDir = join(tempDir, 'mongodb-temp');
98
+ const mongoResult = await dumpDatabase(mongoInfo, mongoDumpTempDir);
99
+
100
+ if (!mongoResult.success) {
101
+ throw new Error(`MongoDB backup failed: ${mongoResult.error}`);
62
102
  }
63
-
64
- // Copy configuration
65
- await execa('cp', [join(installDir, 'docker-compose.yml'), tempDir]);
66
- await execa('cp', [join(installDir, '.env'), join(tempDir, 'env.backup')]);
67
-
103
+
104
+ // Move MongoDB dump to final structure (mongodb/ folder)
105
+ await execa('mv', [
106
+ join(mongoDumpTempDir, mongoInfo.database),
107
+ join(tempDir, 'mongodb')
108
+ ]);
109
+
110
+ // Remove temporary MongoDB dump directory
111
+ await execa('rm', ['-rf', mongoDumpTempDir]);
112
+
68
113
  // Create tar.gz archive
69
114
  spinner.text = 'Creating compressed archive...';
115
+ await execa('mkdir', ['-p', backupDir]);
70
116
  await execa('tar', [
71
117
  '-czf',
72
118
  backupFile,
@@ -74,21 +120,24 @@ export async function backup(options) {
74
120
  tempDir,
75
121
  '.'
76
122
  ]);
77
-
123
+
78
124
  // Cleanup
79
125
  await execa('rm', ['-rf', tempDir]);
80
-
81
- spinner.succeed(`Backup created: ${backupFile}`);
82
-
83
- // Show backup size
126
+
127
+ spinner.succeed(chalk.green('Database backup created successfully!'));
128
+
129
+ // Show backup details
84
130
  const { stdout: size } = await execa('du', ['-h', backupFile]);
85
- console.log(chalk.gray(`Size: ${size.split('\t')[0]}\n`));
86
-
131
+ console.log(chalk.white('\n✓ PostgreSQL database backed up'));
132
+ console.log(chalk.white(`✓ MongoDB database backed up (${mongoResult.collections} collections, ${mongoResult.size})`));
133
+ console.log(chalk.cyan(`\nBackup file: ${backupFile}`));
134
+ console.log(chalk.gray(`Total size: ${size.split('\t')[0]}\n`));
135
+
87
136
  } catch (error) {
88
137
  spinner.fail('Backup failed');
89
138
  throw error;
90
139
  }
91
-
140
+
92
141
  } catch (error) {
93
142
  console.error(chalk.red('Error:'), error.message);
94
143
  process.exit(1);
@@ -102,42 +151,41 @@ export async function listBackups() {
102
151
  try {
103
152
  const installDir = getInstallDir();
104
153
  const backupDir = join(installDir, 'backups');
105
-
154
+
106
155
  console.log(chalk.bold('\n💾 Available Backups\n'));
107
-
156
+
108
157
  const files = readdirSync(backupDir)
109
158
  .filter(f => f.endsWith('.tar.gz'))
110
159
  .sort()
111
160
  .reverse();
112
-
161
+
113
162
  if (files.length === 0) {
114
163
  console.log(chalk.yellow('No backups found\n'));
115
164
  return;
116
165
  }
117
-
166
+
118
167
  const table = new Table({
119
168
  head: [chalk.cyan('Backup File'), chalk.cyan('Size'), chalk.cyan('Date')],
120
169
  style: { head: [], border: ['gray'] }
121
170
  });
122
-
171
+
123
172
  for (const file of files) {
124
173
  const filePath = join(backupDir, file);
125
174
  const { stdout: size } = await execa('du', ['-h', filePath]);
126
175
  const { stdout: date } = await execa('stat', ['-c', '%y', filePath]);
127
-
176
+
128
177
  table.push([
129
178
  file,
130
179
  size.split('\t')[0],
131
180
  date.split('.')[0]
132
181
  ]);
133
182
  }
134
-
183
+
135
184
  console.log(table.toString());
136
185
  console.log();
137
-
186
+
138
187
  } catch (error) {
139
188
  console.error(chalk.red('Error:'), error.message);
140
189
  process.exit(1);
141
190
  }
142
191
  }
143
-
@@ -8,7 +8,7 @@ import { getInstallDir } from '../utils/config.js';
8
8
  import { execInContainer, stopServices, startServices } from '../utils/docker.js';
9
9
 
10
10
  /**
11
- * Restore ANTE ERP from backup
11
+ * Restore ANTE ERP databases from backup (PostgreSQL and MongoDB only)
12
12
  */
13
13
  export async function restore(backupFile, options = {}) {
14
14
  try {
@@ -58,8 +58,8 @@ export async function restore(backupFile, options = {}) {
58
58
  }
59
59
 
60
60
  // Warning and confirmation
61
- console.log(chalk.yellow('\n⚠ WARNING: This will restore ANTE from backup'));
62
- console.log(chalk.gray('Current data will be replaced with backup data'));
61
+ console.log(chalk.yellow('\n⚠ WARNING: This will restore databases from backup'));
62
+ console.log(chalk.gray('Current database data will be replaced with backup data'));
63
63
  console.log(chalk.white('\nBackup file:'), chalk.cyan(basename(backupFile)));
64
64
 
65
65
  if (!options.force) {
@@ -136,42 +136,23 @@ export async function restore(backupFile, options = {}) {
136
136
  spinner.text = 'Restoring MongoDB database...';
137
137
  const mongoDumpDir = join(tempDir, 'mongodb');
138
138
  if (existsSync(mongoDumpDir)) {
139
+ // Copy MongoDB dump directory to container
140
+ // The backup structure is: mongodb/ contains the database files directly
139
141
  await execa('docker', [
140
142
  'cp',
141
143
  mongoDumpDir,
142
- 'ante-mongodb:/tmp/restore'
144
+ 'ante-mongodb:/tmp/'
143
145
  ]);
144
146
 
147
+ // Restore from the mongodb directory (contains BSON files)
148
+ // Use --nsInclude to restore all collections from the dump
145
149
  await execInContainer(
146
150
  composeFile,
147
151
  'mongodb',
148
- ['mongorestore', '--drop', '--db', 'ante', '/tmp/restore/ante']
152
+ ['mongorestore', '--drop', '--dir', '/tmp/mongodb', '--db', 'ante']
149
153
  );
150
154
  }
151
155
 
152
- // Restore uploaded files
153
- spinner.text = 'Restoring uploaded files...';
154
- const uploadsDir = join(tempDir, 'uploads');
155
- if (existsSync(uploadsDir)) {
156
- await execa('docker', [
157
- 'cp',
158
- uploadsDir,
159
- 'ante-backend:/app/'
160
- ]);
161
- }
162
-
163
- // Restore configuration files
164
- spinner.text = 'Restoring configuration...';
165
- const envBackup = join(tempDir, 'env.backup');
166
- if (existsSync(envBackup)) {
167
- await execa('cp', [envBackup, join(installDir, '.env')]);
168
- }
169
-
170
- const composeBackup = join(tempDir, 'docker-compose.yml');
171
- if (existsSync(composeBackup)) {
172
- await execa('cp', [composeBackup, composeFile]);
173
- }
174
-
175
156
  // Start all services
176
157
  spinner.text = 'Starting all services...';
177
158
  await startServices(composeFile);
@@ -180,15 +161,13 @@ export async function restore(backupFile, options = {}) {
180
161
  spinner.text = 'Cleaning up...';
181
162
  await execa('rm', ['-rf', tempDir]);
182
163
 
183
- spinner.succeed(chalk.green('Restore completed successfully!'));
164
+ spinner.succeed(chalk.green('Database restore completed successfully!'));
184
165
 
185
166
  console.log(chalk.white('\n✓ PostgreSQL database restored'));
186
167
  console.log(chalk.white('✓ MongoDB database restored'));
187
- console.log(chalk.white('✓ Uploaded files restored'));
188
- console.log(chalk.white('✓ Configuration restored'));
189
168
  console.log(chalk.white('✓ All services started'));
190
169
 
191
- console.log(chalk.cyan('\nANTE is now running with restored data'));
170
+ console.log(chalk.cyan('\nANTE is now running with restored database data'));
192
171
 
193
172
  } catch (error) {
194
173
  spinner.fail(chalk.red('Restore failed'));