@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
package/README.md CHANGED
@@ -13,11 +13,13 @@ A modern full-stack Node.js framework with React Server Components, Islands Arch
13
13
  ## Installation
14
14
 
15
15
  ```bash
16
- npx @nitronjs/framework my-app
16
+ npx -y @nitronjs/framework my-app
17
17
  cd my-app
18
18
  npm run dev
19
19
  ```
20
20
 
21
+ > **Note:** The `-y` flag skips npm's confirmation prompt for a smoother experience.
22
+
21
23
  Your app will be running at `http://localhost:3000`
22
24
 
23
25
  ## Core Concepts
package/cli/create.js CHANGED
@@ -18,64 +18,55 @@ const COLORS = {
18
18
  cyan: "\x1b[36m"
19
19
  };
20
20
 
21
- function log(message, color = COLORS.reset) {
22
- console.log(`${color}${message}${COLORS.reset}`);
23
- }
21
+ function getFrameworkVersion() {
22
+ const pkgPath = path.join(__dirname, "..", "package.json");
23
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
24
24
 
25
- function logStep(message, status = "pending") {
26
- const icons = {
27
- pending: `${COLORS.dim}○${COLORS.reset}`,
28
- running: `${COLORS.yellow}◐${COLORS.reset}`,
29
- success: `${COLORS.green}✓${COLORS.reset}`,
30
- error: `${COLORS.red}✗${COLORS.reset}`
31
- };
32
- console.log(` ${icons[status]} ${message}`);
25
+ return pkg.version;
33
26
  }
34
27
 
35
- function startSpinner(message) {
36
- if (!process.stdout.isTTY) {
37
- return () => {};
38
- }
28
+ function log(message, color = COLORS.reset) {
29
+ console.log(`${color}${message}${COLORS.reset}`);
30
+ }
39
31
 
40
- const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
41
- let i = 0;
42
- const line = () => ` ${COLORS.yellow}${frames[i]}${COLORS.reset} ${message}`;
43
-
44
- process.stdout.write(line());
45
- const timer = setInterval(() => {
46
- i = (i + 1) % frames.length;
47
- process.stdout.write(`\r${line()}`);
48
- }, 120);
49
-
50
- return () => {
51
- clearInterval(timer);
52
- process.stdout.write(`\r${" ".repeat(message.length + 4)}\r`);
53
- };
32
+ function logStep(icon, message) {
33
+ console.log(` ${icon} ${message}`);
54
34
  }
55
35
 
56
36
  function printBanner() {
37
+ const version = getFrameworkVersion();
38
+ const versionText = `v${version}`;
39
+ const logoWidth = 66;
40
+ const versionLine = `${COLORS.dim}───${COLORS.reset} ${COLORS.bold}${versionText} ${COLORS.dim}───${COLORS.reset}`;
41
+ const versionTextLength = 3 + 1 + versionText.length + 1 + 3;
42
+ const innerWidth = logoWidth - 2;
43
+ const versionPadding = Math.floor((innerWidth - versionTextLength) / 2);
44
+
57
45
  console.log();
58
- log("███╗ ██╗██╗████████╗██████╗ ██████╗ ███╗ ██╗ ██╗███████╗", COLORS.cyan);
59
- log("████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║ ██║██╔════╝", COLORS.cyan);
60
- log("██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║██╔██╗ ██║ ██║███████╗", COLORS.cyan);
61
- log("██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║██ ██║╚════██║", COLORS.cyan);
62
- log("██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║╚█████╔╝███████║", COLORS.cyan);
63
- log("╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚════╝ ╚══════╝", COLORS.cyan);
46
+ console.log(`${COLORS.green}███╗ ██╗██╗████████╗██████╗ ██████╗ ███╗ ██╗ ██╗███████╗${COLORS.reset}`);
47
+ console.log(`${COLORS.green}████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║ ██║██╔════╝${COLORS.reset}`);
48
+ console.log(`${COLORS.green}██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║██╔██╗ ██║ ██║███████╗${COLORS.reset}`);
49
+ console.log(`${COLORS.green}██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║██ ██║╚════██║${COLORS.reset}`);
50
+ console.log(`${COLORS.green}██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║╚█████╔╝███████║${COLORS.reset}`);
51
+ console.log(`${COLORS.green}╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚════╝ ╚══════╝${COLORS.reset}`);
52
+ console.log(`${COLORS.green}┌${"─".repeat(innerWidth)}┐${COLORS.reset}`);
53
+ console.log(`${COLORS.green}│${COLORS.reset}${" ".repeat(versionPadding)}${versionLine}${" ".repeat(innerWidth - versionPadding - versionTextLength)}${COLORS.green}│${COLORS.reset}`);
54
+ console.log(`${COLORS.green}└${"─".repeat(innerWidth)}┘${COLORS.reset}`);
64
55
  console.log();
65
56
  }
66
57
 
67
58
  function printHelp() {
68
59
  console.log(`
69
60
  ${COLORS.bold}Usage:${COLORS.reset}
70
- npx nitronjs <project-name>
61
+ npx @nitronjs/framework <project-name>
71
62
 
72
63
  ${COLORS.bold}Arguments:${COLORS.reset}
73
64
  project-name Name of the project directory to create
74
65
 
75
66
  ${COLORS.bold}Examples:${COLORS.reset}
76
- ${COLORS.cyan}npx nitronjs my-app${COLORS.reset}
77
- ${COLORS.cyan}npx nitronjs blog${COLORS.reset}
78
- ${COLORS.cyan}npx nitronjs e-commerce${COLORS.reset}
67
+ ${COLORS.cyan}npx @nitronjs/framework my-app${COLORS.reset}
68
+ ${COLORS.cyan}npx @nitronjs/framework blog${COLORS.reset}
69
+ ${COLORS.cyan}npx @nitronjs/framework e-commerce${COLORS.reset}
79
70
 
80
71
  ${COLORS.bold}Project name rules:${COLORS.reset}
81
72
  - Can contain letters, numbers, hyphens, and underscores
@@ -122,7 +113,8 @@ function copyDir(src, dest) {
122
113
 
123
114
  if (entry.isDirectory()) {
124
115
  fileCount += copyDir(srcPath, destPath);
125
- } else {
116
+ }
117
+ else {
126
118
  fs.copyFileSync(srcPath, destPath);
127
119
  fileCount++;
128
120
  }
@@ -135,12 +127,6 @@ function generateAppKey() {
135
127
  return crypto.randomBytes(32).toString("base64");
136
128
  }
137
129
 
138
- function getFrameworkVersion() {
139
- const pkgPath = path.join(__dirname, "..", "package.json");
140
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
141
- return pkg.version;
142
- }
143
-
144
130
  function updatePackageJson(projectPath, projectName) {
145
131
  const pkgPath = path.join(projectPath, "package.json");
146
132
 
@@ -168,20 +154,31 @@ function updateEnvFile(projectPath) {
168
154
  }
169
155
  }
170
156
 
171
- function runNpmInstall(projectPath) {
157
+ function runNpmInstall(projectPath, onProgress) {
172
158
  return new Promise((resolve, reject) => {
173
159
  const isWindows = process.platform === "win32";
174
160
  const npm = isWindows ? (process.env.ComSpec || "cmd.exe") : "npm";
175
161
  const npmArgs = isWindows ? ["/d", "/s", "/c", "npm", "install"] : ["install"];
176
-
162
+
177
163
  const child = spawn(npm, npmArgs, {
178
164
  cwd: projectPath,
179
165
  stdio: ["ignore", "pipe", "pipe"]
180
166
  });
181
167
 
182
168
  let output = "";
169
+ let packageCount = 0;
170
+
183
171
  child.stdout.on("data", (data) => {
184
172
  output += data.toString();
173
+ const matches = data.toString().match(/added (\d+) packages/);
174
+
175
+ if (matches) {
176
+ packageCount = parseInt(matches[1]);
177
+ }
178
+
179
+ if (onProgress) {
180
+ onProgress(data.toString());
181
+ }
185
182
  });
186
183
 
187
184
  child.stderr.on("data", (data) => {
@@ -190,27 +187,30 @@ function runNpmInstall(projectPath) {
190
187
 
191
188
  child.on("close", (code) => {
192
189
  if (code === 0) {
193
- resolve();
194
- } else {
190
+ resolve(packageCount);
191
+ }
192
+ else {
195
193
  const error = new Error(`npm install failed with code ${code}`);
196
194
  error.output = output.trim();
197
195
  reject(error);
198
196
  }
199
197
  });
200
198
 
201
- child.on("error", (err) => {
202
- reject(err);
203
- });
199
+ child.on("error", reject);
204
200
  });
205
201
  }
206
202
 
207
203
  function printSuccess(projectName) {
204
+ const logoWidth = 66;
205
+ const innerWidth = logoWidth - 2;
206
+ const stripAnsi = str => str.replace(/\x1b\[[0-9;]*m/g, "");
207
+ const pad = (content, length) => content + " ".repeat(Math.max(0, length - stripAnsi(content).length));
208
+ const successText = `${COLORS.green}${COLORS.bold}✓${COLORS.reset} ${COLORS.bold}Project created successfully!${COLORS.reset}`;
209
+
208
210
  console.log();
209
- log("╔══════════════════════════════════════════════════════════════╗", COLORS.green);
210
- log("║ ║", COLORS.green);
211
- log("║ ✓ Project created successfully! ║", COLORS.green);
212
- log("║ ║", COLORS.green);
213
- log("╚══════════════════════════════════════════════════════════════╝", COLORS.green);
211
+ console.log(`${COLORS.green}┌${"".repeat(innerWidth)}┐${COLORS.reset}`);
212
+ console.log(`${COLORS.green}│${COLORS.reset} ${pad(successText, innerWidth - 2)} ${COLORS.green}│${COLORS.reset}`);
213
+ console.log(`${COLORS.green}└${"".repeat(innerWidth)}┘${COLORS.reset}`);
214
214
  console.log();
215
215
 
216
216
  log(`${COLORS.bold}Next steps:${COLORS.reset}`);
@@ -239,16 +239,18 @@ function printSuccess(projectName) {
239
239
  export default async function create(projectName, options = {}) {
240
240
  if (options.help || projectName === "--help" || projectName === "-h") {
241
241
  printHelp();
242
+
242
243
  return;
243
244
  }
244
245
 
245
246
  printBanner();
246
247
 
247
248
  const validation = validateProjectName(projectName);
249
+
248
250
  if (!validation.valid) {
249
251
  log(`${COLORS.red}Error: ${validation.error}${COLORS.reset}`);
250
252
  console.log();
251
- log(`${COLORS.dim}Run 'npx nitronjs --help' for usage information${COLORS.reset}`);
253
+ log(`${COLORS.dim}Run 'npx @nitronjs/framework --help' for usage information${COLORS.reset}`);
252
254
  process.exit(1);
253
255
  }
254
256
 
@@ -270,30 +272,44 @@ export default async function create(projectName, options = {}) {
270
272
  log(`${COLORS.bold}Creating project: ${COLORS.cyan}${projectName}${COLORS.reset}`);
271
273
  console.log();
272
274
 
273
- logStep("Copying project files...", "running");
275
+ logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Copying project files...");
274
276
  const fileCount = copyDir(SKELETON_DIR, projectPath);
275
- logStep(`Copied ${fileCount} files`, "success");
277
+ process.stdout.write("\x1b[1A\x1b[2K");
278
+ logStep(`${COLORS.green}✓${COLORS.reset}`, `Copied ${fileCount} files`);
276
279
 
277
- logStep("Configuring package.json...", "running");
280
+ logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Configuring package.json...");
278
281
  updatePackageJson(projectPath, projectName);
279
- logStep("Package configured", "success");
282
+ process.stdout.write("\x1b[1A\x1b[2K");
283
+ logStep(`${COLORS.green}✓${COLORS.reset}`, "Package configured");
280
284
 
281
- logStep("Generating APP_KEY...", "running");
285
+ logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Generating APP_KEY...");
282
286
  updateEnvFile(projectPath);
283
- logStep("APP_KEY generated", "success");
287
+ process.stdout.write("\x1b[1A\x1b[2K");
288
+ logStep(`${COLORS.green}✓${COLORS.reset}`, "APP_KEY generated");
289
+
290
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
291
+ let frameIndex = 0;
292
+
293
+ const spinner = setInterval(() => {
294
+ frameIndex = (frameIndex + 1) % frames.length;
295
+ process.stdout.write(`\r ${COLORS.yellow}${frames[frameIndex]}${COLORS.reset} Installing dependencies...`);
296
+ }, 80);
284
297
 
285
- logStep("Installing dependencies (this may take a moment)...", "running");
286
- const stopSpinner = startSpinner("Installing dependencies...");
287
298
  try {
288
299
  await runNpmInstall(projectPath);
289
- stopSpinner();
290
- logStep("Dependencies installed", "success");
291
- } catch (error) {
292
- stopSpinner();
293
- logStep("Failed to install dependencies", "error");
300
+ clearInterval(spinner);
301
+ process.stdout.write("\r\x1b[2K");
302
+ logStep(`${COLORS.green}✓${COLORS.reset}`, "Dependencies installed");
303
+ }
304
+ catch (error) {
305
+ clearInterval(spinner);
306
+ process.stdout.write("\r\x1b[2K");
307
+ logStep(`${COLORS.red}✗${COLORS.reset}`, "Failed to install dependencies");
308
+
294
309
  if (error.output) {
295
310
  log(`${COLORS.dim}${error.output}${COLORS.reset}`);
296
311
  }
312
+
297
313
  log(`${COLORS.yellow}Please run 'npm install' manually in the project directory${COLORS.reset}`);
298
314
  }
299
315
 
package/cli/njs.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const COLORS = {
4
4
  reset: "\x1b[0m",
@@ -108,7 +108,8 @@ async function run() {
108
108
 
109
109
  case "migrate": {
110
110
  const { default: Migrate } = await import("../lib/Console/Commands/MigrateCommand.js");
111
- await Migrate({ seed: additionalArgs.includes("--seed") });
111
+ const success = await Migrate({ seed: additionalArgs.includes("--seed") });
112
+ process.exit(success ? 0 : 1);
112
113
  break;
113
114
  }
114
115
 
@@ -117,32 +118,37 @@ async function run() {
117
118
  const stepArg = additionalArgs.find(arg => arg.startsWith('--step='));
118
119
  const step = stepArg ? parseInt(stepArg.split('=')[1], 10) : 1;
119
120
  const all = additionalArgs.includes('--all');
120
- await Rollback({ step, all });
121
+ const success = await Rollback({ step, all });
122
+ process.exit(success ? 0 : 1);
121
123
  break;
122
124
  }
123
125
 
124
126
  case "migrate:status": {
125
127
  const { default: Status } = await import("../lib/Console/Commands/MigrateStatusCommand.js");
126
128
  await Status();
129
+ process.exit(0);
127
130
  break;
128
131
  }
129
132
 
130
133
  case "migrate:fresh": {
131
134
  const { default: MigrateFresh } = await import("../lib/Console/Commands/MigrateFreshCommand.js");
132
- await MigrateFresh({ seed: additionalArgs.includes("--seed") });
135
+ const success = await MigrateFresh({ seed: additionalArgs.includes("--seed") });
136
+ process.exit(success ? 0 : 1);
133
137
  break;
134
138
  }
135
139
 
136
140
  case "seed": {
137
141
  const { default: Seed } = await import("../lib/Console/Commands/SeedCommand.js");
138
142
  const seederName = additionalArgs.find(a => !a.startsWith('--')) || null;
139
- await Seed(seederName);
143
+ const success = await Seed(seederName);
144
+ process.exit(success ? 0 : 1);
140
145
  break;
141
146
  }
142
147
 
143
148
  case "storage:link": {
144
149
  const { default: StorageLink } = await import("../lib/Console/Commands/StorageLinkCommand.js");
145
- await StorageLink();
150
+ const success = await StorageLink();
151
+ process.exit(success ? 0 : 1);
146
152
  break;
147
153
  }
148
154
 
@@ -162,7 +168,8 @@ async function run() {
162
168
  }
163
169
 
164
170
  const { default: Make } = await import("../lib/Console/Commands/MakeCommand.js");
165
- await Make(type, name);
171
+ const success = await Make(type, name);
172
+ process.exit(success ? 0 : 1);
166
173
  break;
167
174
  }
168
175
 
@@ -0,0 +1,167 @@
1
+ import Hash from "../Hashing/Hash.js";
2
+ import Config from "../Core/Config.js";
3
+
4
+ /**
5
+ * Authentication manager for handling user login, logout, and session management.
6
+ * Supports multiple authentication guards for different user types (admin, user, etc.)
7
+ */
8
+ class Auth {
9
+ /**
10
+ * Sets up authentication hooks on the Fastify server.
11
+ * Decorates requests with auth helper methods.
12
+ * @param {import("fastify").FastifyInstance} server - Fastify server instance
13
+ */
14
+ static setup(server) {
15
+ server.decorateRequest("auth", null);
16
+
17
+ server.addHook("onRequest", async (req, res) => {
18
+ req.auth = {
19
+ guard: (guardName = null) => ({
20
+ attempt: (credentials) => Auth.attempt(req, credentials, guardName),
21
+ logout: () => Auth.logout(req, guardName),
22
+ user: () => Auth.user(req, guardName),
23
+ check: () => Auth.check(req, guardName),
24
+ home: () => Auth.home(res, guardName),
25
+ redirect: () => Auth.redirect(res, guardName)
26
+ }),
27
+ attempt: (credentials) => Auth.attempt(req, credentials, null),
28
+ logout: () => Auth.logout(req, null),
29
+ user: () => Auth.user(req, null),
30
+ check: () => Auth.check(req, null),
31
+ home: () => Auth.home(res, null),
32
+ redirect: () => Auth.redirect(res, null)
33
+ };
34
+ });
35
+ }
36
+
37
+ /**
38
+ * Attempts to authenticate a user with the given credentials.
39
+ * @param {import("fastify").FastifyRequest} req - Fastify request object
40
+ * @param {Object} credentials - Login credentials (identifier + password)
41
+ * @param {string|null} guardName - Optional guard name
42
+ * @returns {Promise<boolean>} True if authentication successful
43
+ */
44
+ static async attempt(req, credentials, guardName = null) {
45
+ const authConfig = Config.all("auth");
46
+ const guard = guardName || authConfig.defaults.guard;
47
+ const config = authConfig.guards[guard];
48
+
49
+ if (!config) {
50
+ throw new Error(`Guard "${guard}" not configured`);
51
+ }
52
+
53
+ const { provider, identifier } = config;
54
+
55
+ const user = await provider
56
+ .where(identifier, "=", credentials[identifier])
57
+ .first();
58
+
59
+ if (!user) {
60
+ return false;
61
+ }
62
+
63
+ const isValid = await Hash.check(credentials.password, user.password);
64
+
65
+ if (!isValid) {
66
+ return false;
67
+ }
68
+
69
+ req.session.regenerate();
70
+ req.session.set(`auth_${guard}`, user.id);
71
+
72
+ return true;
73
+ }
74
+
75
+ /**
76
+ * Logs out the current user by clearing the session.
77
+ * @param {import("fastify").FastifyRequest} req - Fastify request object
78
+ * @param {string|null} guardName - Optional guard name
79
+ */
80
+ static logout(req, guardName = null) {
81
+ const authConfig = Config.all("auth");
82
+ const guard = guardName || authConfig.defaults.guard;
83
+
84
+ req.session.set(`auth_${guard}`, null);
85
+ }
86
+
87
+ /**
88
+ * Gets the currently authenticated user.
89
+ * @param {import("fastify").FastifyRequest} req - Fastify request object
90
+ * @param {string|null} guardName - Optional guard name
91
+ * @returns {Promise<Object|null>} User model instance or null
92
+ */
93
+ static async user(req, guardName = null) {
94
+ const authConfig = Config.all("auth");
95
+ const guard = guardName || authConfig.defaults.guard;
96
+ const config = authConfig.guards[guard];
97
+ const userId = req.session.get(`auth_${guard}`) || null;
98
+
99
+ if (!userId || !config) {
100
+ return null;
101
+ }
102
+
103
+ return await config.provider.find(userId);
104
+ }
105
+
106
+ /**
107
+ * Checks if a user is currently authenticated.
108
+ * @param {import("fastify").FastifyRequest} req - Fastify request object
109
+ * @param {string|null} guardName - Optional guard name
110
+ * @returns {boolean} True if user is authenticated
111
+ */
112
+ static check(req, guardName = null) {
113
+ const authConfig = Config.all("auth");
114
+ const guard = guardName || authConfig.defaults.guard;
115
+ const value = req.session.get(`auth_${guard}`);
116
+
117
+ return value !== null && value !== undefined;
118
+ }
119
+
120
+ /**
121
+ * Redirects to the authenticated user's home page.
122
+ * @param {import("fastify").FastifyReply} res - Fastify response object
123
+ * @param {string|null} guardName - Optional guard name
124
+ * @returns {import("fastify").FastifyReply} Redirect response
125
+ */
126
+ static home(res, guardName = null) {
127
+ const authConfig = Config.all("auth");
128
+ const guard = guardName || authConfig.defaults.guard;
129
+ const config = authConfig.guards[guard];
130
+ const path = config?.home;
131
+
132
+ if (!path) {
133
+ return res.redirect("/");
134
+ }
135
+
136
+ if (path.startsWith("/") || path.startsWith("http://") || path.startsWith("https://")) {
137
+ return res.redirect(path);
138
+ }
139
+
140
+ return res.redirect(route(path));
141
+ }
142
+
143
+ /**
144
+ * Redirects to the login page for unauthenticated users.
145
+ * @param {import("fastify").FastifyReply} res - Fastify response object
146
+ * @param {string|null} guardName - Optional guard name
147
+ * @returns {import("fastify").FastifyReply} Redirect response
148
+ */
149
+ static redirect(res, guardName = null) {
150
+ const authConfig = Config.all("auth");
151
+ const guard = guardName || authConfig.defaults.guard;
152
+ const config = authConfig.guards[guard];
153
+ const path = config?.redirect;
154
+
155
+ if (!path) {
156
+ return res.redirect("/");
157
+ }
158
+
159
+ if (path.startsWith("/") || path.startsWith("http://") || path.startsWith("https://")) {
160
+ return res.redirect(path);
161
+ }
162
+
163
+ return res.redirect(route(path));
164
+ }
165
+ }
166
+
167
+ export default Auth;
@@ -4,6 +4,10 @@ import crypto from "crypto";
4
4
  import postcss from "postcss";
5
5
  import tailwindPostcss from "@tailwindcss/postcss";
6
6
 
7
+ /**
8
+ * Handles CSS file building with Tailwind CSS support.
9
+ * Processes CSS files, handles caching, and detects Tailwind usage.
10
+ */
7
11
  class CssBuilder {
8
12
  #cache;
9
13
  #isDev;
@@ -18,6 +22,11 @@ class CssBuilder {
18
22
  this.#cssOutput = cssOutput;
19
23
  }
20
24
 
25
+ /**
26
+ * Builds all CSS files in the input directory.
27
+ * @param {boolean} viewsChanged - Whether views have changed (forces Tailwind rebuild).
28
+ * @returns {Promise<number>} Number of CSS files processed.
29
+ */
21
30
  async build(viewsChanged) {
22
31
  if (!fs.existsSync(this.#cssInput)) {
23
32
  return 0;
@@ -6,6 +6,7 @@ import Paths from "../Core/Paths.js";
6
6
  import Layout from "../View/Layout.js";
7
7
  import COLORS from "./colors.js";
8
8
 
9
+ /** React hooks that indicate a client component. */
9
10
  const CLIENT_HOOKS = new Set([
10
11
  "useState",
11
12
  "useEffect",
@@ -16,8 +17,13 @@ const CLIENT_HOOKS = new Set([
16
17
  "useMemo"
17
18
  ]);
18
19
 
20
+ /** Maximum depth for dependency resolution to prevent infinite loops. */
19
21
  const MAX_DEPTH = 50;
20
22
 
23
+ /**
24
+ * Analyzes TSX files to determine component types and dependencies.
25
+ * Detects client components, extracts imports, and builds dependency graphs.
26
+ */
21
27
  class FileAnalyzer {
22
28
  #traverse = traverse.default;
23
29
  #cache;
@@ -26,6 +32,11 @@ class FileAnalyzer {
26
32
  this.#cache = cache;
27
33
  }
28
34
 
35
+ /**
36
+ * Discovers entry points and layouts in a directory.
37
+ * @param {string} baseDir - Directory to scan for TSX files.
38
+ * @returns {{entries: string[], layouts: string[], meta: Map<string, object>}}
39
+ */
29
40
  discoverEntries(baseDir) {
30
41
  if (!fs.existsSync(baseDir)) {
31
42
  return { entries: [], layouts: [], meta: new Map() };
@@ -104,6 +115,11 @@ class FileAnalyzer {
104
115
  return { graph, imported, importedBy };
105
116
  }
106
117
 
118
+ /**
119
+ * Analyzes a single TSX file for metadata.
120
+ * @param {string} filePath - Path to the TSX file.
121
+ * @returns {{imports: Set<string>, css: Set<string>, hasDefault: boolean, named: string[], jsx: boolean, needsClient: boolean, isClient: boolean}}
122
+ */
107
123
  analyzeFile(filePath) {
108
124
  const stat = fs.statSync(filePath);
109
125
  const mtime = stat.mtimeMs;
@@ -4,10 +4,19 @@ import crypto from "crypto";
4
4
  import Paths from "../Core/Paths.js";
5
5
  import Layout from "../View/Layout.js";
6
6
 
7
+ /**
8
+ * Sanitizes a name to be a valid JavaScript identifier.
9
+ * @param {string} name - The name to sanitize.
10
+ * @returns {string} A valid JS identifier.
11
+ */
7
12
  function sanitizeName(name) {
8
13
  return name.replace(/[^a-zA-Z0-9_$]/g, "_");
9
14
  }
10
15
 
16
+ /**
17
+ * Generates hydration scripts for client-side component rendering.
18
+ * Creates island bundles for React components that need client interactivity.
19
+ */
11
20
  class HydrationBuilder {
12
21
  #cache;
13
22
  #isDev;
@@ -21,6 +30,14 @@ class HydrationBuilder {
21
30
  this.#analyzer = analyzer;
22
31
  }
23
32
 
33
+ /**
34
+ * Builds hydration bundles for client components.
35
+ * @param {object} userBundle - User view bundle.
36
+ * @param {object} frameworkBundle - Framework view bundle.
37
+ * @param {object} manifest - Build manifest.
38
+ * @param {Set<string>|null} changedViews - Changed view files or null for full build.
39
+ * @returns {Promise<Array<{input: string, output: string}>>}
40
+ */
24
41
  async build(userBundle, frameworkBundle, manifest, changedViews = null) {
25
42
  const hydrationFiles = [];
26
43
  const allChangedFiles = new Set();
@@ -24,6 +24,14 @@ import COLORS from "./colors.js";
24
24
 
25
25
  dotenv.config({ quiet: true });
26
26
 
27
+ /**
28
+ * Main build orchestrator for NitronJS applications.
29
+ * Compiles views, CSS, and hydration bundles with caching support.
30
+ *
31
+ * @example
32
+ * const builder = new Builder();
33
+ * await builder.run(null, true, false); // Dev build with output
34
+ */
27
35
  class Builder {
28
36
  #isDev = false;
29
37
  #manifest = {};
@@ -69,6 +77,13 @@ class Builder {
69
77
  this.#hydrationBuilder = new HydrationBuilder(this.#cache, this.#isDev, this.#paths.templates, this.#analyzer);
70
78
  }
71
79
 
80
+ /**
81
+ * Runs the build process.
82
+ * @param {string|null} only - Build only "views" or "css", or null for full build.
83
+ * @param {boolean} isDev - Whether in development mode.
84
+ * @param {boolean} silent - Whether to suppress console output.
85
+ * @returns {Promise<{success: boolean, changedFiles?: string[], error?: string, time?: number}>}
86
+ */
72
87
  async run(only = null, isDev = false, silent = false) {
73
88
  this.#isDev = isDev;
74
89
  Environment.setDev(isDev);
@@ -1,3 +1,7 @@
1
+ /**
2
+ * ANSI color codes for terminal output.
3
+ * @type {Object<string, string>}
4
+ */
1
5
  const COLORS = {
2
6
  reset: "\x1b[0m",
3
7
  dim: "\x1b[2m",