@nitronjs/framework 0.2.2 → 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 +17 -19
- 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 +4 -3
- package/lib/Console/Commands/MigrateFreshCommand.js +22 -27
- package/lib/Console/Commands/MigrateRollbackCommand.js +8 -4
- package/lib/Console/Commands/MigrateStatusCommand.js +8 -4
- package/lib/Console/Commands/SeedCommand.js +8 -28
- 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 -8
- 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 +59 -67
- package/lib/Database/Model.js +165 -75
- package/lib/Database/QueryBuilder.js +43 -0
- package/lib/Database/QueryValidation.js +51 -30
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +24 -145
- 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 +179 -151
- 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 +49 -27
- package/lib/index.js +19 -13
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/package.json +2 -0
- 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/Database/Migration/migrations/0000_00_00_00_01_create_seeders_table.js +0 -20
- package/lib/Database/Seeder/SeederRepository.js +0 -45
- 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
package/lib/Http/Server.js
CHANGED
|
@@ -1,75 +1,108 @@
|
|
|
1
|
-
import { pathToFileURL } from "node:url";
|
|
2
1
|
import dotenv from "dotenv";
|
|
3
|
-
import path from "node:path";
|
|
4
2
|
import fastify from "fastify";
|
|
5
3
|
import fastifyCors from "@fastify/cors";
|
|
6
4
|
import fastifyStatic from "@fastify/static";
|
|
7
5
|
import fastifyCookie from "@fastify/cookie";
|
|
8
6
|
import fastifyHelmet from "@fastify/helmet";
|
|
9
7
|
import fastifyMultipart from "@fastify/multipart";
|
|
8
|
+
import fastifyFormbody from "@fastify/formbody";
|
|
9
|
+
import fs from "node:fs";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
import path from "node:path";
|
|
10
12
|
import Paths from "../Core/Paths.js";
|
|
11
13
|
import Config from "../Core/Config.js";
|
|
12
14
|
import Environment from "../Core/Environment.js";
|
|
13
|
-
import
|
|
14
|
-
import View from "../View/
|
|
15
|
-
import Auth from "../Auth/
|
|
15
|
+
import Router from "../Route/Router.js";
|
|
16
|
+
import View from "../View/View.js";
|
|
17
|
+
import Auth from "../Auth/Auth.js";
|
|
16
18
|
import SessionManager from "../Session/Manager.js";
|
|
17
19
|
import DB from "../Database/DB.js";
|
|
18
|
-
import Log from "../Logging/
|
|
19
|
-
import Loader from "../Route/Loader.js";
|
|
20
|
+
import Log from "../Logging/Log.js";
|
|
20
21
|
import HMRServer from "../HMR/Server.js";
|
|
21
22
|
|
|
23
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
24
|
+
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* HTTP server manager built on Fastify.
|
|
28
|
+
* Handles server configuration, plugin registration, and lifecycle management.
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* import { Server } from "@nitronjs/framework";
|
|
32
|
+
* await Server.start();
|
|
33
|
+
*/
|
|
22
34
|
class Server {
|
|
23
35
|
static #server;
|
|
24
|
-
static #
|
|
36
|
+
static #config;
|
|
25
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Starts the HTTP server with all configured plugins and routes.
|
|
40
|
+
* @returns {Promise<void>}
|
|
41
|
+
*/
|
|
26
42
|
static async start() {
|
|
27
43
|
dotenv.config({ quiet: true });
|
|
28
|
-
|
|
29
|
-
// Initialize config system first (loads all configs once)
|
|
30
44
|
await Config.initialize();
|
|
31
|
-
|
|
32
|
-
// Load project configs dynamically
|
|
33
|
-
this.#serverConfigs = Config.all("server");
|
|
34
45
|
|
|
35
|
-
|
|
46
|
+
this.#config = Config.all("server");
|
|
47
|
+
const { web_server = {}, cors = {} } = this.#config;
|
|
36
48
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
await Route.setup(this.#server);
|
|
45
|
-
View.setup(this.#server);
|
|
46
|
-
await this.#listen();
|
|
47
|
-
}
|
|
49
|
+
const serverDefaults = {
|
|
50
|
+
bodyLimit: 50 * 1024 * 1024,
|
|
51
|
+
trustProxy: true,
|
|
52
|
+
maxParamLength: 150
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const webServerConfig = { ...serverDefaults, ...web_server };
|
|
48
56
|
|
|
49
|
-
|
|
57
|
+
// Create Fastify instance
|
|
50
58
|
this.#server = fastify({
|
|
51
59
|
logger: false,
|
|
52
|
-
bodyLimit:
|
|
53
|
-
trustProxy:
|
|
60
|
+
bodyLimit: webServerConfig.bodyLimit,
|
|
61
|
+
trustProxy: webServerConfig.trustProxy,
|
|
54
62
|
requestIdHeader: "x-request-id",
|
|
55
63
|
exposeHeadRoutes: false,
|
|
56
64
|
requestTimeout: 60000,
|
|
57
65
|
connectionTimeout: 120000,
|
|
58
66
|
routerOptions: {
|
|
59
67
|
ignoreTrailingSlash: true,
|
|
60
|
-
maxParamLength:
|
|
68
|
+
maxParamLength: webServerConfig.maxParamLength
|
|
61
69
|
}
|
|
62
70
|
});
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
this.#registerHooks();
|
|
66
|
-
this.#registerShutdownHandlers();
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
static #registerPlugins() {
|
|
72
|
+
// Register plugins
|
|
70
73
|
this.#server.register(fastifyCors, {
|
|
71
|
-
origin:
|
|
72
|
-
|
|
74
|
+
origin: (requestOrigin, cb) => {
|
|
75
|
+
if (!requestOrigin) {
|
|
76
|
+
return cb(null, true);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const origin = cors.origin;
|
|
80
|
+
|
|
81
|
+
if (origin === "*") {
|
|
82
|
+
return cb(null, true);
|
|
83
|
+
}
|
|
84
|
+
else if (origin === "auto") {
|
|
85
|
+
const url = process.env.APP_URL;
|
|
86
|
+
const port = process.env.APP_PORT;
|
|
87
|
+
|
|
88
|
+
if (!url) {
|
|
89
|
+
return cb(null, false);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const appOrigin = !port || port === "80" || port === "443" ? url : `${url}:${port}`;
|
|
93
|
+
|
|
94
|
+
return cb(null, requestOrigin === appOrigin);
|
|
95
|
+
}
|
|
96
|
+
else if (typeof origin === "string") {
|
|
97
|
+
return cb(null, requestOrigin === origin);
|
|
98
|
+
}
|
|
99
|
+
else if (Array.isArray(origin)) {
|
|
100
|
+
return cb(null, origin.includes(requestOrigin));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return cb(null, false);
|
|
104
|
+
},
|
|
105
|
+
credentials: cors.origin === "*" ? false : cors.credentials === true
|
|
73
106
|
});
|
|
74
107
|
|
|
75
108
|
this.#server.register(fastifyStatic, {
|
|
@@ -100,108 +133,90 @@ class Server {
|
|
|
100
133
|
xssFilter: true
|
|
101
134
|
});
|
|
102
135
|
|
|
136
|
+
const multipartDefaults = {
|
|
137
|
+
fieldNameSize: 100,
|
|
138
|
+
fieldSize: 2 * 1024 * 1024,
|
|
139
|
+
maxFields: 50,
|
|
140
|
+
maxFileSize: 20 * 1024 * 1024,
|
|
141
|
+
maxFiles: 10,
|
|
142
|
+
maxParts: 60,
|
|
143
|
+
attachFieldsToBody: true
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const multipartConfig = { ...multipartDefaults, ...web_server.multipart };
|
|
147
|
+
|
|
103
148
|
this.#server.register(fastifyMultipart, {
|
|
104
149
|
limits: {
|
|
105
|
-
fieldNameSize:
|
|
106
|
-
fieldSize:
|
|
107
|
-
fields:
|
|
108
|
-
fileSize:
|
|
109
|
-
files:
|
|
110
|
-
parts:
|
|
150
|
+
fieldNameSize: multipartConfig.fieldNameSize,
|
|
151
|
+
fieldSize: multipartConfig.fieldSize,
|
|
152
|
+
fields: multipartConfig.maxFields,
|
|
153
|
+
fileSize: multipartConfig.maxFileSize,
|
|
154
|
+
files: multipartConfig.maxFiles,
|
|
155
|
+
parts: multipartConfig.maxParts
|
|
111
156
|
},
|
|
112
|
-
attachFieldsToBody:
|
|
157
|
+
attachFieldsToBody: multipartConfig.attachFieldsToBody
|
|
113
158
|
});
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
static #getCorsOrigin(requestOrigin, cb) {
|
|
117
|
-
if (!requestOrigin) return cb(null, true);
|
|
118
|
-
|
|
119
|
-
const { origin } = this.#serverConfigs.cors;
|
|
120
|
-
|
|
121
|
-
if (origin === "*") return cb(null, true);
|
|
122
|
-
|
|
123
|
-
if (origin === "auto") {
|
|
124
|
-
const url = process.env.APP_URL;
|
|
125
|
-
const port = process.env.APP_PORT;
|
|
126
|
-
if (!url) return cb(null, false);
|
|
127
159
|
|
|
128
|
-
|
|
129
|
-
return cb(null, requestOrigin === appOrigin);
|
|
130
|
-
}
|
|
160
|
+
this.#server.register(fastifyFormbody);
|
|
131
161
|
|
|
132
|
-
|
|
133
|
-
|
|
162
|
+
// Register hooks
|
|
163
|
+
this.#server.addHook("preHandler", async (req) => {
|
|
164
|
+
if (!req.isMultipart() || !req.body) {
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
134
167
|
|
|
135
|
-
|
|
136
|
-
|
|
168
|
+
const normalized = {};
|
|
169
|
+
const files = {};
|
|
137
170
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
171
|
+
for (const [key, field] of Object.entries(req.body)) {
|
|
172
|
+
if (field?.toBuffer) {
|
|
173
|
+
if (field.filename) {
|
|
174
|
+
files[key] = field;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (field && typeof field === "object" && "value" in field) {
|
|
178
|
+
normalized[key] = field.value;
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
normalized[key] = field;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
142
184
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return data;
|
|
146
|
-
}
|
|
185
|
+
const data = { ...normalized, ...files };
|
|
186
|
+
const hasBrackets = Object.keys(data).some(key => key.includes("["));
|
|
147
187
|
|
|
148
|
-
|
|
188
|
+
if (!hasBrackets) {
|
|
189
|
+
req.body = data;
|
|
149
190
|
|
|
150
|
-
|
|
151
|
-
if (!key.includes("[")) {
|
|
152
|
-
result[key] = value;
|
|
153
|
-
continue;
|
|
191
|
+
return;
|
|
154
192
|
}
|
|
155
193
|
|
|
156
|
-
const
|
|
157
|
-
let current = result;
|
|
158
|
-
|
|
159
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
160
|
-
const part = parts[i];
|
|
161
|
-
const nextPart = parts[i + 1];
|
|
162
|
-
const isNextIndex = /^\d+$/.test(nextPart);
|
|
194
|
+
const result = {};
|
|
163
195
|
|
|
164
|
-
|
|
165
|
-
|
|
196
|
+
for (const [key, value] of Object.entries(data)) {
|
|
197
|
+
if (!key.includes("[")) {
|
|
198
|
+
result[key] = value;
|
|
199
|
+
continue;
|
|
166
200
|
}
|
|
167
201
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
const lastPart = parts[parts.length - 1];
|
|
172
|
-
current[lastPart] = value;
|
|
173
|
-
}
|
|
202
|
+
const parts = key.replace(/\]/g, "").split("[");
|
|
203
|
+
let current = result;
|
|
174
204
|
|
|
175
|
-
|
|
176
|
-
|
|
205
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
206
|
+
const part = parts[i];
|
|
207
|
+
const isNextIndex = /^\d+$/.test(parts[i + 1]);
|
|
177
208
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (req.isMultipart() && req.body) {
|
|
181
|
-
const normalized = {};
|
|
182
|
-
const files = {};
|
|
183
|
-
|
|
184
|
-
for (const [key, field] of Object.entries(req.body)) {
|
|
185
|
-
if (field && typeof field === 'object') {
|
|
186
|
-
if (field.toBuffer) {
|
|
187
|
-
if (!field.filename) continue;
|
|
188
|
-
|
|
189
|
-
files[key] = field;
|
|
190
|
-
}
|
|
191
|
-
else if ('value' in field) {
|
|
192
|
-
normalized[key] = field.value;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
normalized[key] = field;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
199
|
-
normalized[key] = field;
|
|
209
|
+
if (current[part] === undefined) {
|
|
210
|
+
current[part] = isNextIndex ? [] : {};
|
|
200
211
|
}
|
|
212
|
+
|
|
213
|
+
current = current[part];
|
|
201
214
|
}
|
|
202
215
|
|
|
203
|
-
|
|
216
|
+
current[parts[parts.length - 1]] = value;
|
|
204
217
|
}
|
|
218
|
+
|
|
219
|
+
req.body = result;
|
|
205
220
|
});
|
|
206
221
|
|
|
207
222
|
this.#server.addHook("onResponse", async (req, res) => {
|
|
@@ -217,26 +232,51 @@ class Server {
|
|
|
217
232
|
requestId: req.id
|
|
218
233
|
});
|
|
219
234
|
});
|
|
220
|
-
}
|
|
221
235
|
|
|
222
|
-
|
|
236
|
+
// Register shutdown handlers
|
|
223
237
|
const shutdown = async () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
238
|
+
console.log();
|
|
239
|
+
console.log("\x1b[33m⏻ Server shutting down...\x1b[0m");
|
|
240
|
+
console.log("\x1b[32m✓ Server stopped gracefully\x1b[0m");
|
|
241
|
+
|
|
242
|
+
try {
|
|
243
|
+
(await SessionManager.getInstance()).stopGC();
|
|
244
|
+
await Promise.race([
|
|
245
|
+
DB.close(),
|
|
246
|
+
new Promise(resolve => setTimeout(resolve, 1000))
|
|
247
|
+
]);
|
|
248
|
+
await Promise.race([
|
|
249
|
+
this.#server.close(),
|
|
250
|
+
new Promise(resolve => setTimeout(resolve, 1000))
|
|
251
|
+
]);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
// Ignore errors during shutdown
|
|
255
|
+
}
|
|
229
256
|
};
|
|
230
257
|
|
|
231
258
|
process.on("SIGTERM", shutdown);
|
|
232
259
|
process.on("SIGINT", shutdown);
|
|
233
|
-
}
|
|
234
260
|
|
|
235
|
-
|
|
261
|
+
// Setup managers
|
|
262
|
+
await DB.setup();
|
|
263
|
+
await SessionManager.setup(this.#server);
|
|
264
|
+
Auth.setup(this.#server);
|
|
265
|
+
|
|
266
|
+
// Register HMR routes before Router (dev only)
|
|
267
|
+
if (Environment.isDev) {
|
|
268
|
+
HMRServer.registerRoutes(this.#server);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
await Router.setup(this.#server);
|
|
272
|
+
View.setup(this.#server);
|
|
273
|
+
|
|
274
|
+
// Listen
|
|
236
275
|
try {
|
|
237
276
|
const url = process.env.APP_URL ? new URL(process.env.APP_URL) : null;
|
|
238
|
-
const host = url
|
|
277
|
+
const host = url?.hostname || "127.0.0.1";
|
|
239
278
|
const port = Number(process.env.APP_PORT) || 3000;
|
|
279
|
+
|
|
240
280
|
const address = await this.#server.listen({ host, port });
|
|
241
281
|
|
|
242
282
|
if (Environment.isDev) {
|
|
@@ -244,36 +284,30 @@ class Server {
|
|
|
244
284
|
}
|
|
245
285
|
|
|
246
286
|
this.#printBanner({ success: true, address, host, port });
|
|
247
|
-
|
|
248
|
-
Log.info("Server started successfully!", {
|
|
249
|
-
address,
|
|
250
|
-
host,
|
|
251
|
-
port,
|
|
252
|
-
environment: Environment.isDev ? "development" : "production"
|
|
253
|
-
});
|
|
287
|
+
Log.info("Server started successfully!", { address, host, port, environment: Environment.isDev ? "development" : "production" });
|
|
254
288
|
}
|
|
255
289
|
catch (err) {
|
|
256
290
|
this.#printBanner({ success: false, error: err });
|
|
257
|
-
|
|
258
|
-
Log.fatal("Server startup failed", {
|
|
259
|
-
error: err.message,
|
|
260
|
-
code: err.code,
|
|
261
|
-
stack: err.stack
|
|
262
|
-
});
|
|
263
|
-
|
|
291
|
+
Log.fatal("Server startup failed", { error: err.message, code: err.code, stack: err.stack });
|
|
264
292
|
process.exit(1);
|
|
265
293
|
}
|
|
266
294
|
}
|
|
267
295
|
|
|
296
|
+
// Private Methods
|
|
268
297
|
static #printBanner({ success, address, host, port, error }) {
|
|
269
|
-
|
|
270
|
-
|
|
298
|
+
if (this.#config.log.channel !== "none" && this.#config.log.channel !== "file") {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
271
301
|
|
|
272
302
|
const color = success ? "\x1b[32m" : "\x1b[31m";
|
|
273
303
|
const reset = "\x1b[0m";
|
|
274
304
|
const bold = "\x1b[1m";
|
|
275
305
|
const boxWidth = 70;
|
|
306
|
+
const stripAnsi = str => str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
307
|
+
const pad = (content, length) => content + " ".repeat(Math.max(0, length - stripAnsi(content).length));
|
|
276
308
|
|
|
309
|
+
const version = `v${packageJson.version}`;
|
|
310
|
+
|
|
277
311
|
const logo = `
|
|
278
312
|
${color}███╗ ██╗██╗████████╗██████╗ ██████╗ ███╗ ██╗ ██╗███████╗
|
|
279
313
|
████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║ ██║██╔════╝
|
|
@@ -283,12 +317,6 @@ ${color}███╗ ██╗██╗████████╗████
|
|
|
283
317
|
╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚════╝ ╚══════╝${reset}
|
|
284
318
|
`;
|
|
285
319
|
|
|
286
|
-
const stripAnsi = str => str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
287
|
-
const pad = (content, length) => {
|
|
288
|
-
const visible = stripAnsi(content);
|
|
289
|
-
return content + " ".repeat(Math.max(0, length - visible.length));
|
|
290
|
-
};
|
|
291
|
-
|
|
292
320
|
const lines = success
|
|
293
321
|
? [
|
|
294
322
|
`${color}${bold}✓${reset} ${bold}Server started successfully!${reset}`,
|
|
@@ -296,7 +324,8 @@ ${color}███╗ ██╗██╗████████╗████
|
|
|
296
324
|
`${bold}Address:${reset} ${address}`,
|
|
297
325
|
`${bold}Host:${reset} ${host}`,
|
|
298
326
|
`${bold}Port:${reset} ${port}`,
|
|
299
|
-
`${bold}Mode:${reset} ${Environment.isDev ? "development" : "production"}
|
|
327
|
+
`${bold}Mode:${reset} ${Environment.isDev ? "development" : "production"}`,
|
|
328
|
+
`${bold}Version:${reset} ${version}`
|
|
300
329
|
]
|
|
301
330
|
: [
|
|
302
331
|
`${color}${bold}✕${reset} ${bold}Server failed to start${reset}`,
|
|
@@ -314,7 +343,6 @@ ${color}███╗ ██╗██╗████████╗████
|
|
|
314
343
|
|
|
315
344
|
console.log(`${color}│${reset} ${" ".repeat(boxWidth - 4)} ${color}│${reset}`);
|
|
316
345
|
console.log(`${color}└${"─".repeat(boxWidth - 2)}┘${reset}`);
|
|
317
|
-
console.log();
|
|
318
346
|
}
|
|
319
347
|
}
|
|
320
348
|
|