@nitronjs/framework 0.2.3 → 0.2.5
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 +14 -7
- 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 +17 -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 +142 -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 +81 -10
- 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 +94 -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
package/lib/Build/plugins.js
CHANGED
|
@@ -7,39 +7,71 @@ import COLORS from "./colors.js";
|
|
|
7
7
|
|
|
8
8
|
const _traverse = traverse.default;
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Creates an esbuild plugin for path alias resolution.
|
|
12
|
+
* Supports @/, @css/, @views/, @models/, @controllers/, @middlewares/ aliases.
|
|
13
|
+
* @returns {import("esbuild").Plugin}
|
|
14
|
+
*/
|
|
10
15
|
export function createPathAliasPlugin() {
|
|
11
16
|
const root = Paths.project;
|
|
17
|
+
|
|
18
|
+
// Define path aliases for developer convenience
|
|
19
|
+
const aliases = {
|
|
20
|
+
"@/": root, // Project root
|
|
21
|
+
"@css/": path.join(root, "resources/css"), // CSS files
|
|
22
|
+
"@views/": path.join(root, "resources/views"), // View components
|
|
23
|
+
"@models/": path.join(root, "app/Models"), // Models
|
|
24
|
+
"@controllers/": path.join(root, "app/Controllers"), // Controllers
|
|
25
|
+
"@middlewares/": path.join(root, "app/Middlewares"), // Middlewares
|
|
26
|
+
};
|
|
27
|
+
|
|
12
28
|
return {
|
|
13
29
|
name: "path-alias",
|
|
14
30
|
setup: (build) => {
|
|
15
|
-
//
|
|
16
|
-
build.onResolve({ filter:
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// Handle all @ prefixed imports
|
|
32
|
+
build.onResolve({ filter: /^@[a-z]*\// }, async (args) => {
|
|
33
|
+
// Find matching alias (longest match first)
|
|
34
|
+
const sortedPrefixes = Object.keys(aliases).sort((a, b) => b.length - a.length);
|
|
35
|
+
|
|
36
|
+
for (const prefix of sortedPrefixes) {
|
|
37
|
+
if (args.path.startsWith(prefix)) {
|
|
38
|
+
const relativePath = args.path.slice(prefix.length);
|
|
39
|
+
const absolutePath = path.join(aliases[prefix], relativePath);
|
|
40
|
+
const extensions = [".js", ".ts", ".jsx", ".tsx", ".css", ""];
|
|
41
|
+
|
|
42
|
+
for (const ext of extensions) {
|
|
43
|
+
const fullPath = absolutePath + ext;
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(fullPath)) {
|
|
46
|
+
return { path: fullPath, external: false };
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Check for directory with index file
|
|
51
|
+
if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
|
|
52
|
+
for (const ext of [".js", ".ts", ".jsx", ".tsx"]) {
|
|
53
|
+
const indexPath = path.join(absolutePath, "index" + ext);
|
|
54
|
+
|
|
55
|
+
if (fs.existsSync(indexPath)) {
|
|
56
|
+
return { path: indexPath, external: false };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
33
59
|
}
|
|
60
|
+
|
|
61
|
+
return { path: absolutePath, external: false };
|
|
34
62
|
}
|
|
35
63
|
}
|
|
36
|
-
|
|
37
|
-
return
|
|
64
|
+
|
|
65
|
+
return null;
|
|
38
66
|
});
|
|
39
67
|
}
|
|
40
68
|
};
|
|
41
69
|
}
|
|
42
70
|
|
|
71
|
+
/**
|
|
72
|
+
* Creates an esbuild plugin for original JSX runtime resolution.
|
|
73
|
+
* @returns {import("esbuild").Plugin}
|
|
74
|
+
*/
|
|
43
75
|
export function createOriginalJsxPlugin() {
|
|
44
76
|
return {
|
|
45
77
|
name: "original-jsx",
|
|
@@ -52,6 +84,11 @@ export function createOriginalJsxPlugin() {
|
|
|
52
84
|
};
|
|
53
85
|
}
|
|
54
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Creates an esbuild plugin that maps React packages to window globals.
|
|
89
|
+
* Used for client bundles to share React instance across islands.
|
|
90
|
+
* @returns {import("esbuild").Plugin}
|
|
91
|
+
*/
|
|
55
92
|
export function createVendorGlobalsPlugin() {
|
|
56
93
|
const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\\/]/g, "\\$&");
|
|
57
94
|
|
|
@@ -86,6 +123,11 @@ export function createVendorGlobalsPlugin() {
|
|
|
86
123
|
};
|
|
87
124
|
}
|
|
88
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Creates an esbuild plugin that blocks server-only modules in client bundles.
|
|
128
|
+
* Provides helpful error messages when server modules are imported incorrectly.
|
|
129
|
+
* @returns {import("esbuild").Plugin}
|
|
130
|
+
*/
|
|
89
131
|
export function createServerModuleBlockerPlugin() {
|
|
90
132
|
const SERVER_MODULES = /lib\/(DB|Mail|Log|Hash|Environment|Server|Model|Validator|Storage)\.js$/;
|
|
91
133
|
|
|
@@ -118,6 +160,11 @@ export function createServerModuleBlockerPlugin() {
|
|
|
118
160
|
};
|
|
119
161
|
}
|
|
120
162
|
|
|
163
|
+
/**
|
|
164
|
+
* Creates an esbuild plugin that transforms server functions for client use.
|
|
165
|
+
* Replaces csrf() and route() calls with runtime lookups.
|
|
166
|
+
* @returns {import("esbuild").Plugin}
|
|
167
|
+
*/
|
|
121
168
|
export function createServerFunctionsPlugin() {
|
|
122
169
|
return {
|
|
123
170
|
name: "server-functions",
|
|
@@ -152,6 +199,11 @@ export function createServerFunctionsPlugin() {
|
|
|
152
199
|
};
|
|
153
200
|
}
|
|
154
201
|
|
|
202
|
+
/**
|
|
203
|
+
* Creates an esbuild plugin that stubs CSS imports.
|
|
204
|
+
* Prevents CSS from being bundled into JS files.
|
|
205
|
+
* @returns {import("esbuild").Plugin}
|
|
206
|
+
*/
|
|
155
207
|
export function createCssStubPlugin() {
|
|
156
208
|
return {
|
|
157
209
|
name: "css-stub",
|
|
@@ -169,6 +221,13 @@ export function createCssStubPlugin() {
|
|
|
169
221
|
};
|
|
170
222
|
}
|
|
171
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Creates an esbuild plugin that marks client components with a symbol.
|
|
226
|
+
* Used for Islands Architecture to identify hydration boundaries.
|
|
227
|
+
* @param {import("esbuild").BuildOptions} options - Build options.
|
|
228
|
+
* @param {boolean} isDev - Whether in development mode.
|
|
229
|
+
* @returns {import("esbuild").Plugin}
|
|
230
|
+
*/
|
|
172
231
|
export function createMarkerPlugin(options, isDev) {
|
|
173
232
|
return {
|
|
174
233
|
name: "client-marker",
|
|
@@ -215,6 +274,11 @@ export function createMarkerPlugin(options, isDev) {
|
|
|
215
274
|
};
|
|
216
275
|
}
|
|
217
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Finds all exported functions/components in an AST.
|
|
279
|
+
* @param {import("@babel/parser").ParseResult} ast - Parsed AST.
|
|
280
|
+
* @returns {Array<{name: string}>} Array of export names.
|
|
281
|
+
*/
|
|
218
282
|
function findExports(ast) {
|
|
219
283
|
const exports = [];
|
|
220
284
|
|
|
@@ -11,14 +11,16 @@ import Builder from "../../Build/Manager.js";
|
|
|
11
11
|
dotenv.config({ quiet: true });
|
|
12
12
|
Environment.setDev(true);
|
|
13
13
|
|
|
14
|
-
const C = { r: "\x1b[0m", d: "\x1b[2m", red: "\x1b[31m", g: "\x1b[32m", y: "\x1b[33m", b: "\x1b[34m", m: "\x1b[35m", c: "\x1b[36m" };
|
|
14
|
+
const C = { r: "\x1b[0m", d: "\x1b[2m", bold: "\x1b[1m", red: "\x1b[31m", g: "\x1b[32m", y: "\x1b[33m", b: "\x1b[34m", m: "\x1b[35m", c: "\x1b[36m" };
|
|
15
|
+
|
|
16
|
+
// Using consistent-width characters for alignment
|
|
15
17
|
const ICONS = {
|
|
16
|
-
info: `${C.b}
|
|
17
|
-
ok: `${C.g}✓${C.r}
|
|
18
|
-
err: `${C.red}✗${C.r}
|
|
19
|
-
build: `${C.
|
|
20
|
-
hmr: `${C.
|
|
21
|
-
watch: `${C.
|
|
18
|
+
info: `${C.b}i${C.r}`,
|
|
19
|
+
ok: `${C.g}✓${C.r}`,
|
|
20
|
+
err: `${C.red}✗${C.r}`,
|
|
21
|
+
build: `${C.y}○${C.r}`,
|
|
22
|
+
hmr: `${C.m}●${C.r}`,
|
|
23
|
+
watch: `${C.c}◐${C.r}`
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const PATTERNS = {
|
|
@@ -35,8 +37,10 @@ class DevServer {
|
|
|
35
37
|
#debounce = { build: null, restart: null };
|
|
36
38
|
|
|
37
39
|
#log(icon, msg, extra) {
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
+
const time = new Date().toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
41
|
+
const iconStr = ICONS[icon] || ICONS.info;
|
|
42
|
+
const extraStr = extra ? ` ${C.d}(${extra})${C.r}` : "";
|
|
43
|
+
console.log(`${C.d}${time}${C.r} ${iconStr} ${msg}${extraStr}`);
|
|
40
44
|
}
|
|
41
45
|
|
|
42
46
|
async #build(only = null) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import fs from
|
|
3
|
-
import path from
|
|
4
|
-
import Paths from
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import Paths from "../../Core/Paths.js";
|
|
5
|
+
import Output from "../Output.js";
|
|
5
6
|
|
|
6
7
|
const baseDirs = {
|
|
7
8
|
controller: 'app/Controllers',
|
|
@@ -47,22 +48,30 @@ function toPascalCase(str) {
|
|
|
47
48
|
.join('');
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* Creates a new file from a template.
|
|
53
|
+
* @param {string} type - The type of file (controller, model, etc.).
|
|
54
|
+
* @param {string} rawName - The name/path of the file to create.
|
|
55
|
+
* @returns {Promise<boolean>}
|
|
56
|
+
*/
|
|
50
57
|
export default async function make(type, rawName) {
|
|
51
58
|
if (!type || !rawName) {
|
|
52
|
-
|
|
59
|
+
Output.error("Usage: njs make:<type> <Name/Path>");
|
|
60
|
+
Output.dim(" Example: njs make:controller Admin/HomeController");
|
|
61
|
+
|
|
53
62
|
return false;
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
const parts = rawName.split(
|
|
57
|
-
const subDirs = parts.slice(0, -1).join(
|
|
65
|
+
const parts = rawName.split("/");
|
|
66
|
+
const subDirs = parts.slice(0, -1).join("/");
|
|
58
67
|
const outputDir = path.join(Paths.project, baseDirs[type]);
|
|
59
68
|
|
|
60
69
|
let className = parts[parts.length - 1];
|
|
61
70
|
let fileName = `${className}.js`;
|
|
62
71
|
|
|
63
|
-
if (type ===
|
|
72
|
+
if (type === "migration") {
|
|
64
73
|
const now = new Date();
|
|
65
|
-
const pad = (n) => n.toString().padStart(2,
|
|
74
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
66
75
|
|
|
67
76
|
const year = now.getFullYear();
|
|
68
77
|
const month = pad(now.getMonth() + 1);
|
|
@@ -78,7 +87,7 @@ export default async function make(type, rawName) {
|
|
|
78
87
|
const fullPath = path.join(fullDir, fileName);
|
|
79
88
|
|
|
80
89
|
const templatePath = path.join(Paths.frameworkTemplates, templates[type]);
|
|
81
|
-
let template = fs.readFileSync(templatePath,
|
|
90
|
+
let template = fs.readFileSync(templatePath, "utf-8");
|
|
82
91
|
|
|
83
92
|
if (type === "model") {
|
|
84
93
|
template = template.replace(/__CLASS__/g, className).replace(/__TABLE__/g, toTableName(className));
|
|
@@ -93,7 +102,12 @@ export default async function make(type, rawName) {
|
|
|
93
102
|
fs.mkdirSync(fullDir, { recursive: true });
|
|
94
103
|
fs.writeFileSync(fullPath, template);
|
|
95
104
|
|
|
96
|
-
|
|
105
|
+
const typeLabel = type.charAt(0).toUpperCase() + type.slice(1);
|
|
106
|
+
const relativePath = path.relative(Paths.project, fullPath);
|
|
107
|
+
|
|
108
|
+
Output.success(`${typeLabel} created`);
|
|
109
|
+
Output.dim(` ${relativePath}`);
|
|
110
|
+
|
|
97
111
|
return true;
|
|
98
112
|
}
|
|
99
113
|
|
|
@@ -2,16 +2,9 @@ import dotenv from 'dotenv';
|
|
|
2
2
|
import DB from '../../Database/DB.js';
|
|
3
3
|
import Config from '../../Core/Config.js';
|
|
4
4
|
import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
|
|
5
|
+
import Output from '../../Console/Output.js';
|
|
5
6
|
import seed from './SeedCommand.js';
|
|
6
7
|
|
|
7
|
-
const COLORS = {
|
|
8
|
-
reset: '\x1b[0m',
|
|
9
|
-
green: '\x1b[32m',
|
|
10
|
-
yellow: '\x1b[33m',
|
|
11
|
-
cyan: '\x1b[36m',
|
|
12
|
-
dim: '\x1b[2m'
|
|
13
|
-
};
|
|
14
|
-
|
|
15
8
|
export default async function migrateFresh(options = {}) {
|
|
16
9
|
const { seed: shouldSeed = false } = options;
|
|
17
10
|
|
|
@@ -22,27 +15,27 @@ export default async function migrateFresh(options = {}) {
|
|
|
22
15
|
await DB.setup();
|
|
23
16
|
}
|
|
24
17
|
catch (error) {
|
|
25
|
-
|
|
26
|
-
|
|
18
|
+
Output.newline();
|
|
19
|
+
Output.error(`Database setup failed: ${error.message}`);
|
|
20
|
+
Output.errorDetail('Check your .env file and ensure the database exists and is accessible');
|
|
27
21
|
return false;
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
try {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for (const row of tableRows) {
|
|
37
|
-
const tableName = Object.values(row)[0];
|
|
38
|
-
console.log(`${COLORS.dim}Dropping:${COLORS.reset} ${COLORS.cyan}${tableName}${COLORS.reset}`);
|
|
39
|
-
await DB.rawQuery(`DROP TABLE IF EXISTS \`${tableName}\``);
|
|
40
|
-
}
|
|
25
|
+
const [tableRows] = await DB.rawQuery("SHOW TABLES");
|
|
26
|
+
|
|
27
|
+
if (tableRows.length > 0) {
|
|
28
|
+
Output.dropTablesHeader();
|
|
29
|
+
await DB.rawQuery("SET FOREIGN_KEY_CHECKS = 0");
|
|
41
30
|
|
|
42
|
-
|
|
31
|
+
for (const row of tableRows) {
|
|
32
|
+
const tableName = Object.values(row)[0];
|
|
33
|
+
Output.droppingTable(tableName);
|
|
34
|
+
await DB.rawQuery(`DROP TABLE IF EXISTS \`${tableName}\``);
|
|
35
|
+
}
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
|
|
37
|
+
await DB.rawQuery("SET FOREIGN_KEY_CHECKS = 1");
|
|
38
|
+
Output.dropTablesSuccess(tableRows.length);
|
|
46
39
|
}
|
|
47
40
|
|
|
48
41
|
const result = await MigrationRunner.run();
|
|
@@ -53,7 +46,6 @@ export default async function migrateFresh(options = {}) {
|
|
|
53
46
|
}
|
|
54
47
|
|
|
55
48
|
if (shouldSeed && result.ran.length > 0) {
|
|
56
|
-
console.log('\n');
|
|
57
49
|
await seed();
|
|
58
50
|
}
|
|
59
51
|
|
|
@@ -62,7 +54,7 @@ export default async function migrateFresh(options = {}) {
|
|
|
62
54
|
|
|
63
55
|
}
|
|
64
56
|
catch (error) {
|
|
65
|
-
|
|
57
|
+
Output.error(`Fresh migration error: ${error.message}`);
|
|
66
58
|
await DB.close();
|
|
67
59
|
return false;
|
|
68
60
|
}
|
|
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
|
|
|
2
2
|
import DB from '../../Database/DB.js';
|
|
3
3
|
import Config from '../../Core/Config.js';
|
|
4
4
|
import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
|
|
5
|
+
import Output from '../../Console/Output.js';
|
|
5
6
|
|
|
6
7
|
export default async function rollback(options = {}) {
|
|
7
8
|
const { step = 1, all = false } = options;
|
|
@@ -13,8 +14,9 @@ export default async function rollback(options = {}) {
|
|
|
13
14
|
await DB.setup();
|
|
14
15
|
}
|
|
15
16
|
catch (error) {
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
Output.newline();
|
|
18
|
+
Output.error(`Database setup failed: ${error.message}`);
|
|
19
|
+
Output.errorDetail('Check your .env file and ensure the database exists and is accessible');
|
|
18
20
|
return false;
|
|
19
21
|
}
|
|
20
22
|
|
|
@@ -31,7 +33,8 @@ export default async function rollback(options = {}) {
|
|
|
31
33
|
return result.success;
|
|
32
34
|
|
|
33
35
|
} catch (error) {
|
|
34
|
-
|
|
36
|
+
Output.newline();
|
|
37
|
+
Output.error(`Rollback error: ${error.message}`);
|
|
35
38
|
await DB.close();
|
|
36
39
|
return false;
|
|
37
40
|
}
|
|
@@ -2,6 +2,7 @@ import dotenv from 'dotenv';
|
|
|
2
2
|
import DB from '../../Database/DB.js';
|
|
3
3
|
import Config from '../../Core/Config.js';
|
|
4
4
|
import MigrationRunner from '../../Database/Migration/MigrationRunner.js';
|
|
5
|
+
import Output from '../../Console/Output.js';
|
|
5
6
|
|
|
6
7
|
export default async function status() {
|
|
7
8
|
dotenv.config({ quiet: true });
|
|
@@ -11,8 +12,9 @@ export default async function status() {
|
|
|
11
12
|
await DB.setup();
|
|
12
13
|
}
|
|
13
14
|
catch (error) {
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
Output.newline();
|
|
16
|
+
Output.error(`Database setup failed: ${error.message}`);
|
|
17
|
+
Output.errorDetail('Check your .env file and ensure the database exists and is accessible');
|
|
16
18
|
return false;
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -22,7 +24,8 @@ export default async function status() {
|
|
|
22
24
|
return true;
|
|
23
25
|
|
|
24
26
|
} catch (error) {
|
|
25
|
-
|
|
27
|
+
Output.newline();
|
|
28
|
+
Output.error(`Status error: ${error.message}`);
|
|
26
29
|
await DB.close();
|
|
27
30
|
return false;
|
|
28
31
|
}
|
|
@@ -9,7 +9,8 @@ export default async function seed(seederName = null) {
|
|
|
9
9
|
|
|
10
10
|
try {
|
|
11
11
|
await DB.setup();
|
|
12
|
-
}
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
13
14
|
console.error('❌ Database setup failed:', error.message);
|
|
14
15
|
console.error('Check your .env file and ensure the database exists and is accessible');
|
|
15
16
|
return false;
|
|
@@ -20,7 +21,8 @@ export default async function seed(seederName = null) {
|
|
|
20
21
|
await DB.close();
|
|
21
22
|
return result.success;
|
|
22
23
|
|
|
23
|
-
}
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
24
26
|
console.error('❌ Seeding error:', error.message);
|
|
25
27
|
await DB.close();
|
|
26
28
|
return false;
|
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
|
+
import Output from "../Output.js";
|
|
4
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Creates a symbolic link from storage/app/public to public/storage.
|
|
8
|
+
* @returns {Promise<boolean>}
|
|
9
|
+
*/
|
|
5
10
|
export default async function storageLink() {
|
|
6
11
|
const source = path.join(process.cwd(), "storage", "app", "public");
|
|
7
12
|
const target = path.join(process.cwd(), "public", "storage");
|
|
8
|
-
|
|
9
|
-
let linkType = os.platform() === 'win32' ? 'junction' : 'dir';
|
|
13
|
+
const linkType = os.platform() === "win32" ? "junction" : "dir";
|
|
10
14
|
|
|
11
15
|
return new Promise((resolve, reject) => {
|
|
12
16
|
fs.symlink(source, target, linkType, (err) => {
|
|
13
17
|
if (err) {
|
|
14
|
-
|
|
15
|
-
|
|
18
|
+
if (err.code === "EEXIST") {
|
|
19
|
+
Output.warn("Symbolic link already exists");
|
|
20
|
+
Output.dim(` ${source} → ${target}`);
|
|
21
|
+
Output.newline();
|
|
22
|
+
resolve(true);
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
Output.error("Failed to create symbolic link");
|
|
26
|
+
Output.dim(` ${err.message}`);
|
|
27
|
+
reject(err);
|
|
28
|
+
}
|
|
16
29
|
}
|
|
17
30
|
else {
|
|
18
|
-
|
|
31
|
+
Output.success("Symbolic link created");
|
|
32
|
+
Output.dim(` ${source} → ${target}`);
|
|
33
|
+
Output.newline();
|
|
19
34
|
resolve(true);
|
|
20
35
|
}
|
|
21
36
|
});
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Console output utilities for CLI commands.
|
|
3
|
+
* Provides consistent, professional styling for terminal messages.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const C = {
|
|
7
|
+
reset: "\x1b[0m",
|
|
8
|
+
bold: "\x1b[1m",
|
|
9
|
+
dim: "\x1b[2m",
|
|
10
|
+
red: "\x1b[31m",
|
|
11
|
+
green: "\x1b[32m",
|
|
12
|
+
yellow: "\x1b[33m",
|
|
13
|
+
blue: "\x1b[34m",
|
|
14
|
+
magenta: "\x1b[35m",
|
|
15
|
+
cyan: "\x1b[36m",
|
|
16
|
+
white: "\x1b[37m",
|
|
17
|
+
gray: "\x1b[90m"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const I = {
|
|
21
|
+
success: "✔",
|
|
22
|
+
error: "✖",
|
|
23
|
+
warn: "⚠",
|
|
24
|
+
info: "◆",
|
|
25
|
+
pending: "◇",
|
|
26
|
+
arrow: "→",
|
|
27
|
+
bullet: "•",
|
|
28
|
+
sparkle: "✨",
|
|
29
|
+
database: "🗃️",
|
|
30
|
+
seed: "🌱"
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Common patterns
|
|
34
|
+
const LINE = `${C.gray}${"-".repeat(45)}${C.reset}`;
|
|
35
|
+
const DOTS = `${C.gray}···${C.reset}`;
|
|
36
|
+
const log = console.log.bind(console);
|
|
37
|
+
|
|
38
|
+
// Helper: prints section header
|
|
39
|
+
const header = (icon, color, title, subtitle = "") => {
|
|
40
|
+
log(` ${color}${icon}${C.reset} ${C.bold}${title}${C.reset}${subtitle ? ` ${C.gray}${subtitle}${C.reset}` : ""}`);
|
|
41
|
+
log(` ${LINE}`);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
// Helper: prints section footer with success
|
|
45
|
+
const footer = (message) => {
|
|
46
|
+
log(` ${LINE}`);
|
|
47
|
+
log(` ${C.green}${I.sparkle}${C.reset} ${C.green}${C.bold}${message}${C.reset}`);
|
|
48
|
+
log();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Helper: prints step row
|
|
52
|
+
const step = (icon, color, action, target, suffix = "") => {
|
|
53
|
+
log(` ${color}${icon}${C.reset} ${action} ${DOTS} ${target}${suffix}`);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Basic messages
|
|
57
|
+
const success = (msg) => log(`${C.green}${I.success}${C.reset} ${msg}`);
|
|
58
|
+
const error = (msg) => log(`${C.red}${I.error}${C.reset} ${msg}`);
|
|
59
|
+
const warn = (msg) => log(`${C.yellow}${I.warn}${C.reset} ${msg}`);
|
|
60
|
+
const info = (msg) => log(`${C.blue}${I.info}${C.reset} ${msg}`);
|
|
61
|
+
const dim = (msg) => log(`${C.dim}${msg}${C.reset}`);
|
|
62
|
+
const newline = () => log();
|
|
63
|
+
const errorDetail = (msg) => log(` ${C.dim}${msg}${C.reset}`);
|
|
64
|
+
|
|
65
|
+
// Task steps
|
|
66
|
+
const done = (action, target) => step(I.success, C.green, action, `${C.white}${target}${C.reset}`);
|
|
67
|
+
const pending = (action, target) => step(I.pending, C.gray, `${C.dim}${action}${C.reset}`, `${C.gray}${target}${C.reset}`);
|
|
68
|
+
|
|
69
|
+
// Migration
|
|
70
|
+
const migrationHeader = (batch) => header(I.database, C.magenta, "Running Migrations", `(batch ${batch})`);
|
|
71
|
+
const migrationSuccess = () => footer("All migrations completed successfully");
|
|
72
|
+
|
|
73
|
+
const frameworkMigration = (action, file) => {
|
|
74
|
+
const tag = `${C.magenta}[framework]${C.reset}`;
|
|
75
|
+
if (action === 'pending') {
|
|
76
|
+
step(I.pending, C.gray, `${C.dim}Migrating${C.reset}`, `${tag} ${C.gray}${file}${C.reset}`);
|
|
77
|
+
} else {
|
|
78
|
+
step(I.success, C.green, "Migrated ", `${tag} ${C.white}${file}${C.reset}`);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Seeder
|
|
83
|
+
const seederHeader = () => header(I.seed, C.cyan, "Running Seeders");
|
|
84
|
+
const seederSuccess = () => footer("All seeders completed successfully");
|
|
85
|
+
|
|
86
|
+
// Drop tables
|
|
87
|
+
const dropTablesHeader = () => header(I.warn, C.yellow, "Dropping Tables");
|
|
88
|
+
const droppingTable = (name) => step(I.pending, C.yellow, `${C.dim}Dropping${C.reset}`, `${C.yellow}${name}${C.reset}`);
|
|
89
|
+
const dropTablesSuccess = (count) => {
|
|
90
|
+
log(` ${LINE}`);
|
|
91
|
+
log(` ${C.green}${I.success}${C.reset} ${C.dim}Dropped ${count} table(s)${C.reset}`);
|
|
92
|
+
log();
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Rollback
|
|
96
|
+
const rollbackHeader = (count) => header(I.arrow, C.yellow, "Rolling Back", `(${count} migration${count > 1 ? 's' : ''})`);
|
|
97
|
+
const rollbackDone = (file, batch) => step(I.success, C.yellow, "Rolled back", `${C.white}${file}${C.reset}`, ` ${C.gray}(batch ${batch})${C.reset}`);
|
|
98
|
+
const rollbackSuccess = () => footer("Rollback completed successfully");
|
|
99
|
+
|
|
100
|
+
// Status
|
|
101
|
+
const statusHeader = () => header(I.info, C.blue, "Migration Status");
|
|
102
|
+
const statusRow = (status, name, batch) => {
|
|
103
|
+
if (status === 'Ran') {
|
|
104
|
+
step(I.success, C.green, `${C.green}Ran${C.reset} `, `${C.white}${name}${C.reset}`, ` ${C.gray}(batch ${batch})${C.reset}`);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
step(I.pending, C.yellow, `${C.yellow}Pending${C.reset}`, `${C.gray}${name}${C.reset}`);
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const statusFooter = (total, ran, pend) => {
|
|
111
|
+
log(` ${LINE}`);
|
|
112
|
+
log(` ${C.dim}Total: ${total} | Ran: ${ran} | Pending: ${pend}${C.reset}`);
|
|
113
|
+
log();
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export default {
|
|
117
|
+
COLORS: C,
|
|
118
|
+
ICONS: I,
|
|
119
|
+
success,
|
|
120
|
+
error,
|
|
121
|
+
warn,
|
|
122
|
+
info,
|
|
123
|
+
dim,
|
|
124
|
+
newline,
|
|
125
|
+
errorDetail,
|
|
126
|
+
done,
|
|
127
|
+
pending,
|
|
128
|
+
migrationHeader,
|
|
129
|
+
migrationSuccess,
|
|
130
|
+
frameworkMigration,
|
|
131
|
+
seederHeader,
|
|
132
|
+
seederSuccess,
|
|
133
|
+
dropTablesHeader,
|
|
134
|
+
droppingTable,
|
|
135
|
+
dropTablesSuccess,
|
|
136
|
+
rollbackHeader,
|
|
137
|
+
rollbackDone,
|
|
138
|
+
rollbackSuccess,
|
|
139
|
+
statusHeader,
|
|
140
|
+
statusRow,
|
|
141
|
+
statusFooter
|
|
142
|
+
};
|
package/lib/Core/Config.js
CHANGED
package/lib/Core/Paths.js
CHANGED
|
@@ -4,6 +4,14 @@ import { pathToFileURL } from "url";
|
|
|
4
4
|
|
|
5
5
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
6
|
|
|
7
|
+
/**
|
|
8
|
+
* Path manager providing consistent access to framework and project directories.
|
|
9
|
+
* All paths are resolved relative to the project root (process.cwd()).
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* Paths.controllers // /project/app/Controllers
|
|
13
|
+
* Paths.views // /project/resources/views
|
|
14
|
+
*/
|
|
7
15
|
class Paths {
|
|
8
16
|
static #framework = path.resolve(__dirname, "../..");
|
|
9
17
|
static #project = process.cwd();
|