@erwininteractive/mvc 0.5.1 → 0.6.1

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
@@ -546,6 +546,50 @@ NODE_ENV=development # Environment
546
546
 
547
547
  ---
548
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>
573
+ ```
574
+
575
+ ### API-Only Mode
576
+
577
+ Disable EJS engine for pure API responses:
578
+
579
+ ```typescript
580
+ const { app } = await createMvcApp({
581
+ viewsPath: "src/views",
582
+ disableViewEngine: true, // Don't setup EJS
583
+ });
584
+
585
+ app.get("/api/users", async (req, res) => {
586
+ const users = await prisma.user.findMany();
587
+ res.json(users); // Always JSON, no EJS
588
+ });
589
+ ```
590
+
591
+ ---
592
+
549
593
  ## Learn More
550
594
 
551
595
  - [Express.js Documentation](https://expressjs.com/)
package/dist/cli.js CHANGED
@@ -7,11 +7,19 @@ const generateModel_1 = require("./generators/generateModel");
7
7
  const generateController_1 = require("./generators/generateController");
8
8
  const generateResource_1 = require("./generators/generateResource");
9
9
  const generateWebAuthn_1 = require("./generators/generateWebAuthn");
10
+ const listRoutes_1 = require("./generators/listRoutes");
10
11
  const program = new commander_1.Command();
11
12
  program
12
13
  .name("erwinmvc")
13
14
  .description("CLI for @erwininteractive/mvc framework")
14
- .version("0.2.0");
15
+ .version("0.2.0")
16
+ .addHelpText("after", `
17
+ Examples:
18
+ $ erwinmvc init myapp Create a new app
19
+ $ erwinmvc generate resource Post Generate CRUD for Post
20
+ $ erwinmvc webauthn Setup passkey authentication
21
+ $ erwinmvc list:routes Show all routes
22
+ `);
15
23
  // Init command - scaffold a new application
16
24
  program
17
25
  .command("init <dir>")
@@ -94,4 +102,18 @@ program
94
102
  process.exit(1);
95
103
  }
96
104
  });
105
+ // List routes command - shows all defined routes
106
+ program
107
+ .command("list:routes")
108
+ .alias("lr")
109
+ .description("List all routes in the application")
110
+ .action(async () => {
111
+ try {
112
+ (0, listRoutes_1.listRoutes)();
113
+ }
114
+ catch (err) {
115
+ console.error("Error:", err instanceof Error ? err.message : err);
116
+ process.exit(1);
117
+ }
118
+ });
97
119
  program.parse();
@@ -0,0 +1,22 @@
1
+ interface RouteInfo {
2
+ method: string;
3
+ path: string;
4
+ handler: string;
5
+ }
6
+ /**
7
+ * Parse routes from server.ts file
8
+ */
9
+ export declare function parseRoutes(serverPath: string): RouteInfo[];
10
+ /**
11
+ * Format routes as a table
12
+ */
13
+ export declare function formatRoutes(routes: RouteInfo[]): string;
14
+ /**
15
+ * Get server.ts path from current directory
16
+ */
17
+ export declare function getServerPath(cwd?: string): string;
18
+ /**
19
+ * List all routes in the current project
20
+ */
21
+ export declare function listRoutes(cwd?: string): void;
22
+ export {};
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseRoutes = parseRoutes;
7
+ exports.formatRoutes = formatRoutes;
8
+ exports.getServerPath = getServerPath;
9
+ exports.listRoutes = listRoutes;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ /**
13
+ * Parse routes from server.ts file
14
+ */
15
+ function parseRoutes(serverPath) {
16
+ const routes = [];
17
+ if (!fs_1.default.existsSync(serverPath)) {
18
+ return routes;
19
+ }
20
+ const content = fs_1.default.readFileSync(serverPath, "utf-8");
21
+ const lines = content.split("\n");
22
+ for (const line of lines) {
23
+ // Match app.METHOD("/path", handler)
24
+ const routeMatch = line.match(/app\.(\w+)\s*\(\s*["']([^"']+)["']/);
25
+ if (routeMatch) {
26
+ const currentMethod = routeMatch[1].toLowerCase();
27
+ const currentPath = routeMatch[2];
28
+ let currentHandler = "";
29
+ // Extract handler name - look for arrow function or identifier
30
+ const arrowFuncMatch = line.match(/=>\s*\{/);
31
+ if (arrowFuncMatch) {
32
+ currentHandler = "<anonymous>";
33
+ }
34
+ else {
35
+ const handlerMatch = line.match(/,\s*([^\s,)]+)/);
36
+ if (handlerMatch) {
37
+ currentHandler = handlerMatch[1].trim();
38
+ }
39
+ else {
40
+ currentHandler = "<anonymous>";
41
+ }
42
+ }
43
+ routes.push({
44
+ method: currentMethod.toUpperCase(),
45
+ path: currentPath,
46
+ handler: currentHandler,
47
+ });
48
+ }
49
+ }
50
+ return routes;
51
+ }
52
+ /**
53
+ * Format routes as a table
54
+ */
55
+ function formatRoutes(routes) {
56
+ if (routes.length === 0) {
57
+ return "No routes found.";
58
+ }
59
+ const headers = ["Method", "Path", "Handler"];
60
+ const colWidths = [6, 20, 30];
61
+ const formatRow = (cells) => {
62
+ return cells.map((cell, i) => cell.padEnd(colWidths[i])).join(" | ");
63
+ };
64
+ const separator = colWidths.map(w => "-".repeat(w)).join("-+-");
65
+ let output = "";
66
+ output += formatRow(headers) + "\n";
67
+ output += separator + "\n";
68
+ for (const route of routes) {
69
+ output += formatRow([
70
+ route.method,
71
+ route.path,
72
+ route.handler,
73
+ ]) + "\n";
74
+ }
75
+ return output;
76
+ }
77
+ /**
78
+ * Get server.ts path from current directory
79
+ */
80
+ function getServerPath(cwd = process.cwd()) {
81
+ const possiblePaths = [
82
+ path_1.default.join(cwd, "src", "server.ts"),
83
+ path_1.default.join(cwd, "server.ts"),
84
+ ];
85
+ for (const p of possiblePaths) {
86
+ if (fs_1.default.existsSync(p)) {
87
+ return p;
88
+ }
89
+ }
90
+ return possiblePaths[0];
91
+ }
92
+ /**
93
+ * List all routes in the current project
94
+ */
95
+ function listRoutes(cwd = process.cwd()) {
96
+ const serverPath = getServerPath(cwd);
97
+ const routes = parseRoutes(serverPath);
98
+ console.log("\nDefined Routes:\n");
99
+ console.log(formatRoutes(routes));
100
+ if (routes.length === 0) {
101
+ console.log("\nNo routes found. Add routes to src/server.ts");
102
+ }
103
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@erwininteractive/mvc",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
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",
@@ -203,6 +203,32 @@ Then run migrations again:
203
203
  npx prisma migrate dev --name add-post-fields
204
204
  ```
205
205
 
206
+ ## Full-Stack Flexibility
207
+
208
+ ### Island Pattern (EJS + React)
209
+
210
+ Use EJS for layout while adding React components where needed:
211
+
212
+ ```html
213
+ <div id="comment-section" data-post-id="<%= post.id %>"></div>
214
+ <script type="module" src="/src/components/CommentSection.tsx"></script>
215
+ ```
216
+
217
+ ### API-Only Mode
218
+
219
+ For pure API development, disable EJS:
220
+
221
+ ```typescript
222
+ const { app } = await createMvcApp({
223
+ viewsPath: "src/views",
224
+ disableViewEngine: true,
225
+ });
226
+
227
+ app.get("/api/users", async (req, res) => {
228
+ res.json(users); // Always JSON
229
+ });
230
+ ```
231
+
206
232
  ### Step 5: Use in Your Code
207
233
 
208
234
  ```typescript
@@ -286,10 +312,22 @@ Link to them in your templates:
286
312
 
287
313
  | Command | Description |
288
314
  |---------|-------------|
289
- | `npx erwinmvc generate controller <Name>` | Create a CRUD controller |
290
- | `npx erwinmvc generate model <Name>` | Create a database model |
291
-
292
- ---
315
+ | `npx @erwininteractive/mvc init <dir>` | Create a new app |
316
+ | `npx erwinmvc generate resource <name>` | Generate model + controller + views |
317
+ | `npx erwinmvc generate controller <name>` | Generate a CRUD controller |
318
+ | `npx erwinmvc generate model <name>` | Generate a database model |
319
+ | `npx erwinmvc webauthn` | Generate WebAuthn authentication (security keys) |
320
+ | `npx erwinmvc list:routes` | Show all defined routes |
321
+
322
+ ### Init Options
323
+
324
+ | Option | Description |
325
+ |--------|-------------|
326
+ | `--skip-install` | Skip running npm install |
327
+ | `--with-database` | Include Prisma database setup |
328
+ | `--with-ci` | Include GitHub Actions CI workflow |
329
+
330
+ ### Resource Options
293
331
 
294
332
  ## Learn More
295
333