@nitronjs/framework 0.2.3 → 0.2.4
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/README.md +3 -1
- package/cli/create.js +88 -72
- package/cli/njs.js +13 -6
- package/lib/Auth/Auth.js +167 -0
- package/lib/Build/CssBuilder.js +9 -0
- package/lib/Build/FileAnalyzer.js +16 -0
- package/lib/Build/HydrationBuilder.js +17 -0
- package/lib/Build/Manager.js +15 -0
- package/lib/Build/colors.js +4 -0
- package/lib/Build/plugins.js +84 -20
- package/lib/Console/Commands/DevCommand.js +13 -9
- package/lib/Console/Commands/MakeCommand.js +24 -10
- package/lib/Console/Commands/MigrateCommand.js +0 -1
- package/lib/Console/Commands/MigrateFreshCommand.js +18 -25
- package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
- package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
- package/lib/Console/Commands/SeedCommand.js +4 -2
- package/lib/Console/Commands/StorageLinkCommand.js +20 -5
- package/lib/Console/Output.js +143 -0
- package/lib/Core/Config.js +2 -1
- package/lib/Core/Paths.js +8 -0
- package/lib/Database/DB.js +141 -51
- package/lib/Database/Drivers/MySQLDriver.js +102 -157
- package/lib/Database/Migration/Checksum.js +3 -8
- package/lib/Database/Migration/MigrationRepository.js +25 -35
- package/lib/Database/Migration/MigrationRunner.js +56 -61
- package/lib/Database/Model.js +157 -83
- package/lib/Database/QueryBuilder.js +31 -0
- package/lib/Database/QueryValidation.js +36 -44
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +12 -31
- package/lib/Date/DateTime.js +9 -0
- package/lib/Encryption/Encryption.js +52 -0
- package/lib/Faker/Faker.js +11 -0
- package/lib/Filesystem/Storage.js +120 -0
- package/lib/HMR/Server.js +79 -9
- package/lib/Hashing/Hash.js +41 -0
- package/lib/Http/Server.js +177 -152
- package/lib/Logging/{Manager.js → Log.js} +68 -80
- package/lib/Mail/Mail.js +187 -0
- package/lib/Route/Router.js +416 -0
- package/lib/Session/File.js +135 -233
- package/lib/Session/Manager.js +117 -171
- package/lib/Session/Memory.js +28 -38
- package/lib/Session/Session.js +71 -107
- package/lib/Support/Str.js +103 -0
- package/lib/Translation/Lang.js +54 -0
- package/lib/View/Client/hmr-client.js +87 -51
- package/lib/View/Client/nitronjs-icon.png +0 -0
- package/lib/View/{Manager.js → View.js} +44 -29
- package/lib/index.d.ts +42 -8
- package/lib/index.js +19 -12
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/resources/css/global.css +1 -0
- package/skeleton/resources/views/Site/Home.tsx +456 -79
- package/skeleton/tsconfig.json +6 -1
- package/lib/Auth/Manager.js +0 -111
- package/lib/Database/Connection.js +0 -61
- package/lib/Database/Manager.js +0 -162
- package/lib/Encryption/Manager.js +0 -47
- package/lib/Filesystem/Manager.js +0 -74
- package/lib/Hashing/Manager.js +0 -25
- package/lib/Mail/Manager.js +0 -120
- package/lib/Route/Loader.js +0 -80
- package/lib/Route/Manager.js +0 -286
- package/lib/Translation/Manager.js +0 -49
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import DB from "../DB.js";
|
|
2
2
|
|
|
3
3
|
class MigrationRepository {
|
|
4
|
-
|
|
5
4
|
static table = 'migrations';
|
|
6
5
|
|
|
6
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
7
|
+
// Public Methods
|
|
8
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
9
|
+
|
|
7
10
|
static async tableExists() {
|
|
8
11
|
const [rows] = await DB.rawQuery(`SHOW TABLES LIKE '${this.table}'`);
|
|
9
12
|
return rows.length > 0;
|
|
@@ -11,67 +14,46 @@ class MigrationRepository {
|
|
|
11
14
|
|
|
12
15
|
static async getExecuted() {
|
|
13
16
|
if (!await this.tableExists()) return [];
|
|
14
|
-
return await DB.table(this.table)
|
|
15
|
-
.orderBy("batch", "asc")
|
|
16
|
-
.orderBy("id", "asc")
|
|
17
|
-
.get();
|
|
17
|
+
return await DB.table(this.table).orderBy('batch', 'asc').orderBy('id', 'asc').get();
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
static async getExecutedNames() {
|
|
21
|
-
|
|
22
|
-
return new Set(migrations.map(m => m.name));
|
|
21
|
+
return new Set((await this.getExecuted()).map(m => m.name));
|
|
23
22
|
}
|
|
24
23
|
|
|
25
24
|
static async getNextBatchNumber() {
|
|
26
|
-
|
|
27
|
-
const result = await DB.table(this.table)
|
|
28
|
-
.select(DB.rawExpr("MAX(batch) as max_batch"))
|
|
29
|
-
.first();
|
|
30
|
-
return (result?.max_batch || 0) + 1;
|
|
25
|
+
return (await this.#getMaxBatch()) + 1;
|
|
31
26
|
}
|
|
32
27
|
|
|
33
28
|
static async getLastBatchNumber() {
|
|
34
|
-
|
|
35
|
-
const result = await DB.table(this.table)
|
|
36
|
-
.select(DB.rawExpr("MAX(batch) as max_batch"))
|
|
37
|
-
.first();
|
|
38
|
-
return result?.max_batch || 0;
|
|
29
|
+
return await this.#getMaxBatch();
|
|
39
30
|
}
|
|
40
31
|
|
|
41
32
|
static async getByBatch(batch) {
|
|
42
|
-
return await DB.table(this.table)
|
|
43
|
-
.where("batch", batch)
|
|
44
|
-
.orderBy("id", "desc")
|
|
45
|
-
.get();
|
|
33
|
+
return await DB.table(this.table).where('batch', batch).orderBy('id', 'desc').get();
|
|
46
34
|
}
|
|
47
35
|
|
|
48
36
|
static async getLastBatches(steps = 1) {
|
|
49
37
|
const lastBatch = await this.getLastBatchNumber();
|
|
50
38
|
if (lastBatch === 0) return [];
|
|
51
39
|
|
|
52
|
-
const minBatch = Math.max(1, lastBatch - steps + 1);
|
|
53
40
|
return await DB.table(this.table)
|
|
54
|
-
.where(
|
|
55
|
-
.orderBy(
|
|
56
|
-
.orderBy(
|
|
41
|
+
.where('batch', '>=', Math.max(1, lastBatch - steps + 1))
|
|
42
|
+
.orderBy('batch', 'desc')
|
|
43
|
+
.orderBy('id', 'desc')
|
|
57
44
|
.get();
|
|
58
45
|
}
|
|
59
46
|
|
|
60
47
|
static async log(name, batch, checksum) {
|
|
61
|
-
await DB.table(this.table).insert({
|
|
62
|
-
name,
|
|
63
|
-
batch,
|
|
64
|
-
checksum,
|
|
65
|
-
executed_at: new Date()
|
|
66
|
-
});
|
|
48
|
+
await DB.table(this.table).insert({ name, batch, checksum, executed_at: new Date() });
|
|
67
49
|
}
|
|
68
50
|
|
|
69
51
|
static async delete(name) {
|
|
70
|
-
await DB.table(this.table).where(
|
|
52
|
+
await DB.table(this.table).where('name', name).delete();
|
|
71
53
|
}
|
|
72
54
|
|
|
73
55
|
static async find(name) {
|
|
74
|
-
return await DB.table(this.table).where(
|
|
56
|
+
return await DB.table(this.table).where('name', name).first();
|
|
75
57
|
}
|
|
76
58
|
|
|
77
59
|
static async exists(name) {
|
|
@@ -79,10 +61,18 @@ class MigrationRepository {
|
|
|
79
61
|
}
|
|
80
62
|
|
|
81
63
|
static async getChecksum(name) {
|
|
82
|
-
|
|
83
|
-
return migration?.checksum || null;
|
|
64
|
+
return (await this.find(name))?.checksum || null;
|
|
84
65
|
}
|
|
85
66
|
|
|
67
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
68
|
+
// Private Methods
|
|
69
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
static async #getMaxBatch() {
|
|
72
|
+
if (!await this.tableExists()) return 0;
|
|
73
|
+
const result = await DB.table(this.table).select(DB.rawExpr('MAX(batch) as max_batch')).first();
|
|
74
|
+
return result?.max_batch || 0;
|
|
75
|
+
}
|
|
86
76
|
}
|
|
87
77
|
|
|
88
78
|
export default MigrationRepository;
|
|
@@ -4,20 +4,11 @@ import { pathToFileURL, fileURLToPath } from 'url';
|
|
|
4
4
|
import Checksum from './Checksum.js';
|
|
5
5
|
import MigrationRepository from './MigrationRepository.js';
|
|
6
6
|
import Paths from '../../Core/Paths.js';
|
|
7
|
+
import Output from '../../Console/Output.js';
|
|
7
8
|
|
|
8
9
|
const __filename = fileURLToPath(import.meta.url);
|
|
9
10
|
const __dirname = path.dirname(__filename);
|
|
10
11
|
|
|
11
|
-
const COLORS = {
|
|
12
|
-
reset: '\x1b[0m',
|
|
13
|
-
red: '\x1b[31m',
|
|
14
|
-
green: '\x1b[32m',
|
|
15
|
-
yellow: '\x1b[33m',
|
|
16
|
-
cyan: '\x1b[36m',
|
|
17
|
-
dim: '\x1b[2m',
|
|
18
|
-
bold: '\x1b[1m'
|
|
19
|
-
};
|
|
20
|
-
|
|
21
12
|
class MigrationRunner {
|
|
22
13
|
|
|
23
14
|
static get frameworkMigrationsDir() {
|
|
@@ -45,7 +36,7 @@ class MigrationRunner {
|
|
|
45
36
|
const filePath = path.join(frameworkDir, file);
|
|
46
37
|
const fileUrl = pathToFileURL(filePath).href;
|
|
47
38
|
|
|
48
|
-
|
|
39
|
+
Output.frameworkMigration('pending', file);
|
|
49
40
|
|
|
50
41
|
const { default: migration } = await import(fileUrl);
|
|
51
42
|
|
|
@@ -56,7 +47,7 @@ class MigrationRunner {
|
|
|
56
47
|
await migration.up();
|
|
57
48
|
ran.push(file);
|
|
58
49
|
|
|
59
|
-
|
|
50
|
+
Output.frameworkMigration('done', file);
|
|
60
51
|
}
|
|
61
52
|
|
|
62
53
|
return { success: true, ran };
|
|
@@ -71,7 +62,7 @@ class MigrationRunner {
|
|
|
71
62
|
const migrationsDir = Paths.migrations;
|
|
72
63
|
|
|
73
64
|
if (!fs.existsSync(migrationsDir)) {
|
|
74
|
-
|
|
65
|
+
Output.warn("No migrations directory found");
|
|
75
66
|
return { success: true, ran: [] };
|
|
76
67
|
}
|
|
77
68
|
|
|
@@ -80,7 +71,7 @@ class MigrationRunner {
|
|
|
80
71
|
.sort();
|
|
81
72
|
|
|
82
73
|
if (files.length === 0) {
|
|
83
|
-
|
|
74
|
+
Output.warn("No migration files found");
|
|
84
75
|
return { success: true, ran: [] };
|
|
85
76
|
}
|
|
86
77
|
|
|
@@ -93,11 +84,11 @@ class MigrationRunner {
|
|
|
93
84
|
const storedChecksum = await MigrationRepository.getChecksum(file);
|
|
94
85
|
|
|
95
86
|
if (currentChecksum !== storedChecksum) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
87
|
+
Output.error(`Checksum mismatch: ${file}`);
|
|
88
|
+
Output.errorDetail(`Stored: ${storedChecksum}`);
|
|
89
|
+
Output.errorDetail(`Current: ${currentChecksum}`);
|
|
90
|
+
Output.error("Migration files must NEVER be modified after execution.");
|
|
91
|
+
Output.error("Create a NEW migration for any schema changes.");
|
|
101
92
|
return {
|
|
102
93
|
success: false,
|
|
103
94
|
ran: [],
|
|
@@ -110,12 +101,13 @@ class MigrationRunner {
|
|
|
110
101
|
const pending = files.filter(f => !executedNames.has(f));
|
|
111
102
|
|
|
112
103
|
if (pending.length === 0) {
|
|
113
|
-
console.log(
|
|
104
|
+
console.log(` ${Output.COLORS.green}${Output.ICONS.success}${Output.COLORS.reset} ${Output.COLORS.dim}Nothing to migrate. All migrations are up to date.${Output.COLORS.reset}`);
|
|
105
|
+
console.log();
|
|
114
106
|
return { success: true, ran: [] };
|
|
115
107
|
}
|
|
116
108
|
|
|
117
109
|
const batch = await MigrationRepository.getNextBatchNumber();
|
|
118
|
-
|
|
110
|
+
Output.migrationHeader(batch);
|
|
119
111
|
|
|
120
112
|
const executedInBatch = [];
|
|
121
113
|
|
|
@@ -125,7 +117,7 @@ class MigrationRunner {
|
|
|
125
117
|
const fileUrl = pathToFileURL(filePath).href;
|
|
126
118
|
const checksum = Checksum.fromFile(filePath);
|
|
127
119
|
|
|
128
|
-
|
|
120
|
+
Output.pending("Migrating", file);
|
|
129
121
|
|
|
130
122
|
const { default: migration } = await import(fileUrl);
|
|
131
123
|
|
|
@@ -137,32 +129,37 @@ class MigrationRunner {
|
|
|
137
129
|
await MigrationRepository.log(file, batch, checksum);
|
|
138
130
|
executedInBatch.push({ file, migration });
|
|
139
131
|
|
|
140
|
-
|
|
132
|
+
Output.done("Migrated", file);
|
|
141
133
|
}
|
|
142
134
|
|
|
143
|
-
|
|
135
|
+
Output.migrationSuccess();
|
|
144
136
|
return { success: true, ran: executedInBatch.map(e => e.file) };
|
|
145
137
|
|
|
146
138
|
}
|
|
147
139
|
catch (error) {
|
|
148
|
-
|
|
140
|
+
Output.newline();
|
|
141
|
+
Output.error(`Migration failed: ${error.message}`);
|
|
149
142
|
|
|
150
143
|
if (executedInBatch.length > 0) {
|
|
151
|
-
|
|
144
|
+
Output.newline();
|
|
145
|
+
Output.warn(`Rolling back ${executedInBatch.length} migration(s) from this batch...`);
|
|
146
|
+
Output.newline();
|
|
152
147
|
|
|
153
148
|
for (const { file, migration } of executedInBatch.reverse()) {
|
|
154
149
|
try {
|
|
155
|
-
|
|
150
|
+
Output.pending("Rolling back", file);
|
|
156
151
|
|
|
157
152
|
if (typeof migration.down === 'function') {
|
|
158
153
|
await migration.down();
|
|
159
154
|
}
|
|
160
155
|
|
|
161
156
|
await MigrationRepository.delete(file);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
157
|
+
Output.rollback("Rolled back", file);
|
|
158
|
+
Output.newline();
|
|
159
|
+
}
|
|
160
|
+
catch (rollbackError) {
|
|
161
|
+
Output.error(`Rollback failed for ${file}: ${rollbackError.message}`);
|
|
162
|
+
Output.error("Manual intervention may be required.");
|
|
166
163
|
}
|
|
167
164
|
}
|
|
168
165
|
}
|
|
@@ -174,68 +171,70 @@ class MigrationRunner {
|
|
|
174
171
|
static async rollback(steps = 1) {
|
|
175
172
|
const tableExists = await MigrationRepository.tableExists();
|
|
176
173
|
if (!tableExists) {
|
|
177
|
-
|
|
174
|
+
Output.warn("No migrations have been run yet.");
|
|
178
175
|
return { success: true, rolledBack: [] };
|
|
179
176
|
}
|
|
180
177
|
|
|
181
178
|
const lastBatch = await MigrationRepository.getLastBatchNumber();
|
|
182
179
|
if (lastBatch === 0) {
|
|
183
|
-
|
|
180
|
+
Output.warn("Nothing to rollback.");
|
|
184
181
|
return { success: true, rolledBack: [] };
|
|
185
182
|
}
|
|
186
183
|
|
|
187
184
|
const toRollback = await MigrationRepository.getLastBatches(steps);
|
|
188
185
|
|
|
189
186
|
if (toRollback.length === 0) {
|
|
190
|
-
|
|
187
|
+
Output.warn("Nothing to rollback.");
|
|
191
188
|
return { success: true, rolledBack: [] };
|
|
192
189
|
}
|
|
193
190
|
|
|
194
191
|
const migrationsDir = Paths.migrations;
|
|
195
192
|
const rolledBack = [];
|
|
196
193
|
|
|
197
|
-
|
|
194
|
+
Output.rollbackHeader(toRollback.length);
|
|
198
195
|
|
|
199
196
|
try {
|
|
200
197
|
for (const record of toRollback) {
|
|
201
198
|
const filePath = path.join(migrationsDir, record.name);
|
|
202
199
|
|
|
203
200
|
if (!fs.existsSync(filePath)) {
|
|
204
|
-
|
|
205
|
-
|
|
201
|
+
Output.error(`Migration file not found: ${record.name}`);
|
|
202
|
+
Output.errorDetail("Cannot rollback without the migration file.");
|
|
206
203
|
throw new Error(`Migration file not found: ${record.name}`);
|
|
207
204
|
}
|
|
208
205
|
|
|
209
206
|
const currentChecksum = Checksum.fromFile(filePath);
|
|
210
207
|
if (currentChecksum !== record.checksum) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
208
|
+
Output.error(`Checksum mismatch: ${record.name}`);
|
|
209
|
+
Output.errorDetail("Migration file was modified after execution.");
|
|
210
|
+
Output.errorDetail("Rollback cannot proceed safely.");
|
|
214
211
|
throw new Error(`Checksum mismatch for migration: ${record.name}`);
|
|
215
212
|
}
|
|
216
213
|
|
|
217
214
|
const fileUrl = pathToFileURL(filePath).href;
|
|
218
215
|
const { default: migration } = await import(fileUrl);
|
|
219
216
|
|
|
220
|
-
|
|
217
|
+
Output.pending("Rolling back", `${record.name} (batch ${record.batch})`);
|
|
221
218
|
|
|
222
219
|
if (typeof migration.down === 'function') {
|
|
223
220
|
await migration.down();
|
|
224
221
|
} else {
|
|
225
|
-
|
|
222
|
+
Output.warn("No down() method, skipping schema rollback");
|
|
226
223
|
}
|
|
227
224
|
|
|
228
225
|
await MigrationRepository.delete(record.name);
|
|
229
226
|
rolledBack.push(record.name);
|
|
230
227
|
|
|
231
|
-
|
|
228
|
+
Output.rollbackDone(record.name, record.batch);
|
|
232
229
|
}
|
|
233
230
|
|
|
234
|
-
|
|
231
|
+
Output.rollbackSuccess();
|
|
235
232
|
return { success: true, rolledBack };
|
|
236
233
|
|
|
237
|
-
}
|
|
238
|
-
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
Output.newline();
|
|
237
|
+
Output.error(`Rollback failed: ${error.message}`);
|
|
239
238
|
return { success: false, rolledBack, error };
|
|
240
239
|
}
|
|
241
240
|
}
|
|
@@ -243,17 +242,18 @@ class MigrationRunner {
|
|
|
243
242
|
static async reset() {
|
|
244
243
|
const tableExists = await MigrationRepository.tableExists();
|
|
245
244
|
if (!tableExists) {
|
|
246
|
-
|
|
245
|
+
Output.warn("No migrations have been run yet.");
|
|
247
246
|
return { success: true, rolledBack: [] };
|
|
248
247
|
}
|
|
249
248
|
|
|
250
249
|
const lastBatch = await MigrationRepository.getLastBatchNumber();
|
|
251
250
|
if (lastBatch === 0) {
|
|
252
|
-
|
|
251
|
+
Output.warn("Nothing to reset.");
|
|
253
252
|
return { success: true, rolledBack: [] };
|
|
254
253
|
}
|
|
255
254
|
|
|
256
|
-
|
|
255
|
+
Output.warn("Resetting all migrations...");
|
|
256
|
+
Output.newline();
|
|
257
257
|
return await this.rollback(lastBatch);
|
|
258
258
|
}
|
|
259
259
|
|
|
@@ -280,7 +280,8 @@ class MigrationRunner {
|
|
|
280
280
|
batch: record.batch,
|
|
281
281
|
executedAt: record.executed_at
|
|
282
282
|
};
|
|
283
|
-
}
|
|
283
|
+
}
|
|
284
|
+
else {
|
|
284
285
|
return {
|
|
285
286
|
name: file,
|
|
286
287
|
status: 'Pending',
|
|
@@ -297,26 +298,20 @@ class MigrationRunner {
|
|
|
297
298
|
const status = await this.status();
|
|
298
299
|
|
|
299
300
|
if (status.length === 0) {
|
|
300
|
-
console.log(
|
|
301
|
+
console.log(` ${Output.COLORS.green}${Output.ICONS.success}${Output.COLORS.reset} ${Output.COLORS.dim}No migrations found${Output.COLORS.reset}`);
|
|
302
|
+
Output.newline();
|
|
301
303
|
return;
|
|
302
304
|
}
|
|
303
305
|
|
|
304
|
-
|
|
305
|
-
console.log(`${COLORS.dim}${'─'.repeat(80)}${COLORS.reset}`);
|
|
306
|
+
Output.statusHeader();
|
|
306
307
|
|
|
307
308
|
for (const migration of status) {
|
|
308
|
-
|
|
309
|
-
const statusIcon = migration.status === 'Ran' ? '✅' : '⏳';
|
|
310
|
-
const batchInfo = migration.batch ? ` ${COLORS.dim}(batch ${migration.batch})${COLORS.reset}` : '';
|
|
311
|
-
|
|
312
|
-
console.log(`${statusIcon} ${statusColor}${migration.status.padEnd(7)}${COLORS.reset} ${migration.name}${batchInfo}`);
|
|
309
|
+
Output.statusRow(migration.status, migration.name, migration.batch);
|
|
313
310
|
}
|
|
314
311
|
|
|
315
|
-
console.log(`${COLORS.dim}${'─'.repeat(80)}${COLORS.reset}\n`);
|
|
316
|
-
|
|
317
312
|
const ran = status.filter(m => m.status === 'Ran').length;
|
|
318
313
|
const pending = status.filter(m => m.status === 'Pending').length;
|
|
319
|
-
|
|
314
|
+
Output.statusFooter(status.length, ran, pending);
|
|
320
315
|
}
|
|
321
316
|
|
|
322
317
|
}
|