@arikajs/docs 0.0.3 → 0.0.5

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
@@ -10,28 +10,35 @@ The goal of this package is to eliminate the manual work of keeping documentatio
10
10
 
11
11
  ### Status
12
12
 
13
- - **Stage**: Experimental / v0.x
14
- - **Scope (v0.x)**:
13
+ - **Stage**: Beta / v0.x
14
+ - **Extensibility**: Driver-based architecture (add your own documentation formats)
15
+ - **Features**:
15
16
  - Route metadata extraction from `@arikajs/router`
16
17
  - Postman Collection (v2.1.0) generation
17
18
  - Arika-themed interactive HTML documentation
18
19
  - Markdown (DOCS.md) generation
19
20
  - OpenAPI 3.0 specification generation
21
+ - **New**: Route filtering by prefix (e.g., generate docs only for `api/v1`)
20
22
 
21
23
  ---
22
24
 
23
25
  ## Features
24
26
 
25
- - **Multi-Format Generation**
27
+ - **🚀 Extensible architecture**
28
+ - Uses a **Driver Pattern** for all documentation formats.
29
+ - Easily add new drivers by implementing the `DocDriver` interface.
30
+
31
+ - **🎨 Multi-Format Generation**
26
32
  - **HTML**: A premium, interactive web page for your API.
27
33
  - **Postman**: Ready-to-import JSON collection with pre-configured headers.
28
34
  - **OpenAPI**: Industry-standard Swagger/OpenAPI 3.0 specification.
29
35
  - **Markdown**: Clean, readable `DOCS.md` for GitHub or local documentation.
30
36
 
31
- - **Route Analysis**
37
+ - **🔍 Intelligent Route Analysis**
38
+ - **Prefix Filtering**: Only document specific sections of your app.
32
39
  - Automatically groups endpoints by prefix (e.g., `api`, `admin`).
33
40
  - Captures route names, methods, and full path hierarchies.
34
- - Displays middleware information for each endpoint.
41
+ - Displays middleware and parameter information.
35
42
 
36
43
  - **Environment Support**
37
44
  - Automatically generates Postman environment JSON with your `base_url`.
@@ -0,0 +1,6 @@
1
+ import { ParsedRoute } from '../types';
2
+ export interface DocDriver {
3
+ generate(routes: ParsedRoute[], appName: string, baseUrl?: string): any;
4
+ getExtension(): string;
5
+ getFilename(appName: string): string;
6
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,12 +1,9 @@
1
- import { PostmanGenerator } from './PostmanGenerator';
2
- import { MarkdownGenerator } from './MarkdownGenerator';
3
- import { HtmlGenerator } from './HtmlGenerator';
4
- import { OpenApiGenerator } from './OpenApiGenerator';
1
+ import { ParsedRoute } from './types';
5
2
  export declare class DocumentationGenerator {
6
- protected postman: PostmanGenerator;
7
- protected markdown: MarkdownGenerator;
8
- protected html: HtmlGenerator;
9
- protected openApi: OpenApiGenerator;
10
- constructor();
11
- generateAll(appName: string, outputDir: string): void;
3
+ /**
4
+ * Generate all documentation formats based on parsed routes.
5
+ */
6
+ generateAll(routes: ParsedRoute[], appName: string, outputDir: string, options?: {
7
+ baseUrl?: string;
8
+ }): void;
12
9
  }
package/dist/Generator.js CHANGED
@@ -4,7 +4,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.DocumentationGenerator = void 0;
7
- const router_1 = require("@arikajs/router");
8
7
  const PostmanGenerator_1 = require("./PostmanGenerator");
9
8
  const MarkdownGenerator_1 = require("./MarkdownGenerator");
10
9
  const HtmlGenerator_1 = require("./HtmlGenerator");
@@ -12,41 +11,46 @@ const OpenApiGenerator_1 = require("./OpenApiGenerator");
12
11
  const fs_1 = __importDefault(require("fs"));
13
12
  const path_1 = __importDefault(require("path"));
14
13
  class DocumentationGenerator {
15
- postman;
16
- markdown;
17
- html;
18
- openApi;
19
- constructor() {
20
- this.postman = new PostmanGenerator_1.PostmanGenerator();
21
- this.markdown = new MarkdownGenerator_1.MarkdownGenerator();
22
- this.html = new HtmlGenerator_1.HtmlGenerator();
23
- this.openApi = new OpenApiGenerator_1.OpenApiGenerator();
24
- }
25
- generateAll(appName, outputDir) {
26
- const routes = router_1.RouteRegistry.getInstance().getRoutes();
14
+ /**
15
+ * Generate all documentation formats based on parsed routes.
16
+ */
17
+ generateAll(routes, appName, outputDir, options = {}) {
18
+ const baseUrl = options.baseUrl || 'http://localhost:3000';
27
19
  if (!fs_1.default.existsSync(outputDir)) {
28
20
  fs_1.default.mkdirSync(outputDir, { recursive: true });
29
21
  }
30
- // Postman
31
- const postmanJson = this.postman.generate(routes, appName);
32
- fs_1.default.writeFileSync(path_1.default.join(outputDir, 'postman_collection.json'), JSON.stringify(postmanJson, null, 2));
33
- // Environment
22
+ // Generate Postman Environment
34
23
  const envJson = {
35
24
  name: `${appName} Env`,
36
25
  values: [
37
- { key: "base_url", value: "http://localhost:3000", enabled: true }
26
+ { key: "base_url", value: baseUrl, enabled: true },
27
+ { key: "token", value: "", enabled: true }
38
28
  ]
39
29
  };
40
- fs_1.default.writeFileSync(path_1.default.join(outputDir, 'postman_environment.json'), JSON.stringify(envJson, null, 2));
41
- // Markdown
42
- const md = this.markdown.generate(routes, appName);
43
- fs_1.default.writeFileSync(path_1.default.join(outputDir, 'DOCS.md'), md);
44
- // HTML
45
- const html = this.html.generate(routes, appName);
46
- fs_1.default.writeFileSync(path_1.default.join(outputDir, 'api_docs.html'), html);
47
- // OpenAPI
48
- const openApiJson = this.openApi.generate(routes, appName);
49
- fs_1.default.writeFileSync(path_1.default.join(outputDir, 'openapi.json'), JSON.stringify(openApiJson, null, 2));
30
+ const envContent = JSON.stringify(envJson, null, 2);
31
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'postman_environment.json'), envContent);
32
+ // Generate Postman Collection
33
+ const postmanGenerator = new PostmanGenerator_1.PostmanGenerator();
34
+ const postmanJson = postmanGenerator.generate(routes, appName, baseUrl);
35
+ const postmanContent = JSON.stringify(postmanJson, null, 2);
36
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'postman_collection.json'), postmanContent);
37
+ // Generate OpenAPI Spec
38
+ const openApiGenerator = new OpenApiGenerator_1.OpenApiGenerator();
39
+ const openApiJson = openApiGenerator.generate(routes, appName, baseUrl);
40
+ const openApiContent = JSON.stringify(openApiJson, null, 2);
41
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'openapi.json'), openApiContent);
42
+ // Generate Markdown
43
+ const markdownGenerator = new MarkdownGenerator_1.MarkdownGenerator();
44
+ const markdownContent = markdownGenerator.generate(routes, appName, baseUrl);
45
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'DOCS.md'), markdownContent);
46
+ // Generate HTML with embedded JSONs
47
+ const htmlGenerator = new HtmlGenerator_1.HtmlGenerator();
48
+ const htmlContent = htmlGenerator.generate(routes, appName, baseUrl, {
49
+ postmanContent,
50
+ envContent,
51
+ openApiContent
52
+ });
53
+ fs_1.default.writeFileSync(path_1.default.join(outputDir, 'api_docs.html'), htmlContent);
50
54
  }
51
55
  }
52
56
  exports.DocumentationGenerator = DocumentationGenerator;
@@ -1,6 +1,9 @@
1
- import { RouteEntry } from '@arikajs/router';
1
+ import { ParsedRoute } from './types';
2
2
  export declare class HtmlGenerator {
3
- generate(routes: RouteEntry[], appName: string): string;
4
- private renderGroup;
3
+ generate(routes: ParsedRoute[], appName: string, baseUrl: string, inlineData: {
4
+ postmanContent: string;
5
+ envContent: string;
6
+ openApiContent: string;
7
+ }): string;
5
8
  private groupByPrefix;
6
9
  }
@@ -2,112 +2,307 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.HtmlGenerator = void 0;
4
4
  class HtmlGenerator {
5
- generate(routes, appName) {
5
+ generate(routes, appName, baseUrl, inlineData) {
6
6
  const groups = this.groupByPrefix(routes);
7
- const groupHtml = Object.entries(groups).map(([name, groupRoutes]) => this.renderGroup(name, groupRoutes)).join('');
7
+ const escapeJs = (s) => s.replace(/\\/g, '\\\\').replace(/`/g, '\\`').replace(/\$/g, '\\$');
8
+ const postmanEscaped = escapeJs(inlineData.postmanContent || '{}');
9
+ const envEscaped = escapeJs(inlineData.envContent || '{}');
10
+ const openapiEscaped = escapeJs(inlineData.openApiContent || '{}');
11
+ const methodColors = {
12
+ GET: '#10b981', POST: '#3b82f6', PUT: '#f59e0b', PATCH: '#a78bfa', DELETE: '#ef4444', OPTIONS: '#64748b'
13
+ };
14
+ const sidebarLinks = Object.keys(groups).map(g => {
15
+ const anchor = g.toLowerCase().replace(/[^a-z0-9]/g, '-');
16
+ return `<li><a href="#${anchor}" onclick="setActive(this)">${g}</a></li>`;
17
+ }).join('\n');
18
+ const groupHtml = Object.entries(groups).map(([groupName, groupRoutes]) => {
19
+ const anchor = groupName.toLowerCase().replace(/[^a-z0-9]/g, '-');
20
+ const routeCards = groupRoutes.map(r => {
21
+ const color = methodColors[r.method] || '#64748b';
22
+ const requiresAuth = r.middleware.includes('auth');
23
+ const mwHtml = r.middleware.map(m => `<span class="mw-badge">${m}</span>`).join('');
24
+ return `<div class="route-card">
25
+ <div class="route-header">
26
+ <span class="method-badge" style="background:${color}">${r.method}</span>
27
+ <code class="route-path">${r.path}</code>
28
+ ${requiresAuth ? '<span class="auth-badge">🔒 Auth</span>' : ''}
29
+ ${r.name ? `<span class="route-name">${r.name}</span>` : ''}
30
+ </div>
31
+ ${(r.handler || r.middleware.length) ? `<div class="route-meta">
32
+ ${r.handler ? `<span class="handler-label">⚙ ${r.handler}</span>` : ''}
33
+ ${mwHtml}
34
+ </div>` : ''}
35
+ </div>`;
36
+ }).join('\n');
37
+ return `<section class="route-group" id="${anchor}">
38
+ <h2>${groupName}</h2>
39
+ <div class="routes">${routeCards}</div>
40
+ </section>`;
41
+ }).join('\n');
8
42
  return `<!DOCTYPE html>
9
- <html lang="en">
43
+ <html lang="en" data-theme="light">
10
44
  <head>
11
45
  <meta charset="UTF-8">
12
46
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
13
- <title>${appName} API Documentation</title>
14
- <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@300;400;600;700&display=swap" rel="stylesheet">
47
+ <title>${appName} | API Docs — ArikaJS</title>
48
+ <link rel="icon" type="image/png" href="./assets/img/favicon.png">
49
+ <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&family=Plus+Jakarta+Sans:wght@300;400;600;800&family=JetBrains+Mono:wght@400;600&display=swap" rel="stylesheet">
15
50
  <style>
16
51
  :root {
17
52
  --primary: #8b5cf6;
53
+ --primary-glow: rgba(139, 92, 246, 0.4);
54
+ --secondary: #ec4899;
18
55
  --bg: #f8fafc;
19
- --card: #ffffff;
56
+ --sidebar-bg: rgba(255,255,255,0.85);
57
+ --card-bg: rgba(255, 255, 255, 0.8);
58
+ --card-border: rgba(15, 23, 42, 0.08);
20
59
  --text-main: #0f172a;
21
60
  --text-muted: #64748b;
22
- --border: #e2e8f0;
23
- --get: #10b981;
24
- --post: #3b82f6;
25
- --put: #f59e0b;
26
- --delete: #ef4444;
61
+ --border: rgba(15, 23, 42, 0.08);
62
+ --aurora-opacity: 0.3;
63
+ --btn-sec-bg: rgba(15, 23, 42, 0.05);
64
+ --h1-gradient: linear-gradient(135deg, #0f172a 30%, #334155 100%);
65
+ --sidebar-border: rgba(15, 23, 42, 0.08);
66
+ --route-path-bg: rgba(15, 23, 42, 0.05);
67
+ --route-path-color: #1e293b;
68
+ --handler-color: #64748b;
69
+ --mw-bg: rgba(15, 23, 42, 0.05);
70
+ }
71
+ [data-theme="dark"] {
72
+ --bg: #030712;
73
+ --sidebar-bg: rgba(10, 15, 30, 0.9);
74
+ --card-bg: rgba(17, 24, 39, 0.7);
75
+ --card-border: rgba(255, 255, 255, 0.08);
76
+ --text-main: #f8fafc;
77
+ --text-muted: #94a3b8;
78
+ --border: rgba(255, 255, 255, 0.08);
79
+ --aurora-opacity: 0.5;
80
+ --btn-sec-bg: rgba(255, 255, 255, 0.05);
81
+ --h1-gradient: linear-gradient(135deg, #fff 30%, rgba(255, 255, 255, 0.5) 100%);
82
+ --sidebar-border: rgba(255,255,255,0.08);
83
+ --route-path-bg: rgba(255, 255, 255, 0.06);
84
+ --route-path-color: #e2e8f0;
85
+ --handler-color: #475569;
86
+ --mw-bg: rgba(255, 255, 255, 0.06);
87
+ }
88
+ * { margin: 0; padding: 0; box-sizing: border-box; transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease; }
89
+ body { font-family: 'Plus Jakarta Sans', sans-serif; background: var(--bg); color: var(--text-main); display: block; min-height: 100vh; overflow-x: hidden; }
90
+
91
+ .aurora { position: fixed; top: 0; left: 0; width: 100%; height: 100%; z-index: 0; filter: blur(100px); opacity: var(--aurora-opacity); pointer-events: none; }
92
+ .aurora-blur { position: absolute; border-radius: 50%; animation: move 20s infinite alternate; }
93
+ .blur-1 { width: 50vw; height: 50vw; background: radial-gradient(circle, var(--primary) 0%, transparent 70%); top: -10%; left: -10%; }
94
+ .blur-2 { width: 40vw; height: 40vw; background: radial-gradient(circle, var(--secondary) 0%, transparent 70%); bottom: -10%; right: -10%; animation-duration: 25s; }
95
+ @keyframes move { from { transform: translate(0,0) scale(1); } to { transform: translate(10%,10%) scale(1.1); } }
96
+
97
+ /* Sidebar */
98
+ .sidebar {
99
+ width: 270px; min-height: 100vh; background: var(--sidebar-bg);
100
+ border-right: 1px solid var(--sidebar-border);
101
+ position: fixed; top: 0; left: 0; overflow-y: auto;
102
+ display: flex; flex-direction: column; z-index: 50;
103
+ backdrop-filter: blur(20px);
104
+ }
105
+ .sidebar-header { padding: 24px 20px 20px; border-bottom: 1px solid var(--sidebar-border); }
106
+ .logo-wrap { display: flex; align-items: center; gap: 10px; margin-bottom: 4px; }
107
+ .logo-wrap img { width: 110px; height: auto; }
108
+ [data-theme="dark"] .logo-wrap img { filter: invert(1) drop-shadow(0 2px 6px rgba(139,92,246,0.3)); }
109
+ .sidebar-subtitle { font-size: 11px; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.1em; font-weight: 600; margin-top: 6px; }
110
+
111
+ .sidebar nav { padding: 16px 0; flex: 1; }
112
+ .sidebar nav h3 { font-size: 10px; font-weight: 700; color: var(--text-muted); text-transform: uppercase; letter-spacing: .1em; padding: 0 20px 8px; }
113
+ .sidebar nav ul { list-style: none; }
114
+ .sidebar nav ul li a {
115
+ display: flex; align-items: center; gap: 8px; padding: 8px 20px;
116
+ font-size: 13px; color: var(--text-muted); text-decoration: none;
117
+ border-left: 3px solid transparent; transition: all .2s;
118
+ }
119
+ .sidebar nav ul li a:hover, .sidebar nav ul li a.active {
120
+ color: var(--primary); border-left-color: var(--primary); background: rgba(139,92,246,.06);
121
+ }
122
+ .sidebar-footer { padding: 16px 20px; border-top: 1px solid var(--sidebar-border); }
123
+ .sidebar-footer span { display: block; font-size: 11px; color: var(--text-muted); }
124
+ .sidebar-footer strong { color: var(--primary); }
125
+
126
+ /* Header */
127
+ header {
128
+ position: fixed; top: 0; left: 270px; right: 0; min-height: 64px;
129
+ display: flex; align-items: center; justify-content: space-between;
130
+ padding: 12px 32px; background: var(--sidebar-bg); backdrop-filter: blur(20px);
131
+ border-bottom: 1px solid var(--border); z-index: 40;
27
132
  }
133
+ .header-title { font-family: 'Outfit', sans-serif; font-size: 16px; font-weight: 700;
134
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
135
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
136
+ .header-right { display: flex; align-items: center; gap: 12px; }
28
137
 
29
- * { margin:0; padding:0; box-sizing:border-box; }
30
- body { font-family: 'Plus Jakarta Sans', sans-serif; background: var(--bg); color: var(--text-main); line-height: 1.6; }
31
- .container { max-width: 1000px; margin: 0 auto; padding: 4rem 2rem; }
32
- header { margin-bottom: 4rem; }
33
- h1 { font-size: 2.5rem; margin-bottom: 0.5rem; }
34
- .badge { display: inline-block; padding: 0.25rem 0.75rem; background: var(--primary); color: white; border-radius: 99px; font-size: 0.8rem; font-weight: 600; }
35
-
36
- .group { margin-bottom: 3rem; }
37
- .group-title { font-size: 1.5rem; margin-bottom: 1.5rem; border-bottom: 2px solid var(--border); padding-bottom: 0.5rem; text-transform: capitalize; }
38
-
39
- .route-card { background: var(--card); border: 1px solid var(--border); border-radius: 12px; padding: 1.5rem; margin-bottom: 1rem; transition: transform 0.2s; }
40
- .route-card:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(0,0,0,0.05); }
41
-
42
- .route-header { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
43
- .method { padding: 0.25rem 0.75rem; border-radius: 6px; font-weight: 700; font-size: 0.8rem; min-width: 70px; text-align: center; color: white; }
44
- .method.GET { background: var(--get); }
45
- .method.POST { background: var(--post); }
46
- .method.PUT { background: var(--put); }
47
- .method.DELETE { background: var(--delete); }
48
- .path { font-family: monospace; font-size: 1.1rem; font-weight: 600; color: var(--text-main); }
49
-
50
- .route-info { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; font-size: 0.9rem; }
51
- .label { color: var(--text-muted); font-weight: 600; margin-bottom: 0.25rem; }
52
- .value { color: var(--text-main); }
53
-
54
- .middleware-tag { display: inline-block; padding: 0.1rem 0.5rem; background: #f1f5f9; border: 1px solid var(--border); border-radius: 4px; font-size: 0.75rem; margin-right: 0.25rem; }
55
-
56
- footer { text-align: center; margin-top: 4rem; color: var(--text-muted); font-size: 0.8rem; }
138
+ /* Download Buttons */
139
+ .dl-btn {
140
+ display: inline-flex; align-items: center; gap: 6px;
141
+ padding: 7px 14px; border-radius: 8px; font-size: 12px; font-weight: 600;
142
+ cursor: pointer; border: 1px solid var(--border); text-decoration: none;
143
+ background: var(--btn-sec-bg); color: var(--text-main);
144
+ transition: all 0.2s cubic-bezier(0.4,0,0.2,1);
145
+ }
146
+ .dl-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(139,92,246,0.2); border-color: var(--primary); color: var(--primary); }
147
+ .dl-btn svg { width: 14px; height: 14px; flex-shrink: 0; }
148
+ .dl-btn.postman { border-color: rgba(255,108,55,0.3); }
149
+ .dl-btn.postman:hover { border-color: #ff6c37; color: #ff6c37; box-shadow: 0 4px 12px rgba(255,108,55,0.2); }
150
+ .dl-btn.openapi { border-color: rgba(16,185,129,0.3); }
151
+ .dl-btn.openapi:hover { border-color: #10b981; color: #10b981; box-shadow: 0 4px 12px rgba(16,185,129,0.2); }
152
+
153
+ /* Theme Toggle */
154
+ .theme-toggle {
155
+ background: var(--card-bg); border: 1px solid var(--border);
156
+ padding: 7px; border-radius: 99px; cursor: pointer;
157
+ display: flex; align-items: center; gap: 4px;
158
+ }
159
+ .theme-toggle svg { width: 18px; height: 18px; color: var(--text-main); transition: color 0.3s; }
160
+
161
+ /* Main */
162
+ .main { margin-left: 270px; margin-top: 72px; padding: 40px; position: relative; z-index: 10; }
163
+
164
+ /* Route Group */
165
+ .route-group { width: 100%; margin-bottom: 56px; scroll-margin-top: 80px; }
166
+ .route-group h2 {
167
+ font-family: 'Outfit', sans-serif; font-size: 18px; font-weight: 700;
168
+ color: var(--text-main); margin-bottom: 16px;
169
+ display: flex; align-items: center; gap: 10px;
170
+ }
171
+ .route-group h2::before { content: ''; width: 4px; height: 20px; background: linear-gradient(var(--primary), var(--secondary)); border-radius: 2px; flex-shrink: 0; }
172
+ .routes { display: flex; flex-direction: column; gap: 10px; width: 100%; }
173
+
174
+ /* Route Card */
175
+ .route-card {
176
+ width: 100%; background: var(--card-bg); border: 1px solid var(--card-border);
177
+ border-radius: 14px; padding: 16px 20px;
178
+ transition: border-color .25s, transform .25s, box-shadow .25s;
179
+ backdrop-filter: blur(10px);
180
+ }
181
+ .route-card:hover { border-color: var(--primary); transform: translateX(4px); box-shadow: 0 8px 30px var(--primary-glow); }
182
+ .route-header { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
183
+ .method-badge {
184
+ padding: 4px 14px; border-radius: 6px; font-family: 'JetBrains Mono', monospace;
185
+ font-size: 11px; font-weight: 700; color: #fff; min-width: 72px; text-align: center; letter-spacing: .05em;
186
+ }
187
+ .route-path {
188
+ font-family: 'JetBrains Mono', monospace; font-size: 14px;
189
+ color: var(--route-path-color); background: var(--route-path-bg);
190
+ padding: 4px 10px; border-radius: 6px; border: 1px solid var(--border);
191
+ }
192
+ .auth-badge { font-size: 11px; color: #f59e0b; background: rgba(245,158,11,.1); padding: 3px 8px; border-radius: 4px; border: 1px solid rgba(245,158,11,.2); }
193
+ .route-name { font-size: 11px; color: var(--text-muted); margin-left: auto; font-family: 'JetBrains Mono', monospace; }
194
+ .route-meta { display: flex; align-items: center; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
195
+ .handler-label { font-size: 12px; color: var(--handler-color); font-family: 'JetBrains Mono', monospace; }
196
+ .mw-badge { padding: 2px 8px; background: var(--mw-bg); border-radius: 4px; font-size: 11px; color: var(--text-muted); border: 1px solid var(--border); }
57
197
  </style>
58
198
  </head>
59
199
  <body>
60
- <div class="container">
61
- <header>
62
- <div class="badge">ArikaJS Docs</div>
63
- <h1>${appName}</h1>
64
- <p>API documentation generated on ${new Date().toLocaleDateString()}</p>
65
- </header>
66
-
67
- ${groupHtml}
68
-
69
- <footer>
70
- Generated by @arikajs/docs &copy; 2026
71
- </footer>
200
+
201
+ <div class="aurora">
202
+ <div class="aurora-blur blur-1"></div>
203
+ <div class="aurora-blur blur-2"></div>
204
+ </div>
205
+
206
+ <aside class="sidebar">
207
+ <div class="sidebar-header">
208
+ <div class="logo-wrap">
209
+ <img src="./assets/img/logo.png" alt="ArikaJS">
210
+ </div>
211
+ <div class="sidebar-subtitle">API Documentation</div>
212
+ </div>
213
+ <nav>
214
+ <h3>Sections</h3>
215
+ <ul id="nav-links">
216
+ ${sidebarLinks}
217
+ </ul>
218
+ </nav>
219
+ <div class="sidebar-footer">
220
+ <span>Total Routes: <strong>${routes.length}</strong></span>
221
+ <span style="margin-top:4px">Generated: <strong>${new Date().toLocaleDateString()}</strong></span>
222
+ </div>
223
+ </aside>
224
+
225
+ <header>
226
+ <div class="header-left">
227
+ <div class="header-title">${appName} &mdash; API Reference</div>
228
+ <div style="font-size: 12px; color: var(--text-muted); margin-top: 4px;">Complete API documentation &mdash; auto-generated from your ArikaJS route definitions.</div>
72
229
  </div>
230
+ <div class="header-right">
231
+ <button class="dl-btn postman" id="btn-postman" title="Download Postman Collection">
232
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
233
+ Postman
234
+ </button>
235
+ <button class="dl-btn" id="btn-env" title="Download Postman Environment">
236
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
237
+ Environment
238
+ </button>
239
+ <button class="dl-btn openapi" id="btn-openapi" title="Download OpenAPI Spec">
240
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
241
+ OpenAPI
242
+ </button>
243
+ <button class="theme-toggle" id="theme-toggle" title="Toggle Theme">
244
+ <svg id="moon-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
245
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"/>
246
+ </svg>
247
+ <svg id="sun-icon" style="display:none" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
248
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"/>
249
+ </svg>
250
+ </button>
251
+ </div>
252
+ </header>
253
+
254
+ <main class="main">
255
+ ${groupHtml}
256
+ </main>
257
+
258
+ <script>
259
+ const html = document.documentElement;
260
+ const toggle = document.getElementById('theme-toggle');
261
+ const sun = document.getElementById('sun-icon');
262
+ const moon = document.getElementById('moon-icon');
263
+
264
+ function setTheme(t) {
265
+ html.setAttribute('data-theme', t);
266
+ localStorage.setItem('arika_theme', t);
267
+ if (t === 'dark') { sun.style.display = 'block'; moon.style.display = 'none'; }
268
+ else { sun.style.display = 'none'; moon.style.display = 'block'; }
269
+ }
270
+ setTheme(localStorage.getItem('arika_theme') || 'light');
271
+ toggle.addEventListener('click', () => setTheme(html.getAttribute('data-theme') === 'dark' ? 'light' : 'dark'));
272
+
273
+ function setActive(el) {
274
+ document.querySelectorAll('#nav-links a').forEach(a => a.classList.remove('active'));
275
+ el.classList.add('active');
276
+ }
277
+
278
+ function download(filename, content) {
279
+ const blob = new Blob([content], { type: 'application/json' });
280
+ const url = URL.createObjectURL(blob);
281
+ const a = document.createElement('a');
282
+ a.href = url; a.download = filename; a.click();
283
+ URL.revokeObjectURL(url);
284
+ }
285
+
286
+ const postmanData = \`${postmanEscaped}\`;
287
+ const envData = \`${envEscaped}\`;
288
+ const openapiData = \`${openapiEscaped}\`;
289
+
290
+ document.getElementById('btn-postman').addEventListener('click', () => download('postman_collection.json', postmanData));
291
+ document.getElementById('btn-env').addEventListener('click', () => download('postman_environment.json', envData));
292
+ document.getElementById('btn-openapi').addEventListener('click', () => download('openapi.json', openapiData));
293
+ </script>
73
294
  </body>
74
295
  </html>`;
75
296
  }
76
- renderGroup(name, routes) {
77
- const routesHtml = routes.map(route => `
78
- <div class="route-card">
79
- <div class="route-header">
80
- <span class="method ${route.method}">${route.method}</span>
81
- <span class="path">${route.path}</span>
82
- </div>
83
- <div class="route-info">
84
- <div>
85
- <div class="label">Route Name</div>
86
- <div class="value">${route.name || '-'}</div>
87
- </div>
88
- <div>
89
- <div class="label">Middleware</div>
90
- <div class="value">
91
- ${route.middleware.map(m => `<span class="middleware-tag">${typeof m === 'string' ? m : m.name || 'Closure'}</span>`).join('') || '-'}
92
- </div>
93
- </div>
94
- </div>
95
- </div>
96
- `).join('');
97
- return `
98
- <div class="group">
99
- <h2 class="group-title">${name.replace('/', '')}</h2>
100
- ${routesHtml}
101
- </div>
102
- `;
103
- }
104
297
  groupByPrefix(routes) {
105
298
  const groups = {};
106
- routes.forEach(route => {
107
- const group = route.prefix || 'General';
299
+ routes.forEach(r => {
300
+ let group = r.prefix ? r.prefix.replace(/^\/+/, '').split('/').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' / ') : 'General';
301
+ if (!group)
302
+ group = 'General';
108
303
  if (!groups[group])
109
304
  groups[group] = [];
110
- groups[group].push(route);
305
+ groups[group].push(r);
111
306
  });
112
307
  return groups;
113
308
  }
@@ -1,5 +1,5 @@
1
- import { RouteEntry } from '@arikajs/router';
1
+ import { ParsedRoute } from './types';
2
2
  export declare class MarkdownGenerator {
3
- generate(routes: RouteEntry[], appName: string): string;
3
+ generate(routes: ParsedRoute[], appName: string, baseUrl?: string): string;
4
4
  private groupByPrefix;
5
5
  }
@@ -2,17 +2,20 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MarkdownGenerator = void 0;
4
4
  class MarkdownGenerator {
5
- generate(routes, appName) {
5
+ generate(routes, appName, baseUrl) {
6
6
  let markdown = `# API Documentation: ${appName}\n\n`;
7
7
  markdown += `Generated on ${new Date().toLocaleDateString()}\n\n`;
8
+ markdown += `> **Total Routes:** ${routes.length}\n\n`;
8
9
  const groups = this.groupByPrefix(routes);
9
10
  for (const [group, groupRoutes] of Object.entries(groups)) {
10
11
  markdown += `## ${group}\n\n`;
11
- markdown += `| Method | Path | Name | Middleware |\n`;
12
- markdown += `| :--- | :--- | :--- | :--- |\n`;
12
+ markdown += `| Method | Path | Handler | Name | Middleware |\n`;
13
+ markdown += `| :--- | :--- | :--- | :--- | :--- |\n`;
13
14
  groupRoutes.forEach(route => {
14
- const middleware = route.middleware.map(m => typeof m === 'string' ? m : m.name || 'Closure').join(', ') || '-';
15
- markdown += `| **${route.method}** | \`${route.path}\` | ${route.name || '-'} | ${middleware} |\n`;
15
+ const middleware = route.middleware.length > 0 ? route.middleware.join(', ') : '-';
16
+ const handler = route.handler || '-';
17
+ const name = route.name || '-';
18
+ markdown += `| **${route.method}** | \`${route.path}\` | ${handler} | ${name} | ${middleware} |\n`;
16
19
  });
17
20
  markdown += `\n`;
18
21
  }
@@ -20,11 +23,13 @@ class MarkdownGenerator {
20
23
  }
21
24
  groupByPrefix(routes) {
22
25
  const groups = {};
23
- routes.forEach(route => {
24
- const group = route.prefix || 'General';
26
+ routes.forEach(r => {
27
+ let group = r.prefix ? r.prefix.replace(/^\/+/, '').split('/').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' / ') : 'General';
28
+ if (!group)
29
+ group = 'General';
25
30
  if (!groups[group])
26
31
  groups[group] = [];
27
- groups[group].push(route);
32
+ groups[group].push(r);
28
33
  });
29
34
  return groups;
30
35
  }
@@ -1,6 +1,4 @@
1
- import { RouteEntry } from '@arikajs/router';
1
+ import { ParsedRoute } from './types';
2
2
  export declare class OpenApiGenerator {
3
- generate(routes: RouteEntry[], appName: string): any;
4
- private formatPaths;
5
- private extractParameters;
3
+ generate(routes: ParsedRoute[], appName: string, baseUrl: string): any;
6
4
  }
@@ -2,63 +2,65 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.OpenApiGenerator = void 0;
4
4
  class OpenApiGenerator {
5
- generate(routes, appName) {
5
+ generate(routes, appName, baseUrl) {
6
+ const paths = {};
7
+ routes.forEach(route => {
8
+ const rawPath = route.path.replace(/^\/+/, '');
9
+ const openApiPath = '/' + rawPath.replace(/:([a-zA-Z0-9_]+)|\{([a-zA-Z0-9_]+)\}/g, '{$1$2}');
10
+ if (!paths[openApiPath])
11
+ paths[openApiPath] = {};
12
+ const method = route.method.toLowerCase();
13
+ const group = route.prefix ? route.prefix.replace(/^\/+/, '').split('/').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' / ') : 'General';
14
+ const routeName = route.name || `${method}_${rawPath.replace(/\//g, '_')}`;
15
+ const parameters = route.paramKeys.map(key => ({
16
+ name: key,
17
+ in: 'path',
18
+ required: true,
19
+ schema: { type: 'string' }
20
+ }));
21
+ const operation = {
22
+ summary: routeName,
23
+ tags: [group || 'General'],
24
+ parameters: parameters.length > 0 ? parameters : undefined,
25
+ responses: {
26
+ '200': { description: 'Successful response' },
27
+ '401': route.middleware.includes('auth') ? { description: 'Unauthorized' } : undefined,
28
+ }
29
+ };
30
+ if (route.middleware.includes('auth')) {
31
+ operation.security = [{ bearerAuth: [] }];
32
+ }
33
+ if (['post', 'put', 'patch'].includes(method)) {
34
+ operation.requestBody = {
35
+ content: {
36
+ 'application/json': {
37
+ schema: { type: 'object', properties: {} }
38
+ }
39
+ }
40
+ };
41
+ }
42
+ paths[openApiPath][method] = operation;
43
+ });
44
+ const servers = [{ url: baseUrl }];
6
45
  return {
7
- openapi: "3.0.0",
46
+ openapi: '3.0.0',
8
47
  info: {
9
48
  title: appName,
10
- description: `API Specification for ${appName}`,
11
- version: "1.0.0"
49
+ version: '1.0.0',
50
+ description: `API documentation for ${appName}`
12
51
  },
13
- paths: this.formatPaths(routes),
52
+ servers,
53
+ paths,
14
54
  components: {
15
- schemas: {},
16
55
  securitySchemes: {
17
56
  bearerAuth: {
18
- type: "http",
19
- scheme: "bearer",
20
- bearerFormat: "JWT"
57
+ type: 'http',
58
+ scheme: 'bearer',
59
+ bearerFormat: 'JWT'
21
60
  }
22
61
  }
23
62
  }
24
63
  };
25
64
  }
26
- formatPaths(routes) {
27
- const paths = {};
28
- routes.forEach(route => {
29
- const path = route.path.replace(/:([a-zA-Z0-9_]+)|\{([a-zA-Z0-9_]+)\}/g, '{$1$2}');
30
- if (!paths[path]) {
31
- paths[path] = {};
32
- }
33
- paths[path][route.method.toLowerCase()] = {
34
- summary: route.name || `Endpoint for ${path}`,
35
- tags: [route.prefix || 'General'],
36
- parameters: this.extractParameters(route),
37
- responses: {
38
- "200": {
39
- description: "Successful response",
40
- content: {
41
- "application/json": {
42
- schema: {
43
- type: "object"
44
- }
45
- }
46
- }
47
- }
48
- }
49
- };
50
- });
51
- return paths;
52
- }
53
- extractParameters(route) {
54
- return route.paramKeys.map(key => ({
55
- name: key,
56
- in: "path",
57
- required: true,
58
- schema: {
59
- type: "string"
60
- }
61
- }));
62
- }
63
65
  }
64
66
  exports.OpenApiGenerator = OpenApiGenerator;
@@ -1,5 +1,5 @@
1
- import { RouteEntry } from '@arikajs/router';
1
+ import { ParsedRoute } from './types';
2
2
  export declare class PostmanGenerator {
3
- generate(routes: RouteEntry[], appName: string): any;
4
- private formatRoutes;
3
+ generate(routes: ParsedRoute[], appName: string, baseUrl: string): any;
4
+ private groupByPrefix;
5
5
  }
@@ -2,59 +2,83 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PostmanGenerator = void 0;
4
4
  class PostmanGenerator {
5
- generate(routes, appName) {
5
+ generate(routes, appName, baseUrl) {
6
+ const groups = this.groupByPrefix(routes);
7
+ const item = [];
8
+ for (const [groupName, groupRoutes] of Object.entries(groups)) {
9
+ const groupItem = {
10
+ name: groupName,
11
+ item: []
12
+ };
13
+ groupRoutes.forEach(route => {
14
+ const requestName = route.name || `${route.method} ${route.path}`;
15
+ // Parse params out of path
16
+ const parts = route.path.replace(/^\/+/, '').split('/');
17
+ const urlPath = parts.map(p => {
18
+ const match = p.match(/^:([a-zA-Z0-9_]+)$/) || p.match(/^\{([a-zA-Z0-9_]+)\}$/);
19
+ if (match)
20
+ return `:${match[1]}`;
21
+ return p;
22
+ });
23
+ const variables = route.paramKeys.map(key => ({
24
+ key, value: `<string>`
25
+ }));
26
+ const req = {
27
+ name: requestName,
28
+ request: {
29
+ method: route.method,
30
+ header: [
31
+ { key: 'Accept', value: 'application/json', type: 'text' },
32
+ { key: 'Content-Type', value: 'application/json', type: 'text' }
33
+ ],
34
+ url: {
35
+ raw: `{{base_url}}/${urlPath.join('/')}`,
36
+ host: ['{{base_url}}'],
37
+ path: urlPath,
38
+ variable: variables.length > 0 ? variables : undefined
39
+ }
40
+ },
41
+ response: []
42
+ };
43
+ if (route.middleware.includes('auth')) {
44
+ req.request.auth = {
45
+ type: 'bearer',
46
+ bearer: [
47
+ { key: "token", value: "{{token}}", type: "string" }
48
+ ]
49
+ };
50
+ }
51
+ if (['POST', 'PUT', 'PATCH'].includes(route.method)) {
52
+ req.request.body = {
53
+ mode: 'raw',
54
+ raw: JSON.stringify({}, null, 2),
55
+ options: { raw: { language: 'json' } }
56
+ };
57
+ }
58
+ groupItem.item.push(req);
59
+ });
60
+ item.push(groupItem);
61
+ }
6
62
  return {
7
63
  info: {
8
- name: appName,
9
- _postman_id: Math.random().toString(36).substring(7),
10
- description: `API Collection for ${appName}`,
11
- schema: "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
64
+ name: `${appName} API`,
65
+ description: `Auto-generated API collection for ${appName}`,
66
+ schema: 'https://schema.getpostman.com/json/collection/v2.1.0/collection.json'
12
67
  },
13
- item: this.formatRoutes(routes),
14
- variable: [
15
- {
16
- key: "base_url",
17
- value: "http://localhost:3000",
18
- type: "string"
19
- }
20
- ]
68
+ item
21
69
  };
22
70
  }
23
- formatRoutes(routes) {
24
- // Group routes by prefix/module
71
+ groupByPrefix(routes) {
25
72
  const groups = {};
26
- routes.forEach(route => {
27
- const groupName = route.prefix || 'General';
28
- if (!groups[groupName]) {
29
- groups[groupName] = [];
30
- }
31
- groups[groupName].push({
32
- name: route.name || route.path,
33
- request: {
34
- method: route.method,
35
- header: [
36
- {
37
- key: "Content-Type",
38
- value: "application/json"
39
- },
40
- {
41
- key: "Accept",
42
- value: "application/json"
43
- }
44
- ],
45
- url: {
46
- raw: "{{base_url}}" + route.path,
47
- host: ["{{base_url}}"],
48
- path: route.path.split('/').filter(p => p !== '')
49
- }
50
- },
51
- response: []
52
- });
73
+ routes.forEach(r => {
74
+ let group = r.prefix ? r.prefix.replace(/^\/+/, '').split('/').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' / ') : 'General';
75
+ if (!group)
76
+ group = 'General';
77
+ if (!groups[group])
78
+ groups[group] = [];
79
+ groups[group].push(r);
53
80
  });
54
- return Object.keys(groups).map(name => ({
55
- name: name.charAt(0).toUpperCase() + name.slice(1).replace('/', ''),
56
- item: groups[name]
57
- }));
81
+ return groups;
58
82
  }
59
83
  }
60
84
  exports.PostmanGenerator = PostmanGenerator;
@@ -0,0 +1,9 @@
1
+ export declare class RouteParser {
2
+ parseApplicationRoutes(appDir: string, routeFileName?: string | null): any[];
3
+ private extractRoutesFromContent;
4
+ private extractGroupRoutes;
5
+ private stripGroupBlocks;
6
+ private extractFlatRoutes;
7
+ private joinPaths;
8
+ private extractParamKeys;
9
+ }
@@ -0,0 +1,178 @@
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.RouteParser = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ class RouteParser {
10
+ parseApplicationRoutes(appDir, routeFileName = null) {
11
+ const routesDir = path_1.default.join(appDir, 'routes');
12
+ if (!fs_1.default.existsSync(routesDir))
13
+ return [];
14
+ const routes = [];
15
+ let filesToParse = [];
16
+ if (routeFileName) {
17
+ const specificFile = path_1.default.join(routesDir, routeFileName.endsWith('.ts') ? routeFileName : `${routeFileName}.ts`);
18
+ if (fs_1.default.existsSync(specificFile)) {
19
+ filesToParse = [specificFile];
20
+ }
21
+ else {
22
+ console.warn(`\n ⚠ Route file not found: ${specificFile}\n`);
23
+ return [];
24
+ }
25
+ }
26
+ else {
27
+ const allFiles = fs_1.default.readdirSync(routesDir).filter(f => f.endsWith('.ts'));
28
+ if (allFiles.includes('api.ts')) {
29
+ filesToParse = [path_1.default.join(routesDir, 'api.ts')];
30
+ }
31
+ else if (allFiles.length > 0) {
32
+ filesToParse = [path_1.default.join(routesDir, allFiles[0])];
33
+ }
34
+ }
35
+ for (const file of filesToParse) {
36
+ const content = fs_1.default.readFileSync(file, 'utf8');
37
+ this.extractRoutesFromContent(content, routes);
38
+ }
39
+ return routes;
40
+ }
41
+ extractRoutesFromContent(content, routes) {
42
+ const strippedContent = this.extractGroupRoutes(content, routes);
43
+ this.extractFlatRoutes(strippedContent, routes, '', []);
44
+ }
45
+ extractGroupRoutes(content, routes) {
46
+ const groupRegex = /Route\.group\s*\(\s*\{([^}]+)\}\s*,\s*\(\s*\)\s*=>\s*\{([\s\S]*?)\}\s*\)/g;
47
+ let match;
48
+ let contentCopy = content;
49
+ while ((match = groupRegex.exec(content)) !== null) {
50
+ const groupConfig = match[1];
51
+ const groupBody = match[2];
52
+ let prefix = '';
53
+ let middleware = [];
54
+ const prefixMatch = groupConfig.match(/prefix\s*:\s*['"`]([^'"`]+)['"`]/);
55
+ if (prefixMatch)
56
+ prefix = prefixMatch[1];
57
+ const mwMatch = groupConfig.match(/middleware\s*:\s*(['"`][^'"`]+['"`]|\[[^\]]+\])/);
58
+ if (mwMatch) {
59
+ const mwStr = mwMatch[1];
60
+ if (mwStr.startsWith('[')) {
61
+ const mws = mwStr.match(/['"`]([^'"`]+)['"`]/g);
62
+ if (mws)
63
+ middleware = mws.map(m => m.replace(/['"`]/g, ''));
64
+ }
65
+ else {
66
+ middleware.push(mwStr.replace(/['"`]/g, ''));
67
+ }
68
+ }
69
+ this.extractFlatRoutes(groupBody, routes, prefix, middleware);
70
+ }
71
+ return this.stripGroupBlocks(contentCopy);
72
+ }
73
+ stripGroupBlocks(content) {
74
+ let result = content;
75
+ let changed = true;
76
+ while (changed) {
77
+ changed = false;
78
+ const groupStart = result.indexOf('Route.group(');
79
+ if (groupStart === -1)
80
+ break;
81
+ const arrowIdx = result.indexOf('=>', groupStart);
82
+ if (arrowIdx === -1)
83
+ break;
84
+ const braceStart = result.indexOf('{', arrowIdx);
85
+ if (braceStart === -1)
86
+ break;
87
+ let depth = 0;
88
+ let i = braceStart;
89
+ for (; i < result.length; i++) {
90
+ if (result[i] === '{')
91
+ depth++;
92
+ else if (result[i] === '}') {
93
+ depth--;
94
+ if (depth === 0)
95
+ break;
96
+ }
97
+ }
98
+ let endIdx = result.indexOf(';', i);
99
+ if (endIdx === -1)
100
+ endIdx = i + 1;
101
+ result = result.substring(0, groupStart) + result.substring(endIdx + 1);
102
+ changed = true;
103
+ }
104
+ return result;
105
+ }
106
+ extractFlatRoutes(content, routes, prefix, middleware) {
107
+ const normalized = content.replace(/\s+/g, ' ');
108
+ const routeRegex = /Route\.(get|post|put|patch|delete|options)\s*\(\s*['"`]([^'"`]*)['"`]\s*,\s*((?:[^\(\)]*|\([^\)]*\))*)\)\s*([^;]*)?;?/g;
109
+ let match;
110
+ while ((match = routeRegex.exec(normalized)) !== null) {
111
+ const method = match[1].toUpperCase();
112
+ const routePath = match[2];
113
+ const handlerStr = match[3]?.trim() || '';
114
+ const modifiersStr = match[4] || '';
115
+ const controllerMatch = handlerStr.match(/\[\s*(\w+)\s*,\s*['"`](\w+)['"`]\s*\]/);
116
+ const instanceMatch = handlerStr.match(/(\w+)\.(\w+)/);
117
+ let handlerLabel;
118
+ if (controllerMatch) {
119
+ handlerLabel = `${controllerMatch[1]}@${controllerMatch[2]}`;
120
+ }
121
+ else if (instanceMatch && !handlerStr.includes('=>')) {
122
+ handlerLabel = `${instanceMatch[1]}.${instanceMatch[2]}`;
123
+ }
124
+ else if (handlerStr.includes('=>')) {
125
+ handlerLabel = '[Closure]';
126
+ }
127
+ else {
128
+ handlerLabel = handlerStr.substring(0, 40);
129
+ }
130
+ const routeMiddleware = [...middleware];
131
+ const withMwRegex = /\.withMiddleware\s*\(\s*(['"`][^'"`]+['"`]|\[[^\]]+\])\s*\)/;
132
+ const withMwMatch = modifiersStr.match(withMwRegex);
133
+ if (withMwMatch) {
134
+ const mwStr = withMwMatch[1];
135
+ if (mwStr.startsWith('[')) {
136
+ const mws = mwStr.match(/['"`]([^'"`]+)['"`]/g);
137
+ if (mws)
138
+ routeMiddleware.push(...mws.map(m => m.replace(/['"`]/g, '')));
139
+ }
140
+ else {
141
+ routeMiddleware.push(mwStr.replace(/['"`]/g, ''));
142
+ }
143
+ }
144
+ let name;
145
+ const asRegex = /\.as\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/;
146
+ const asMatch = modifiersStr.match(asRegex);
147
+ if (asMatch)
148
+ name = asMatch[1];
149
+ const fullPath = this.joinPaths(prefix, routePath);
150
+ const paramKeys = this.extractParamKeys(fullPath);
151
+ routes.push({
152
+ method,
153
+ path: fullPath,
154
+ handler: handlerLabel !== '[Closure]' && handlerLabel.length > 0 ? handlerLabel : undefined,
155
+ name,
156
+ middleware: routeMiddleware,
157
+ prefix,
158
+ paramKeys
159
+ });
160
+ }
161
+ }
162
+ joinPaths(prefix, path) {
163
+ const cleanPrefix = prefix.replace(/^\/+|\/+$/g, '');
164
+ const cleanPath = path.replace(/^\/+|\/+$/g, '');
165
+ const sep = cleanPrefix && cleanPath ? '/' : '';
166
+ return `/${cleanPrefix}${sep}${cleanPath}`;
167
+ }
168
+ extractParamKeys(pathStr) {
169
+ const keys = [];
170
+ const regex = /:([a-zA-Z0-9_]+)|\{([a-zA-Z0-9_]+)\}/g;
171
+ let match;
172
+ while ((match = regex.exec(pathStr)) !== null) {
173
+ keys.push(match[1] || match[2]);
174
+ }
175
+ return keys;
176
+ }
177
+ }
178
+ exports.RouteParser = RouteParser;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
- export { DocumentationGenerator } from './Generator';
2
- export { PostmanGenerator } from './PostmanGenerator';
3
- export { MarkdownGenerator } from './MarkdownGenerator';
4
- export { HtmlGenerator } from './HtmlGenerator';
5
- export { OpenApiGenerator } from './OpenApiGenerator';
1
+ export * from './types';
2
+ export * from './RouteParser';
3
+ export * from './Generator';
4
+ export * from './MarkdownGenerator';
5
+ export * from './PostmanGenerator';
6
+ export * from './OpenApiGenerator';
7
+ export * from './HtmlGenerator';
package/dist/index.js CHANGED
@@ -1,13 +1,23 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.OpenApiGenerator = exports.HtmlGenerator = exports.MarkdownGenerator = exports.PostmanGenerator = exports.DocumentationGenerator = void 0;
4
- var Generator_1 = require("./Generator");
5
- Object.defineProperty(exports, "DocumentationGenerator", { enumerable: true, get: function () { return Generator_1.DocumentationGenerator; } });
6
- var PostmanGenerator_1 = require("./PostmanGenerator");
7
- Object.defineProperty(exports, "PostmanGenerator", { enumerable: true, get: function () { return PostmanGenerator_1.PostmanGenerator; } });
8
- var MarkdownGenerator_1 = require("./MarkdownGenerator");
9
- Object.defineProperty(exports, "MarkdownGenerator", { enumerable: true, get: function () { return MarkdownGenerator_1.MarkdownGenerator; } });
10
- var HtmlGenerator_1 = require("./HtmlGenerator");
11
- Object.defineProperty(exports, "HtmlGenerator", { enumerable: true, get: function () { return HtmlGenerator_1.HtmlGenerator; } });
12
- var OpenApiGenerator_1 = require("./OpenApiGenerator");
13
- Object.defineProperty(exports, "OpenApiGenerator", { enumerable: true, get: function () { return OpenApiGenerator_1.OpenApiGenerator; } });
17
+ __exportStar(require("./types"), exports);
18
+ __exportStar(require("./RouteParser"), exports);
19
+ __exportStar(require("./Generator"), exports);
20
+ __exportStar(require("./MarkdownGenerator"), exports);
21
+ __exportStar(require("./PostmanGenerator"), exports);
22
+ __exportStar(require("./OpenApiGenerator"), exports);
23
+ __exportStar(require("./HtmlGenerator"), exports);
@@ -0,0 +1,9 @@
1
+ export interface ParsedRoute {
2
+ method: string;
3
+ path: string;
4
+ name?: string;
5
+ prefix?: string;
6
+ middleware: string[];
7
+ paramKeys: string[];
8
+ handler?: string;
9
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arikajs/docs",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "API documentation and Postman collection generator for ArikaJS.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -8,7 +8,8 @@
8
8
  "scripts": {
9
9
  "build": "tsc -p tsconfig.json",
10
10
  "clean": "rm -rf dist",
11
- "test": "npm run build && node --test 'dist/tests/**/*.test.js'"
11
+ "test": "npm run build && node --test 'dist/tests/**/*.test.js'",
12
+ "dev": "tsc -p tsconfig.json --watch"
12
13
  },
13
14
  "files": [
14
15
  "dist"
@@ -22,11 +23,20 @@
22
23
  "documentation"
23
24
  ],
24
25
  "dependencies": {
25
- "@arikajs/router": "file:../router"
26
+ "@arikajs/router": "*"
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/node": "^20.11.24",
29
30
  "typescript": "^5.3.3"
30
31
  },
31
- "author": "Prakash Tank"
32
+ "author": "Prakash Tank",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/ArikaJs/arikajs.git",
36
+ "directory": "packages/docs"
37
+ },
38
+ "bugs": {
39
+ "url": "https://github.com/ArikaJs/arikajs/issues"
40
+ },
41
+ "homepage": "https://github.com/ArikaJs/arikajs/tree/main/packages/docs#readme"
32
42
  }