@erwininteractive/mvc 0.7.1 → 0.8.2

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 CHANGED
@@ -4,13 +4,21 @@ A lightweight, full-featured MVC framework for Node.js 20+ built with TypeScript
4
4
 
5
5
  ## Features
6
6
 
7
- - **Express** - Fast, minimal web framework
8
- - **EJS + Alpine.js** - Server-side templating with reactive client-side components
9
- - **Optional Database** - Add Prisma + PostgreSQL when you need it
10
- - **Optional Redis Sessions** - Scalable session management
7
+ - **Express** - Fast, unopinionated web framework for routing and middleware
8
+ - **EJS Templating** - Server-side templating with embedded JavaScript
9
+ - **Prisma ORM** - Type-safe database client (optional, PostgreSQL/MySQL/SQLite support)
10
+ - **Redis Sessions** - Scalable session management with connect-redis
11
11
  - **JWT Authentication** - Secure token-based auth with bcrypt password hashing
12
+ - **Session-Based Auth** - Traditional session management with Express-Session
12
13
  - **WebAuthn (Passkeys)** - Passwordless authentication with security keys (YubiKey, Touch ID, Face ID)
13
- - **CLI Tools** - Scaffold apps and generate models/controllers
14
+ - **Tailwind CSS** - Modern, utility-first CSS framework (optional, included with `--with-tailwind`)
15
+ - **Zod Validation** - Type-safe form validation with TypeScript-first schemas
16
+ - **CLI Tools** - Scaffold apps with `--with-tailwind`, generate models/controllers/resources
17
+ - **Flash Messages** - Session-based success/error messages for forms
18
+ - **Cookie Parser** - Built-in cookie parsing for JWT and session support
19
+ - **Helmet Security** - HTTP header security middleware
20
+ - **CORS Support** - Cross-origin resource sharing middleware
21
+ - **GitHub Actions CI** - Automated testing with optional CI setup
14
22
 
15
23
  ## Quick Start
16
24
 
@@ -22,581 +30,268 @@ npm run dev
22
30
 
23
31
  Visit http://localhost:3000 - your app is running!
24
32
 
25
- ---
26
-
27
- ## Getting Started
28
-
29
- ### Step 1: Create a New Page
30
-
31
- Create `src/views/about.ejs`:
32
-
33
- ```html
34
- <!doctype html>
35
- <html>
36
- <head>
37
- <title><%= title %></title>
38
- </head>
39
- <body>
40
- <h1><%= title %></h1>
41
- <p>Welcome to my about page!</p>
42
- </body>
43
- </html>
44
- ```
45
-
46
- ### Step 2: Add a Route
47
-
48
- Edit `src/server.ts`:
49
-
50
- ```typescript
51
- app.get("/about", (req, res) => {
52
- res.render("about", { title: "About Us" });
53
- });
54
- ```
55
-
56
- ### Step 3: View Your Page
57
-
58
- Visit http://localhost:3000/about
59
-
60
- ---
61
-
62
- ## Creating Pages
63
-
64
- ### EJS Templates
65
-
66
- Create `.ejs` files in `src/views/`. EJS lets you use JavaScript in HTML:
67
-
68
- ```html
69
- <!-- Output a variable (escaped) -->
70
- <h1><%= title %></h1>
71
-
72
- <!-- Output raw HTML -->
73
- <%- htmlContent %>
74
-
75
- <!-- JavaScript logic -->
76
- <% if (user) { %>
77
- <p>Welcome, <%= user.name %>!</p>
78
- <% } %>
79
-
80
- <!-- Loop through items -->
81
- <ul>
82
- <% items.forEach(item => { %>
83
- <li><%= item.name %></li>
84
- <% }); %>
85
- </ul>
86
-
87
- <!-- Include another template -->
88
- <%- include('partials/header') %>
89
- ```
90
-
91
- ### Adding Routes
92
-
93
- ```typescript
94
- // Simple page
95
- app.get("/contact", (req, res) => {
96
- res.render("contact", { title: "Contact Us" });
97
- });
98
-
99
- // Handle form submission
100
- app.post("/contact", (req, res) => {
101
- const { name, email, message } = req.body;
102
- console.log(`Message from ${name}: ${message}`);
103
- res.redirect("/contact?sent=true");
104
- });
105
-
106
- // JSON API endpoint
107
- app.get("/api/users", (req, res) => {
108
- res.json([{ id: 1, name: "John" }]);
109
- });
110
- ```
111
-
112
- ---
113
-
114
- ## Resources
33
+ ## CLI Commands
115
34
 
116
- Generate a complete resource (model + controller + views) with one command:
35
+ ### Initialize a new app
117
36
 
118
37
  ```bash
119
- npx erwinmvc generate resource Post
120
- ```
121
-
122
- This creates:
123
- - `prisma/schema.prisma` - Adds the Post model
124
- - `src/controllers/PostController.ts` - Full CRUD controller with form handling
125
- - `src/views/posts/index.ejs` - List view
126
- - `src/views/posts/show.ejs` - Detail view
127
- - `src/views/posts/create.ejs` - Create form
128
- - `src/views/posts/edit.ejs` - Edit form
38
+ # Basic app
39
+ npx @erwininteractive/mvc init myapp
129
40
 
130
- ### Resource Routes
41
+ # With Tailwind CSS
42
+ npx @erwininteractive/mvc init myapp --with-tailwind
131
43
 
132
- | Action | HTTP Method | URL | Description |
133
- |-----------|-------------|------------------|------------------|
134
- | `index` | GET | /posts | List all |
135
- | `create` | GET | /posts/create | Show create form |
136
- | `store` | POST | /posts | Create new |
137
- | `show` | GET | /posts/:id | Show one |
138
- | `edit` | GET | /posts/:id/edit | Show edit form |
139
- | `update` | PUT | /posts/:id | Update |
140
- | `destroy` | DELETE | /posts/:id | Delete |
44
+ # With database support (Prisma)
45
+ npx @erwininteractive/mvc init myapp --with-database
141
46
 
142
- ### Wiring Up Routes
47
+ # With database + Tailwind
48
+ npx @erwininteractive/mvc init myapp --with-database --with-tailwind
143
49
 
144
- Add to `src/server.ts`:
50
+ # With GitHub Actions CI
51
+ npx @erwininteractive/mvc init myapp --with-ci
145
52
 
146
- ```typescript
147
- import * as PostController from "./controllers/PostController";
148
-
149
- app.get("/posts", PostController.index);
150
- app.get("/posts/create", PostController.create);
151
- app.post("/posts", PostController.store);
152
- app.get("/posts/:id", PostController.show);
153
- app.get("/posts/:id/edit", PostController.edit);
154
- app.put("/posts/:id", PostController.update);
155
- app.delete("/posts/:id", PostController.destroy);
53
+ # Skip npm install (install manually later)
54
+ npx @erwininteractive/mvc init myapp --skip-install
156
55
  ```
157
56
 
158
- ---
159
-
160
- ## Controllers
161
-
162
- Generate just a controller (without model/views):
57
+ ### Generate models
163
58
 
164
59
  ```bash
165
- npx erwinmvc generate controller Product
166
- ```
167
-
168
- This creates `src/controllers/ProductController.ts` with CRUD actions:
169
-
170
- | Action | HTTP Method | URL | Description |
171
- |-----------|-------------|------------------|-------------|
172
- | `index` | GET | /products | List all |
173
- | `show` | GET | /products/:id | Show one |
174
- | `store` | POST | /products | Create |
175
- | `update` | PUT | /products/:id | Update |
176
- | `destroy` | DELETE | /products/:id | Delete |
177
-
178
- ### Using Controllers
179
-
180
- ```typescript
181
- import * as ProductController from "./controllers/ProductController";
60
+ # Generate a Prisma model
61
+ npx erwinmvc generate model User
62
+ npx erwinmvc g model User
182
63
 
183
- app.get("/products", ProductController.index);
184
- app.get("/products/:id", ProductController.show);
185
- app.post("/products", ProductController.store);
186
- app.put("/products/:id", ProductController.update);
187
- app.delete("/products/:id", ProductController.destroy);
64
+ # Generate without running migration
65
+ npx erwinmvc generate model User --skip-migrate
188
66
  ```
189
67
 
190
- ---
191
-
192
- ## Database (Optional)
193
-
194
- Your app works without a database. Add one when you need it.
195
-
196
- ### Setup
68
+ ### Generate controllers
197
69
 
198
70
  ```bash
199
- npm run db:setup
200
- ```
201
-
202
- Edit `.env` with your database URL:
203
-
204
- ```
205
- DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
206
- ```
207
-
208
- Run migrations:
71
+ # Generate a CRUD controller
72
+ npx erwinmvc generate controller User
73
+ npx erwinmvc g controller User
209
74
 
210
- ```bash
211
- npx prisma migrate dev --name init
75
+ # Generate without views
76
+ npx erwinmvc generate controller User --no-views
212
77
  ```
213
78
 
214
- ### Generate Models
79
+ ### Generate resources (model + controller + views)
215
80
 
216
81
  ```bash
217
- npx erwinmvc generate model Post
218
- ```
82
+ # Complete resource with all features
83
+ npx erwinmvc generate resource Post
84
+ npx erwinmvc g resource Post
219
85
 
220
- Edit `prisma/schema.prisma` to add fields:
86
+ # Skip specific parts
87
+ npx erwinmvc generate resource Post --skip-model
88
+ npx erwinmvc generate resource Post --skip-views
89
+ npx erwinmvc generate resource Post --skip-controller
221
90
 
222
- ```prisma
223
- model Post {
224
- id Int @id @default(autoincrement())
225
- title String
226
- content String?
227
- published Boolean @default(false)
228
- createdAt DateTime @default(now())
229
- updatedAt DateTime @updatedAt
91
+ # Skip database migration
92
+ npx erwinmvc generate resource Post --skip-migrate
230
93
 
231
- @@map("posts")
232
- }
94
+ # API-only resource (no views, JSON responses)
95
+ npx erwinmvc generate resource Post --api-only
233
96
  ```
234
97
 
235
- Run migrations again:
98
+ ### Authentication commands
236
99
 
237
100
  ```bash
238
- npx prisma migrate dev --name add-post-fields
239
- ```
101
+ # Generate complete authentication system (login/register)
102
+ npx erwinmvc make:auth
103
+ npx erwinmvc ma
240
104
 
241
- ### Use in Code
105
+ # Skip User model (use existing)
106
+ npx erwinmvc make:auth --without-model
242
107
 
243
- ```typescript
244
- import { getPrismaClient } from "@erwininteractive/mvc";
108
+ # Only JWT (no sessions)
109
+ npx erwinmvc make:auth --jwt-only
245
110
 
246
- const prisma = getPrismaClient();
247
-
248
- app.get("/posts", async (req, res) => {
249
- const posts = await prisma.post.findMany();
250
- res.render("posts/index", { posts });
251
- });
252
- ```
253
-
254
- ---
255
-
256
- ## Authentication
257
-
258
- ```typescript
259
- import {
260
- hashPassword,
261
- verifyPassword,
262
- signToken,
263
- verifyToken,
264
- authenticate,
265
- } from "@erwininteractive/mvc";
266
-
267
- // Hash a password
268
- const hash = await hashPassword("secret123");
269
-
270
- // Verify a password
271
- const isValid = await verifyPassword("secret123", hash);
272
-
273
- // Sign a JWT
274
- const token = signToken({ userId: 1, email: "user@example.com" });
275
-
276
- // Protect routes with middleware
277
- app.get("/protected", authenticate, (req, res) => {
278
- res.json({ user: req.user });
279
- });
280
- ```
281
-
282
- ### Auto-Inject User to Views
283
-
284
- When a user is authenticated, their information is automatically available in all EJS templates via `res.locals.user`:
285
-
286
- ```typescript
287
- // In any EJS template
288
- <% if (user) { %>
289
- <p>Welcome, <%= user.email %></p>
290
- <% } else { %>
291
- <a href="/auth/login">Login</a>
292
- <% } %>
111
+ # Only sessions (no JWT)
112
+ npx erwinmvc make:auth --session-only
293
113
  ```
294
114
 
295
- The middleware automatically:
296
- - Extracts JWT from `req.cookies.token` or `Authorization` header
297
- - Verifies the token using `JWT_SECRET`
298
- - Sets `req.user` and `res.locals.user` for use in views
299
-
300
- ---
301
-
302
115
  ### WebAuthn (Passkeys)
303
116
 
304
- Passwordless authentication with security keys:
305
-
306
117
  ```bash
118
+ # Generate WebAuthn authentication (security key login)
307
119
  npx erwinmvc webauthn
308
- ```
309
-
310
- This generates:
311
- - `src/controllers/WebAuthnController.ts` - Registration and login handlers
312
- - `src/views/webauthn/` - EJS views for register/login pages
313
- - Prisma `WebAuthnCredential` model for storing security key data
314
-
315
- ```typescript
316
- import {
317
- startRegistration,
318
- completeRegistration,
319
- startAuthentication,
320
- completeAuthentication,
321
- getRPConfig,
322
- } from "@erwininteractive/mvc";
323
-
324
- const { rpID, rpName } = getRPConfig();
120
+ npx erwinmvc w
325
121
 
326
- const options = await startRegistration(req, res);
327
- await completeRegistration(req, res);
328
-
329
- const options = await startAuthentication(req, res);
330
- await completeAuthentication(req, res);
122
+ # Skip database migration
123
+ npx erwinmvc webauthn --skip-migrate
331
124
  ```
332
125
 
333
- Environment variables:
334
- - `WEBAUTHN_RP_ID` - Your domain (e.g., "localhost" or "example.com")
335
- - `WEBAUTHN_RP_NAME` - Your app name (e.g., "ErwinMVC App")
336
-
337
- Note: WebAuthn requires HTTPS or localhost.
338
-
339
- ---
340
-
341
- ## CLI Commands
342
-
343
- | Command | Description |
344
- |---------|-------------|
345
- | `npx @erwininteractive/mvc init <dir>` | Create a new app |
346
- | `npx erwinmvc generate resource <name>` | Generate model + controller + views |
347
- | `npx erwinmvc generate controller <name>` | Generate a CRUD controller |
348
- | `npx erwinmvc generate model <name>` | Generate a database model |
349
- | `npx erwinmvc webauthn` | Generate WebAuthn authentication (security keys) |
350
-
351
- ### Init Options
352
-
353
- | Option | Description |
354
- |--------|-------------|
355
- | `--skip-install` | Skip running npm install |
356
- | `--with-database` | Include Prisma database setup |
357
- | `--with-ci` | Include GitHub Actions CI workflow |
358
-
359
- ### Resource Options
360
-
361
- | Option | Description |
362
- |--------|-------------|
363
- | `--skip-model` | Skip generating Prisma model |
364
- | `--skip-controller` | Skip generating controller |
365
- | `--skip-views` | Skip generating views |
366
- | `--skip-migrate` | Skip running Prisma migrate |
367
- | `--api-only` | Generate API-only controller (no views) |
126
+ ### List routes
368
127
 
369
- ### Other Generate Options
370
-
371
- | Option | Description |
372
- |--------|-------------|
373
- | `--skip-migrate` | Skip running Prisma migrate (model) |
374
- | `--no-views` | Skip generating EJS views (controller) |
375
-
376
- ---
128
+ ```bash
129
+ # Show all defined routes
130
+ npx erwinmvc list:routes
131
+ npx erwinmvc lr
132
+ ```
377
133
 
378
134
  ## Project Structure
379
135
 
380
136
  ```
381
137
  myapp/
382
138
  ├── src/
383
- │ ├── server.ts # Main app - add routes here
384
- │ ├── views/ # EJS templates
385
- ├── controllers/ # Route handlers (optional)
386
- │ └── middleware/ # Express middleware (optional)
387
- ├── public/ # Static files (CSS, JS, images)
388
- ├── prisma/ # Database (after db:setup)
389
- │ └── schema.prisma
390
- ├── .env.example
391
- ├── .gitignore
139
+ │ ├── controllers/ # MVC controllers
140
+ │ ├── views/ # EJS templates
141
+ └── server.ts # App entry point
142
+ ├── public/ # Static assets
143
+ │ └── dist/ # Compiled CSS (Tailwind)
144
+ ├── prisma/ # Database (optional)
145
+ │ └── schema.prisma # Prisma schema
146
+ ├── .github/ # CI workflows (optional)
392
147
  ├── package.json
393
- └── tsconfig.json
148
+ ├── tsconfig.json
149
+ ├── tailwind.config.ts # Tailwind config (optional)
150
+ └── postcss.config.cjs # PostCSS config (optional)
394
151
  ```
395
152
 
396
- ### Static Files
153
+ ## Database Setup
397
154
 
398
- Files in `public/` are served at the root URL:
155
+ ```bash
156
+ # Setup Prisma database
157
+ npx erwinmvc init myapp --with-database
158
+ cd myapp
399
159
 
400
- ```
401
- public/css/style.css → /css/style.css
402
- public/images/logo.png → /images/logo.png
160
+ # Edit .env with your DATABASE_URL
161
+ # DATABASE_URL="postgresql://user:password@localhost:5432/mydb"
162
+
163
+ # Run migrations
164
+ npx prisma migrate dev --name init
165
+
166
+ # Generate Prisma client (auto-run by CLI)
167
+ npx prisma generate
403
168
  ```
404
169
 
405
- ---
170
+ ## Tailwind CSS Setup
406
171
 
407
- ## App Commands
172
+ ```bash
173
+ npx erwinmvc init myapp --with-tailwind
174
+ cd myapp
408
175
 
409
- | Command | Description |
410
- |---------|-------------|
411
- | `npm run dev` | Start development server (auto-reload) |
412
- | `npm run build` | Build for production |
413
- | `npm start` | Run production build |
414
- | `npm run db:setup` | Install database dependencies |
415
- | `npm run db:migrate` | Run database migrations |
176
+ # Configure content paths in tailwind.config.ts
177
+ # Edit src/assets/tailwind.css for custom styles
416
178
 
417
- ---
179
+ # Build CSS
180
+ npx tailwindcss -i ./src/assets/tailwind.css -o ./public/dist/tailwind.css --watch
181
+ ```
418
182
 
419
- ## CI/CD (Optional)
183
+ ## Authentication
420
184
 
421
- Add GitHub Actions CI to your project for automated testing:
185
+ ### Session + JWT Auth
422
186
 
423
- ```bash
424
- npx @erwininteractive/mvc init myapp --with-ci
425
- ```
187
+ The `make:auth` command generates:
426
188
 
427
- Or add CI to an existing project by creating `.github/workflows/test.yml`:
189
+ - User model with password hashing
190
+ - Register/login forms with Zod validation
191
+ - Session-based authentication
192
+ - JWT token generation for API access
193
+ - Password verification with bcrypt
194
+ - Flash messages for errors/success
428
195
 
429
- ```yaml
430
- name: Test
196
+ ### WebAuthn (Passkeys)
431
197
 
432
- on:
433
- push:
434
- branches: [main]
435
- pull_request:
436
- branches: [main]
198
+ The `webauthn` command generates:
437
199
 
438
- jobs:
439
- test:
440
- runs-on: ubuntu-latest
200
+ - Passkey registration flow
201
+ - Passkey authentication flow
202
+ - User credential storage in database
203
+ - Security key support (YubiKey, Touch ID, Face ID)
441
204
 
442
- steps:
443
- - name: Checkout
444
- uses: actions/checkout@v4
205
+ ## Validation
445
206
 
446
- - name: Setup Node.js
447
- uses: actions/setup-node@v4
448
- with:
449
- node-version: "20"
450
- cache: "npm"
207
+ Use Zod schemas for type-safe form validation:
451
208
 
452
- - name: Install dependencies
453
- run: npm ci
209
+ ```typescript
210
+ import { z } from "zod";
211
+ import { validate } from "@erwininteractive/mvc";
454
212
 
455
- - name: Run tests
456
- run: npm test
213
+ const userSchema = z.object({
214
+ username: z.string().min(3),
215
+ email: z.string().email(),
216
+ password: z.string().min(8)
217
+ });
457
218
 
458
- - name: Build
459
- run: npm run build
219
+ app.post("/users", validate(userSchema), async (req, res) => {
220
+ const user = req.validatedBody; // Type-safe validated data
221
+ // ...
222
+ });
460
223
  ```
461
224
 
462
- ### Adding Database Tests
463
-
464
- If your app uses a database, add PostgreSQL as a service:
465
-
466
- ```yaml
467
- jobs:
468
- test:
469
- runs-on: ubuntu-latest
470
-
471
- services:
472
- postgres:
473
- image: postgres:16
474
- env:
475
- POSTGRES_USER: postgres
476
- POSTGRES_PASSWORD: postgres
477
- POSTGRES_DB: test
478
- ports:
479
- - 5432:5432
480
- options: >-
481
- --health-cmd pg_isready
482
- --health-interval 10s
483
- --health-timeout 5s
484
- --health-retries 5
485
-
486
- steps:
487
- - name: Checkout
488
- uses: actions/checkout@v4
489
-
490
- - name: Setup Node.js
491
- uses: actions/setup-node@v4
492
- with:
493
- node-version: "20"
494
- cache: "npm"
495
-
496
- - name: Install dependencies
497
- run: npm ci
498
-
499
- - name: Run migrations
500
- run: npx prisma migrate deploy
501
- env:
502
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
503
-
504
- - name: Run tests
505
- run: npm test
506
- env:
507
- DATABASE_URL: postgresql://postgres:postgres@localhost:5432/test
508
-
509
- - name: Build
510
- run: npm run build
511
- ```
225
+ ## API Reference
512
226
 
513
- ### Secrets
227
+ ### Core Functions
514
228
 
515
- For production deployments, add these secrets in your GitHub repository settings:
229
+ - `createMvcApp(options)` - Create Express app with views, static files
230
+ - `startServer(app)` - Start HTTP server on port 3000
231
+ - `hashPassword(plain)` - Hash password with bcrypt
232
+ - `verifyPassword(plain, hash)` - Verify password
233
+ - `signToken(payload, expiresIn)` - Sign JWT token
234
+ - `verifyToken(token)` - Verify and decode JWT
235
+ - `authenticate` - Express middleware for JWT authentication
236
+ - `validate(schema, strategy)` - Zod validation middleware
516
237
 
517
- | Secret | Description |
518
- |--------|-------------|
519
- | `DATABASE_URL` | Production database connection string |
520
- | `REDIS_URL` | Production Redis connection string |
521
- | `JWT_SECRET` | Secret key for JWT signing |
522
- | `SESSION_SECRET` | Secret key for session encryption |
238
+ ### WebAuthn Functions
523
239
 
524
- Access secrets in your workflow:
240
+ - `startRegistration(req, res)` - Begin WebAuthn registration
241
+ - `completeRegistration(req, res)` - Complete WebAuthn registration
242
+ - `startAuthentication(req, res)` - Begin WebAuthn login
243
+ - `completeAuthentication(req, res)` - Complete WebAuthn login
244
+ - `getRPConfig()` - Get relying party configuration
525
245
 
526
- ```yaml
527
- env:
528
- DATABASE_URL: ${{ secrets.DATABASE_URL }}
529
- JWT_SECRET: ${{ secrets.JWT_SECRET }}
530
- ```
246
+ ### Validation Helpers
531
247
 
532
- ---
248
+ - `getFieldErrors(error)` - Extract field errors from Zod error
249
+ - `getOldInput(req)` - Get previously submitted form data
250
+ - `getErrors(req)` - Get flash error messages
251
+ - `hasFieldError(field, errors)` - Check if field has errors
252
+ - `getFieldError(field, errors)` - Get error message for field
533
253
 
534
254
  ## Environment Variables
535
255
 
536
- All optional. Create `.env` from `.env.example`:
256
+ Required for full functionality:
537
257
 
538
258
  ```env
539
- DATABASE_URL="postgresql://user:pass@localhost:5432/mydb" # For database
540
- REDIS_URL="redis://localhost:6379" # For sessions
541
- JWT_SECRET="your-secret-key" # For auth
542
- SESSION_SECRET="your-session-secret" # For sessions
543
- PORT=3000 # Server port
544
- NODE_ENV=development # Environment
545
- ```
259
+ # Required
260
+ JWT_SECRET=your-secret-key-here
546
261
 
547
- ---
548
-
549
- ## Escape Hatch Documentation
550
-
551
- ### Island Pattern (EJS + React)
552
-
553
- Use EJS for layout while adding React components where needed:
554
-
555
- ```html
556
- <!-- layout.ejs -->
557
- <!DOCTYPE html>
558
- <html>
559
- <head><title><%= title %></title></head>
560
- <body>
561
- <header><%- include('partial/header') %></header>
562
-
563
- <main id="app"><%- body %></main>
564
-
565
- <!-- React island -->
566
- <div id="comment-section" data-post-id="<%= post.id %>"></div>
567
-
568
- <footer><%- include('partial/footer') %></footer>
569
-
570
- <script type="module" src="/src/components/CommentSection.tsx"></script>
571
- </body>
572
- </html>
262
+ # Database (optional)
263
+ DATABASE_URL=postgresql://user:password@localhost:5432/mydb
264
+
265
+ # WebAuthn (optional, defaults to localhost)
266
+ WEBAUTHN_RP_ID=localhost
267
+ WEBAUTHN_RP_NAME=MyApp
573
268
  ```
574
269
 
575
- ### API-Only Mode
270
+ ## Testing
576
271
 
577
- Disable EJS engine for pure API responses:
272
+ ```bash
273
+ # Run all tests
274
+ npm test
578
275
 
579
- ```typescript
580
- const { app } = await createMvcApp({
581
- viewsPath: "src/views",
582
- disableViewEngine: true, // Don't setup EJS
583
- });
276
+ # Run specific test file
277
+ npm test -- auth
278
+ npm test -- cli
279
+ npm test -- generators
584
280
 
585
- app.get("/api/users", async (req, res) => {
586
- const users = await prisma.user.findMany();
587
- res.json(users); // Always JSON, no EJS
588
- });
281
+ # With coverage
282
+ npm run test -- --coverage
589
283
  ```
590
284
 
591
- ---
285
+ ## Production Build
592
286
 
593
- ## Learn More
287
+ ```bash
288
+ # Build TypeScript and CLI
289
+ npm run build
594
290
 
595
- - [Express.js Documentation](https://expressjs.com/)
596
- - [EJS Documentation](https://ejs.co/)
597
- - [Prisma Documentation](https://www.prisma.io/docs/)
598
- - [Alpine.js Documentation](https://alpinejs.dev/)
291
+ # The app is ready for production deployment
292
+ # Server runs on port 3000
293
+ ```
599
294
 
600
295
  ## License
601
296
 
602
- MIT
297
+ MIT &copy; [Erwin Interactive](https://github.com/erwininteractive)
@@ -131,48 +131,38 @@ function setupDatabase(targetDir) {
131
131
  }
132
132
  }
133
133
  /**
134
- * Setup Tailwind CSS with PostCSS.
134
+ * Setup Tailwind CSS with pre-built CSS.
135
135
  */
136
136
  function setupTailwind(targetDir) {
137
137
  console.log("\nSetting up Tailwind CSS...");
138
138
  try {
139
139
  (0, child_process_1.execSync)("npm install -D tailwindcss postcss autoprefixer", { cwd: targetDir, stdio: "inherit" });
140
- // Removed npx command - using manual config
141
- // Create assets directory
140
+ const templateDir = (0, paths_1.getTemplatesDir)();
141
+ const templateCss = path_1.default.join(templateDir, "appScaffold", "public", "dist", "tailwind.css");
142
+ const targetCss = path_1.default.join(targetDir, "public", "dist", "tailwind.css");
143
+ fs_1.default.mkdirSync(path_1.default.dirname(targetCss), { recursive: true });
144
+ fs_1.default.copyFileSync(templateCss, targetCss);
142
145
  const assetsDir = path_1.default.join(targetDir, "src", "assets");
143
146
  fs_1.default.mkdirSync(assetsDir, { recursive: true });
144
- // Create tailwind.css
145
- const tailwindCss = `@tailwind base;
146
- @tailwind components;
147
- @tailwind utilities;
148
- `;
149
- fs_1.default.writeFileSync(path_1.default.join(assetsDir, "tailwind.css"), tailwindCss);
150
- // Update postcss.config.cjs
151
- const postcssConfig = `/** @type {import('postcss-load-config').Config} */
152
- const config = {
153
- plugins: {
154
- tailwindcss: {},
155
- autoprefixer: {},
156
- },
157
- };
147
+ fs_1.default.writeFileSync(path_1.default.join(assetsDir, "tailwind.css"), `@import "tailwindcss";\n`);
148
+ const config = `import { type Config } from "tailwindcss";
158
149
 
159
- export default config;
150
+ export default {
151
+ content: ["./src/**/*.{js,ts,tsx}"],
152
+ theme: { extend: {} },
153
+ plugins: [],
154
+ } satisfies Config;
160
155
  `;
161
- fs_1.default.writeFileSync(path_1.default.join(targetDir, "postcss.config.cjs"), postcssConfig);
162
- // Update package.json with tailwind build script
163
- const packageJsonPath = path_1.default.join(targetDir, "package.json");
164
- if (fs_1.default.existsSync(packageJsonPath)) {
165
- const pkg = JSON.parse(fs_1.default.readFileSync(packageJsonPath, "utf-8"));
166
- pkg.scripts.tailwind = "tailwindcss -i ./src/assets/tailwind.css -o ./public/dist/tailwind.css --watch";
167
- pkg.scripts.build = "tsc && npm run tailwind";
168
- fs_1.default.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2));
169
- console.log("✓ Added Tailwind CSS support");
170
- }
156
+ fs_1.default.writeFileSync(path_1.default.join(targetDir, "tailwind.config.ts"), config);
157
+ console.log("✓ Added Tailwind CSS support");
171
158
  }
172
159
  catch {
173
- console.error("Failed to setup Tailwind CSS. Run 'npm install -D tailwindcss postcss autoprefixer' manually.");
160
+ console.error("Failed to setup Tailwind CSS.");
174
161
  }
175
162
  }
163
+ /**
164
+ * Recursively copy a directory.
165
+ */
176
166
  /**
177
167
  * Recursively copy a directory.
178
168
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erwininteractive/mvc",
3
- "version": "0.7.1",
3
+ "version": "0.8.2",
4
4
  "description": "A lightweight, full-featured MVC framework for Node.js with Express, Prisma, and EJS",
5
5
  "main": "dist/framework/index.js",
6
6
  "types": "dist/framework/index.d.ts",
@@ -51,8 +51,10 @@
51
51
  "express-session": "^1.18.0",
52
52
  "helmet": "^8.0.0",
53
53
  "jsonwebtoken": "^9.0.2",
54
+ "postcss": "^8.4.38",
54
55
  "prisma": "^6.0.0",
55
56
  "redis": "^4.7.0",
57
+ "tailwindcss": "^4.2.2",
56
58
  "zod": "^3.23.8"
57
59
  },
58
60
  "devDependencies": {
@@ -1,3 +1 @@
1
- @tailwind base;
2
- @tailwind components;
3
- @tailwind utilities;
1
+ @import "tailwindcss";
@@ -37,7 +37,7 @@ jobs:
37
37
  cache: "npm"
38
38
 
39
39
  - name: Install dependencies
40
- run: npm ci
40
+ run: npm install
41
41
 
42
42
  - name: Run tests
43
43
  run: npm test