nitronjs 0.1.0
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 +429 -0
- package/cli/create.js +260 -0
- package/cli/njs.js +164 -0
- package/lib/Auth/Manager.js +111 -0
- package/lib/Build/Manager.js +1232 -0
- package/lib/Console/Commands/BuildCommand.js +25 -0
- package/lib/Console/Commands/DevCommand.js +385 -0
- package/lib/Console/Commands/MakeCommand.js +110 -0
- package/lib/Console/Commands/MigrateCommand.js +98 -0
- package/lib/Console/Commands/MigrateFreshCommand.js +97 -0
- package/lib/Console/Commands/SeedCommand.js +92 -0
- package/lib/Console/Commands/StorageLinkCommand.js +31 -0
- package/lib/Console/Stubs/controller.js +19 -0
- package/lib/Console/Stubs/middleware.js +9 -0
- package/lib/Console/Stubs/migration.js +23 -0
- package/lib/Console/Stubs/model.js +7 -0
- package/lib/Console/Stubs/page-hydration.tsx +54 -0
- package/lib/Console/Stubs/seeder.js +9 -0
- package/lib/Console/Stubs/vendor.tsx +11 -0
- package/lib/Core/Config.js +86 -0
- package/lib/Core/Environment.js +21 -0
- package/lib/Core/Paths.js +188 -0
- package/lib/Database/Connection.js +61 -0
- package/lib/Database/DB.js +84 -0
- package/lib/Database/Drivers/MySQLDriver.js +234 -0
- package/lib/Database/Manager.js +162 -0
- package/lib/Database/Model.js +161 -0
- package/lib/Database/QueryBuilder.js +714 -0
- package/lib/Database/QueryValidation.js +62 -0
- package/lib/Database/Schema/Blueprint.js +126 -0
- package/lib/Database/Schema/Manager.js +116 -0
- package/lib/Date/DateTime.js +108 -0
- package/lib/Date/Locale.js +68 -0
- package/lib/Encryption/Manager.js +47 -0
- package/lib/Filesystem/Manager.js +49 -0
- package/lib/Hashing/Manager.js +25 -0
- package/lib/Http/Server.js +317 -0
- package/lib/Logging/Manager.js +153 -0
- package/lib/Mail/Manager.js +120 -0
- package/lib/Route/Loader.js +81 -0
- package/lib/Route/Manager.js +265 -0
- package/lib/Runtime/Entry.js +11 -0
- package/lib/Session/File.js +299 -0
- package/lib/Session/Manager.js +259 -0
- package/lib/Session/Memory.js +67 -0
- package/lib/Session/Session.js +196 -0
- package/lib/Support/Str.js +100 -0
- package/lib/Translation/Manager.js +49 -0
- package/lib/Validation/MimeTypes.js +39 -0
- package/lib/Validation/Validator.js +691 -0
- package/lib/View/Manager.js +544 -0
- package/lib/View/Templates/default/Home.tsx +262 -0
- package/lib/View/Templates/default/MainLayout.tsx +44 -0
- package/lib/View/Templates/errors/404.tsx +13 -0
- package/lib/View/Templates/errors/500.tsx +13 -0
- package/lib/View/Templates/errors/ErrorLayout.tsx +112 -0
- package/lib/View/Templates/messages/Maintenance.tsx +17 -0
- package/lib/View/Templates/messages/MessageLayout.tsx +136 -0
- package/lib/index.js +57 -0
- package/package.json +48 -0
- package/skeleton/.env.example +26 -0
- package/skeleton/app/Controllers/HomeController.js +9 -0
- package/skeleton/app/Kernel.js +11 -0
- package/skeleton/app/Middlewares/Authentication.js +9 -0
- package/skeleton/app/Middlewares/Guest.js +9 -0
- package/skeleton/app/Middlewares/VerifyCsrf.js +24 -0
- package/skeleton/app/Models/User.js +7 -0
- package/skeleton/config/app.js +4 -0
- package/skeleton/config/auth.js +16 -0
- package/skeleton/config/database.js +27 -0
- package/skeleton/config/hash.js +3 -0
- package/skeleton/config/server.js +28 -0
- package/skeleton/config/session.js +21 -0
- package/skeleton/database/migrations/2025_01_01_00_00_users.js +20 -0
- package/skeleton/database/seeders/UserSeeder.js +15 -0
- package/skeleton/globals.d.ts +1 -0
- package/skeleton/package.json +24 -0
- package/skeleton/public/.gitkeep +0 -0
- package/skeleton/resources/css/.gitkeep +0 -0
- package/skeleton/resources/langs/.gitkeep +0 -0
- package/skeleton/resources/views/Site/Home.tsx +66 -0
- package/skeleton/routes/web.js +4 -0
- package/skeleton/storage/app/private/.gitkeep +0 -0
- package/skeleton/storage/app/public/.gitkeep +0 -0
- package/skeleton/storage/framework/sessions/.gitkeep +0 -0
- package/skeleton/storage/logs/.gitkeep +0 -0
- package/skeleton/tsconfig.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,429 @@
|
|
|
1
|
+
# NitronJS
|
|
2
|
+
|
|
3
|
+
A modern and extensible Node.js MVC framework built on Fastify. NitronJS focuses on clean architecture, modular structure, and developer productivity, offering built-in routing, middleware, configuration management, CLI tooling, and native React integration for scalable full-stack applications.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **MVC Architecture** - Clean separation of concerns with Models, Views, and Controllers
|
|
8
|
+
- **React SSR** - Built-in React server-side rendering with TypeScript support
|
|
9
|
+
- **Fast & Lightweight** - Built on Fastify for high performance
|
|
10
|
+
- **CLI Tools** - Powerful command-line interface for scaffolding and development
|
|
11
|
+
- **Database ORM** - Intuitive query builder and model system for MySQL
|
|
12
|
+
- **Hot Reload** - Development mode with automatic reloading
|
|
13
|
+
- **Middleware System** - Flexible middleware pipeline with route-specific middleware
|
|
14
|
+
- **Session Management** - Built-in session support with multiple drivers
|
|
15
|
+
- **Security** - CSRF protection, authentication, and input validation
|
|
16
|
+
- **Modern Build System** - esbuild + PostCSS + Tailwind CSS integration
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
Create a new NitronJS project:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx nitronjs my-app
|
|
24
|
+
cd my-app
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Dependencies are installed automatically during project creation.
|
|
28
|
+
|
|
29
|
+
## Quick Start
|
|
30
|
+
|
|
31
|
+
1. **Configure your database** in `config/database.js`
|
|
32
|
+
|
|
33
|
+
2. **Run migrations**:
|
|
34
|
+
```bash
|
|
35
|
+
npx njs migrate --seed
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
3. **Start development server**:
|
|
39
|
+
```bash
|
|
40
|
+
npm run dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Your application will be running at `http://localhost:3000`
|
|
44
|
+
|
|
45
|
+
## CLI Commands
|
|
46
|
+
|
|
47
|
+
NitronJS comes with a powerful CLI tool (`njs`):
|
|
48
|
+
|
|
49
|
+
### Development & Build
|
|
50
|
+
```bash
|
|
51
|
+
njs dev # Start development server with hot reload
|
|
52
|
+
njs build # Build views for production
|
|
53
|
+
njs start # Start production server
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Database
|
|
57
|
+
```bash
|
|
58
|
+
njs migrate # Run pending migrations
|
|
59
|
+
njs migrate:fresh # Drop all tables and re-run migrations
|
|
60
|
+
njs migrate:fresh --seed # Drop, migrate, and seed
|
|
61
|
+
njs seed # Run database seeders
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Code Generation
|
|
65
|
+
```bash
|
|
66
|
+
njs make:controller <name> # Generate a new controller
|
|
67
|
+
njs make:model <name> # Generate a new model
|
|
68
|
+
njs make:middleware <name> # Generate a new middleware
|
|
69
|
+
njs make:migration <name> # Generate a new migration
|
|
70
|
+
njs make:seeder <name> # Generate a new seeder
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Utilities
|
|
74
|
+
```bash
|
|
75
|
+
njs storage:link # Create symbolic link for storage
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Project Structure
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
my-app/
|
|
82
|
+
├── app/
|
|
83
|
+
│ ├── Controllers/ # Request handlers
|
|
84
|
+
│ ├── Middlewares/ # Custom middleware
|
|
85
|
+
│ └── Models/ # Database models
|
|
86
|
+
├── config/ # Configuration files
|
|
87
|
+
│ ├── app.js
|
|
88
|
+
│ ├── database.js
|
|
89
|
+
│ └── server.js
|
|
90
|
+
├── database/
|
|
91
|
+
│ ├── migrations/ # Database migrations
|
|
92
|
+
│ └── seeders/ # Database seeders
|
|
93
|
+
├── public/ # Static assets
|
|
94
|
+
├── resources/
|
|
95
|
+
│ ├── css/ # Stylesheets
|
|
96
|
+
│ └── views/ # React components (TSX)
|
|
97
|
+
├── routes/
|
|
98
|
+
│ └── web.js # Route definitions
|
|
99
|
+
└── storage/ # File storage
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Routing
|
|
103
|
+
|
|
104
|
+
Define routes in `routes/web.js`:
|
|
105
|
+
|
|
106
|
+
```javascript
|
|
107
|
+
import { Route } from 'nitronjs';
|
|
108
|
+
import HomeController from '#app/Controllers/HomeController.js';
|
|
109
|
+
|
|
110
|
+
Route.get('/', HomeController.index).name('home');
|
|
111
|
+
Route.get('/about', HomeController.about);
|
|
112
|
+
Route.post('/contact', HomeController.contact);
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Route Groups & Middleware
|
|
116
|
+
|
|
117
|
+
```javascript
|
|
118
|
+
Route.prefix('/admin').middleware('auth').group(() => {
|
|
119
|
+
Route.get('/dashboard', AdminController.dashboard);
|
|
120
|
+
Route.get('/users', AdminController.users);
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Controllers
|
|
125
|
+
|
|
126
|
+
Create controllers with `njs make:controller`:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
class HomeController {
|
|
130
|
+
static async index(request, reply) {
|
|
131
|
+
return reply.view('Site/Home', {
|
|
132
|
+
title: 'Welcome'
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static async contact(request, reply) {
|
|
137
|
+
const { name, email } = request.body;
|
|
138
|
+
// Handle contact form
|
|
139
|
+
return reply.redirect('/thank-you');
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export default HomeController;
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Views (React SSR)
|
|
147
|
+
|
|
148
|
+
Create React views in `resources/views/`:
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
// resources/views/Site/Home.tsx
|
|
152
|
+
export default function Home({ title, items }) {
|
|
153
|
+
return (
|
|
154
|
+
<html>
|
|
155
|
+
<head>
|
|
156
|
+
<title>{title}</title>
|
|
157
|
+
<link rel="stylesheet" href="/css/site_style.css" />
|
|
158
|
+
</head>
|
|
159
|
+
<body>
|
|
160
|
+
<h1>{title}</h1>
|
|
161
|
+
<ul>
|
|
162
|
+
{items.map(item => (
|
|
163
|
+
<li key={item.id}>{item.name}</li>
|
|
164
|
+
))}
|
|
165
|
+
</ul>
|
|
166
|
+
</body>
|
|
167
|
+
</html>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Views are automatically compiled and bundled with your application.
|
|
173
|
+
|
|
174
|
+
## Models
|
|
175
|
+
|
|
176
|
+
Create models with `njs make:model`:
|
|
177
|
+
|
|
178
|
+
```javascript
|
|
179
|
+
import { Model } from 'nitronjs';
|
|
180
|
+
|
|
181
|
+
export default class User extends Model {
|
|
182
|
+
static table = 'users';
|
|
183
|
+
static primaryKey = 'id';
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Query Examples
|
|
188
|
+
|
|
189
|
+
```javascript
|
|
190
|
+
// Find all users
|
|
191
|
+
const users = await User.get();
|
|
192
|
+
|
|
193
|
+
// Find by ID
|
|
194
|
+
const user = await User.find(1);
|
|
195
|
+
|
|
196
|
+
// Where clauses
|
|
197
|
+
const admins = await User.where('role', '=', 'admin').get();
|
|
198
|
+
|
|
199
|
+
// Create new record
|
|
200
|
+
const user = new User();
|
|
201
|
+
user.name = 'John';
|
|
202
|
+
user.email = 'john@example.com';
|
|
203
|
+
await user.save();
|
|
204
|
+
|
|
205
|
+
// Update
|
|
206
|
+
await User.where('id', '=', 1).update({
|
|
207
|
+
name: 'Jane'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Delete
|
|
211
|
+
await User.where('id', '=', 1).delete();
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Middleware
|
|
215
|
+
|
|
216
|
+
Create middleware with `njs make:middleware`:
|
|
217
|
+
|
|
218
|
+
```javascript
|
|
219
|
+
class CheckAge {
|
|
220
|
+
static async handler(req, res) {
|
|
221
|
+
const age = req.query.age;
|
|
222
|
+
|
|
223
|
+
if (age < 18) {
|
|
224
|
+
return res.status(403).send('Access denied');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// If no return, continues to next middleware/handler
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default CheckAge;
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Register middleware in `app/Kernel.js`:
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
export default {
|
|
238
|
+
routeMiddlewares: {
|
|
239
|
+
"check-age": CheckAge,
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Apply to routes:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
Route.middleware('check-age').get('/restricted', Controller.method);
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
## Database Migrations
|
|
251
|
+
|
|
252
|
+
Create migrations with `njs make:migration`:
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
import { Schema } from 'nitronjs';
|
|
256
|
+
|
|
257
|
+
class CreateUsersTable {
|
|
258
|
+
static async up() {
|
|
259
|
+
await Schema.create('users', (table) => {
|
|
260
|
+
table.id();
|
|
261
|
+
table.string('name');
|
|
262
|
+
table.string('email').unique();
|
|
263
|
+
table.string('password');
|
|
264
|
+
table.timestamp('created_at');
|
|
265
|
+
table.timestamp('updated_at').nullable();
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static async down() {
|
|
270
|
+
await Schema.dropIfExists('users');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export default CreateUsersTable;
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Configuration
|
|
278
|
+
|
|
279
|
+
All configuration files are in the `config/` directory:
|
|
280
|
+
|
|
281
|
+
- **app.js** - Application settings (name, env, debug mode)
|
|
282
|
+
- **database.js** - Database connections
|
|
283
|
+
- **server.js** - Server configuration (port, host, CORS)
|
|
284
|
+
- **session.js** - Session management
|
|
285
|
+
- **auth.js** - Authentication settings
|
|
286
|
+
- **hash.js** - Hashing algorithm configuration
|
|
287
|
+
|
|
288
|
+
Access config values:
|
|
289
|
+
|
|
290
|
+
```javascript
|
|
291
|
+
import { Config } from 'nitronjs';
|
|
292
|
+
|
|
293
|
+
const appName = Config.get('app.name');
|
|
294
|
+
const dbHost = Config.get('database.host');
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Session Management
|
|
298
|
+
|
|
299
|
+
```javascript
|
|
300
|
+
// Set session data
|
|
301
|
+
req.session.set('user', { id: 1, name: 'John' });
|
|
302
|
+
|
|
303
|
+
// Get session data
|
|
304
|
+
const user = req.session.get('user');
|
|
305
|
+
|
|
306
|
+
// Check if session has value
|
|
307
|
+
if (req.session.get('user')) {
|
|
308
|
+
// User is logged in
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Remove session data
|
|
312
|
+
req.session.set('user', null);
|
|
313
|
+
|
|
314
|
+
// Get all session data
|
|
315
|
+
const allData = req.session.all();
|
|
316
|
+
|
|
317
|
+
// Regenerate session (security - after login)
|
|
318
|
+
req.session.regenerate();
|
|
319
|
+
|
|
320
|
+
// Flash messages (one-time)
|
|
321
|
+
req.session.flash('message', 'Success!');
|
|
322
|
+
const message = req.session.getFlash('message');
|
|
323
|
+
|
|
324
|
+
// CSRF token
|
|
325
|
+
const token = req.session.getCsrfToken();
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Authentication
|
|
329
|
+
|
|
330
|
+
Built-in authentication system (request-based):
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
// Attempt login (inside controller or middleware)
|
|
334
|
+
const success = await req.auth.attempt({
|
|
335
|
+
email: 'admin@example.com',
|
|
336
|
+
password: 'secret'
|
|
337
|
+
});
|
|
338
|
+
// veya belirli bir guard ile:
|
|
339
|
+
// const success = await req.auth.guard('user').attempt({ email, password });
|
|
340
|
+
|
|
341
|
+
// Kullanıcıya erişim
|
|
342
|
+
const user = await req.auth.user();
|
|
343
|
+
// veya
|
|
344
|
+
// const user = await req.auth.guard('user').user();
|
|
345
|
+
|
|
346
|
+
// Giriş yapmış mı?
|
|
347
|
+
if (req.auth.check()) {
|
|
348
|
+
// Authenticated
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Çıkış
|
|
352
|
+
await req.auth.logout();
|
|
353
|
+
// veya
|
|
354
|
+
// await req.auth.guard('user').logout();
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
## Validation
|
|
358
|
+
|
|
359
|
+
```javascript
|
|
360
|
+
import { Validator } from 'nitronjs';
|
|
361
|
+
|
|
362
|
+
const validation = Validator.make(req.body, {
|
|
363
|
+
email: 'required|email',
|
|
364
|
+
password: 'required|string|min:8',
|
|
365
|
+
age: 'required|numeric|min:18'
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
if (validation.fails()) {
|
|
369
|
+
return res.status(422).send({
|
|
370
|
+
errors: validation.errors()
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
## Environment Variables
|
|
376
|
+
|
|
377
|
+
Create a `.env` file in your project root:
|
|
378
|
+
|
|
379
|
+
```env
|
|
380
|
+
APP_NAME=nitronjs
|
|
381
|
+
APP_DEV=true
|
|
382
|
+
APP_DEBUG=true
|
|
383
|
+
APP_PORT=3000
|
|
384
|
+
APP_URL=http://localhost
|
|
385
|
+
|
|
386
|
+
DATABASE_DRIVER=mysql
|
|
387
|
+
DATABASE_HOST=127.0.0.1
|
|
388
|
+
DATABASE_PORT=3306
|
|
389
|
+
DATABASE_NAME=nitronjs
|
|
390
|
+
DATABASE_USERNAME=root
|
|
391
|
+
DATABASE_PASSWORD=
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Response Methods
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
// Render view
|
|
398
|
+
return res.view('Site/Home', { title: 'Welcome' });
|
|
399
|
+
|
|
400
|
+
// JSON response
|
|
401
|
+
return res.json({ success: true, data: users });
|
|
402
|
+
|
|
403
|
+
// Redirect
|
|
404
|
+
return res.redirect('/dashboard');
|
|
405
|
+
|
|
406
|
+
// Status code
|
|
407
|
+
return res.status(404).send('Not Found');
|
|
408
|
+
|
|
409
|
+
// Download file
|
|
410
|
+
return res.download('/path/to/file.pdf');
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
## Testing Your Application
|
|
414
|
+
|
|
415
|
+
Build and run in production mode:
|
|
416
|
+
|
|
417
|
+
```bash
|
|
418
|
+
npm run build
|
|
419
|
+
npm run start
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Requirements
|
|
423
|
+
|
|
424
|
+
- Node.js 18.x or higher
|
|
425
|
+
- MySQL 5.7+ or MariaDB 10.3+
|
|
426
|
+
|
|
427
|
+
## License
|
|
428
|
+
|
|
429
|
+
ISC
|
package/cli/create.js
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import crypto from "crypto";
|
|
6
|
+
import { spawn } from "child_process";
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const SKELETON_DIR = path.join(__dirname, "..", "skeleton");
|
|
10
|
+
|
|
11
|
+
const COLORS = {
|
|
12
|
+
reset: "\x1b[0m",
|
|
13
|
+
bold: "\x1b[1m",
|
|
14
|
+
dim: "\x1b[2m",
|
|
15
|
+
red: "\x1b[31m",
|
|
16
|
+
green: "\x1b[32m",
|
|
17
|
+
yellow: "\x1b[33m",
|
|
18
|
+
cyan: "\x1b[36m"
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function log(message, color = COLORS.reset) {
|
|
22
|
+
console.log(`${color}${message}${COLORS.reset}`);
|
|
23
|
+
}
|
|
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}`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function printBanner() {
|
|
36
|
+
console.log();
|
|
37
|
+
log("███╗ ██╗██╗████████╗██████╗ ██████╗ ███╗ ██╗ ██╗███████╗", COLORS.cyan);
|
|
38
|
+
log("████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║ ██║██╔════╝", COLORS.cyan);
|
|
39
|
+
log("██╔██╗ ██║██║ ██║ ██████╔╝██║ ██║██╔██╗ ██║ ██║███████╗", COLORS.cyan);
|
|
40
|
+
log("██║╚██╗██║██║ ██║ ██╔══██╗██║ ██║██║╚██╗██║██ ██║╚════██║", COLORS.cyan);
|
|
41
|
+
log("██║ ╚████║██║ ██║ ██║ ██║╚██████╔╝██║ ╚████║╚█████╔╝███████║", COLORS.cyan);
|
|
42
|
+
log("╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚════╝ ╚══════╝", COLORS.cyan);
|
|
43
|
+
console.log();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function printHelp() {
|
|
47
|
+
console.log(`
|
|
48
|
+
${COLORS.bold}Usage:${COLORS.reset}
|
|
49
|
+
npx nitronjs <project-name>
|
|
50
|
+
|
|
51
|
+
${COLORS.bold}Arguments:${COLORS.reset}
|
|
52
|
+
project-name Name of the project directory to create
|
|
53
|
+
|
|
54
|
+
${COLORS.bold}Examples:${COLORS.reset}
|
|
55
|
+
${COLORS.cyan}npx nitronjs my-app${COLORS.reset}
|
|
56
|
+
${COLORS.cyan}npx nitronjs blog${COLORS.reset}
|
|
57
|
+
${COLORS.cyan}npx nitronjs e-commerce${COLORS.reset}
|
|
58
|
+
|
|
59
|
+
${COLORS.bold}Project name rules:${COLORS.reset}
|
|
60
|
+
- Can contain letters, numbers, hyphens, and underscores
|
|
61
|
+
- Cannot start with a number
|
|
62
|
+
- Cannot contain spaces or special characters
|
|
63
|
+
`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function validateProjectName(name) {
|
|
67
|
+
if (!name) {
|
|
68
|
+
return { valid: false, error: "Project name is required" };
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (/^\d/.test(name)) {
|
|
72
|
+
return { valid: false, error: "Project name cannot start with a number" };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_-]*$/.test(name)) {
|
|
76
|
+
return { valid: false, error: "Project name can only contain letters, numbers, hyphens, and underscores" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (name.length < 2) {
|
|
80
|
+
return { valid: false, error: "Project name must be at least 2 characters" };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (name.length > 50) {
|
|
84
|
+
return { valid: false, error: "Project name cannot exceed 50 characters" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return { valid: true };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function copyDir(src, dest) {
|
|
91
|
+
if (!fs.existsSync(dest)) {
|
|
92
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
96
|
+
let fileCount = 0;
|
|
97
|
+
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
const srcPath = path.join(src, entry.name);
|
|
100
|
+
const destPath = path.join(dest, entry.name);
|
|
101
|
+
|
|
102
|
+
if (entry.isDirectory()) {
|
|
103
|
+
fileCount += copyDir(srcPath, destPath);
|
|
104
|
+
} else {
|
|
105
|
+
fs.copyFileSync(srcPath, destPath);
|
|
106
|
+
fileCount++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return fileCount;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function generateAppKey() {
|
|
114
|
+
return crypto.randomBytes(32).toString("base64");
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function updatePackageJson(projectPath, projectName) {
|
|
118
|
+
const pkgPath = path.join(projectPath, "package.json");
|
|
119
|
+
|
|
120
|
+
if (fs.existsSync(pkgPath)) {
|
|
121
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
122
|
+
pkg.name = projectName;
|
|
123
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 4));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function updateEnvFile(projectPath) {
|
|
128
|
+
const envPath = path.join(projectPath, ".env");
|
|
129
|
+
|
|
130
|
+
if (fs.existsSync(envPath)) {
|
|
131
|
+
let content = fs.readFileSync(envPath, "utf-8");
|
|
132
|
+
const appKey = generateAppKey();
|
|
133
|
+
content = content.replace(/APP_KEY=.*/, `APP_KEY=${appKey}`);
|
|
134
|
+
fs.writeFileSync(envPath, content);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function runNpmInstall(projectPath) {
|
|
139
|
+
return new Promise((resolve, reject) => {
|
|
140
|
+
const isWindows = process.platform === "win32";
|
|
141
|
+
const npm = isWindows ? "npm.cmd" : "npm";
|
|
142
|
+
|
|
143
|
+
const child = spawn(npm, ["install"], {
|
|
144
|
+
cwd: projectPath,
|
|
145
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
let output = "";
|
|
149
|
+
|
|
150
|
+
child.stdout.on("data", (data) => {
|
|
151
|
+
output += data.toString();
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
child.stderr.on("data", (data) => {
|
|
155
|
+
output += data.toString();
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
child.on("close", (code) => {
|
|
159
|
+
if (code === 0) {
|
|
160
|
+
resolve(output);
|
|
161
|
+
} else {
|
|
162
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
child.on("error", (err) => {
|
|
167
|
+
reject(err);
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function printSuccess(projectName) {
|
|
173
|
+
console.log();
|
|
174
|
+
log("╔══════════════════════════════════════════════════════════════╗", COLORS.green);
|
|
175
|
+
log("║ ║", COLORS.green);
|
|
176
|
+
log("║ ✓ Project created successfully! ║", COLORS.green);
|
|
177
|
+
log("║ ║", COLORS.green);
|
|
178
|
+
log("╚══════════════════════════════════════════════════════════════╝", COLORS.green);
|
|
179
|
+
console.log();
|
|
180
|
+
|
|
181
|
+
log(`${COLORS.bold}Next steps:${COLORS.reset}`);
|
|
182
|
+
console.log();
|
|
183
|
+
log(` ${COLORS.dim}1.${COLORS.reset} ${COLORS.cyan}cd ${projectName}${COLORS.reset}`);
|
|
184
|
+
log(` ${COLORS.dim}2.${COLORS.reset} Configure your ${COLORS.yellow}.env${COLORS.reset} file`);
|
|
185
|
+
log(` ${COLORS.dim}3.${COLORS.reset} ${COLORS.cyan}njs migrate${COLORS.reset}`);
|
|
186
|
+
log(` ${COLORS.dim}4.${COLORS.reset} ${COLORS.cyan}njs dev${COLORS.reset}`);
|
|
187
|
+
console.log();
|
|
188
|
+
|
|
189
|
+
log(`${COLORS.bold}Useful commands:${COLORS.reset}`);
|
|
190
|
+
console.log();
|
|
191
|
+
log(` ${COLORS.cyan}njs dev${COLORS.reset} ${COLORS.dim}Start development server${COLORS.reset}`);
|
|
192
|
+
log(` ${COLORS.cyan}njs build${COLORS.reset} ${COLORS.dim}Build for production${COLORS.reset}`);
|
|
193
|
+
log(` ${COLORS.cyan}njs start${COLORS.reset} ${COLORS.dim}Start production server${COLORS.reset}`);
|
|
194
|
+
log(` ${COLORS.cyan}njs migrate${COLORS.reset} ${COLORS.dim}Run database migrations${COLORS.reset}`);
|
|
195
|
+
log(` ${COLORS.cyan}njs seed${COLORS.reset} ${COLORS.dim}Run database seeders${COLORS.reset}`);
|
|
196
|
+
log(` ${COLORS.cyan}njs make:controller${COLORS.reset} ${COLORS.dim}Create a new controller${COLORS.reset}`);
|
|
197
|
+
log(` ${COLORS.cyan}njs make:model${COLORS.reset} ${COLORS.dim}Create a new model${COLORS.reset}`);
|
|
198
|
+
console.log();
|
|
199
|
+
|
|
200
|
+
log(`${COLORS.dim}Documentation: https://nitronjs.dev/docs${COLORS.reset}`);
|
|
201
|
+
console.log();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default async function create(projectName, options = {}) {
|
|
205
|
+
if (options.help || projectName === "--help" || projectName === "-h") {
|
|
206
|
+
printHelp();
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
printBanner();
|
|
211
|
+
|
|
212
|
+
const validation = validateProjectName(projectName);
|
|
213
|
+
if (!validation.valid) {
|
|
214
|
+
log(`${COLORS.red}Error: ${validation.error}${COLORS.reset}`);
|
|
215
|
+
console.log();
|
|
216
|
+
log(`${COLORS.dim}Run 'npx nitronjs --help' for usage information${COLORS.reset}`);
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
|
221
|
+
|
|
222
|
+
if (fs.existsSync(projectPath)) {
|
|
223
|
+
log(`${COLORS.red}Error: Directory "${projectName}" already exists${COLORS.reset}`);
|
|
224
|
+
console.log();
|
|
225
|
+
log(`${COLORS.dim}Please choose a different project name or remove the existing directory${COLORS.reset}`);
|
|
226
|
+
process.exit(1);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (!fs.existsSync(SKELETON_DIR)) {
|
|
230
|
+
log(`${COLORS.red}Error: Skeleton directory not found${COLORS.reset}`);
|
|
231
|
+
log(`${COLORS.dim}This may indicate a corrupted installation. Try reinstalling NitronJS.${COLORS.reset}`);
|
|
232
|
+
process.exit(1);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
log(`${COLORS.bold}Creating project: ${COLORS.cyan}${projectName}${COLORS.reset}`);
|
|
236
|
+
console.log();
|
|
237
|
+
|
|
238
|
+
logStep("Copying project files...", "running");
|
|
239
|
+
const fileCount = copyDir(SKELETON_DIR, projectPath);
|
|
240
|
+
logStep(`Copied ${fileCount} files`, "success");
|
|
241
|
+
|
|
242
|
+
logStep("Configuring package.json...", "running");
|
|
243
|
+
updatePackageJson(projectPath, projectName);
|
|
244
|
+
logStep("Package configured", "success");
|
|
245
|
+
|
|
246
|
+
logStep("Generating APP_KEY...", "running");
|
|
247
|
+
updateEnvFile(projectPath);
|
|
248
|
+
logStep("APP_KEY generated", "success");
|
|
249
|
+
|
|
250
|
+
logStep("Installing dependencies (this may take a moment)...", "running");
|
|
251
|
+
try {
|
|
252
|
+
await runNpmInstall(projectPath);
|
|
253
|
+
logStep("Dependencies installed", "success");
|
|
254
|
+
} catch (error) {
|
|
255
|
+
logStep("Failed to install dependencies", "error");
|
|
256
|
+
log(`${COLORS.yellow}Please run 'npm install' manually in the project directory${COLORS.reset}`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
printSuccess(projectName);
|
|
260
|
+
}
|