@nitronjs/framework 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/cli/create.js +88 -72
- package/cli/njs.js +17 -19
- package/lib/Auth/Auth.js +167 -0
- package/lib/Build/CssBuilder.js +9 -0
- package/lib/Build/FileAnalyzer.js +16 -0
- package/lib/Build/HydrationBuilder.js +17 -0
- package/lib/Build/Manager.js +15 -0
- package/lib/Build/colors.js +4 -0
- package/lib/Build/plugins.js +84 -20
- package/lib/Console/Commands/DevCommand.js +13 -9
- package/lib/Console/Commands/MakeCommand.js +24 -10
- package/lib/Console/Commands/MigrateCommand.js +4 -3
- package/lib/Console/Commands/MigrateFreshCommand.js +22 -27
- package/lib/Console/Commands/MigrateRollbackCommand.js +8 -4
- package/lib/Console/Commands/MigrateStatusCommand.js +8 -4
- package/lib/Console/Commands/SeedCommand.js +8 -28
- package/lib/Console/Commands/StorageLinkCommand.js +20 -5
- package/lib/Console/Output.js +143 -0
- package/lib/Core/Config.js +2 -1
- package/lib/Core/Paths.js +8 -8
- package/lib/Database/DB.js +141 -51
- package/lib/Database/Drivers/MySQLDriver.js +102 -157
- package/lib/Database/Migration/Checksum.js +3 -8
- package/lib/Database/Migration/MigrationRepository.js +25 -35
- package/lib/Database/Migration/MigrationRunner.js +59 -67
- package/lib/Database/Model.js +165 -75
- package/lib/Database/QueryBuilder.js +43 -0
- package/lib/Database/QueryValidation.js +51 -30
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +24 -145
- package/lib/Date/DateTime.js +9 -0
- package/lib/Encryption/Encryption.js +52 -0
- package/lib/Faker/Faker.js +11 -0
- package/lib/Filesystem/Storage.js +120 -0
- package/lib/HMR/Server.js +79 -9
- package/lib/Hashing/Hash.js +41 -0
- package/lib/Http/Server.js +179 -151
- package/lib/Logging/{Manager.js → Log.js} +68 -80
- package/lib/Mail/Mail.js +187 -0
- package/lib/Route/Router.js +416 -0
- package/lib/Session/File.js +135 -233
- package/lib/Session/Manager.js +117 -171
- package/lib/Session/Memory.js +28 -38
- package/lib/Session/Session.js +71 -107
- package/lib/Support/Str.js +103 -0
- package/lib/Translation/Lang.js +54 -0
- package/lib/View/Client/hmr-client.js +87 -51
- package/lib/View/Client/nitronjs-icon.png +0 -0
- package/lib/View/{Manager.js → View.js} +44 -29
- package/lib/index.d.ts +49 -27
- package/lib/index.js +19 -13
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/package.json +2 -0
- package/skeleton/resources/css/global.css +1 -0
- package/skeleton/resources/views/Site/Home.tsx +456 -79
- package/skeleton/tsconfig.json +6 -1
- package/lib/Auth/Manager.js +0 -111
- package/lib/Database/Connection.js +0 -61
- package/lib/Database/Manager.js +0 -162
- package/lib/Database/Migration/migrations/0000_00_00_00_01_create_seeders_table.js +0 -20
- package/lib/Database/Seeder/SeederRepository.js +0 -45
- package/lib/Encryption/Manager.js +0 -47
- package/lib/Filesystem/Manager.js +0 -74
- package/lib/Hashing/Manager.js +0 -25
- package/lib/Mail/Manager.js +0 -120
- package/lib/Route/Loader.js +0 -80
- package/lib/Route/Manager.js +0 -286
- package/lib/Translation/Manager.js +0 -49
package/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",
|
|
@@ -32,10 +32,8 @@ ${COLORS.bold}Database:${COLORS.reset}
|
|
|
32
32
|
${COLORS.cyan}njs migrate:status${COLORS.reset} Show the status of each migration
|
|
33
33
|
${COLORS.cyan}njs migrate:fresh${COLORS.reset} Drop all tables and re-migrate
|
|
34
34
|
${COLORS.cyan}njs migrate:fresh --seed${COLORS.reset} Fresh migrate with seeders
|
|
35
|
-
${COLORS.cyan}njs seed${COLORS.reset} Run
|
|
36
|
-
${COLORS.cyan}njs seed
|
|
37
|
-
${COLORS.cyan}njs seed --all${COLORS.reset} Run all seeders (prod + dev)
|
|
38
|
-
${COLORS.cyan}njs seed --status${COLORS.reset} Show the status of each seeder
|
|
35
|
+
${COLORS.cyan}njs seed${COLORS.reset} Run all seeders
|
|
36
|
+
${COLORS.cyan}njs seed <Name>${COLORS.reset} Run a specific seeder
|
|
39
37
|
|
|
40
38
|
${COLORS.bold}Generators:${COLORS.reset}
|
|
41
39
|
${COLORS.cyan}njs make:controller${COLORS.reset} Create a new controller
|
|
@@ -110,7 +108,8 @@ async function run() {
|
|
|
110
108
|
|
|
111
109
|
case "migrate": {
|
|
112
110
|
const { default: Migrate } = await import("../lib/Console/Commands/MigrateCommand.js");
|
|
113
|
-
await Migrate({ seed: additionalArgs.includes("--seed") });
|
|
111
|
+
const success = await Migrate({ seed: additionalArgs.includes("--seed") });
|
|
112
|
+
process.exit(success ? 0 : 1);
|
|
114
113
|
break;
|
|
115
114
|
}
|
|
116
115
|
|
|
@@ -119,39 +118,37 @@ async function run() {
|
|
|
119
118
|
const stepArg = additionalArgs.find(arg => arg.startsWith('--step='));
|
|
120
119
|
const step = stepArg ? parseInt(stepArg.split('=')[1], 10) : 1;
|
|
121
120
|
const all = additionalArgs.includes('--all');
|
|
122
|
-
await Rollback({ step, all });
|
|
121
|
+
const success = await Rollback({ step, all });
|
|
122
|
+
process.exit(success ? 0 : 1);
|
|
123
123
|
break;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
126
|
case "migrate:status": {
|
|
127
127
|
const { default: Status } = await import("../lib/Console/Commands/MigrateStatusCommand.js");
|
|
128
128
|
await Status();
|
|
129
|
+
process.exit(0);
|
|
129
130
|
break;
|
|
130
131
|
}
|
|
131
132
|
|
|
132
133
|
case "migrate:fresh": {
|
|
133
134
|
const { default: MigrateFresh } = await import("../lib/Console/Commands/MigrateFreshCommand.js");
|
|
134
|
-
await MigrateFresh({ seed: additionalArgs.includes("--seed") });
|
|
135
|
+
const success = await MigrateFresh({ seed: additionalArgs.includes("--seed") });
|
|
136
|
+
process.exit(success ? 0 : 1);
|
|
135
137
|
break;
|
|
136
138
|
}
|
|
137
139
|
|
|
138
140
|
case "seed": {
|
|
139
141
|
const { default: Seed } = await import("../lib/Console/Commands/SeedCommand.js");
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
let environment = 'prod';
|
|
145
|
-
if (isDev) environment = 'dev';
|
|
146
|
-
if (isAll) environment = 'all';
|
|
147
|
-
|
|
148
|
-
await Seed({ environment, status: showStatus });
|
|
142
|
+
const seederName = additionalArgs.find(a => !a.startsWith('--')) || null;
|
|
143
|
+
const success = await Seed(seederName);
|
|
144
|
+
process.exit(success ? 0 : 1);
|
|
149
145
|
break;
|
|
150
146
|
}
|
|
151
147
|
|
|
152
148
|
case "storage:link": {
|
|
153
149
|
const { default: StorageLink } = await import("../lib/Console/Commands/StorageLinkCommand.js");
|
|
154
|
-
await StorageLink();
|
|
150
|
+
const success = await StorageLink();
|
|
151
|
+
process.exit(success ? 0 : 1);
|
|
155
152
|
break;
|
|
156
153
|
}
|
|
157
154
|
|
|
@@ -171,7 +168,8 @@ async function run() {
|
|
|
171
168
|
}
|
|
172
169
|
|
|
173
170
|
const { default: Make } = await import("../lib/Console/Commands/MakeCommand.js");
|
|
174
|
-
await Make(type, name);
|
|
171
|
+
const success = await Make(type, name);
|
|
172
|
+
process.exit(success ? 0 : 1);
|
|
175
173
|
break;
|
|
176
174
|
}
|
|
177
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);
|