mwalajs 1.1.16 → 1.1.18
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/LICENSE +1 -1
- package/bin/mwala.mjs +95 -33
- package/createProject.mjs +178 -51
- package/package.json +1 -1
- package/runMigrations.mjs +143 -0
package/LICENSE
CHANGED
package/bin/mwala.mjs
CHANGED
|
@@ -39,7 +39,24 @@ const __dirname = path.dirname(__filename);
|
|
|
39
39
|
// ────────────────────────────────────────────────
|
|
40
40
|
|
|
41
41
|
let imports = {};
|
|
42
|
-
let setupMwalajs, createProject, dropAllTables, getDbConnection;
|
|
42
|
+
// let setupMwalajs, createProject, dropAllTables, getDbConnection;
|
|
43
|
+
let {
|
|
44
|
+
setupMwalajs,
|
|
45
|
+
createProject,
|
|
46
|
+
dropAllTables,
|
|
47
|
+
getDbConnection,
|
|
48
|
+
listTables,
|
|
49
|
+
createTable,
|
|
50
|
+
dropTable,
|
|
51
|
+
migrateAll,
|
|
52
|
+
rollbackLastMigration,
|
|
53
|
+
showDatabaseSize,
|
|
54
|
+
listIndexes,
|
|
55
|
+
analyzeTable,
|
|
56
|
+
vacuumDatabase,
|
|
57
|
+
showConnections,
|
|
58
|
+
killConnections,
|
|
59
|
+
} = imports;
|
|
43
60
|
|
|
44
61
|
try {
|
|
45
62
|
const [
|
|
@@ -48,6 +65,7 @@ try {
|
|
|
48
65
|
setup,
|
|
49
66
|
proj,
|
|
50
67
|
dbUtilsRaw,
|
|
68
|
+
maintenance,
|
|
51
69
|
] = await Promise.all([
|
|
52
70
|
import(pathToFileURL(path.join(__dirname, '../config/createdatabase.mjs')).href),
|
|
53
71
|
import(pathToFileURL(path.join(__dirname, '../runMigrations.mjs')).href),
|
|
@@ -64,16 +82,31 @@ try {
|
|
|
64
82
|
...normalize(setup),
|
|
65
83
|
...normalize(proj),
|
|
66
84
|
...normalize(dbUtilsRaw),
|
|
85
|
+
...normalize(maintenance), // ← important
|
|
67
86
|
};
|
|
68
87
|
|
|
69
88
|
// 🔥 IMPORTANT FIX
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
({
|
|
90
|
+
setupMwalajs,
|
|
91
|
+
createProject,
|
|
92
|
+
dropAllTables,
|
|
93
|
+
getDbConnection,
|
|
94
|
+
listTables,
|
|
95
|
+
createTable,
|
|
96
|
+
dropTable,
|
|
97
|
+
migrateAll,
|
|
98
|
+
rollbackLastMigration,
|
|
99
|
+
// ongeza hizi zingine ukizihitaji baadaye
|
|
100
|
+
showDatabaseSize,
|
|
101
|
+
listIndexes,
|
|
102
|
+
analyzeTable,
|
|
103
|
+
vacuumDatabase,
|
|
104
|
+
showConnections,
|
|
105
|
+
killConnections,
|
|
106
|
+
// n.k.
|
|
107
|
+
} = imports);
|
|
108
|
+
|
|
109
|
+
|
|
77
110
|
} catch (err) {
|
|
78
111
|
error(`Failed to load required modules:\n${err.stack || err.message}`);
|
|
79
112
|
process.exit(1);
|
|
@@ -596,38 +629,67 @@ case 'db:restore': {
|
|
|
596
629
|
}
|
|
597
630
|
|
|
598
631
|
|
|
632
|
+
// case 'db:size':
|
|
633
|
+
// await runSafe(showDatabaseSize, 'Database size shown');
|
|
634
|
+
// break;
|
|
635
|
+
|
|
636
|
+
// case 'db:indexes':
|
|
637
|
+
// if (!args[1]) return error('Table name required');
|
|
638
|
+
// await runSafe(() => listIndexes(args[1]), `Indexes for ${args[1]}`);
|
|
639
|
+
// break;
|
|
640
|
+
|
|
641
|
+
// case 'db:analyze':
|
|
642
|
+
// if (!args[1]) return error('Table name required');
|
|
643
|
+
// await runSafe(() => analyzeTable(args[1]), `Table ${args[1]} analyzed`);
|
|
644
|
+
// break;
|
|
645
|
+
|
|
646
|
+
// case 'db:reindex':
|
|
647
|
+
// if (!args[1]) return error('Table name required');
|
|
648
|
+
// await runSafe(() => reindexTable(args[1]), `Table ${args[1]} reindexed`);
|
|
649
|
+
// break;
|
|
650
|
+
|
|
651
|
+
// case 'db:vacuum':
|
|
652
|
+
// await runSafe(vacuumDatabase, 'Database vacuumed');
|
|
653
|
+
// break;
|
|
654
|
+
|
|
655
|
+
// case 'db:connections':
|
|
656
|
+
// await runSafe(showConnections, 'Active connections shown');
|
|
657
|
+
// break;
|
|
658
|
+
|
|
659
|
+
|
|
599
660
|
case 'db:size':
|
|
600
|
-
|
|
601
|
-
|
|
661
|
+
await runSafe(() => imports.showDatabaseSize(), 'Database size shown');
|
|
662
|
+
break;
|
|
602
663
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
664
|
+
case 'db:indexes':
|
|
665
|
+
if (!args[1]) return error('Table name required: mwala db:indexes <table>');
|
|
666
|
+
await runSafe(() => imports.listIndexes(args[1]), `Indexes for ${args[1]}`);
|
|
667
|
+
break;
|
|
607
668
|
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
669
|
+
case 'db:analyze':
|
|
670
|
+
if (!args[1]) return error('Table name required');
|
|
671
|
+
await runSafe(() => imports.analyzeTable(args[1]), `Table ${args[1]} analyzed`);
|
|
672
|
+
break;
|
|
612
673
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
break;
|
|
674
|
+
case 'db:vacuum':
|
|
675
|
+
await runSafe(() => imports.vacuumDatabase(), 'Database vacuumed');
|
|
676
|
+
break;
|
|
617
677
|
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
678
|
+
case 'db:connections':
|
|
679
|
+
await runSafe(() => imports.showConnections(), 'Active connections shown');
|
|
680
|
+
break;
|
|
621
681
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
682
|
+
case 'db:kill-connections':
|
|
683
|
+
if (readlineSync.keyInYNStrict('⚠️ Kill ALL other database connections? (hatari!)')) {
|
|
684
|
+
await runSafe(() => imports.killConnections(), 'Other connections killed');
|
|
685
|
+
}
|
|
686
|
+
break;
|
|
625
687
|
|
|
626
|
-
case 'db:kill-connections':
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
688
|
+
// case 'db:kill-connections':
|
|
689
|
+
// if (readlineSync.keyInYNStrict('⚠️ Kill ALL other database connections?')) {
|
|
690
|
+
// await runSafe(killConnections, 'Other connections killed');
|
|
691
|
+
// }
|
|
692
|
+
// break;
|
|
631
693
|
|
|
632
694
|
case 'db:drop-all-tables':
|
|
633
695
|
if (readlineSync.keyInYNStrict('⚠️⚠️ THIS WILL DROP **ALL** TABLES! Continue?')) {
|
package/createProject.mjs
CHANGED
|
@@ -1,44 +1,67 @@
|
|
|
1
|
-
// createProject.mjs
|
|
1
|
+
// createProject.mjs (ULTIMATE CROSS-PLATFORM VERSION)
|
|
2
|
+
|
|
2
3
|
import fs from "fs-extra";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import readline from "readline";
|
|
5
6
|
import os from "os";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
|
|
9
|
+
/* =========================
|
|
10
|
+
SAFE PATH HELPERS
|
|
11
|
+
========================= */
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
6
15
|
|
|
7
16
|
/**
|
|
8
|
-
*
|
|
17
|
+
* Detect best possible MwalaJS template path
|
|
18
|
+
* Works on ALL OS
|
|
9
19
|
*/
|
|
10
20
|
function getMwalajsPath() {
|
|
11
21
|
const envPath = process.env.MWALAJSPATH;
|
|
12
22
|
|
|
13
|
-
const
|
|
23
|
+
const possiblePaths = [
|
|
14
24
|
envPath,
|
|
25
|
+
path.join(process.cwd(), "template"),
|
|
26
|
+
path.join(__dirname, "template"),
|
|
27
|
+
path.join(__dirname, "../template"),
|
|
28
|
+
path.join(__dirname, "../../template"),
|
|
15
29
|
"C:\\Program Files\\mwalajs",
|
|
30
|
+
"C:\\mwalajs",
|
|
16
31
|
"/usr/local/lib/mwalajs",
|
|
32
|
+
"/usr/lib/mwalajs",
|
|
17
33
|
"/var/www/mwalajs",
|
|
18
|
-
|
|
34
|
+
"/opt/mwalajs"
|
|
19
35
|
];
|
|
20
36
|
|
|
21
|
-
for (const p of
|
|
22
|
-
if (p && fs.existsSync(p))
|
|
37
|
+
for (const p of possiblePaths) {
|
|
38
|
+
if (p && fs.existsSync(p)) {
|
|
39
|
+
return fs.realpathSync(p);
|
|
40
|
+
}
|
|
23
41
|
}
|
|
24
42
|
|
|
25
|
-
return null;
|
|
43
|
+
return null;
|
|
26
44
|
}
|
|
27
45
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
|
|
46
|
+
/* =========================
|
|
47
|
+
CLI INPUT
|
|
48
|
+
========================= */
|
|
49
|
+
|
|
50
|
+
function ask(rl, question) {
|
|
51
|
+
return new Promise((resolve) => {
|
|
52
|
+
rl.question(question, (ans) => resolve(ans.trim()));
|
|
53
|
+
});
|
|
33
54
|
}
|
|
34
55
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
56
|
+
/* =========================
|
|
57
|
+
MANUAL TEMPLATE GENERATOR
|
|
58
|
+
(ALWAYS WORKS - FALLBACK)
|
|
59
|
+
========================= */
|
|
60
|
+
|
|
38
61
|
function createManualTemplate(target) {
|
|
39
|
-
console.log("⚠ No template found.
|
|
62
|
+
console.log("\n⚠ No template found. Generating FULL MwalaJS scaffold...\n");
|
|
40
63
|
|
|
41
|
-
const
|
|
64
|
+
const dirs = [
|
|
42
65
|
"controllers",
|
|
43
66
|
"routes",
|
|
44
67
|
"models",
|
|
@@ -47,16 +70,23 @@ function createManualTemplate(target) {
|
|
|
47
70
|
"views/pages",
|
|
48
71
|
"middlewares",
|
|
49
72
|
"migrations",
|
|
73
|
+
"config",
|
|
74
|
+
"public",
|
|
50
75
|
"public/css",
|
|
51
76
|
"public/js",
|
|
52
|
-
"public/images"
|
|
77
|
+
"public/images",
|
|
78
|
+
"storage",
|
|
79
|
+
"logs"
|
|
53
80
|
];
|
|
54
81
|
|
|
55
|
-
|
|
82
|
+
dirs.forEach((dir) => {
|
|
56
83
|
fs.mkdirSync(path.join(target, dir), { recursive: true });
|
|
57
84
|
});
|
|
58
85
|
|
|
59
|
-
|
|
86
|
+
/* =========================
|
|
87
|
+
APP ENTRY
|
|
88
|
+
========================= */
|
|
89
|
+
|
|
60
90
|
fs.writeFileSync(
|
|
61
91
|
path.join(target, "app.mjs"),
|
|
62
92
|
`import mwalajs from 'mwalajs';
|
|
@@ -68,30 +98,43 @@ const __dirname = path.dirname(__filename);
|
|
|
68
98
|
|
|
69
99
|
mwalajs.set('view engine', 'ejs');
|
|
70
100
|
mwalajs.set('views', path.join(__dirname, 'views'));
|
|
101
|
+
|
|
71
102
|
mwalajs.useStatic(path.join(__dirname, 'public'));
|
|
72
103
|
|
|
104
|
+
/* ROUTES */
|
|
73
105
|
mwalajs.get('/', (req, res) => {
|
|
74
|
-
res.render('pages/index', {
|
|
106
|
+
res.render('pages/index', {
|
|
107
|
+
title: '🚀 MwalaJS Application Running'
|
|
108
|
+
});
|
|
75
109
|
});
|
|
76
110
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
111
|
+
/* SERVER */
|
|
112
|
+
const PORT = process.env.PORT || 3000;
|
|
113
|
+
mwalajs.listen(PORT, () => {
|
|
114
|
+
console.log(\`🚀 Server running on http://localhost:\${PORT}\`);
|
|
80
115
|
});
|
|
81
116
|
`
|
|
82
117
|
);
|
|
83
118
|
|
|
84
|
-
|
|
119
|
+
/* =========================
|
|
120
|
+
CONTROLLER
|
|
121
|
+
========================= */
|
|
122
|
+
|
|
85
123
|
fs.writeFileSync(
|
|
86
124
|
path.join(target, "controllers/homeController.mjs"),
|
|
87
125
|
`export const homeController = {
|
|
88
|
-
|
|
89
|
-
res.render('pages/index', {
|
|
126
|
+
index: (req, res) => {
|
|
127
|
+
res.render('pages/index', {
|
|
128
|
+
title: 'Home Page'
|
|
129
|
+
});
|
|
90
130
|
}
|
|
91
131
|
};`
|
|
92
132
|
);
|
|
93
133
|
|
|
94
|
-
|
|
134
|
+
/* =========================
|
|
135
|
+
ROUTES
|
|
136
|
+
========================= */
|
|
137
|
+
|
|
95
138
|
fs.writeFileSync(
|
|
96
139
|
path.join(target, "routes/homeRoutes.mjs"),
|
|
97
140
|
`import mwalajs from 'mwalajs';
|
|
@@ -99,70 +142,127 @@ import { homeController } from '../controllers/homeController.mjs';
|
|
|
99
142
|
|
|
100
143
|
const router = mwalajs.Router();
|
|
101
144
|
|
|
102
|
-
router.get('/', homeController.
|
|
145
|
+
router.get('/', homeController.index);
|
|
103
146
|
|
|
104
147
|
export { router as homeRoutes };
|
|
105
148
|
`
|
|
106
149
|
);
|
|
107
150
|
|
|
108
|
-
|
|
151
|
+
/* =========================
|
|
152
|
+
MODEL EXAMPLE
|
|
153
|
+
========================= */
|
|
154
|
+
|
|
155
|
+
fs.writeFileSync(
|
|
156
|
+
path.join(target, "models/User.mjs"),
|
|
157
|
+
`export class User {
|
|
158
|
+
constructor() {
|
|
159
|
+
this.table = "users";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Add model logic here
|
|
163
|
+
}
|
|
164
|
+
`
|
|
165
|
+
);
|
|
166
|
+
|
|
167
|
+
/* =========================
|
|
168
|
+
VIEW
|
|
169
|
+
========================= */
|
|
170
|
+
|
|
109
171
|
fs.writeFileSync(
|
|
110
172
|
path.join(target, "views/pages/index.ejs"),
|
|
111
173
|
`<!DOCTYPE html>
|
|
112
174
|
<html>
|
|
113
175
|
<head>
|
|
176
|
+
<meta charset="UTF-8" />
|
|
114
177
|
<title><%= title %></title>
|
|
115
178
|
<style>
|
|
116
|
-
body {
|
|
117
|
-
|
|
179
|
+
body {
|
|
180
|
+
font-family: Arial;
|
|
181
|
+
background: linear-gradient(135deg,#0f172a,#1e293b);
|
|
182
|
+
color: white;
|
|
183
|
+
text-align: center;
|
|
184
|
+
padding: 60px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.box {
|
|
188
|
+
background: rgba(255,255,255,0.05);
|
|
189
|
+
padding: 30px;
|
|
190
|
+
border-radius: 16px;
|
|
191
|
+
display: inline-block;
|
|
192
|
+
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
h1 {
|
|
196
|
+
font-size: 40px;
|
|
197
|
+
}
|
|
118
198
|
</style>
|
|
119
199
|
</head>
|
|
120
200
|
<body>
|
|
121
|
-
<div class="
|
|
122
|
-
<h1>🚀
|
|
201
|
+
<div class="box">
|
|
202
|
+
<h1>🚀 MwalaJS Ready</h1>
|
|
123
203
|
<p><%= title %></p>
|
|
124
204
|
</div>
|
|
125
205
|
</body>
|
|
126
206
|
</html>`
|
|
127
207
|
);
|
|
128
208
|
|
|
129
|
-
|
|
209
|
+
/* =========================
|
|
210
|
+
PACKAGE JSON
|
|
211
|
+
========================= */
|
|
212
|
+
|
|
130
213
|
fs.writeFileSync(
|
|
131
214
|
path.join(target, "package.json"),
|
|
132
215
|
`{
|
|
133
216
|
"name": "mwalajs-app",
|
|
217
|
+
"version": "1.0.0",
|
|
134
218
|
"type": "module",
|
|
135
219
|
"scripts": {
|
|
136
220
|
"start": "node app.mjs"
|
|
137
221
|
},
|
|
138
222
|
"dependencies": {
|
|
139
|
-
"mwalajs": "*"
|
|
223
|
+
"mwalajs": "*",
|
|
224
|
+
"ejs": "*"
|
|
140
225
|
}
|
|
141
226
|
}`
|
|
142
227
|
);
|
|
143
228
|
|
|
229
|
+
/* =========================
|
|
230
|
+
README
|
|
231
|
+
========================= */
|
|
232
|
+
|
|
144
233
|
fs.writeFileSync(
|
|
145
234
|
path.join(target, "README.md"),
|
|
146
|
-
`# MwalaJS
|
|
235
|
+
`# 🚀 MwalaJS Project
|
|
236
|
+
|
|
237
|
+
## Run project
|
|
147
238
|
|
|
148
|
-
Run:
|
|
149
239
|
npm install
|
|
150
240
|
npm start
|
|
241
|
+
|
|
242
|
+
## Structure
|
|
243
|
+
- MVC architecture
|
|
244
|
+
- Controllers
|
|
245
|
+
- Routes
|
|
246
|
+
- Models
|
|
247
|
+
- Views
|
|
151
248
|
`
|
|
152
249
|
);
|
|
153
250
|
|
|
154
|
-
console.log("✅
|
|
251
|
+
console.log("✅ FULL manual scaffold created successfully!");
|
|
155
252
|
}
|
|
156
253
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
*/
|
|
254
|
+
/* =========================
|
|
255
|
+
MAIN PROJECT CREATOR
|
|
256
|
+
========================= */
|
|
257
|
+
|
|
160
258
|
export async function createProject(projectArg) {
|
|
161
259
|
const rl = readline.createInterface({
|
|
162
260
|
input: process.stdin,
|
|
163
261
|
output: process.stdout
|
|
164
262
|
});
|
|
165
263
|
|
|
264
|
+
let createdMode = "unknown";
|
|
265
|
+
|
|
166
266
|
try {
|
|
167
267
|
let projectName = projectArg?.trim();
|
|
168
268
|
|
|
@@ -171,7 +271,7 @@ export async function createProject(projectArg) {
|
|
|
171
271
|
}
|
|
172
272
|
|
|
173
273
|
if (!projectName) {
|
|
174
|
-
console.log("❌ Project name required");
|
|
274
|
+
console.log("❌ Project name is required");
|
|
175
275
|
return;
|
|
176
276
|
}
|
|
177
277
|
|
|
@@ -186,10 +286,12 @@ export async function createProject(projectArg) {
|
|
|
186
286
|
|
|
187
287
|
const templatePath = getMwalajsPath();
|
|
188
288
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
289
|
+
/* =========================
|
|
290
|
+
TEMPLATE MODE
|
|
291
|
+
========================= */
|
|
292
|
+
|
|
293
|
+
if (templatePath) {
|
|
294
|
+
console.log("📦 Template found:", templatePath);
|
|
193
295
|
|
|
194
296
|
const items = [
|
|
195
297
|
"controllers",
|
|
@@ -203,6 +305,8 @@ export async function createProject(projectArg) {
|
|
|
203
305
|
"README.md"
|
|
204
306
|
];
|
|
205
307
|
|
|
308
|
+
let copied = 0;
|
|
309
|
+
|
|
206
310
|
for (const item of items) {
|
|
207
311
|
const src = path.join(templatePath, item);
|
|
208
312
|
const dest = path.join(target, item);
|
|
@@ -210,19 +314,39 @@ export async function createProject(projectArg) {
|
|
|
210
314
|
if (fs.existsSync(src)) {
|
|
211
315
|
fs.copySync(src, dest);
|
|
212
316
|
console.log("✔ copied:", item);
|
|
317
|
+
copied++;
|
|
213
318
|
} else {
|
|
214
319
|
console.log("⚠ missing:", item);
|
|
215
320
|
}
|
|
216
321
|
}
|
|
217
322
|
|
|
218
|
-
|
|
219
|
-
|
|
323
|
+
if (copied === 0) {
|
|
324
|
+
console.log("⚠ Template empty → switching to manual mode...");
|
|
220
325
|
createManualTemplate(target);
|
|
326
|
+
createdMode = "manual";
|
|
327
|
+
} else {
|
|
328
|
+
createdMode = "template";
|
|
221
329
|
}
|
|
330
|
+
|
|
331
|
+
} else {
|
|
332
|
+
/* =========================
|
|
333
|
+
MANUAL MODE
|
|
334
|
+
========================= */
|
|
335
|
+
|
|
336
|
+
createManualTemplate(target);
|
|
337
|
+
createdMode = "manual";
|
|
222
338
|
}
|
|
223
339
|
|
|
224
|
-
|
|
340
|
+
/* =========================
|
|
341
|
+
FINAL OUTPUT (NO FAKE SUCCESS)
|
|
342
|
+
========================= */
|
|
343
|
+
|
|
344
|
+
console.log("\n============================");
|
|
345
|
+
console.log("🎉 PROJECT CREATED SUCCESSFULLY");
|
|
346
|
+
console.log("============================");
|
|
225
347
|
console.log("📁 Path:", target);
|
|
348
|
+
console.log("⚙ Mode:", createdMode.toUpperCase());
|
|
349
|
+
console.log("============================\n");
|
|
226
350
|
|
|
227
351
|
} catch (err) {
|
|
228
352
|
console.error("❌ Create project failed:", err.message);
|
|
@@ -231,7 +355,10 @@ export async function createProject(projectArg) {
|
|
|
231
355
|
}
|
|
232
356
|
}
|
|
233
357
|
|
|
234
|
-
|
|
358
|
+
/* =========================
|
|
359
|
+
CLI EXECUTION
|
|
360
|
+
========================= */
|
|
361
|
+
|
|
235
362
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
236
363
|
createProject(process.argv[2]);
|
|
237
364
|
}
|
package/package.json
CHANGED
package/runMigrations.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// runMigrations.mjs
|
|
2
|
+
|
|
1
3
|
import fs from 'fs';
|
|
2
4
|
import path from 'path';
|
|
3
5
|
import readline from 'readline';
|
|
@@ -134,6 +136,38 @@ export const listTables = async () => {
|
|
|
134
136
|
}
|
|
135
137
|
};
|
|
136
138
|
|
|
139
|
+
|
|
140
|
+
export const dropAllTables = async () => {
|
|
141
|
+
try {
|
|
142
|
+
const tables = await sequelize.getQueryInterface().showAllTables();
|
|
143
|
+
|
|
144
|
+
if (tables.length === 0) {
|
|
145
|
+
console.log('No tables exist in the database.');
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
console.log(`Dropping ${tables.length} table(s): ${tables.join(', ')}`);
|
|
150
|
+
|
|
151
|
+
// Drop in reverse order to respect foreign key dependencies if any
|
|
152
|
+
for (const table of tables.reverse()) {
|
|
153
|
+
await sequelize.getQueryInterface().dropTable(table, { cascade: true });
|
|
154
|
+
console.log(` Dropped: ${table}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Clear the migration tracking file so future migrates start clean
|
|
158
|
+
fs.writeFileSync(migrationLog, JSON.stringify([]));
|
|
159
|
+
console.log('Migration log cleared – ready for fresh migrations.');
|
|
160
|
+
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error('Error while dropping all tables:', error.message);
|
|
163
|
+
if (error.stack) console.error(error.stack);
|
|
164
|
+
throw error; // let runSafe show the full error
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
|
|
137
171
|
const askUser = (question) => {
|
|
138
172
|
return new Promise((resolve) => {
|
|
139
173
|
const rl = readline.createInterface({
|
|
@@ -146,3 +180,112 @@ const askUser = (question) => {
|
|
|
146
180
|
});
|
|
147
181
|
});
|
|
148
182
|
};
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
// ────────────────────────────────────────────────
|
|
187
|
+
// MAINTENANCE TOOLS (MariaDB / MySQL compatible)
|
|
188
|
+
// ────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
export const showDatabaseSize = async () => {
|
|
191
|
+
const dbName = sequelize.getDatabaseName(); // au tumia sequelize.config.database kama haifanyi kazi
|
|
192
|
+
const [results] = await sequelize.query(`
|
|
193
|
+
SELECT
|
|
194
|
+
ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size_mb
|
|
195
|
+
FROM information_schema.tables
|
|
196
|
+
WHERE table_schema = '${dbName}'
|
|
197
|
+
`);
|
|
198
|
+
|
|
199
|
+
const size = results[0]?.size_mb || '0.00';
|
|
200
|
+
console.log(`Database size: ${size} MB`);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
export const listIndexes = async (tableName) => {
|
|
204
|
+
const dbName = sequelize.getDatabaseName();
|
|
205
|
+
const [results] = await sequelize.query(`
|
|
206
|
+
SHOW INDEXES FROM \`${tableName}\`
|
|
207
|
+
`);
|
|
208
|
+
|
|
209
|
+
if (results.length === 0) {
|
|
210
|
+
console.log(`Hakuna indexes kwenye table "${tableName}".`);
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
console.log(`Indexes kwenye "${tableName}":`);
|
|
215
|
+
results.forEach(row => {
|
|
216
|
+
const unique = row.Non_unique === 0 ? 'UNIQUE' : 'NON-UNIQUE';
|
|
217
|
+
console.log(` • ${row.Key_name.padEnd(35)} → ${unique} | Column: ${row.Column_name} | Type: ${row.Index_type}`);
|
|
218
|
+
});
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
export const analyzeTable = async (tableName) => {
|
|
222
|
+
await sequelize.query(`ANALYZE TABLE \`${tableName}\`;`);
|
|
223
|
+
console.log(`Table "${tableName}" ime-analyze (statistics zime-update).`);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const vacuumDatabase = async () => {
|
|
227
|
+
// MySQL/MariaDB haina VACUUM kama PostgreSQL, lakini tunaweza optimize tables zote
|
|
228
|
+
const dbName = sequelize.getDatabaseName();
|
|
229
|
+
const [tables] = await sequelize.query(`
|
|
230
|
+
SELECT table_name
|
|
231
|
+
FROM information_schema.tables
|
|
232
|
+
WHERE table_schema = '${dbName}'
|
|
233
|
+
AND table_type = 'BASE TABLE'
|
|
234
|
+
`);
|
|
235
|
+
|
|
236
|
+
if (tables.length === 0) {
|
|
237
|
+
console.log('Hakuna tables za ku-optimize.');
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
console.log(`Optimizing ${tables.length} table(s)...`);
|
|
242
|
+
for (const row of tables) {
|
|
243
|
+
const table = row.table_name;
|
|
244
|
+
await sequelize.query(`OPTIMIZE TABLE \`${table}\`;`);
|
|
245
|
+
console.log(` Optimized: ${table}`);
|
|
246
|
+
}
|
|
247
|
+
console.log('Optimization imekamilika (space reclaimed na stats updated).');
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
export const showConnections = async () => {
|
|
251
|
+
const [results] = await sequelize.query(`
|
|
252
|
+
SHOW PROCESSLIST
|
|
253
|
+
`);
|
|
254
|
+
|
|
255
|
+
const active = results.filter(row => row.Command !== 'Sleep' && row.Id !== 0); // exclude idle + our connection
|
|
256
|
+
|
|
257
|
+
if (active.length === 0) {
|
|
258
|
+
console.log('Hakuna connections zingine active (isipokuwa yako).');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
console.log(`Active connections (${active.length}):`);
|
|
263
|
+
active.forEach(r => {
|
|
264
|
+
const querySnippet = r.Info ? r.Info.substring(0, 60) + '...' : '(idle)';
|
|
265
|
+
console.log(` ID ${r.Id} | User: ${r.User} | Host: ${r.Host} | State: ${r.State} | Query: ${querySnippet}`);
|
|
266
|
+
});
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export const killConnections = async () => {
|
|
270
|
+
const [results] = await sequelize.query(`
|
|
271
|
+
SHOW PROCESSLIST
|
|
272
|
+
`);
|
|
273
|
+
|
|
274
|
+
const toKill = results.filter(row => row.Id !== 0 && row.Command !== 'Sleep'); // exclude our connection + idle
|
|
275
|
+
|
|
276
|
+
if (toKill.length === 0) {
|
|
277
|
+
console.log('Hakuna connections za ku-kill.');
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
console.log(`Killing ${toKill.length} connection(s)...`);
|
|
282
|
+
for (const row of toKill) {
|
|
283
|
+
try {
|
|
284
|
+
await sequelize.query(`KILL ${row.Id};`);
|
|
285
|
+
console.log(` Killed ID ${row.Id}`);
|
|
286
|
+
} catch (e) {
|
|
287
|
+
console.warn(` Failed to kill ${row.Id}: ${e.message}`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
console.log('Kill operation imekamilika.');
|
|
291
|
+
};
|