@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/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
|
|
22
|
-
|
|
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
|
-
|
|
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
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
}
|
|
28
|
+
function log(message, color = COLORS.reset) {
|
|
29
|
+
console.log(`${color}${message}${COLORS.reset}`);
|
|
30
|
+
}
|
|
39
31
|
|
|
40
|
-
|
|
41
|
-
|
|
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(
|
|
59
|
-
log(
|
|
60
|
-
log(
|
|
61
|
-
log(
|
|
62
|
-
log(
|
|
63
|
-
log(
|
|
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
|
-
}
|
|
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
|
-
}
|
|
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",
|
|
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("
|
|
210
|
-
log(
|
|
211
|
-
log("
|
|
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..."
|
|
275
|
+
logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Copying project files...");
|
|
274
276
|
const fileCount = copyDir(SKELETON_DIR, projectPath);
|
|
275
|
-
|
|
277
|
+
process.stdout.write("\x1b[1A\x1b[2K");
|
|
278
|
+
logStep(`${COLORS.green}✓${COLORS.reset}`, `Copied ${fileCount} files`);
|
|
276
279
|
|
|
277
|
-
logStep("Configuring package.json..."
|
|
280
|
+
logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Configuring package.json...");
|
|
278
281
|
updatePackageJson(projectPath, projectName);
|
|
279
|
-
|
|
282
|
+
process.stdout.write("\x1b[1A\x1b[2K");
|
|
283
|
+
logStep(`${COLORS.green}✓${COLORS.reset}`, "Package configured");
|
|
280
284
|
|
|
281
|
-
logStep("Generating APP_KEY..."
|
|
285
|
+
logStep(`${COLORS.yellow}◐${COLORS.reset}`, "Generating APP_KEY...");
|
|
282
286
|
updateEnvFile(projectPath);
|
|
283
|
-
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
|
package/lib/Auth/Auth.js
ADDED
|
@@ -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;
|
package/lib/Build/CssBuilder.js
CHANGED
|
@@ -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();
|
package/lib/Build/Manager.js
CHANGED
|
@@ -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);
|