millas 0.2.11 → 0.2.12-beta-1
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 +6 -5
- package/src/auth/Auth.js +13 -8
- package/src/auth/AuthController.js +45 -134
- package/src/auth/AuthMiddleware.js +12 -23
- package/src/auth/AuthUser.js +98 -0
- package/src/auth/RoleMiddleware.js +7 -17
- package/src/cli.js +1 -1
- package/src/commands/migrate.js +46 -31
- package/src/commands/serve.js +238 -38
- package/src/container/AppInitializer.js +158 -0
- package/src/container/Application.js +288 -183
- package/src/container/HttpServer.js +156 -0
- package/src/container/MillasApp.js +23 -280
- package/src/container/MillasConfig.js +163 -0
- package/src/controller/Controller.js +79 -300
- package/src/core/auth.js +9 -0
- package/src/core/db.js +8 -0
- package/src/core/foundation.js +67 -0
- package/src/core/http.js +11 -0
- package/src/core/mail.js +6 -0
- package/src/core/queue.js +7 -0
- package/src/core/validation.js +29 -0
- package/src/errors/ErrorRenderer.js +640 -0
- package/src/facades/Admin.js +49 -0
- package/src/facades/Auth.js +29 -0
- package/src/facades/Cache.js +28 -0
- package/src/facades/Database.js +43 -0
- package/src/facades/Events.js +25 -0
- package/src/facades/Facade.js +197 -0
- package/src/facades/Http.js +51 -0
- package/src/facades/Log.js +32 -0
- package/src/facades/Mail.js +35 -0
- package/src/facades/Queue.js +30 -0
- package/src/facades/Storage.js +25 -0
- package/src/facades/Url.js +53 -0
- package/src/http/HttpClient.js +673 -0
- package/src/http/MillasRequest.js +253 -0
- package/src/http/MillasResponse.js +196 -0
- package/src/http/RequestContext.js +176 -0
- package/src/http/ResponseDispatcher.js +51 -0
- package/src/http/UrlGenerator.js +375 -0
- package/src/http/WelcomePage.js +273 -0
- package/src/http/adapters/ExpressAdapter.js +315 -0
- package/src/http/adapters/HttpAdapter.js +168 -0
- package/src/http/adapters/index.js +9 -0
- package/src/http/helpers.js +164 -0
- package/src/http/index.js +13 -0
- package/src/index.js +5 -91
- package/src/logger/formatters/PrettyFormatter.js +15 -5
- package/src/logger/internal.js +76 -0
- package/src/logger/patchConsole.js +145 -0
- package/src/middleware/CorsMiddleware.js +22 -30
- package/src/middleware/LogMiddleware.js +27 -59
- package/src/middleware/Middleware.js +24 -15
- package/src/middleware/MiddlewarePipeline.js +30 -67
- package/src/middleware/MiddlewareRegistry.js +106 -0
- package/src/middleware/ThrottleMiddleware.js +22 -26
- package/src/orm/fields/index.js +124 -56
- package/src/orm/migration/ModelInspector.js +339 -336
- package/src/orm/model/Model.js +96 -6
- package/src/orm/query/QueryBuilder.js +141 -3
- package/src/providers/AuthServiceProvider.js +9 -5
- package/src/providers/CacheStorageServiceProvider.js +3 -1
- package/src/providers/EventServiceProvider.js +2 -1
- package/src/providers/LogServiceProvider.js +88 -17
- package/src/providers/MailServiceProvider.js +3 -2
- package/src/providers/ProviderRegistry.js +14 -1
- package/src/providers/QueueServiceProvider.js +3 -2
- package/src/providers/ServiceProvider.js +40 -8
- package/src/router/Router.js +121 -222
- package/src/scaffold/maker.js +24 -59
- package/src/scaffold/templates.js +21 -19
- package/src/validation/BaseValidator.js +193 -0
- package/src/validation/Validator.js +680 -0
package/src/commands/migrate.js
CHANGED
|
@@ -6,17 +6,15 @@ const fs = require('fs-extra');
|
|
|
6
6
|
|
|
7
7
|
module.exports = function (program) {
|
|
8
8
|
|
|
9
|
-
// ── makemigrations
|
|
10
|
-
|
|
9
|
+
// ── makemigrations ──────────────────────────────────────────────────────────
|
|
11
10
|
program
|
|
12
11
|
.command('makemigrations')
|
|
13
12
|
.description('Scan model files, detect schema changes, generate migration files')
|
|
14
13
|
.action(async () => {
|
|
15
14
|
try {
|
|
16
|
-
const ctx
|
|
17
|
-
// Fixed: was incorrectly destructured as { ModelInspector }
|
|
15
|
+
const ctx = getProjectContext();
|
|
18
16
|
const ModelInspector = require('../orm/migration/ModelInspector');
|
|
19
|
-
const inspector
|
|
17
|
+
const inspector = new ModelInspector(
|
|
20
18
|
ctx.modelsPath,
|
|
21
19
|
ctx.migrationsPath,
|
|
22
20
|
ctx.snapshotPath,
|
|
@@ -31,14 +29,12 @@ module.exports = function (program) {
|
|
|
31
29
|
console.log(chalk.gray('\n Run: millas migrate to apply these migrations.\n'));
|
|
32
30
|
}
|
|
33
31
|
} catch (err) {
|
|
34
|
-
|
|
35
|
-
if (process.env.DEBUG) console.error(err.stack);
|
|
36
|
-
process.exit(1);
|
|
32
|
+
bail('makemigrations', err);
|
|
37
33
|
}
|
|
34
|
+
// makemigrations doesn't open a DB connection so no closeDb() needed
|
|
38
35
|
});
|
|
39
36
|
|
|
40
|
-
// ── migrate
|
|
41
|
-
|
|
37
|
+
// ── migrate ─────────────────────────────────────────────────────────────────
|
|
42
38
|
program
|
|
43
39
|
.command('migrate')
|
|
44
40
|
.description('Run all pending migrations')
|
|
@@ -46,14 +42,15 @@ module.exports = function (program) {
|
|
|
46
42
|
try {
|
|
47
43
|
const runner = await getRunner();
|
|
48
44
|
const result = await runner.migrate();
|
|
49
|
-
|
|
45
|
+
printMigrationResult(result, 'Ran');
|
|
50
46
|
} catch (err) {
|
|
51
47
|
bail('migrate', err);
|
|
48
|
+
} finally {
|
|
49
|
+
await closeDb();
|
|
52
50
|
}
|
|
53
51
|
});
|
|
54
52
|
|
|
55
|
-
// ── migrate:fresh
|
|
56
|
-
|
|
53
|
+
// ── migrate:fresh ────────────────────────────────────────────────────────────
|
|
57
54
|
program
|
|
58
55
|
.command('migrate:fresh')
|
|
59
56
|
.description('Drop ALL tables then re-run every migration from scratch')
|
|
@@ -62,14 +59,15 @@ module.exports = function (program) {
|
|
|
62
59
|
console.log(chalk.yellow('\n ⚠ Dropping all tables…\n'));
|
|
63
60
|
const runner = await getRunner();
|
|
64
61
|
const result = await runner.fresh();
|
|
65
|
-
|
|
62
|
+
printMigrationResult(result, 'Ran');
|
|
66
63
|
} catch (err) {
|
|
67
64
|
bail('migrate:fresh', err);
|
|
65
|
+
} finally {
|
|
66
|
+
await closeDb();
|
|
68
67
|
}
|
|
69
68
|
});
|
|
70
69
|
|
|
71
|
-
// ── migrate:rollback
|
|
72
|
-
|
|
70
|
+
// ── migrate:rollback ─────────────────────────────────────────────────────────
|
|
73
71
|
program
|
|
74
72
|
.command('migrate:rollback')
|
|
75
73
|
.description('Rollback the last batch of migrations')
|
|
@@ -78,14 +76,15 @@ module.exports = function (program) {
|
|
|
78
76
|
try {
|
|
79
77
|
const runner = await getRunner();
|
|
80
78
|
const result = await runner.rollback(Number(options.steps));
|
|
81
|
-
|
|
79
|
+
printMigrationResult(result, 'Rolled back');
|
|
82
80
|
} catch (err) {
|
|
83
81
|
bail('migrate:rollback', err);
|
|
82
|
+
} finally {
|
|
83
|
+
await closeDb();
|
|
84
84
|
}
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
-
// ── migrate:reset
|
|
88
|
-
|
|
87
|
+
// ── migrate:reset ────────────────────────────────────────────────────────────
|
|
89
88
|
program
|
|
90
89
|
.command('migrate:reset')
|
|
91
90
|
.description('Rollback ALL migrations')
|
|
@@ -93,14 +92,15 @@ module.exports = function (program) {
|
|
|
93
92
|
try {
|
|
94
93
|
const runner = await getRunner();
|
|
95
94
|
const result = await runner.reset();
|
|
96
|
-
|
|
95
|
+
printMigrationResult(result, 'Rolled back');
|
|
97
96
|
} catch (err) {
|
|
98
97
|
bail('migrate:reset', err);
|
|
98
|
+
} finally {
|
|
99
|
+
await closeDb();
|
|
99
100
|
}
|
|
100
101
|
});
|
|
101
102
|
|
|
102
|
-
// ── migrate:refresh
|
|
103
|
-
|
|
103
|
+
// ── migrate:refresh ──────────────────────────────────────────────────────────
|
|
104
104
|
program
|
|
105
105
|
.command('migrate:refresh')
|
|
106
106
|
.description('Rollback all then re-run all migrations')
|
|
@@ -108,14 +108,15 @@ module.exports = function (program) {
|
|
|
108
108
|
try {
|
|
109
109
|
const runner = await getRunner();
|
|
110
110
|
const result = await runner.refresh();
|
|
111
|
-
|
|
111
|
+
printMigrationResult(result, 'Ran');
|
|
112
112
|
} catch (err) {
|
|
113
113
|
bail('migrate:refresh', err);
|
|
114
|
+
} finally {
|
|
115
|
+
await closeDb();
|
|
114
116
|
}
|
|
115
117
|
});
|
|
116
118
|
|
|
117
|
-
// ── migrate:status
|
|
118
|
-
|
|
119
|
+
// ── migrate:status ───────────────────────────────────────────────────────────
|
|
119
120
|
program
|
|
120
121
|
.command('migrate:status')
|
|
121
122
|
.description('Show the status of all migration files')
|
|
@@ -143,11 +144,12 @@ module.exports = function (program) {
|
|
|
143
144
|
console.log();
|
|
144
145
|
} catch (err) {
|
|
145
146
|
bail('migrate:status', err);
|
|
147
|
+
} finally {
|
|
148
|
+
await closeDb();
|
|
146
149
|
}
|
|
147
150
|
});
|
|
148
151
|
|
|
149
|
-
// ── db:seed
|
|
150
|
-
|
|
152
|
+
// ── db:seed ──────────────────────────────────────────────────────────────────
|
|
151
153
|
program
|
|
152
154
|
.command('db:seed')
|
|
153
155
|
.description('Run all database seeders')
|
|
@@ -180,6 +182,8 @@ module.exports = function (program) {
|
|
|
180
182
|
console.log();
|
|
181
183
|
} catch (err) {
|
|
182
184
|
bail('db:seed', err);
|
|
185
|
+
} finally {
|
|
186
|
+
await closeDb();
|
|
183
187
|
}
|
|
184
188
|
});
|
|
185
189
|
};
|
|
@@ -208,14 +212,25 @@ async function getDbConnection() {
|
|
|
208
212
|
}
|
|
209
213
|
|
|
210
214
|
async function getRunner() {
|
|
211
|
-
// Fixed: was incorrectly destructured as { MigrationRunner }
|
|
212
215
|
const MigrationRunner = require('../orm/migration/MigrationRunner');
|
|
213
216
|
const ctx = getProjectContext();
|
|
214
217
|
const db = await getDbConnection();
|
|
215
218
|
return new MigrationRunner(db, ctx.migrationsPath);
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
|
|
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
|
+
async function closeDb() {
|
|
227
|
+
try {
|
|
228
|
+
const DatabaseManager = require('../orm/drivers/DatabaseManager');
|
|
229
|
+
await DatabaseManager.closeAll();
|
|
230
|
+
} catch { /* already closed or never opened — safe to ignore */ }
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function printMigrationResult(result, verb) {
|
|
219
234
|
const list = result.ran || result.rolledBack || [];
|
|
220
235
|
if (list.length === 0) {
|
|
221
236
|
console.log(chalk.yellow(`\n ${result.message}\n`));
|
|
@@ -223,7 +238,7 @@ function printResult(result, verb) {
|
|
|
223
238
|
}
|
|
224
239
|
console.log(chalk.green(`\n ✔ ${result.message}`));
|
|
225
240
|
list.forEach(f =>
|
|
226
|
-
console.log(chalk.cyan(` ${verb === 'Ran' ? '+' : '-'} ${f}`))
|
|
241
|
+
console.log(chalk.cyan(` ${verb === 'Ran' ? '+' : '-'} ${f}`))
|
|
227
242
|
);
|
|
228
243
|
console.log();
|
|
229
244
|
}
|
|
@@ -231,5 +246,5 @@ function printResult(result, verb) {
|
|
|
231
246
|
function bail(cmd, err) {
|
|
232
247
|
console.error(chalk.red(`\n ✖ ${cmd} failed: ${err.message}\n`));
|
|
233
248
|
if (process.env.DEBUG) console.error(err.stack);
|
|
234
|
-
process.exit(1);
|
|
249
|
+
closeDb().finally(() => process.exit(1));
|
|
235
250
|
}
|
package/src/commands/serve.js
CHANGED
|
@@ -3,48 +3,248 @@
|
|
|
3
3
|
const chalk = require('chalk');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const fs = require('fs-extra');
|
|
6
|
+
const fsnative = require('fs');
|
|
7
|
+
const {fork} = require('child_process');
|
|
8
|
+
const chokidar = require('chokidar');
|
|
9
|
+
const patchConsole = require("../logger/patchConsole");
|
|
10
|
+
const Logger = require("../logger/internal");
|
|
11
|
+
// ── ASCII banner ───────────────────────────────────────────────────────────────
|
|
12
|
+
|
|
13
|
+
const BANNER_LINES = [
|
|
14
|
+
' ███╗ ███╗██╗██╗ ██╗ █████╗ ███████╗',
|
|
15
|
+
' ████╗ ████║██║██║ ██║ ██╔══██╗██╔════╝',
|
|
16
|
+
' ██╔████╔██║██║██║ ██║ ███████║███████╗',
|
|
17
|
+
' ██║╚██╔╝██║██║██║ ██║ ██╔══██║╚════██║',
|
|
18
|
+
' ██║ ╚═╝ ██║██║███████╗███████╗██║ ██║███████║',
|
|
19
|
+
' ╚═╝ ╚═╝╚═╝╚══════╝╚══════╝╚═╝ ╚═╝╚══════╝',
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
function printBanner(host, port) {
|
|
23
|
+
const env = process.env.NODE_ENV || 'development';
|
|
24
|
+
const ver = 'v' + (require('../../package.json').version || '0.1.0');
|
|
25
|
+
const url = `http://${host}:${port}`;
|
|
26
|
+
const hr = chalk.dim(' ' + '─'.repeat(54));
|
|
27
|
+
const envColour = env === 'production' ? chalk.red
|
|
28
|
+
: env === 'staging' ? chalk.yellow
|
|
29
|
+
: chalk.green;
|
|
30
|
+
|
|
31
|
+
process.stdout.write('\n');
|
|
32
|
+
for (const line of BANNER_LINES) {
|
|
33
|
+
process.stdout.write(chalk.bold.cyan(line) + '\n');
|
|
34
|
+
}
|
|
35
|
+
process.stdout.write('\n' + hr + '\n');
|
|
36
|
+
process.stdout.write(
|
|
37
|
+
' ' + chalk.dim(ver.padEnd(8)) +
|
|
38
|
+
chalk.dim('│') + ' ' + envColour('⬤ ' + env) + ' ' +
|
|
39
|
+
chalk.dim('│') + ' ' + chalk.bold.white(url) + '\n'
|
|
40
|
+
);
|
|
41
|
+
process.stdout.write(hr + '\n\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ── Constants ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
const WATCH_DIRS = ['app', 'routes', 'config', 'bootstrap', 'providers', 'middleware'];
|
|
47
|
+
const WATCH_EXTS = new Set(['.js', '.mjs', '.cjs', '.json', '.njk', '.env']);
|
|
48
|
+
const DEBOUNCE_MS = 250;
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
// ── HotReloader ───────────────────────────────────────────────────────────────
|
|
52
|
+
|
|
53
|
+
class HotReloader {
|
|
54
|
+
constructor(bootstrapPath, publicPort, publicHost) {
|
|
55
|
+
this._bootstrap = bootstrapPath;
|
|
56
|
+
this._initialised = false
|
|
57
|
+
|
|
58
|
+
this._child = null;
|
|
59
|
+
this._starting = false;
|
|
60
|
+
this._restarts = 0;
|
|
61
|
+
|
|
62
|
+
// Queued { req, res, timer } entries while child is restarting
|
|
63
|
+
this._queue = [];
|
|
64
|
+
this._watchers = [];
|
|
65
|
+
this._timer = null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
start() {
|
|
69
|
+
this._spawnChild();
|
|
70
|
+
this._watch();
|
|
71
|
+
this._handleSignals();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
_spawnChild() {
|
|
75
|
+
if (this._starting) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
this._starting = true;
|
|
79
|
+
const extra = {}
|
|
80
|
+
if (!this._initialised) {
|
|
81
|
+
extra["MILLAS_START_UP"] = true
|
|
82
|
+
this._initialised = true
|
|
83
|
+
}
|
|
84
|
+
this._child = fork(this._bootstrap, [], {
|
|
85
|
+
env: {
|
|
86
|
+
...extra,
|
|
87
|
+
MILLAS_CHILD: '1',
|
|
88
|
+
},
|
|
89
|
+
stdio: 'inherit',
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Application.listen() sends { type:'ready' } via IPC once bound
|
|
93
|
+
this._child.on('message', msg => {
|
|
94
|
+
if (msg && msg.type === 'ready') {
|
|
95
|
+
this._starting = false;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
//
|
|
99
|
+
this._child.on('exit', (code, signal) => {
|
|
100
|
+
this._starting = false;
|
|
101
|
+
|
|
102
|
+
if (code !== 0 && signal !== 'SIGTERM' && signal !== 'SIGKILL') {
|
|
103
|
+
console.error(chalk.red('✖ App crashed') +
|
|
104
|
+
chalk.dim(` (exit ${code ?? signal})`) +
|
|
105
|
+
chalk.dim(' — fix the error, file watcher will reload…')
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
//
|
|
110
|
+
this._child.on('error', err => {
|
|
111
|
+
this._starting = false;
|
|
112
|
+
console.error(chalk.red(`✖ ${err.message}`));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
_killChild(cb) {
|
|
117
|
+
if (!this._child || this._child.exitCode !== null) return cb();
|
|
118
|
+
this._child.once('exit', cb);
|
|
119
|
+
this._child.kill('SIGTERM');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_restart(changedFile) {
|
|
123
|
+
const link = changedFile
|
|
124
|
+
? `\x1b]8;;file://${changedFile}\x07${path.relative(process.cwd(), changedFile)}\x1b]8;;\x07`
|
|
125
|
+
: '';
|
|
126
|
+
console.warn(
|
|
127
|
+
chalk.yellow('↺') + ' ' +
|
|
128
|
+
chalk.white('Reloading') +
|
|
129
|
+
(link ? chalk.blueBright(' ' + link) : '')
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
this._restarts++;
|
|
133
|
+
this._killChild(() => this._spawnChild());
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Watcher ───────────────────────────────────────────────────────────────
|
|
137
|
+
|
|
138
|
+
_watch() {
|
|
139
|
+
console.log(chalk.green('✔') + ' ' +
|
|
140
|
+
chalk.dim('Watching for changes…')
|
|
141
|
+
);
|
|
142
|
+
const cwd = process.cwd();
|
|
143
|
+
const watchPaths = [
|
|
144
|
+
...WATCH_DIRS.map(d => path.join(cwd, d)),
|
|
145
|
+
path.join(cwd, '.env'),
|
|
146
|
+
path.join(cwd, '.env.local'),
|
|
147
|
+
];
|
|
148
|
+
|
|
149
|
+
const watcher = chokidar.watch(watchPaths, {
|
|
150
|
+
persistent: true,
|
|
151
|
+
ignoreInitial: true,
|
|
152
|
+
ignored: /(^|[\/\\])\..(?!env)/,
|
|
153
|
+
// Wait for the file write to settle before reloading.
|
|
154
|
+
// Prevents double-restarts on editors that truncate then rewrite.
|
|
155
|
+
awaitWriteFinish: {stabilityThreshold: 80, pollInterval: 20},
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
watcher.on('all', (event, filePath) => {
|
|
159
|
+
if (WATCH_EXTS.has(path.extname(filePath))) {
|
|
160
|
+
this._scheduleRestart(filePath);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this._watchers.push(watcher);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
_scheduleRestart(changedFile) {
|
|
168
|
+
clearTimeout(this._timer);
|
|
169
|
+
this._timer = setTimeout(() => this._restart(changedFile), DEBOUNCE_MS);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_stopWatching() {
|
|
173
|
+
for (const w of this._watchers) {
|
|
174
|
+
try {
|
|
175
|
+
w.close();
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
this._watchers = [];
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// ── Signals ───────────────────────────────────────────────────────────────
|
|
183
|
+
|
|
184
|
+
_handleSignals() {
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
clearTimeout(this._timer);
|
|
187
|
+
this._stopWatching();
|
|
188
|
+
if (this._child) this._child.kill('SIGTERM');
|
|
189
|
+
process.exit(0);
|
|
190
|
+
};
|
|
191
|
+
process.once('SIGINT', cleanup);
|
|
192
|
+
process.once('SIGTERM', cleanup);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Command ────────────────────────────────────────────────────────────────────
|
|
6
197
|
|
|
7
198
|
module.exports = function (program) {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
if (!fs.existsSync(appBootstrap)) {
|
|
17
|
-
console.error(chalk.red('\n ✖ No Millas project found in current directory.\n'));
|
|
18
|
-
console.log(` Make sure you are inside a Millas project and bootstrap/app.js exists.\n`);
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
199
|
+
program
|
|
200
|
+
.command('serve')
|
|
201
|
+
.description('Start the development server with hot reload')
|
|
202
|
+
.option('-p, --port <port>', 'Port to listen on', '3000')
|
|
203
|
+
.option('-h, --host <host>', 'Host to bind to', 'localhost')
|
|
204
|
+
.option('--no-reload', 'Disable hot reload (run once, like production)')
|
|
205
|
+
.action((options) => {
|
|
21
206
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log(chalk.gray(` Starting on http://${options.host}:${options.port}\n`));
|
|
207
|
+
const restoreAfterPatch = patchConsole(Logger,"SystemOut")
|
|
208
|
+
const appBootstrap = path.resolve(process.cwd(), 'bootstrap/app.js');
|
|
25
209
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
210
|
+
if (!fs.existsSync(appBootstrap)) {
|
|
211
|
+
process.stderr.write(chalk.red('\n ✖ No Millas project found here.\n'));
|
|
212
|
+
process.stderr.write(chalk.dim(' Make sure bootstrap/app.js exists.\n\n'));
|
|
213
|
+
process.exit(1);
|
|
214
|
+
}
|
|
29
215
|
|
|
30
|
-
try {
|
|
31
|
-
require(appBootstrap);
|
|
32
|
-
} catch (err) {
|
|
33
|
-
console.error(chalk.red(`\n ✖ Failed to start server: ${err.message}\n`));
|
|
34
|
-
console.error(err.stack);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
};
|
|
39
216
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
217
|
+
const publicPort = parseInt(options.port, 10) || 3000;
|
|
218
|
+
const publicHost = options.host || 'localhost';
|
|
219
|
+
|
|
220
|
+
const env = {
|
|
221
|
+
NODE_ENV: process.env.APP_ENV || 'development',
|
|
222
|
+
MILLERS_NODE_ENV: true,
|
|
223
|
+
MILLAS_HOST: publicHost,
|
|
224
|
+
MILLAS_PORT: String(publicPort),
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
Object.assign(process.env, env);
|
|
228
|
+
printBanner(publicHost, publicPort);
|
|
229
|
+
|
|
230
|
+
if (options.reload !== false) {
|
|
231
|
+
|
|
232
|
+
new HotReloader(appBootstrap, publicPort, publicHost).start();
|
|
233
|
+
} else {
|
|
234
|
+
try {
|
|
235
|
+
require(appBootstrap);
|
|
236
|
+
} catch (e) {
|
|
237
|
+
console.log("Error starting server: ", +e)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
|
|
50
242
|
};
|
|
243
|
+
|
|
244
|
+
module.exports.requireProject = function (command) {
|
|
245
|
+
const bootstrapPath = path.resolve(process.cwd(), 'bootstrap/app.js');
|
|
246
|
+
if (!fsnative.existsSync(bootstrapPath)) {
|
|
247
|
+
process.stderr.write(chalk.red(`\n ✖ Not inside a Millas project (${command}).\n\n`));
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const express = require('express');
|
|
4
|
+
const Application = require('./Application');
|
|
5
|
+
const HttpServer = require('./HttpServer');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* AppInitialiser
|
|
9
|
+
*
|
|
10
|
+
* The internal engine that receives what bootstrap/app.js exports
|
|
11
|
+
* (a MillasInstance), extracts its config, and wires up everything:
|
|
12
|
+
*
|
|
13
|
+
* - ExpressAdapter
|
|
14
|
+
* - Application kernel (DI container, providers, routes, middleware)
|
|
15
|
+
* - Admin panel
|
|
16
|
+
* - HttpServer (port, signals, startup log, IPC ready signal)
|
|
17
|
+
*
|
|
18
|
+
* Developers never see or interact with this class.
|
|
19
|
+
* It is used exclusively by runner.js (millas serve) and the no-reload path.
|
|
20
|
+
*
|
|
21
|
+
* ── Flow ─────────────────────────────────────────────────────────────────────
|
|
22
|
+
*
|
|
23
|
+
* bootstrap/app.js → MillasInstance → AppInitialiser.boot()
|
|
24
|
+
* ├─ builds ExpressAdapter
|
|
25
|
+
* ├─ builds Application
|
|
26
|
+
* ├─ registers providers
|
|
27
|
+
* ├─ registers routes
|
|
28
|
+
* ├─ registers middleware aliases
|
|
29
|
+
* ├─ boots providers
|
|
30
|
+
* ├─ mounts routes
|
|
31
|
+
* ├─ mounts admin (if configured)
|
|
32
|
+
* ├─ mounts fallbacks
|
|
33
|
+
* └─ starts HttpServer
|
|
34
|
+
*/
|
|
35
|
+
class AppInitializer {
|
|
36
|
+
/**
|
|
37
|
+
* @param {Object} config — the sealed export of bootstrap/app.js
|
|
38
|
+
*/
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this._config = config;
|
|
41
|
+
this._kernel = null;
|
|
42
|
+
this._adapter = null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Boot the full application and start the HTTP server.
|
|
47
|
+
* Returns a Promise that resolves once the server is listening.
|
|
48
|
+
*/
|
|
49
|
+
async boot() {
|
|
50
|
+
const cfg = this._config;
|
|
51
|
+
|
|
52
|
+
// ── Build the HTTP adapter ───────────────────────────────────────────────
|
|
53
|
+
const ExpressAdapter = require('../http/adapters/ExpressAdapter');
|
|
54
|
+
const expressApp = express();
|
|
55
|
+
this._adapter = new ExpressAdapter(expressApp);
|
|
56
|
+
this._adapter.applyBodyParsers();
|
|
57
|
+
|
|
58
|
+
// Raw adapter middleware (helmet, compression, etc.)
|
|
59
|
+
for (const mw of (cfg.adapterMiddleware || [])) {
|
|
60
|
+
this._adapter.applyMiddleware(mw);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ── Build the Application kernel ─────────────────────────────────────────
|
|
64
|
+
this._kernel = new Application(this._adapter);
|
|
65
|
+
|
|
66
|
+
// Core providers (auto-enabled unless disabled in config)
|
|
67
|
+
const coreProviders = this._buildCoreProviders(cfg);
|
|
68
|
+
this._kernel.providers([...coreProviders, ...cfg.providers]);
|
|
69
|
+
|
|
70
|
+
// Named middleware aliases
|
|
71
|
+
for (const {alias, handler} of (cfg.middleware || [])) {
|
|
72
|
+
this._kernel.middleware(alias, handler);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Route definitions
|
|
76
|
+
if (cfg.routes) {
|
|
77
|
+
this._kernel.routes(cfg.routes);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// ── Boot providers ───────────────────────────────────────────────────────
|
|
81
|
+
await this._kernel.boot();
|
|
82
|
+
|
|
83
|
+
// ── Mount routes ─────────────────────────────────────────────────────────
|
|
84
|
+
if (!process.env.MILLAS_ROUTE_LIST) {
|
|
85
|
+
this._kernel.mountRoutes();
|
|
86
|
+
|
|
87
|
+
// Admin panel — mounted between routes and fallbacks
|
|
88
|
+
if (cfg.admin !== null) {
|
|
89
|
+
try {
|
|
90
|
+
const Admin = require('../admin/Admin');
|
|
91
|
+
if (cfg.admin && Object.keys(cfg.admin).length) {
|
|
92
|
+
Admin.configure(cfg.admin);
|
|
93
|
+
}
|
|
94
|
+
Admin.mount(expressApp);
|
|
95
|
+
} catch (err) {
|
|
96
|
+
process.stderr.write(`[millas] Admin mount failed: ${err.message}\n`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
this._kernel.mountFallbacks();
|
|
101
|
+
|
|
102
|
+
// ── Start the HTTP server ──────────────────────────────────────────────
|
|
103
|
+
const server = new HttpServer(this._kernel, {
|
|
104
|
+
onStart: cfg.onStart || undefined,
|
|
105
|
+
onShutdown: cfg.onShutdown || undefined,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await server.start();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ── Internal ───────────────────────────────────────────────────────────────
|
|
113
|
+
|
|
114
|
+
_buildCoreProviders(cfg) {
|
|
115
|
+
const providers = [];
|
|
116
|
+
const load = (p) => {
|
|
117
|
+
try {
|
|
118
|
+
return require(p);
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (cfg.logging !== false) {
|
|
125
|
+
const p = load('../providers/LogServiceProvider');
|
|
126
|
+
if (p) providers.push(p);
|
|
127
|
+
}
|
|
128
|
+
if (cfg.database !== false) {
|
|
129
|
+
const p = load('../providers/DatabaseServiceProvider');
|
|
130
|
+
if (p) providers.push(p);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (cfg.cache !== false || cfg.storage !== false) {
|
|
134
|
+
const p = load('../providers/CacheStorageServiceProvider');
|
|
135
|
+
if (p) {
|
|
136
|
+
if (cfg.cache !== false && p.CacheServiceProvider) providers.push(p.CacheServiceProvider);
|
|
137
|
+
if (cfg.storage !== false && p.StorageServiceProvider) providers.push(p.StorageServiceProvider);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (cfg.mail !== false) {
|
|
142
|
+
const p = load('../providers/MailServiceProvider');
|
|
143
|
+
if (p) providers.push(p);
|
|
144
|
+
}
|
|
145
|
+
if (cfg.queue !== false) {
|
|
146
|
+
const p = load('../providers/QueueServiceProvider');
|
|
147
|
+
if (p) providers.push(p);
|
|
148
|
+
}
|
|
149
|
+
if (cfg.events !== false) {
|
|
150
|
+
const p = load('../providers/EventServiceProvider');
|
|
151
|
+
if (p) providers.push(p);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return providers;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
module.exports = AppInitializer;
|