mwalajs 1.0.8 → 1.1.0
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/mwala.mjs +215 -162
- package/package.json +1 -1
- package/utils/dbUtils.mjs +457 -64
package/bin/mwala.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawn } from 'child_process';
|
|
4
4
|
import fs from 'fs';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
@@ -9,19 +9,20 @@ import readlineSync from 'readline-sync';
|
|
|
9
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
10
|
const __dirname = path.dirname(__filename);
|
|
11
11
|
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
const command = args[0];
|
|
14
|
+
|
|
12
15
|
// Dynamic imports
|
|
13
16
|
const { getDbConnection } = await import(pathToFileURL(path.join(__dirname, '../config/createdatabase.mjs')).href);
|
|
14
|
-
const { createTable, dropTable, migrateAll, rollbackLastMigration } = await import(pathToFileURL(path.join(__dirname, '../runMigrations.mjs')).href);
|
|
15
17
|
const { setupMwalajs } = await import(pathToFileURL(path.join(__dirname, '../setupMwalajs.mjs')).href);
|
|
16
18
|
const { createProject } = await import(pathToFileURL(path.join(__dirname, '../createProject.mjs')).href);
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
backupDatabase,
|
|
20
|
+
const {
|
|
21
|
+
listTables,
|
|
22
|
+
describeTable,
|
|
23
|
+
truncateTable,
|
|
24
|
+
renameTable,
|
|
25
|
+
backupDatabase,
|
|
25
26
|
restoreDatabase,
|
|
26
27
|
seedDatabase,
|
|
27
28
|
showDatabaseSize,
|
|
@@ -37,88 +38,74 @@ const {
|
|
|
37
38
|
showConnections,
|
|
38
39
|
killConnections,
|
|
39
40
|
checkTableExists
|
|
40
|
-
} = await import(pathToFileURL(path.join(__dirname, '../dbUtils.mjs')).href);
|
|
41
|
-
|
|
42
|
-
const args = process.argv.slice(2);
|
|
43
|
-
const command = args[0];
|
|
41
|
+
} = await import(pathToFileURL(path.join(__dirname, '../utils/dbUtils.mjs')).href);
|
|
44
42
|
|
|
45
43
|
if (!command || command === 'help' || command === 'h') {
|
|
46
44
|
console.log(`
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
Maintenance & Stats:
|
|
101
|
-
- mwala db:size → Show database size
|
|
102
|
-
- mwala db:indexes <table> → List indexes on table
|
|
103
|
-
- mwala db:analyze <table> → Analyze table (PostgreSQL)
|
|
104
|
-
- mwala db:reindex <table> → Rebuild indexes
|
|
105
|
-
- mwala db:vacuum → Vacuum database (SQLite/PostgreSQL)
|
|
106
|
-
- mwala db:connections → Show active connections
|
|
107
|
-
- mwala db:kill-connections → Kill all other connections (admin)
|
|
108
|
-
- mwala db:drop-all-tables → ⚠️ Drop ALL tables (dangerous!)
|
|
109
|
-
|
|
110
|
-
Use: mwala <command> [options]
|
|
45
|
+
╔══════════════════════════════════════════════════════════╗
|
|
46
|
+
║ MwalaJS CLI v1.2.0 ║
|
|
47
|
+
╚══════════════════════════════════════════════════════════╝
|
|
48
|
+
|
|
49
|
+
General:
|
|
50
|
+
mwala -v | --version → Show version
|
|
51
|
+
mwala help | h → Show this help
|
|
52
|
+
|
|
53
|
+
Project:
|
|
54
|
+
mwala create-project → Create new project
|
|
55
|
+
mwala init → Initialize MwalaJS
|
|
56
|
+
|
|
57
|
+
Run:
|
|
58
|
+
mwala serve → Start the server (runs app.mjs)
|
|
59
|
+
|
|
60
|
+
Generate:
|
|
61
|
+
mwala generate model <name>
|
|
62
|
+
mwala generate controller <name>
|
|
63
|
+
mwala generate route <name>
|
|
64
|
+
mwala generate view <name>
|
|
65
|
+
mwala generate midware <name>
|
|
66
|
+
|
|
67
|
+
Database Setup:
|
|
68
|
+
mwala create-db → Interactive DB setup & creation
|
|
69
|
+
mwala db:config → Reconfigure database (.env)
|
|
70
|
+
|
|
71
|
+
Database Commands:
|
|
72
|
+
mwala db:table list → List all tables
|
|
73
|
+
mwala db:table describe <name> → Show table structure
|
|
74
|
+
mwala db:table count <name> → Count rows
|
|
75
|
+
mwala db:table truncate <name> → Empty table
|
|
76
|
+
mwala db:table rename <old> <new> → Rename table
|
|
77
|
+
mwala db:table copy <src> <dest> → Copy table
|
|
78
|
+
mwala db:table exists <name> → Check if exists
|
|
79
|
+
|
|
80
|
+
mwala db:backup → Backup database
|
|
81
|
+
mwala db:restore <file.sql> → Restore from backup
|
|
82
|
+
mwala db:size → Show database size
|
|
83
|
+
mwala db:indexes <table> → List indexes
|
|
84
|
+
mwala db:vacuum → Optimize (SQLite/PostgreSQL)
|
|
85
|
+
mwala db:connections → Show active connections
|
|
86
|
+
mwala db:kill-connections → Kill other connections (admin)
|
|
87
|
+
mwala db:drop-all-tables → DROP ALL TABLES!
|
|
88
|
+
|
|
89
|
+
mwala db:export <table> <file.csv> → Export to CSV
|
|
90
|
+
mwala db:import <file.csv> <table> → Import from CSV
|
|
91
|
+
mwala db:seed <seedFile.mjs> → Run seed file
|
|
92
|
+
|
|
93
|
+
Migrations:
|
|
94
|
+
mwala migrate all → Run all migrations
|
|
95
|
+
mwala rollback last → Rollback last migration
|
|
96
|
+
|
|
97
|
+
Enjoy building with MwalaJS!
|
|
111
98
|
`);
|
|
112
99
|
process.exit(0);
|
|
113
100
|
}
|
|
114
101
|
|
|
102
|
+
// ====================== COMMAND SWITCH ======================
|
|
115
103
|
switch (command) {
|
|
116
|
-
|
|
117
|
-
case 'version':
|
|
118
104
|
case '-v':
|
|
105
|
+
case 'version':
|
|
119
106
|
case '--version':
|
|
120
107
|
console.log('MwalaJS Version: 1.1.0');
|
|
121
|
-
|
|
108
|
+
process.exit(0);
|
|
122
109
|
|
|
123
110
|
case 'create-project':
|
|
124
111
|
createProject();
|
|
@@ -129,143 +116,178 @@ switch (command) {
|
|
|
129
116
|
break;
|
|
130
117
|
|
|
131
118
|
case 'serve':
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
119
|
+
const child = spawn('node', ['app.mjs'], {
|
|
120
|
+
stdio: 'inherit',
|
|
121
|
+
cwd: process.cwd()
|
|
122
|
+
});
|
|
135
123
|
|
|
136
124
|
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
137
125
|
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
138
126
|
|
|
139
|
-
child.on('exit', (code) => process.exit(code
|
|
127
|
+
child.on('exit', (code) => process.exit(code ?? 0));
|
|
140
128
|
child.on('error', (err) => {
|
|
141
129
|
console.error(`Failed to start app: ${err.message}`);
|
|
142
130
|
process.exit(1);
|
|
143
131
|
});
|
|
144
132
|
break;
|
|
145
133
|
|
|
146
|
-
case 'generate':
|
|
147
|
-
|
|
148
|
-
|
|
134
|
+
case 'generate': {
|
|
135
|
+
const type = args[1]?.toLowerCase();
|
|
136
|
+
const name = args[2];
|
|
137
|
+
|
|
138
|
+
if (!type || !name) {
|
|
139
|
+
console.error('Usage: mwala generate <model|controller|route|view|midware> <name>');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const folders = {
|
|
144
|
+
model: 'models',
|
|
145
|
+
controller: 'controllers',
|
|
146
|
+
route: 'routes',
|
|
147
|
+
view: 'views',
|
|
148
|
+
midware: 'middlewares'
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
if (!folders[type]) {
|
|
152
|
+
console.error(`Invalid type. Use: ${Object.keys(folders).join(', ')}`);
|
|
153
|
+
process.exit(1);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const filePath = path.join(process.cwd(), folders[type], `${name}.mjs`);
|
|
157
|
+
|
|
158
|
+
if (fs.existsSync(filePath)) {
|
|
159
|
+
console.error(`${name} ${type} already exists.`);
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
let content = '';
|
|
164
|
+
switch (type) {
|
|
165
|
+
case 'model':
|
|
166
|
+
content = `export const ${name}Model = {};\n`;
|
|
167
|
+
break;
|
|
168
|
+
case 'controller':
|
|
169
|
+
content = `export const ${name}Controller = {\n getPage: (req, res) => {\n res.render('${name}', { title: '${name}' });\n }\n};\n`;
|
|
170
|
+
break;
|
|
171
|
+
case 'route':
|
|
172
|
+
content = `import mwalajs from 'mwalajs';\nimport { ${name}Controller } from '../controllers/${name}.mjs';\n\nconst router = mwalajs.Router();\nrouter.get('/', ${name}Controller.getPage);\nexport { router as ${name}Route };\n`;
|
|
173
|
+
break;
|
|
174
|
+
case 'view':
|
|
175
|
+
content = `<!DOCTYPE html>\n<html lang="en">\n<head>\n <meta charset="UTF-8">\n <title>${name}</title>\n</head>\n<body>\n <h1>Welcome to ${name} Page</h1>\n</body>\n</html>\n`;
|
|
176
|
+
break;
|
|
177
|
+
case 'midware':
|
|
178
|
+
content = `export const ${name} = (req, res, next) => {\n next();\n};\n`;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
183
|
+
fs.writeFileSync(filePath, content);
|
|
184
|
+
console.log(`✅ ${name} ${type} created at ${folders[type]}/${name}.mjs`);
|
|
149
185
|
break;
|
|
186
|
+
}
|
|
150
187
|
|
|
151
|
-
//
|
|
188
|
+
// ==================== DATABASE COMMANDS ====================
|
|
152
189
|
|
|
153
190
|
case 'create-db':
|
|
154
|
-
getDbConnection()
|
|
155
|
-
console.
|
|
156
|
-
|
|
157
|
-
|
|
191
|
+
getDbConnection()
|
|
192
|
+
.then(() => console.log('✅ Database configured and connected successfully!'))
|
|
193
|
+
.catch(err => {
|
|
194
|
+
console.error('❌ Database setup failed:', err.message);
|
|
195
|
+
process.exit(1);
|
|
196
|
+
});
|
|
158
197
|
break;
|
|
159
198
|
|
|
160
199
|
case 'db:config':
|
|
161
|
-
getDbConnection();
|
|
200
|
+
getDbConnection();
|
|
162
201
|
break;
|
|
163
202
|
|
|
164
203
|
case 'db:table':
|
|
165
|
-
const
|
|
204
|
+
const action = args[1];
|
|
166
205
|
const tableName = args[2];
|
|
167
|
-
const
|
|
206
|
+
const extraArg = args[3];
|
|
168
207
|
|
|
169
|
-
if (!
|
|
170
|
-
console.
|
|
208
|
+
if (!action) {
|
|
209
|
+
console.error('Usage: mwala db:table <list|describe|count|truncate|rename|copy|exists> [args]');
|
|
171
210
|
process.exit(1);
|
|
172
211
|
}
|
|
173
212
|
|
|
174
|
-
switch (
|
|
213
|
+
switch (action) {
|
|
175
214
|
case 'list':
|
|
176
215
|
listTables();
|
|
177
216
|
break;
|
|
178
|
-
case '
|
|
179
|
-
if (!tableName)
|
|
180
|
-
|
|
217
|
+
case 'describe':
|
|
218
|
+
if (!tableName) {
|
|
219
|
+
console.error('Table name required');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
describeTable(tableName);
|
|
181
223
|
break;
|
|
182
|
-
case '
|
|
183
|
-
if (!tableName)
|
|
184
|
-
|
|
224
|
+
case 'count':
|
|
225
|
+
if (!tableName) {
|
|
226
|
+
console.error('Table name required');
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
countRows(tableName);
|
|
185
230
|
break;
|
|
186
231
|
case 'truncate':
|
|
187
|
-
if (!tableName)
|
|
188
|
-
|
|
232
|
+
if (!tableName) {
|
|
233
|
+
console.error('Table name required');
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
if (readlineSync.keyInYN(`Truncate ${tableName}? This cannot be undone.`)) {
|
|
237
|
+
truncateTable(tableName);
|
|
238
|
+
}
|
|
189
239
|
break;
|
|
190
240
|
case 'rename':
|
|
191
|
-
if (!tableName || !
|
|
192
|
-
|
|
241
|
+
if (!tableName || !extraArg) {
|
|
242
|
+
console.error('Usage: mwala db:table rename <oldName> <newName>');
|
|
243
|
+
process.exit(1);
|
|
244
|
+
}
|
|
245
|
+
renameTable(tableName, extraArg);
|
|
193
246
|
break;
|
|
194
247
|
case 'copy':
|
|
195
|
-
if (!tableName || !
|
|
196
|
-
|
|
248
|
+
if (!tableName || !extraArg) {
|
|
249
|
+
console.error('Usage: mwala db:table copy <source> <destination>');
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
copyTable(tableName, extraArg);
|
|
197
253
|
break;
|
|
198
254
|
case 'exists':
|
|
199
|
-
if (!tableName)
|
|
255
|
+
if (!tableName) {
|
|
256
|
+
console.error('Table name required');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
200
259
|
checkTableExists(tableName);
|
|
201
260
|
break;
|
|
202
|
-
case 'describe':
|
|
203
|
-
if (!tableName) return console.error('Table name required');
|
|
204
|
-
describeTable(tableName);
|
|
205
|
-
break;
|
|
206
|
-
case 'count':
|
|
207
|
-
if (!tableName) return console.error('Table name required');
|
|
208
|
-
countRows(tableName);
|
|
209
|
-
break;
|
|
210
261
|
default:
|
|
211
|
-
console.
|
|
262
|
+
console.error(`Unknown db:table action: ${action}`);
|
|
263
|
+
process.exit(1);
|
|
212
264
|
}
|
|
213
265
|
break;
|
|
214
266
|
|
|
215
|
-
case 'migrate':
|
|
216
|
-
if (args[1] === 'all') migrateAll();
|
|
217
|
-
else console.error('Use: mwala migrate all');
|
|
218
|
-
break;
|
|
219
|
-
|
|
220
|
-
case 'rollback':
|
|
221
|
-
if (args[1] === 'last') rollbackLastMigration();
|
|
222
|
-
else if (args[1] === 'all') dropAllTables(); // or full rollback
|
|
223
|
-
else console.error('Use: mwala rollback last');
|
|
224
|
-
break;
|
|
225
|
-
|
|
226
|
-
case 'db:seed':
|
|
227
|
-
if (!args[1]) return console.error('Seed file required');
|
|
228
|
-
seedDatabase(args[1]);
|
|
229
|
-
break;
|
|
230
|
-
|
|
231
267
|
case 'db:backup':
|
|
232
268
|
backupDatabase();
|
|
233
269
|
break;
|
|
234
270
|
|
|
235
271
|
case 'db:restore':
|
|
236
|
-
if (!args[1])
|
|
272
|
+
if (!args[1]) {
|
|
273
|
+
console.error('Usage: mwala db:restore <backup-file.sql>');
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
237
276
|
restoreDatabase(args[1]);
|
|
238
277
|
break;
|
|
239
278
|
|
|
240
|
-
case 'db:export':
|
|
241
|
-
if (!args[1] || !args[2]) return console.error('Usage: db:export <table> <file.csv>');
|
|
242
|
-
exportTableToCsv(args[1], args[2]);
|
|
243
|
-
break;
|
|
244
|
-
|
|
245
|
-
case 'db:import':
|
|
246
|
-
if (!args[1] || !args[2]) return console.error('Usage: db:import <file.csv> <table>');
|
|
247
|
-
importCsvToTable(args[1], args[2]);
|
|
248
|
-
break;
|
|
249
|
-
|
|
250
279
|
case 'db:size':
|
|
251
280
|
showDatabaseSize();
|
|
252
281
|
break;
|
|
253
282
|
|
|
254
283
|
case 'db:indexes':
|
|
255
|
-
if (!args[1])
|
|
284
|
+
if (!args[1]) {
|
|
285
|
+
console.error('Table name required');
|
|
286
|
+
process.exit(1);
|
|
287
|
+
}
|
|
256
288
|
listIndexes(args[1]);
|
|
257
289
|
break;
|
|
258
290
|
|
|
259
|
-
case 'db:analyze':
|
|
260
|
-
if (!args[1]) return console.error('Table name required');
|
|
261
|
-
analyzeTable(args[1]);
|
|
262
|
-
break;
|
|
263
|
-
|
|
264
|
-
case 'db:reindex':
|
|
265
|
-
if (!args[1]) return console.error('Table name required');
|
|
266
|
-
reindexTable(args[1]);
|
|
267
|
-
break;
|
|
268
|
-
|
|
269
291
|
case 'db:vacuum':
|
|
270
292
|
vacuumDatabase();
|
|
271
293
|
break;
|
|
@@ -275,18 +297,49 @@ switch (command) {
|
|
|
275
297
|
break;
|
|
276
298
|
|
|
277
299
|
case 'db:kill-connections':
|
|
278
|
-
if (readlineSync.keyInYN('
|
|
300
|
+
if (readlineSync.keyInYN(' Kill all other database connections?')) {
|
|
279
301
|
killConnections();
|
|
302
|
+
} else {
|
|
303
|
+
console.log('Cancelled.');
|
|
280
304
|
}
|
|
281
305
|
break;
|
|
282
306
|
|
|
283
307
|
case 'db:drop-all-tables':
|
|
284
|
-
if (readlineSync.keyInYN('
|
|
285
|
-
|
|
308
|
+
if (readlineSync.keyInYN(' DROP ALL TABLES PERMANENTLY? Type YES to confirm:')) {
|
|
309
|
+
if (readlineSync.question('Type database name to confirm: ') === process.env.DB_NAME) {
|
|
310
|
+
dropAllTables();
|
|
311
|
+
} else {
|
|
312
|
+
console.log('Database name mismatch. Aborted.');
|
|
313
|
+
}
|
|
286
314
|
}
|
|
287
315
|
break;
|
|
288
316
|
|
|
317
|
+
case 'db:export':
|
|
318
|
+
if (!args[1] || !args[2]) {
|
|
319
|
+
console.error('Usage: mwala db:export <table> <output.csv>');
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
exportTableToCsv(args[1], args[2]);
|
|
323
|
+
break;
|
|
324
|
+
|
|
325
|
+
case 'db:import':
|
|
326
|
+
if (!args[1] || !args[2]) {
|
|
327
|
+
console.error('Usage: mwala db:import <input.csv> <table>');
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
importCsvToTable(args[1], args[2]);
|
|
331
|
+
break;
|
|
332
|
+
|
|
333
|
+
case 'db:seed':
|
|
334
|
+
if (!args[1]) {
|
|
335
|
+
console.error('Usage: mwala db:seed <seedFile.mjs>');
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
seedDatabase(args[1]);
|
|
339
|
+
break;
|
|
340
|
+
|
|
289
341
|
default:
|
|
290
|
-
console.error(`Unknown command: ${command}
|
|
342
|
+
console.error(`Unknown command: ${command}`);
|
|
343
|
+
console.log('Run "mwala help" for available commands.');
|
|
291
344
|
process.exit(1);
|
|
292
345
|
}
|
package/package.json
CHANGED
package/utils/dbUtils.mjs
CHANGED
|
@@ -1,124 +1,517 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { execSync } from 'child_process';
|
|
4
|
+
import { createWriteStream, createReadStream } from 'fs';
|
|
5
|
+
import csv from 'csv-parser';
|
|
6
|
+
import { stringify } from 'csv-stringify';
|
|
3
7
|
import dotenv from 'dotenv';
|
|
4
8
|
import mysql from 'mysql2/promise';
|
|
5
9
|
import { Client } from 'pg';
|
|
6
10
|
import { MongoClient } from 'mongodb';
|
|
7
11
|
import sqlite3 from 'sqlite3';
|
|
8
|
-
import { execSync } from 'child_process';
|
|
9
12
|
import readlineSync from 'readline-sync';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
10
14
|
|
|
15
|
+
// Load .env
|
|
11
16
|
dotenv.config();
|
|
12
17
|
|
|
13
|
-
const { DB_TYPE, DB_HOST, DB_USER, DB_PASSWORD, DB_NAME } = process.env;
|
|
18
|
+
const { DB_TYPE, DB_HOST = 'localhost', DB_USER, DB_PASSWORD, DB_NAME, DB_PORT } = process.env;
|
|
19
|
+
|
|
20
|
+
// Helper to check if DB is configured
|
|
21
|
+
const isDbConfigured = () => {
|
|
22
|
+
return DB_TYPE && DB_NAME && (DB_TYPE === 'sqlite' || (DB_USER && DB_PASSWORD));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const suggestDbSetup = () => {
|
|
26
|
+
console.log('\n⚠️ Database not configured or connection failed.');
|
|
27
|
+
console.log(' Run: mwala create-db → to set up your database interactively');
|
|
28
|
+
console.log(' Or manually create a .env file with:');
|
|
29
|
+
console.log(' DB_TYPE=mysql|postgresql|sqlite|mongodb');
|
|
30
|
+
console.log(' DB_NAME=your_db_name');
|
|
31
|
+
console.log(' DB_HOST=localhost');
|
|
32
|
+
console.log(' DB_USER=your_user');
|
|
33
|
+
console.log(' DB_PASSWORD=your_pass\n');
|
|
34
|
+
};
|
|
14
35
|
|
|
36
|
+
// Global connection cache
|
|
15
37
|
let connection = null;
|
|
16
38
|
|
|
17
39
|
const getConnection = async () => {
|
|
40
|
+
if (!isDbConfigured()) {
|
|
41
|
+
suggestDbSetup();
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
18
45
|
if (connection) return connection;
|
|
19
46
|
|
|
20
|
-
|
|
21
|
-
|
|
47
|
+
try {
|
|
48
|
+
if (DB_TYPE === 'mysql') {
|
|
22
49
|
connection = await mysql.createConnection({
|
|
23
|
-
host: DB_HOST,
|
|
50
|
+
host: DB_HOST,
|
|
51
|
+
user: DB_USER,
|
|
52
|
+
password: DB_PASSWORD,
|
|
53
|
+
database: DB_NAME,
|
|
54
|
+
port: DB_PORT || 3306
|
|
55
|
+
});
|
|
56
|
+
} else if (DB_TYPE === 'postgresql') {
|
|
57
|
+
connection = new Client({
|
|
58
|
+
host: DB_HOST,
|
|
59
|
+
user: DB_USER,
|
|
60
|
+
password: DB_PASSWORD,
|
|
61
|
+
database: DB_NAME,
|
|
62
|
+
port: DB_PORT || 5432
|
|
24
63
|
});
|
|
25
|
-
break;
|
|
26
|
-
case 'postgresql':
|
|
27
|
-
connection = new Client({ host: DB_HOST, user: DB_USER, password: DB_PASSWORD, database: DB_NAME });
|
|
28
64
|
await connection.connect();
|
|
29
|
-
|
|
30
|
-
case 'mongodb':
|
|
31
|
-
connection = await MongoClient.connect(`mongodb://${DB_HOST}:27017/${DB_NAME}`);
|
|
32
|
-
break;
|
|
33
|
-
case 'sqlite':
|
|
65
|
+
} else if (DB_TYPE === 'sqlite') {
|
|
34
66
|
connection = new sqlite3.Database(`./${DB_NAME}.sqlite`);
|
|
35
|
-
|
|
67
|
+
connection.serialize();
|
|
68
|
+
} else if (DB_TYPE === 'mongodb') {
|
|
69
|
+
const client = await MongoClient.connect(`mongodb://${DB_HOST}:${DB_PORT || 27017}`);
|
|
70
|
+
connection = client.db(DB_NAME);
|
|
71
|
+
} else {
|
|
72
|
+
console.error(' Unsupported DB_TYPE:', DB_TYPE);
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
} catch (err) {
|
|
76
|
+
console.error(' Failed to connect to database:', err.message);
|
|
77
|
+
suggestDbSetup();
|
|
78
|
+
process.exit(1);
|
|
36
79
|
}
|
|
80
|
+
|
|
37
81
|
return connection;
|
|
38
82
|
};
|
|
39
83
|
|
|
84
|
+
// ====================== IMPLEMENTED COMMANDS ======================
|
|
85
|
+
|
|
40
86
|
export const listTables = async () => {
|
|
41
87
|
const conn = await getConnection();
|
|
88
|
+
|
|
42
89
|
if (DB_TYPE === 'mysql') {
|
|
43
90
|
const [rows] = await conn.query('SHOW TABLES');
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
console.log('
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
|
|
91
|
+
const tables = rows.map(r => Object.values(r)[0]);
|
|
92
|
+
console.log('📋 Tables in database:');
|
|
93
|
+
tables.forEach(t => console.log(` • ${t}`));
|
|
94
|
+
if (tables.length === 0) console.log(' (No tables found)');
|
|
95
|
+
}
|
|
96
|
+
else if (DB_TYPE === 'postgresql') {
|
|
97
|
+
const res = await conn.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
|
|
98
|
+
console.log('📋 Tables:');
|
|
99
|
+
res.rows.forEach(r => console.log(` • ${r.tablename}`));
|
|
100
|
+
}
|
|
101
|
+
else if (DB_TYPE === 'sqlite') {
|
|
102
|
+
return new Promise((resolve) => {
|
|
103
|
+
conn.all("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'", (err, rows) => {
|
|
104
|
+
if (err) return console.error(err.message);
|
|
105
|
+
console.log('📋 Tables:');
|
|
106
|
+
rows.forEach(r => console.log(` • ${r.name}`));
|
|
107
|
+
if (rows.length === 0) console.log(' (No tables found)');
|
|
108
|
+
resolve();
|
|
109
|
+
});
|
|
51
110
|
});
|
|
111
|
+
}
|
|
112
|
+
else if (DB_TYPE === 'mongodb') {
|
|
113
|
+
const collections = await conn.listCollections().toArray();
|
|
114
|
+
console.log('📋 Collections:');
|
|
115
|
+
collections.forEach(c => console.log(` • ${c.name}`));
|
|
52
116
|
}
|
|
53
117
|
};
|
|
54
118
|
|
|
55
119
|
export const describeTable = async (table) => {
|
|
56
120
|
const conn = await getConnection();
|
|
121
|
+
|
|
57
122
|
if (DB_TYPE === 'mysql') {
|
|
58
123
|
const [rows] = await conn.query(`DESCRIBE \`${table}\``);
|
|
59
124
|
console.table(rows);
|
|
60
|
-
}
|
|
125
|
+
}
|
|
126
|
+
else if (DB_TYPE === 'postgresql') {
|
|
61
127
|
const res = await conn.query(`
|
|
62
|
-
SELECT column_name, data_type, is_nullable
|
|
128
|
+
SELECT column_name, data_type, is_nullable, column_default
|
|
63
129
|
FROM information_schema.columns
|
|
64
|
-
WHERE table_name = $1
|
|
130
|
+
WHERE table_name = $1
|
|
131
|
+
ORDER BY ordinal_position
|
|
132
|
+
`, [table]);
|
|
65
133
|
console.table(res.rows);
|
|
134
|
+
}
|
|
135
|
+
else if (DB_TYPE === 'sqlite') {
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
conn.all(`PRAGMA table_info(\`${table}\`)`, (err, rows) => {
|
|
138
|
+
if (err) return console.error('Table not found or error:', err.message);
|
|
139
|
+
console.table(rows);
|
|
140
|
+
resolve();
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else if (DB_TYPE === 'mongodb') {
|
|
145
|
+
const sample = await conn.collection(table).findOne();
|
|
146
|
+
if (!sample) {
|
|
147
|
+
console.log(`Collection "${table}" is empty.`);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
console.log(`📄 Sample document from "${table}":`);
|
|
151
|
+
console.dir(sample, { depth: null, colors: true });
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
export const countRows = async (table) => {
|
|
156
|
+
const conn = await getConnection();
|
|
157
|
+
|
|
158
|
+
if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
|
|
159
|
+
const [res] = await conn.query(`SELECT COUNT(*) AS count FROM \`${table}\``);
|
|
160
|
+
console.log(`📊 Rows in "${table}": ${res[0].count}`);
|
|
161
|
+
}
|
|
162
|
+
else if (DB_TYPE === 'sqlite') {
|
|
163
|
+
return new Promise((resolve) => {
|
|
164
|
+
conn.get(`SELECT COUNT(*) AS count FROM \`${table}\``, (err, row) => {
|
|
165
|
+
if (err) return console.error(err.message);
|
|
166
|
+
console.log(`📊 Rows in "${table}": ${row.count}`);
|
|
167
|
+
resolve();
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
else if (DB_TYPE === 'mongodb') {
|
|
172
|
+
const count = await conn.collection(table).estimatedDocumentCount();
|
|
173
|
+
console.log(`📊 Documents in "${table}": ${count}`);
|
|
66
174
|
}
|
|
67
175
|
};
|
|
68
176
|
|
|
69
177
|
export const truncateTable = async (table) => {
|
|
70
178
|
const conn = await getConnection();
|
|
71
|
-
|
|
72
|
-
|
|
179
|
+
|
|
180
|
+
if (DB_TYPE === 'mysql') {
|
|
181
|
+
await conn.query(`TRUNCATE TABLE \`${table}\``);
|
|
182
|
+
}
|
|
183
|
+
else if (DB_TYPE === 'postgresql') {
|
|
184
|
+
await conn.query(`TRUNCATE TABLE "${table}" RESTART IDENTITY CASCADE`);
|
|
185
|
+
}
|
|
186
|
+
else if (DB_TYPE === 'sqlite') {
|
|
187
|
+
await new Promise((resolve, reject) => {
|
|
188
|
+
conn.run(`DELETE FROM \`${table}\``, function(err) {
|
|
189
|
+
if (err) reject(err);
|
|
190
|
+
conn.run(`DELETE FROM sqlite_sequence WHERE name='${table}'`, () => resolve());
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
else if (DB_TYPE === 'mongodb') {
|
|
195
|
+
await conn.collection(table).deleteMany({});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
console.log(` "${table}" truncated successfully`);
|
|
73
199
|
};
|
|
74
200
|
|
|
75
201
|
export const renameTable = async (oldName, newName) => {
|
|
76
202
|
const conn = await getConnection();
|
|
77
|
-
|
|
78
|
-
|
|
203
|
+
|
|
204
|
+
if (DB_TYPE === 'mysql') {
|
|
205
|
+
await conn.query(`ALTER TABLE \`${oldName}\` RENAME TO \`${newName}\``);
|
|
206
|
+
}
|
|
207
|
+
else if (DB_TYPE === 'postgresql') {
|
|
208
|
+
await conn.query(`ALTER TABLE "${oldName}" RENAME TO "${newName}"`);
|
|
209
|
+
}
|
|
210
|
+
else if (DB_TYPE === 'sqlite') {
|
|
211
|
+
await new Promise((resolve, reject) => {
|
|
212
|
+
conn.run(`ALTER TABLE \`${oldName}\` RENAME TO \`${newName}\``, err => {
|
|
213
|
+
err ? reject(err) : resolve();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
else if (DB_TYPE === 'mongodb') {
|
|
218
|
+
await conn.collection(oldName).rename(newName);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
console.log(` Renamed "${oldName}" → "${newName}"`);
|
|
79
222
|
};
|
|
80
223
|
|
|
81
|
-
export const
|
|
82
|
-
const
|
|
83
|
-
const backupFile = `backup-${DB_NAME}-${timestamp}.sql`;
|
|
224
|
+
export const checkTableExists = async (table) => {
|
|
225
|
+
const conn = await getConnection();
|
|
84
226
|
|
|
227
|
+
let exists = false;
|
|
85
228
|
if (DB_TYPE === 'mysql') {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
229
|
+
const [rows] = await conn.query(`SHOW TABLES LIKE '${table}'`);
|
|
230
|
+
exists = rows.length > 0;
|
|
231
|
+
}
|
|
232
|
+
else if (DB_TYPE === 'postgresql') {
|
|
233
|
+
const res = await conn.query(`SELECT to_regclass('public.${table}')`);
|
|
234
|
+
exists = res.rows[0].to_regclass !== null;
|
|
235
|
+
}
|
|
236
|
+
else if (DB_TYPE === 'sqlite') {
|
|
237
|
+
await new Promise((resolve) => {
|
|
238
|
+
conn.get(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`, (err, row) => {
|
|
239
|
+
exists = !!row;
|
|
240
|
+
resolve();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
else if (DB_TYPE === 'mongodb') {
|
|
245
|
+
const collections = await conn.listCollections({ name: table }).toArray();
|
|
246
|
+
exists = collections.length > 0;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log(`Table/collection "${table}" ${exists ? ' exists' : ' does not exist'}`);
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
export const copyTable = async (source, destination) => {
|
|
253
|
+
const conn = await getConnection();
|
|
254
|
+
|
|
255
|
+
if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
|
|
256
|
+
const sql = DB_TYPE === 'mysql'
|
|
257
|
+
? `CREATE TABLE \`${destination}\` LIKE \`${source}\`; INSERT INTO \`${destination}\` SELECT * FROM \`${source}\`;`
|
|
258
|
+
: `CREATE TABLE "${destination}" AS TABLE "${source}" WITH DATA;`;
|
|
259
|
+
await conn.query(sql);
|
|
260
|
+
}
|
|
261
|
+
else if (DB_TYPE === 'sqlite') {
|
|
262
|
+
await new Promise((resolve, reject) => {
|
|
263
|
+
conn.run(`CREATE TABLE \`${destination}\` AS SELECT * FROM \`${source}\``, err => {
|
|
264
|
+
err ? reject(err) : resolve();
|
|
265
|
+
});
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(` Copied "${source}" → "${destination}"`);
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
export const backupDatabase = () => {
|
|
273
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
274
|
+
const file = `backup-${DB_NAME}-${timestamp}.sql`;
|
|
275
|
+
|
|
276
|
+
try {
|
|
277
|
+
if (DB_TYPE === 'mysql') {
|
|
278
|
+
execSync(`mysqldump -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} > "${file}"`);
|
|
279
|
+
}
|
|
280
|
+
else if (DB_TYPE === 'postgresql') {
|
|
281
|
+
process.env.PGPASSWORD = DB_PASSWORD;
|
|
282
|
+
execSync(`pg_dump -h ${DB_HOST} -U ${DB_USER} ${DB_NAME} > "${file}"`);
|
|
283
|
+
}
|
|
284
|
+
else if (DB_TYPE === 'sqlite') {
|
|
285
|
+
fs.copyFileSync(`./${DB_NAME}.sqlite`, file.replace('.sql', '.sqlite'));
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
console.log(` Backup saved: ${file}`);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
console.error(' Backup failed:', err.message);
|
|
89
291
|
}
|
|
90
|
-
console.log(`Backup saved: ${backupFile}`);
|
|
91
292
|
};
|
|
92
293
|
|
|
93
294
|
export const restoreDatabase = (file) => {
|
|
94
|
-
if (!fs.existsSync(file))
|
|
295
|
+
if (!fs.existsSync(file)) {
|
|
296
|
+
console.error(' Backup file not found:', file);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
if (DB_TYPE === 'mysql') {
|
|
302
|
+
execSync(`mysql -h ${DB_HOST} -u ${DB_USER} -p${DB_PASSWORD} ${DB_NAME} < "${file}"`);
|
|
303
|
+
}
|
|
304
|
+
else if (DB_TYPE === 'postgresql') {
|
|
305
|
+
process.env.PGPASSWORD = DB_PASSWORD;
|
|
306
|
+
execSync(`psql -h ${DB_HOST} -U ${DB_USER} -d ${DB_NAME} -f "${file}"`);
|
|
307
|
+
}
|
|
308
|
+
else if (DB_TYPE === 'sqlite') {
|
|
309
|
+
fs.copyFileSync(file, `./${DB_NAME}.sqlite`);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
console.log(' Database restored from:', file);
|
|
313
|
+
} catch (err) {
|
|
314
|
+
console.error(' Restore failed:', err.message);
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export const showDatabaseSize = async () => {
|
|
319
|
+
const conn = await getConnection();
|
|
95
320
|
|
|
96
321
|
if (DB_TYPE === 'mysql') {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
export const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
322
|
+
const [res] = await conn.query(`
|
|
323
|
+
SELECT table_schema AS "Database",
|
|
324
|
+
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS "Size (MB)"
|
|
325
|
+
FROM information_schema.TABLES
|
|
326
|
+
WHERE table_schema = '${DB_NAME}'
|
|
327
|
+
GROUP BY table_schema
|
|
328
|
+
`);
|
|
329
|
+
console.table(res);
|
|
330
|
+
}
|
|
331
|
+
else if (DB_TYPE === 'postgresql') {
|
|
332
|
+
const res = await conn.query(`SELECT pg_size_pretty(pg_database_size('${DB_NAME}')) AS size`);
|
|
333
|
+
console.log(`Database size: ${res.rows[0].size}`);
|
|
334
|
+
}
|
|
335
|
+
else if (DB_TYPE === 'sqlite') {
|
|
336
|
+
const stats = fs.statSync(`./${DB_NAME}.sqlite`);
|
|
337
|
+
console.log(`Database file size: ${(stats.size / 1024 / 1024).toFixed(2)} MB`);
|
|
338
|
+
}
|
|
339
|
+
else if (DB_TYPE === 'mongodb') {
|
|
340
|
+
const stats = await conn.stats();
|
|
341
|
+
console.log(`Database size: ${(stats.dataSize / 1024 / 1024).toFixed(2)} MB`);
|
|
342
|
+
}
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
export const listIndexes = async (table) => {
|
|
346
|
+
const conn = await getConnection();
|
|
347
|
+
|
|
348
|
+
if (DB_TYPE === 'mysql') {
|
|
349
|
+
const [rows] = await conn.query(`SHOW INDEX FROM \`${table}\``);
|
|
350
|
+
console.table(rows);
|
|
351
|
+
}
|
|
352
|
+
else if (DB_TYPE === 'postgresql') {
|
|
353
|
+
const res = await conn.query(`
|
|
354
|
+
SELECT indexname, indexdef
|
|
355
|
+
FROM pg_indexes
|
|
356
|
+
WHERE tablename = $1
|
|
357
|
+
`, [table]);
|
|
358
|
+
console.table(res.rows);
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
|
|
362
|
+
export const vacuumDatabase = async () => {
|
|
363
|
+
const conn = await getConnection();
|
|
364
|
+
|
|
365
|
+
if (DB_TYPE === 'postgresql') {
|
|
366
|
+
await conn.query('VACUUM ANALYZE');
|
|
367
|
+
console.log(' VACUUM ANALYZE completed');
|
|
368
|
+
}
|
|
369
|
+
else if (DB_TYPE === 'sqlite') {
|
|
370
|
+
await new Promise((resolve) => {
|
|
371
|
+
conn.exec('VACUUM', () => {
|
|
372
|
+
console.log(' VACUUM completed');
|
|
373
|
+
resolve();
|
|
374
|
+
});
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
else {
|
|
378
|
+
console.log('VACUUM not supported for', DB_TYPE);
|
|
379
|
+
}
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
export const showConnections = async () => {
|
|
383
|
+
const conn = await getConnection();
|
|
384
|
+
|
|
385
|
+
if (DB_TYPE === 'postgresql') {
|
|
386
|
+
const res = await conn.query(`
|
|
387
|
+
SELECT count(*), state FROM pg_stat_activity
|
|
388
|
+
WHERE datname = '${DB_NAME}'
|
|
389
|
+
GROUP BY state
|
|
390
|
+
`);
|
|
391
|
+
console.table(res.rows);
|
|
392
|
+
}
|
|
393
|
+
else if (DB_TYPE === 'mysql') {
|
|
394
|
+
const [rows] = await conn.query('SHOW PROCESSLIST');
|
|
395
|
+
console.log(`Active connections: ${rows.length}`);
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
export const killConnections = async () => {
|
|
400
|
+
const conn = await getConnection();
|
|
401
|
+
|
|
402
|
+
if (DB_TYPE === 'postgresql') {
|
|
403
|
+
await conn.query(`
|
|
404
|
+
SELECT pg_terminate_backend(pg_stat_activity.pid)
|
|
405
|
+
FROM pg_stat_activity
|
|
406
|
+
WHERE pg_stat_activity.datname = '${DB_NAME}'
|
|
407
|
+
AND pid <> pg_backend_pid()
|
|
408
|
+
`);
|
|
409
|
+
console.log(' All other connections terminated');
|
|
410
|
+
}
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
export const dropAllTables = async () => {
|
|
414
|
+
const conn = await getConnection();
|
|
415
|
+
|
|
416
|
+
if (DB_TYPE === 'mysql') {
|
|
417
|
+
await conn.query('SET FOREIGN_KEY_CHECKS = 0');
|
|
418
|
+
const [tables] = await conn.query('SHOW TABLES');
|
|
419
|
+
for (const t of tables) {
|
|
420
|
+
const tableName = Object.values(t)[0];
|
|
421
|
+
await conn.query(`DROP TABLE IF EXISTS \`${tableName}\``);
|
|
422
|
+
}
|
|
423
|
+
await conn.query('SET FOREIGN_KEY_CHECKS = 1');
|
|
424
|
+
}
|
|
425
|
+
else if (DB_TYPE === 'postgresql') {
|
|
426
|
+
const res = await conn.query("SELECT tablename FROM pg_tables WHERE schemaname = 'public'");
|
|
427
|
+
for (const row of res.rows) {
|
|
428
|
+
await conn.query(`DROP TABLE IF EXISTS "${row.tablename}" CASCADE`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
else if (DB_TYPE === 'mongodb') {
|
|
432
|
+
const collections = await conn.listCollections().toArray();
|
|
433
|
+
for (const col of collections) {
|
|
434
|
+
await conn.collection(col.name).drop();
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
console.log('⚠️ All tables/collections dropped!');
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
export const exportTableToCsv = async (table, csvFile) => {
|
|
442
|
+
const conn = await getConnection();
|
|
443
|
+
|
|
444
|
+
const writableStream = createWriteStream(csvFile);
|
|
445
|
+
|
|
446
|
+
if (DB_TYPE === 'mysql' || DB_TYPE === 'postgresql') {
|
|
447
|
+
const [rows] = await conn.query(`SELECT * FROM \`${table}\``);
|
|
448
|
+
const stringifier = stringify({ header: true });
|
|
449
|
+
stringifier.pipe(writableStream);
|
|
450
|
+
rows.forEach(row => stringifier.write(row));
|
|
451
|
+
stringifier.end();
|
|
452
|
+
}
|
|
453
|
+
else if (DB_TYPE === 'sqlite') {
|
|
454
|
+
await new Promise((resolve) => {
|
|
455
|
+
conn.all(`SELECT * FROM \`${table}\``, (err, rows) => {
|
|
456
|
+
const stringifier = stringify({ header: true });
|
|
457
|
+
stringifier.pipe(writableStream);
|
|
458
|
+
rows.forEach(row => stringifier.write(row));
|
|
459
|
+
stringifier.end();
|
|
460
|
+
resolve();
|
|
461
|
+
});
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log(` Exported "${table}" → ${csvFile}`);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
export const importCsvToTable = async (csvFile, table) => {
|
|
469
|
+
if (!fs.existsSync(csvFile)) {
|
|
470
|
+
console.error('CSV file not found:', csvFile);
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
const conn = await getConnection();
|
|
475
|
+
const results = [];
|
|
476
|
+
|
|
477
|
+
createReadStream(csvFile)
|
|
478
|
+
.pipe(csv())
|
|
479
|
+
.on('data', (data) => results.push(data))
|
|
480
|
+
.on('end', async () => {
|
|
481
|
+
if (results.length === 0) {
|
|
482
|
+
console.log('No data to import');
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const columns = Object.keys(results[0]).map(c => `\`${c}\``).join(', ');
|
|
487
|
+
const placeholders = Object.keys(results[0]).map(() => '?').join(', ');
|
|
488
|
+
|
|
489
|
+
for (const row of results) {
|
|
490
|
+
const values = Object.values(row);
|
|
491
|
+
await conn.query(`INSERT INTO \`${table}\` (${columns}) VALUES (${placeholders})`, values);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
console.log(` Imported ${results.length} rows into "${table}"`);
|
|
495
|
+
});
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
export const seedDatabase = async (seedFile) => {
|
|
499
|
+
const fullPath = path.resolve(seedFile);
|
|
500
|
+
|
|
501
|
+
if (!fs.existsSync(fullPath)) {
|
|
502
|
+
console.error(' Seed file not found:', fullPath);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
try {
|
|
507
|
+
const seedModule = await import(`file://${fullPath}`);
|
|
508
|
+
if (typeof seedModule.default === 'function') {
|
|
509
|
+
await seedModule.default();
|
|
510
|
+
console.log(' Seed completed');
|
|
511
|
+
} else {
|
|
512
|
+
console.log('Seed file should export a default async function');
|
|
513
|
+
}
|
|
514
|
+
} catch (err) {
|
|
515
|
+
console.error(' Seed failed:', err.message);
|
|
516
|
+
}
|
|
517
|
+
};
|