@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.
Files changed (68) hide show
  1. package/README.md +3 -1
  2. package/cli/create.js +88 -72
  3. package/cli/njs.js +14 -7
  4. package/lib/Auth/Auth.js +167 -0
  5. package/lib/Build/CssBuilder.js +9 -0
  6. package/lib/Build/FileAnalyzer.js +16 -0
  7. package/lib/Build/HydrationBuilder.js +17 -0
  8. package/lib/Build/Manager.js +15 -0
  9. package/lib/Build/colors.js +4 -0
  10. package/lib/Build/plugins.js +84 -20
  11. package/lib/Console/Commands/DevCommand.js +13 -9
  12. package/lib/Console/Commands/MakeCommand.js +24 -10
  13. package/lib/Console/Commands/MigrateCommand.js +0 -1
  14. package/lib/Console/Commands/MigrateFreshCommand.js +17 -25
  15. package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
  16. package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
  17. package/lib/Console/Commands/SeedCommand.js +4 -2
  18. package/lib/Console/Commands/StorageLinkCommand.js +20 -5
  19. package/lib/Console/Output.js +142 -0
  20. package/lib/Core/Config.js +2 -1
  21. package/lib/Core/Paths.js +8 -0
  22. package/lib/Database/DB.js +141 -51
  23. package/lib/Database/Drivers/MySQLDriver.js +102 -157
  24. package/lib/Database/Migration/Checksum.js +3 -8
  25. package/lib/Database/Migration/MigrationRepository.js +25 -35
  26. package/lib/Database/Migration/MigrationRunner.js +56 -61
  27. package/lib/Database/Model.js +157 -83
  28. package/lib/Database/QueryBuilder.js +31 -0
  29. package/lib/Database/QueryValidation.js +36 -44
  30. package/lib/Database/Schema/Blueprint.js +25 -36
  31. package/lib/Database/Schema/Manager.js +31 -68
  32. package/lib/Database/Seeder/SeederRunner.js +12 -31
  33. package/lib/Date/DateTime.js +9 -0
  34. package/lib/Encryption/Encryption.js +52 -0
  35. package/lib/Faker/Faker.js +11 -0
  36. package/lib/Filesystem/Storage.js +120 -0
  37. package/lib/HMR/Server.js +81 -10
  38. package/lib/Hashing/Hash.js +41 -0
  39. package/lib/Http/Server.js +177 -152
  40. package/lib/Logging/{Manager.js → Log.js} +68 -80
  41. package/lib/Mail/Mail.js +187 -0
  42. package/lib/Route/Router.js +416 -0
  43. package/lib/Session/File.js +135 -233
  44. package/lib/Session/Manager.js +117 -171
  45. package/lib/Session/Memory.js +28 -38
  46. package/lib/Session/Session.js +71 -107
  47. package/lib/Support/Str.js +103 -0
  48. package/lib/Translation/Lang.js +54 -0
  49. package/lib/View/Client/hmr-client.js +94 -51
  50. package/lib/View/Client/nitronjs-icon.png +0 -0
  51. package/lib/View/{Manager.js → View.js} +44 -29
  52. package/lib/index.d.ts +42 -8
  53. package/lib/index.js +19 -12
  54. package/package.json +1 -1
  55. package/skeleton/app/Controllers/HomeController.js +7 -1
  56. package/skeleton/resources/css/global.css +1 -0
  57. package/skeleton/resources/views/Site/Home.tsx +456 -79
  58. package/skeleton/tsconfig.json +6 -1
  59. package/lib/Auth/Manager.js +0 -111
  60. package/lib/Database/Connection.js +0 -61
  61. package/lib/Database/Manager.js +0 -162
  62. package/lib/Encryption/Manager.js +0 -47
  63. package/lib/Filesystem/Manager.js +0 -74
  64. package/lib/Hashing/Manager.js +0 -25
  65. package/lib/Mail/Manager.js +0 -120
  66. package/lib/Route/Loader.js +0 -80
  67. package/lib/Route/Manager.js +0 -286
  68. package/lib/Translation/Manager.js +0 -49
@@ -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
- // @ resolves to project root
16
- build.onResolve({ filter: /^@\// }, async (args) => {
17
- const relativePath = args.path.replace(/^@\//, "");
18
- const absolutePath = path.join(root, relativePath);
19
-
20
- const extensions = [".js", ".ts", ".jsx", ".tsx", ""];
21
- for (const ext of extensions) {
22
- const fullPath = absolutePath + ext;
23
- if (fs.existsSync(fullPath)) {
24
- return { path: fullPath, external: false };
25
- }
26
- }
27
-
28
- if (fs.existsSync(absolutePath) && fs.statSync(absolutePath).isDirectory()) {
29
- for (const ext of [".js", ".ts", ".jsx", ".tsx"]) {
30
- const indexPath = path.join(absolutePath, "index" + ext);
31
- if (fs.existsSync(indexPath)) {
32
- return { path: indexPath, external: false };
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 { path: absolutePath + ".js", external: false };
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}ℹ${C.r} `,
17
- ok: `${C.g}✓${C.r} `,
18
- err: `${C.red}✗${C.r} `,
19
- build: `${C.m}⟳${C.r} `,
20
- hmr: `${C.c}⚡${C.r}`,
21
- watch: `${C.y}○${C.r} `
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 t = `${C.d}${new Date().toLocaleTimeString()}${C.r}`;
39
- console.log(`${t} ${ICONS[icon] || ICONS.info} ${msg}${extra ? ` ${C.d}(${extra})${C.r}` : ""}`);
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 'node:fs';
3
- import path from 'node:path';
4
- import Paths from '../../Core/Paths.js';
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
- console.error("\x1b[31m%s\x1b[0m", "Usage: njs make:<type> <Name/Path> (e.g., njs make:controller Admin/HomeController)");
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 === 'migration') {
72
+ if (type === "migration") {
64
73
  const now = new Date();
65
- const pad = (n) => n.toString().padStart(2, '0');
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, 'utf-8');
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
- console.log("\x1b[32m%s\x1b[0m", `${type[0].toUpperCase() + type.slice(1)} successfully created: ${fullPath}`);
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
 
@@ -28,7 +28,6 @@ export default async function migrate(options = {}) {
28
28
  }
29
29
 
30
30
  if (shouldSeed && result.ran.length > 0) {
31
- console.log('\n');
32
31
  await seed();
33
32
  }
34
33
 
@@ -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
- console.error('❌ Database setup failed:', error.message);
26
- console.error('Check your .env file and ensure the database exists and is accessible');
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
- console.log(`${COLORS.yellow}⚠️ Dropping all tables...${COLORS.reset}\n`);
32
-
33
- const [tableRows] = await DB.query("SHOW TABLES");
34
- await DB.rawQuery("SET FOREIGN_KEY_CHECKS = 0");
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
- await DB.rawQuery("SET FOREIGN_KEY_CHECKS = 1");
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
- if (tableRows.length > 0) {
45
- console.log(`\n${COLORS.green}✅ Dropped ${tableRows.length} table(s)${COLORS.reset}\n`);
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
- console.error('❌ Fresh migration error:', error.message);
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
- console.error('❌ Database setup failed:', error.message);
17
- console.error('Check your .env file and ensure the database exists and is accessible');
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
- console.error('❌ Rollback error:', error.message);
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
- console.error('❌ Database setup failed:', error.message);
15
- console.error('Check your .env file and ensure the database exists and is accessible');
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
- console.error('❌ Status error:', error.message);
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
- } catch (error) {
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
- } catch (error) {
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
- console.error('Error creating symbolic link:', err);
15
- reject(err);
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
- console.log('Symbolic link has been created successfully.');
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
+ };
@@ -21,7 +21,8 @@ class Config {
21
21
  for (const name of configFiles) {
22
22
  try {
23
23
  this.#configs[name] = (await import(Paths.configUrl(name))).default;
24
- } catch (err) {
24
+ }
25
+ catch (err) {
25
26
  // Config file might not exist, that's ok
26
27
  this.#configs[name] = {};
27
28
  }
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();