millas 0.2.12-beta-1 → 0.2.12-beta-2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/admin/ActivityLog.js +153 -52
- package/src/admin/Admin.js +400 -167
- package/src/admin/AdminAuth.js +213 -98
- package/src/admin/FormGenerator.js +372 -0
- package/src/admin/HookRegistry.js +256 -0
- package/src/admin/QueryEngine.js +263 -0
- package/src/admin/ViewContext.js +309 -0
- package/src/admin/WidgetRegistry.js +406 -0
- package/src/admin/index.js +17 -0
- package/src/admin/resources/AdminResource.js +383 -97
- package/src/admin/static/admin.css +1341 -0
- package/src/admin/static/date-picker.css +157 -0
- package/src/admin/static/date-picker.js +316 -0
- package/src/admin/static/json-editor.css +649 -0
- package/src/admin/static/json-editor.js +1429 -0
- package/src/admin/static/ui.js +1044 -0
- package/src/admin/views/layouts/base.njk +65 -1013
- package/src/admin/views/pages/detail.njk +40 -16
- package/src/admin/views/pages/form.njk +47 -599
- package/src/admin/views/pages/list.njk +145 -62
- package/src/admin/views/partials/form-field.njk +53 -0
- package/src/admin/views/partials/form-footer.njk +28 -0
- package/src/admin/views/partials/form-readonly.njk +114 -0
- package/src/admin/views/partials/form-scripts.njk +476 -0
- package/src/admin/views/partials/form-widget.njk +296 -0
- package/src/admin/views/partials/json-dialog.njk +80 -0
- package/src/admin/views/partials/json-editor.njk +37 -0
- package/src/admin.zip +0 -0
- package/src/auth/Auth.js +18 -2
- package/src/auth/AuthUser.js +65 -44
- package/src/cli.js +3 -1
- package/src/commands/createsuperuser.js +254 -0
- package/src/commands/lang.js +589 -0
- package/src/commands/migrate.js +154 -81
- package/src/commands/serve.js +1 -0
- package/src/container/AppInitializer.js +65 -8
- package/src/container/MillasApp.js +10 -3
- package/src/container/MillasConfig.js +35 -6
- package/src/core/admin.js +5 -0
- package/src/core/db.js +2 -1
- package/src/core/foundation.js +1 -9
- package/src/core/lang.js +1 -0
- package/src/i18n/I18nServiceProvider.js +91 -0
- package/src/i18n/Translator.js +635 -0
- package/src/i18n/defaults.js +122 -0
- package/src/i18n/index.js +164 -0
- package/src/i18n/locales/en.js +55 -0
- package/src/i18n/locales/sw.js +48 -0
- package/src/logger/formatters/PrettyFormatter.js +101 -65
- package/src/migrations/system/0001_users.js +21 -0
- package/src/migrations/system/0002_admin_log.js +25 -0
- package/src/migrations/system/0003_sessions.js +23 -0
- package/src/orm/fields/index.js +210 -188
- package/src/orm/migration/DefaultValueParser.js +325 -0
- package/src/orm/migration/InteractiveResolver.js +191 -0
- package/src/orm/migration/Makemigrations.js +312 -0
- package/src/orm/migration/MigrationGraph.js +227 -0
- package/src/orm/migration/MigrationRunner.js +202 -108
- package/src/orm/migration/MigrationWriter.js +463 -0
- package/src/orm/migration/ModelInspector.js +143 -74
- package/src/orm/migration/ModelScanner.js +225 -0
- package/src/orm/migration/ProjectState.js +213 -0
- package/src/orm/migration/RenameDetector.js +175 -0
- package/src/orm/migration/SchemaBuilder.js +8 -81
- package/src/orm/migration/operations/base.js +57 -0
- package/src/orm/migration/operations/column.js +191 -0
- package/src/orm/migration/operations/fields.js +252 -0
- package/src/orm/migration/operations/index.js +55 -0
- package/src/orm/migration/operations/models.js +152 -0
- package/src/orm/migration/operations/registry.js +131 -0
- package/src/orm/migration/operations/special.js +51 -0
- package/src/orm/migration/utils.js +208 -0
- package/src/orm/model/Model.js +81 -13
- package/src/providers/AdminServiceProvider.js +66 -9
- package/src/providers/AuthServiceProvider.js +40 -5
- package/src/providers/CacheStorageServiceProvider.js +2 -2
- package/src/providers/DatabaseServiceProvider.js +3 -2
- package/src/providers/LogServiceProvider.js +4 -1
- package/src/providers/MailServiceProvider.js +1 -1
- package/src/providers/QueueServiceProvider.js +1 -1
- package/src/scaffold/templates.js +77 -21
package/src/commands/migrate.js
CHANGED
|
@@ -9,40 +9,81 @@ module.exports = function (program) {
|
|
|
9
9
|
// ── makemigrations ──────────────────────────────────────────────────────────
|
|
10
10
|
program
|
|
11
11
|
.command('makemigrations')
|
|
12
|
-
.description('
|
|
13
|
-
.
|
|
12
|
+
.description('Detect model changes and generate migration files (never touches DB)')
|
|
13
|
+
.option('--dry-run', 'Show what would be generated without writing files')
|
|
14
|
+
.option('--noinput', 'Non-interactive mode — fails if dangerous fields need resolution')
|
|
15
|
+
.action(async (options) => {
|
|
14
16
|
try {
|
|
15
|
-
const ctx
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
);
|
|
22
|
-
const result = await inspector.makeMigrations();
|
|
17
|
+
const ctx = getProjectContext();
|
|
18
|
+
const Mk = require('../orm/migration/Makemigrations');
|
|
19
|
+
const mk = new Mk(ctx.modelsPath, ctx.appMigPath, ctx.systemMigPath, {
|
|
20
|
+
nonInteractive: options.noinput || false,
|
|
21
|
+
});
|
|
22
|
+
const result = await mk.run({ dryRun: options.dryRun });
|
|
23
23
|
|
|
24
24
|
if (result.files.length === 0) {
|
|
25
|
-
console.log(chalk.yellow(`\n
|
|
25
|
+
console.log(chalk.yellow(`\n No changes detected.\n`));
|
|
26
26
|
} else {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (options.dryRun) {
|
|
28
|
+
console.log(chalk.cyan('\n Would generate:'));
|
|
29
|
+
} else {
|
|
30
|
+
console.log(chalk.green('\n Migrations generated:'));
|
|
31
|
+
}
|
|
32
|
+
result.files.forEach(f => console.log(chalk.cyan(` + ${f}`)));
|
|
33
|
+
result.ops.forEach(op => {
|
|
34
|
+
// Match Django's +/-/~ prefix style exactly
|
|
35
|
+
let prefix, label;
|
|
36
|
+
switch (op.type) {
|
|
37
|
+
case 'CreateModel':
|
|
38
|
+
prefix = chalk.green('+'); label = `Create model ${op.table}`; break;
|
|
39
|
+
case 'DeleteModel':
|
|
40
|
+
prefix = chalk.red('-'); label = `Delete model ${op.table}`; break;
|
|
41
|
+
case 'AddField':
|
|
42
|
+
prefix = chalk.green('+'); label = `Add field ${op.column} to ${op.table}`; break;
|
|
43
|
+
case 'RemoveField':
|
|
44
|
+
prefix = chalk.red('-'); label = `Remove field ${op.column} from ${op.table}`; break;
|
|
45
|
+
case 'AlterField':
|
|
46
|
+
prefix = chalk.yellow('~'); label = `Alter field ${op.column} on ${op.table}`; break;
|
|
47
|
+
case 'RenameField':
|
|
48
|
+
prefix = chalk.yellow('~'); label = `Rename field ${op.oldColumn} on ${op.table} to ${op.newColumn}`; break;
|
|
49
|
+
case 'RenameModel':
|
|
50
|
+
prefix = chalk.yellow('~'); label = `Rename model ${op.oldTable} to ${op.newTable}`; break;
|
|
51
|
+
default:
|
|
52
|
+
prefix = chalk.gray(' '); label = op.type;
|
|
53
|
+
}
|
|
54
|
+
console.log(chalk.gray(` ${prefix} ${label}`));
|
|
55
|
+
});
|
|
56
|
+
if (!options.dryRun) {
|
|
57
|
+
console.log(chalk.gray('\n Run: millas migrate to apply.\n'));
|
|
58
|
+
} else {
|
|
59
|
+
console.log();
|
|
60
|
+
}
|
|
30
61
|
}
|
|
31
62
|
} catch (err) {
|
|
32
63
|
bail('makemigrations', err);
|
|
33
64
|
}
|
|
34
|
-
// makemigrations doesn't open a DB connection so no closeDb() needed
|
|
35
65
|
});
|
|
36
66
|
|
|
37
67
|
// ── migrate ─────────────────────────────────────────────────────────────────
|
|
38
68
|
program
|
|
39
69
|
.command('migrate')
|
|
40
|
-
.description('
|
|
41
|
-
.
|
|
70
|
+
.description('Apply pending migrations in dependency order (never generates migrations)')
|
|
71
|
+
.option('--fake <name>', 'Mark a migration as applied without running it')
|
|
72
|
+
.action(async (options) => {
|
|
42
73
|
try {
|
|
43
74
|
const runner = await getRunner();
|
|
75
|
+
|
|
76
|
+
if (options.fake) {
|
|
77
|
+
const [source, name] = options.fake.includes(':')
|
|
78
|
+
? options.fake.split(':')
|
|
79
|
+
: ['app', options.fake];
|
|
80
|
+
const result = await runner.fake(source, name);
|
|
81
|
+
console.log(chalk.green(`\n Marked "${result.key}" as applied (fake).\n`));
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
44
85
|
const result = await runner.migrate();
|
|
45
|
-
|
|
86
|
+
printResult(result.ran, 'Applying');
|
|
46
87
|
} catch (err) {
|
|
47
88
|
bail('migrate', err);
|
|
48
89
|
} finally {
|
|
@@ -50,18 +91,69 @@ module.exports = function (program) {
|
|
|
50
91
|
}
|
|
51
92
|
});
|
|
52
93
|
|
|
53
|
-
// ── migrate:
|
|
94
|
+
// ── migrate:plan ─────────────────────────────────────────────────────────────
|
|
54
95
|
program
|
|
55
|
-
.command('migrate:
|
|
56
|
-
.description('
|
|
96
|
+
.command('migrate:plan')
|
|
97
|
+
.description('Preview which migrations would run without applying them')
|
|
98
|
+
.action(async () => {
|
|
99
|
+
try {
|
|
100
|
+
const runner = await getRunner();
|
|
101
|
+
const pending = await runner.plan();
|
|
102
|
+
|
|
103
|
+
if (pending.length === 0) {
|
|
104
|
+
console.log(chalk.yellow('\n Nothing to migrate.\n'));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log(chalk.cyan('\n Migration plan:\n'));
|
|
109
|
+
pending.forEach(p => {
|
|
110
|
+
const color = p.source === 'system' ? chalk.gray : chalk.cyan;
|
|
111
|
+
console.log(color(` ${p.key}`));
|
|
112
|
+
});
|
|
113
|
+
console.log();
|
|
114
|
+
} catch (err) {
|
|
115
|
+
bail('migrate:plan', err);
|
|
116
|
+
} finally {
|
|
117
|
+
await closeDb();
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// ── migrate:status ───────────────────────────────────────────────────────────
|
|
122
|
+
program
|
|
123
|
+
.command('migrate:status')
|
|
124
|
+
.description('Show the status of all migrations')
|
|
57
125
|
.action(async () => {
|
|
58
126
|
try {
|
|
59
|
-
console.log(chalk.yellow('\n ⚠ Dropping all tables…\n'));
|
|
60
127
|
const runner = await getRunner();
|
|
61
|
-
const
|
|
62
|
-
|
|
128
|
+
const rows = await runner.status();
|
|
129
|
+
|
|
130
|
+
if (rows.length === 0) {
|
|
131
|
+
console.log(chalk.yellow('\n No migrations found.\n'));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const colW = Math.max(...rows.map(r => r.key.length)) + 2;
|
|
136
|
+
console.log(`\n ${'Migration'.padEnd(colW)} ${'Status'.padEnd(10)} Batch`);
|
|
137
|
+
console.log(chalk.gray(' ' + '─'.repeat(colW + 20)));
|
|
138
|
+
|
|
139
|
+
let lastSource = null;
|
|
140
|
+
for (const row of rows) {
|
|
141
|
+
if (row.source !== lastSource) {
|
|
142
|
+
if (lastSource !== null) console.log();
|
|
143
|
+
lastSource = row.source;
|
|
144
|
+
}
|
|
145
|
+
const status = row.status === 'Applied'
|
|
146
|
+
? chalk.green(row.status.padEnd(10))
|
|
147
|
+
: chalk.yellow(row.status.padEnd(10));
|
|
148
|
+
const batch = row.batch ? chalk.gray(String(row.batch)) : chalk.gray('—');
|
|
149
|
+
const label = row.source === 'system'
|
|
150
|
+
? chalk.gray(row.key.padEnd(colW))
|
|
151
|
+
: chalk.cyan(row.key.padEnd(colW));
|
|
152
|
+
console.log(` ${label} ${status} ${batch}`);
|
|
153
|
+
}
|
|
154
|
+
console.log();
|
|
63
155
|
} catch (err) {
|
|
64
|
-
bail('migrate:
|
|
156
|
+
bail('migrate:status', err);
|
|
65
157
|
} finally {
|
|
66
158
|
await closeDb();
|
|
67
159
|
}
|
|
@@ -76,7 +168,7 @@ module.exports = function (program) {
|
|
|
76
168
|
try {
|
|
77
169
|
const runner = await getRunner();
|
|
78
170
|
const result = await runner.rollback(Number(options.steps));
|
|
79
|
-
|
|
171
|
+
printResult(result.rolledBack, 'Reverting');
|
|
80
172
|
} catch (err) {
|
|
81
173
|
bail('migrate:rollback', err);
|
|
82
174
|
} finally {
|
|
@@ -84,66 +176,50 @@ module.exports = function (program) {
|
|
|
84
176
|
}
|
|
85
177
|
});
|
|
86
178
|
|
|
87
|
-
// ── migrate:
|
|
179
|
+
// ── migrate:fresh ────────────────────────────────────────────────────────────
|
|
88
180
|
program
|
|
89
|
-
.command('migrate:
|
|
90
|
-
.description('
|
|
181
|
+
.command('migrate:fresh')
|
|
182
|
+
.description('Drop all tables and re-run every migration from scratch')
|
|
91
183
|
.action(async () => {
|
|
92
184
|
try {
|
|
185
|
+
console.log(chalk.yellow('\n ⚠ Dropping all tables…\n'));
|
|
93
186
|
const runner = await getRunner();
|
|
94
|
-
const result = await runner.
|
|
95
|
-
|
|
187
|
+
const result = await runner.fresh();
|
|
188
|
+
printResult(result.ran, 'Applying');
|
|
96
189
|
} catch (err) {
|
|
97
|
-
bail('migrate:
|
|
190
|
+
bail('migrate:fresh', err);
|
|
98
191
|
} finally {
|
|
99
192
|
await closeDb();
|
|
100
193
|
}
|
|
101
194
|
});
|
|
102
195
|
|
|
103
|
-
// ── migrate:
|
|
196
|
+
// ── migrate:reset ────────────────────────────────────────────────────────────
|
|
104
197
|
program
|
|
105
|
-
.command('migrate:
|
|
106
|
-
.description('Rollback
|
|
198
|
+
.command('migrate:reset')
|
|
199
|
+
.description('Rollback ALL migrations')
|
|
107
200
|
.action(async () => {
|
|
108
201
|
try {
|
|
109
202
|
const runner = await getRunner();
|
|
110
|
-
const result = await runner.
|
|
111
|
-
|
|
203
|
+
const result = await runner.reset();
|
|
204
|
+
printResult(result.rolledBack, 'Reverting');
|
|
112
205
|
} catch (err) {
|
|
113
|
-
bail('migrate:
|
|
206
|
+
bail('migrate:reset', err);
|
|
114
207
|
} finally {
|
|
115
208
|
await closeDb();
|
|
116
209
|
}
|
|
117
210
|
});
|
|
118
211
|
|
|
119
|
-
// ── migrate:
|
|
212
|
+
// ── migrate:refresh ──────────────────────────────────────────────────────────
|
|
120
213
|
program
|
|
121
|
-
.command('migrate:
|
|
122
|
-
.description('
|
|
214
|
+
.command('migrate:refresh')
|
|
215
|
+
.description('Rollback all then re-run all migrations')
|
|
123
216
|
.action(async () => {
|
|
124
217
|
try {
|
|
125
218
|
const runner = await getRunner();
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
if (rows.length === 0) {
|
|
129
|
-
console.log(chalk.yellow('\n No migration files found.\n'));
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const colW = Math.max(...rows.map(r => r.name.length)) + 2;
|
|
134
|
-
console.log(`\n ${'Migration'.padEnd(colW)} ${'Status'.padEnd(10)} Batch`);
|
|
135
|
-
console.log(chalk.gray(' ' + '─'.repeat(colW + 20)));
|
|
136
|
-
|
|
137
|
-
for (const row of rows) {
|
|
138
|
-
const status = row.status === 'Ran'
|
|
139
|
-
? chalk.green(row.status.padEnd(10))
|
|
140
|
-
: chalk.yellow(row.status.padEnd(10));
|
|
141
|
-
const batch = row.batch ? chalk.gray(String(row.batch)) : chalk.gray('—');
|
|
142
|
-
console.log(` ${chalk.cyan(row.name.padEnd(colW))} ${status} ${batch}`);
|
|
143
|
-
}
|
|
144
|
-
console.log();
|
|
219
|
+
const result = await runner.refresh();
|
|
220
|
+
printResult(result.ran, 'Applying');
|
|
145
221
|
} catch (err) {
|
|
146
|
-
bail('migrate:
|
|
222
|
+
bail('migrate:refresh', err);
|
|
147
223
|
} finally {
|
|
148
224
|
await closeDb();
|
|
149
225
|
}
|
|
@@ -193,10 +269,10 @@ module.exports = function (program) {
|
|
|
193
269
|
function getProjectContext() {
|
|
194
270
|
const cwd = process.cwd();
|
|
195
271
|
return {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
272
|
+
appMigPath: path.join(cwd, 'database/migrations'),
|
|
273
|
+
systemMigPath: path.join(__dirname, '../migrations/system'),
|
|
274
|
+
seedersPath: path.join(cwd, 'database/seeders'),
|
|
275
|
+
modelsPath: path.join(cwd, 'app/models'),
|
|
200
276
|
};
|
|
201
277
|
}
|
|
202
278
|
|
|
@@ -215,31 +291,28 @@ async function getRunner() {
|
|
|
215
291
|
const MigrationRunner = require('../orm/migration/MigrationRunner');
|
|
216
292
|
const ctx = getProjectContext();
|
|
217
293
|
const db = await getDbConnection();
|
|
218
|
-
return new MigrationRunner(db, ctx.
|
|
294
|
+
return new MigrationRunner(db, ctx.appMigPath, ctx.systemMigPath);
|
|
219
295
|
}
|
|
220
296
|
|
|
221
|
-
/**
|
|
222
|
-
* Destroy all open knex connection pools so the CLI process exits cleanly.
|
|
223
|
-
* Without this, knex keeps the event loop alive indefinitely after the
|
|
224
|
-
* command finishes, causing the terminal to appear to hang.
|
|
225
|
-
*/
|
|
226
297
|
async function closeDb() {
|
|
227
298
|
try {
|
|
228
299
|
const DatabaseManager = require('../orm/drivers/DatabaseManager');
|
|
229
300
|
await DatabaseManager.closeAll();
|
|
230
|
-
} catch {
|
|
301
|
+
} catch {}
|
|
231
302
|
}
|
|
232
303
|
|
|
233
|
-
function
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
console.log(chalk.yellow(`\n ${result.message}\n`));
|
|
304
|
+
function printResult(list, verb) {
|
|
305
|
+
if (!list || list.length === 0) {
|
|
306
|
+
console.log(chalk.yellow('\n Nothing to do.\n'));
|
|
237
307
|
return;
|
|
238
308
|
}
|
|
239
|
-
console.log(chalk.green(`\n
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
309
|
+
console.log(chalk.green(`\n Running migrations:`));
|
|
310
|
+
for (const entry of list) {
|
|
311
|
+
const label = typeof entry === 'object' ? entry.label || entry.key : entry;
|
|
312
|
+
const source = typeof entry === 'object' ? entry.source : null;
|
|
313
|
+
const color = source === 'system' ? chalk.gray : chalk.cyan;
|
|
314
|
+
console.log(color(` ${verb} ${label}... OK`));
|
|
315
|
+
}
|
|
243
316
|
console.log();
|
|
244
317
|
}
|
|
245
318
|
|
|
@@ -247,4 +320,4 @@ function bail(cmd, err) {
|
|
|
247
320
|
console.error(chalk.red(`\n ✖ ${cmd} failed: ${err.message}\n`));
|
|
248
321
|
if (process.env.DEBUG) console.error(err.stack);
|
|
249
322
|
closeDb().finally(() => process.exit(1));
|
|
250
|
-
}
|
|
323
|
+
}
|
package/src/commands/serve.js
CHANGED
|
@@ -23,12 +23,15 @@ const HttpServer = require('./HttpServer');
|
|
|
23
23
|
* bootstrap/app.js → MillasInstance → AppInitialiser.boot()
|
|
24
24
|
* ├─ builds ExpressAdapter
|
|
25
25
|
* ├─ builds Application
|
|
26
|
-
* ├─ registers providers
|
|
26
|
+
* ├─ registers core providers
|
|
27
|
+
* │ Log → Database → Auth → Admin
|
|
28
|
+
* │ → Cache → Mail → Queue → Events
|
|
29
|
+
* ├─ registers app providers
|
|
27
30
|
* ├─ registers routes
|
|
28
31
|
* ├─ registers middleware aliases
|
|
29
|
-
* ├─ boots providers
|
|
32
|
+
* ├─ boots all providers
|
|
30
33
|
* ├─ mounts routes
|
|
31
|
-
* ├─ mounts admin
|
|
34
|
+
* ├─ mounts admin panel
|
|
32
35
|
* ├─ mounts fallbacks
|
|
33
36
|
* └─ starts HttpServer
|
|
34
37
|
*/
|
|
@@ -63,6 +66,11 @@ class AppInitializer {
|
|
|
63
66
|
// ── Build the Application kernel ─────────────────────────────────────────
|
|
64
67
|
this._kernel = new Application(this._adapter);
|
|
65
68
|
|
|
69
|
+
// Bind basePath so all providers can resolve config/model paths
|
|
70
|
+
// without calling process.cwd() at runtime.
|
|
71
|
+
const basePath = cfg.basePath || process.cwd();
|
|
72
|
+
this._kernel._container.instance('basePath', basePath);
|
|
73
|
+
|
|
66
74
|
// Core providers (auto-enabled unless disabled in config)
|
|
67
75
|
const coreProviders = this._buildCoreProviders(cfg);
|
|
68
76
|
this._kernel.providers([...coreProviders, ...cfg.providers]);
|
|
@@ -114,22 +122,39 @@ class AppInitializer {
|
|
|
114
122
|
_buildCoreProviders(cfg) {
|
|
115
123
|
const providers = [];
|
|
116
124
|
const load = (p) => {
|
|
117
|
-
try {
|
|
118
|
-
return require(p);
|
|
119
|
-
} catch {
|
|
120
|
-
return null;
|
|
121
|
-
}
|
|
125
|
+
try { return require(p); } catch { return null; }
|
|
122
126
|
};
|
|
123
127
|
|
|
128
|
+
// ── 1. Logging ───────────────────────────────────────────────────────
|
|
124
129
|
if (cfg.logging !== false) {
|
|
125
130
|
const p = load('../providers/LogServiceProvider');
|
|
126
131
|
if (p) providers.push(p);
|
|
127
132
|
}
|
|
133
|
+
|
|
134
|
+
// ── 2. Database ──────────────────────────────────────────────────────
|
|
128
135
|
if (cfg.database !== false) {
|
|
129
136
|
const p = load('../providers/DatabaseServiceProvider');
|
|
130
137
|
if (p) providers.push(p);
|
|
131
138
|
}
|
|
132
139
|
|
|
140
|
+
// ── 3. Auth — always on unless explicitly disabled ───────────────────
|
|
141
|
+
// Mirrors Django: django.contrib.auth is in INSTALLED_APPS by default.
|
|
142
|
+
// Provides Auth.login/register, JWT middleware, the User model.
|
|
143
|
+
// Requires Database to be booted first.
|
|
144
|
+
if (cfg.auth !== false) {
|
|
145
|
+
const p = load('../providers/AuthServiceProvider');
|
|
146
|
+
if (p) providers.push(p);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ── 4. Admin — on when .withAdmin() was called ───────────────────────
|
|
150
|
+
// Mirrors Django: django.contrib.admin is in INSTALLED_APPS by default.
|
|
151
|
+
// Requires Auth to be booted first (needs the resolved User model).
|
|
152
|
+
if (cfg.admin !== null && cfg.admin !== undefined) {
|
|
153
|
+
const p = load('../providers/AdminServiceProvider');
|
|
154
|
+
if (p) providers.push(p);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── 5. Cache + Storage ───────────────────────────────────────────────
|
|
133
158
|
if (cfg.cache !== false || cfg.storage !== false) {
|
|
134
159
|
const p = load('../providers/CacheStorageServiceProvider');
|
|
135
160
|
if (p) {
|
|
@@ -138,21 +163,53 @@ class AppInitializer {
|
|
|
138
163
|
}
|
|
139
164
|
}
|
|
140
165
|
|
|
166
|
+
// ── 6. Mail ──────────────────────────────────────────────────────────
|
|
141
167
|
if (cfg.mail !== false) {
|
|
142
168
|
const p = load('../providers/MailServiceProvider');
|
|
143
169
|
if (p) providers.push(p);
|
|
144
170
|
}
|
|
171
|
+
|
|
172
|
+
// ── 7. Queue ─────────────────────────────────────────────────────────
|
|
145
173
|
if (cfg.queue !== false) {
|
|
146
174
|
const p = load('../providers/QueueServiceProvider');
|
|
147
175
|
if (p) providers.push(p);
|
|
148
176
|
}
|
|
177
|
+
|
|
178
|
+
// ── 8. Events ────────────────────────────────────────────────────────
|
|
149
179
|
if (cfg.events !== false) {
|
|
150
180
|
const p = load('../providers/EventServiceProvider');
|
|
151
181
|
if (p) providers.push(p);
|
|
152
182
|
}
|
|
153
183
|
|
|
184
|
+
// ── 9. i18n — opt-in via config/app.js use_i18n: true ───────────────
|
|
185
|
+
// Mirrors Django's USE_I18N = True in settings.py.
|
|
186
|
+
// Booted last so translations are available in all request handlers.
|
|
187
|
+
if (this._resolveI18nEnabled(cfg)) {
|
|
188
|
+
const p = load('../i18n/I18nServiceProvider');
|
|
189
|
+
if (p) providers.push(p);
|
|
190
|
+
}
|
|
191
|
+
|
|
154
192
|
return providers;
|
|
155
193
|
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Resolve whether i18n should be enabled.
|
|
197
|
+
* Reads use_i18n from config/app.js — the single source of truth.
|
|
198
|
+
*
|
|
199
|
+
* // config/app.js
|
|
200
|
+
* module.exports = {
|
|
201
|
+
* use_i18n: true,
|
|
202
|
+
* locale: 'sw',
|
|
203
|
+
* fallback: 'en',
|
|
204
|
+
* };
|
|
205
|
+
*/
|
|
206
|
+
_resolveI18nEnabled(cfg) {
|
|
207
|
+
try {
|
|
208
|
+
const basePath = cfg.basePath || process.cwd();
|
|
209
|
+
const appConfig = require(basePath + '/config/app');
|
|
210
|
+
return appConfig.use_i18n === true;
|
|
211
|
+
} catch { return false; }
|
|
212
|
+
}
|
|
156
213
|
}
|
|
157
214
|
|
|
158
215
|
module.exports = AppInitializer;
|
|
@@ -30,12 +30,19 @@ const MillasConfig = require('./MillasConfig');
|
|
|
30
30
|
const Millas = {
|
|
31
31
|
/**
|
|
32
32
|
* Start building an application config.
|
|
33
|
-
*
|
|
33
|
+
* Equivalent to Millas.config().configure(basePath).
|
|
34
34
|
*
|
|
35
|
+
* Usage (bootstrap/app.js):
|
|
36
|
+
* module.exports = Millas.configure(__dirname)
|
|
37
|
+
* .withAdmin()
|
|
38
|
+
* .routes(Route => { ... })
|
|
39
|
+
* .create();
|
|
40
|
+
*
|
|
41
|
+
* @param {string} basePath — pass __dirname from bootstrap/app.js
|
|
35
42
|
* @returns {MillasConfig}
|
|
36
43
|
*/
|
|
37
|
-
|
|
38
|
-
return new MillasConfig();
|
|
44
|
+
configure(basePath) {
|
|
45
|
+
return new MillasConfig().configure(basePath);
|
|
39
46
|
},
|
|
40
47
|
};
|
|
41
48
|
|
|
@@ -25,6 +25,11 @@ const AppInitializer = require("./AppInitializer");
|
|
|
25
25
|
class MillasConfig {
|
|
26
26
|
constructor() {
|
|
27
27
|
this._config = {
|
|
28
|
+
// Absolute path to the project root — passed to all providers
|
|
29
|
+
// so they never need to call process.cwd() at runtime.
|
|
30
|
+
// Set via .configure(__dirname) in bootstrap/app.js.
|
|
31
|
+
basePath: null,
|
|
32
|
+
|
|
28
33
|
// Service providers
|
|
29
34
|
providers: [],
|
|
30
35
|
|
|
@@ -35,13 +40,14 @@ class MillasConfig {
|
|
|
35
40
|
middleware: [],
|
|
36
41
|
|
|
37
42
|
// Core service toggles
|
|
43
|
+
logging: true,
|
|
38
44
|
database: true,
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
+
auth: true, // AuthServiceProvider — always on by default
|
|
46
|
+
cache: true,
|
|
47
|
+
storage: true,
|
|
48
|
+
mail: true,
|
|
49
|
+
queue: true,
|
|
50
|
+
events: true,
|
|
45
51
|
|
|
46
52
|
// Admin panel — null means disabled, {} or options object means enabled
|
|
47
53
|
admin: null,
|
|
@@ -57,6 +63,22 @@ class MillasConfig {
|
|
|
57
63
|
|
|
58
64
|
// ── Chainable config methods ───────────────────────────────────────────────
|
|
59
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Set the application base path. Must be the first call.
|
|
68
|
+
* Pass __dirname from bootstrap/app.js — Laravel style.
|
|
69
|
+
*
|
|
70
|
+
* Millas.configure(__dirname)
|
|
71
|
+
*
|
|
72
|
+
* All providers use this to locate config files, models, and routes
|
|
73
|
+
* without relying on process.cwd() at runtime.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} basePath — absolute path to the project root
|
|
76
|
+
*/
|
|
77
|
+
configure(basePath) {
|
|
78
|
+
this._config.basePath = basePath;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
|
|
60
82
|
/**
|
|
61
83
|
* Register application service providers.
|
|
62
84
|
*
|
|
@@ -93,8 +115,15 @@ class MillasConfig {
|
|
|
93
115
|
/**
|
|
94
116
|
* Enable the admin panel.
|
|
95
117
|
*
|
|
118
|
+
* AuthServiceProvider and AdminServiceProvider are registered
|
|
119
|
+
* automatically — no need to add them to .providers([]).
|
|
120
|
+
*
|
|
96
121
|
* .withAdmin()
|
|
97
122
|
* .withAdmin({ prefix: '/cms', title: 'My CMS' })
|
|
123
|
+
*
|
|
124
|
+
* First-time setup:
|
|
125
|
+
* millas migrate # creates users + admin_log + sessions tables
|
|
126
|
+
* millas createsuperuser # creates your first admin user
|
|
98
127
|
*/
|
|
99
128
|
withAdmin(options = {}) {
|
|
100
129
|
this._config.admin = options;
|
package/src/core/db.js
CHANGED
package/src/core/foundation.js
CHANGED
|
@@ -56,12 +56,4 @@ module.exports = {
|
|
|
56
56
|
Cache, MemoryDriver, FileDriver, NullDriver, CacheServiceProvider,
|
|
57
57
|
// Storage
|
|
58
58
|
Storage, LocalDriver, StorageServiceProvider,
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
// ── Admin ─────────────────────────────────────────────────────────
|
|
62
|
-
const { Admin, AdminResource, AdminField, AdminFilter } = require('../admin');
|
|
63
|
-
const AdminServiceProvider = require('../providers/AdminServiceProvider');
|
|
64
|
-
|
|
65
|
-
Object.assign(module.exports, {
|
|
66
|
-
Admin, AdminResource, AdminField, AdminFilter, AdminServiceProvider,
|
|
67
|
-
});
|
|
59
|
+
};
|
package/src/core/lang.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('../i18n')
|