@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 +44 -0
- package/dist/cli.js +23 -1
- package/dist/generators/listRoutes.d.ts +22 -0
- package/dist/generators/listRoutes.js +103 -0
- package/package.json +1 -1
- package/templates/appScaffold/README.md +42 -4
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
|
@@ -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
|
|
290
|
-
| `npx erwinmvc generate
|
|
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
|
|