hasancode-api-docs 1.0.7 → 1.0.9

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.
@@ -1,9 +1,6 @@
1
1
  import { Router } from "express";
2
2
  import { RouteMetadata } from "../registry/RouteRegistry";
3
3
  import { CodeThemeValues } from "../code-theme";
4
- /**
5
- * API Documentation configuration
6
- */
7
4
  export interface ApiDocConfig {
8
5
  title: string;
9
6
  version: string;
@@ -22,54 +19,20 @@ export interface ApiDocConfig {
22
19
  bearerFormat?: string;
23
20
  };
24
21
  }
25
- /**
26
- * Main API Documentation class
27
- */
28
22
  export declare class ApiDoc {
29
23
  private config;
30
24
  private clientPath;
31
25
  private readonly DOCS_PATH;
32
26
  private configReady;
33
27
  constructor(config: ApiDocConfig);
34
- /**
35
- * Normalize configuration with defaults
36
- */
37
28
  private normalizeConfig;
38
- /**
39
- * Resolve client path
40
- */
41
29
  private resolveClientPath;
42
- /**
43
- * Convert route metadata to API documentation format
44
- */
45
30
  private convertRoutesToSections;
46
- /**
47
- * Convert single route to section format
48
- */
49
31
  private convertRouteToSection;
50
- /**
51
- * Get Express middleware - automatically mounts at /api-docs
52
- * Usage: app.use(apiDoc.middleware())
53
- */
54
32
  middleware(): Router;
55
- /**
56
- * Serve configuration
57
- */
58
33
  private serveConfig;
59
- /**
60
- * Serve the React app
61
- */
62
34
  private serveApp;
63
- /**
64
- * Get all registered routes
65
- */
66
35
  getRoutes(): RouteMetadata[];
67
- /**
68
- * Clear all routes (useful for testing)
69
- */
70
36
  clearRoutes(): void;
71
- /**
72
- * Get the documentation path
73
- */
74
37
  getDocsPath(): string;
75
38
  }
@@ -49,30 +49,23 @@ const fs = __importStar(require("fs"));
49
49
  const marked_1 = require("marked");
50
50
  const RouteRegistry_1 = require("../registry/RouteRegistry");
51
51
  const code_theme_1 = require("../code-theme");
52
- /**
53
- * Main API Documentation class
54
- */
55
52
  class ApiDoc {
56
53
  constructor(config) {
57
- this.DOCS_PATH = "/api-docs"; // Fixed path
54
+ this.DOCS_PATH = "/api-docs";
58
55
  this.clientPath = this.resolveClientPath();
59
- this.config = config; // Set initial config
56
+ this.config = config;
60
57
  this.configReady = this.normalizeConfig(config);
61
58
  }
62
- /**
63
- * Normalize configuration with defaults
64
- */
65
59
  normalizeConfig(config) {
66
- // Convert description to HTML if it exists
67
- if (config === null || config === void 0 ? void 0 : config.description) {
68
- const html = marked_1.marked.parse(config.description.toString());
69
- config.description = '<div class="md__code">' + html + "</div>";
70
- }
71
- this.config = Object.assign(Object.assign({}, config), { baseUrl: config.baseUrl || "http://localhost:5000", theme: Object.assign({ primaryColor: "#3b82f6", secondaryColor: "#8b5cf6", accentColor: "#10b981", backgroundColor: "#ffffff", sidebarBackgroundColor: "#1f2937", codeTheme: code_theme_1.CODE_THEMES.VITESSE_BLACK }, config.theme) });
60
+ return __awaiter(this, void 0, void 0, function* () {
61
+ // Convert description to HTML if it exists
62
+ if (config === null || config === void 0 ? void 0 : config.description) {
63
+ const html = yield marked_1.marked.parse(config.description.toString());
64
+ config.description = '<div class="md__code">' + html + "</div>";
65
+ }
66
+ this.config = Object.assign(Object.assign({}, config), { baseUrl: config.baseUrl || "http://localhost:5000", theme: Object.assign({ primaryColor: "#3b82f6", secondaryColor: "#8b5cf6", accentColor: "#10b981", backgroundColor: "#ffffff", sidebarBackgroundColor: "#1f2937", codeTheme: code_theme_1.CODE_THEMES.VITESSE_BLACK }, config.theme) });
67
+ });
72
68
  }
73
- /**
74
- * Resolve client path
75
- */
76
69
  resolveClientPath() {
77
70
  const possiblePaths = [
78
71
  path.join(__dirname, "../../client/dist"),
@@ -87,219 +80,197 @@ class ApiDoc {
87
80
  }
88
81
  return path.join(__dirname, "../../client/dist");
89
82
  }
90
- /**
91
- * Convert route metadata to API documentation format
92
- */
93
83
  convertRoutesToSections() {
94
- const routes = RouteRegistry_1.routeRegistry.getAll();
95
- const sections = [];
96
- // Group routes by tags
97
- const grouped = new Map();
98
- const untagged = [];
99
- for (const route of routes) {
100
- if (route.tags && route.tags.length > 0) {
101
- for (const tag of route.tags) {
102
- if (!grouped.has(tag)) {
103
- grouped.set(tag, []);
84
+ return __awaiter(this, void 0, void 0, function* () {
85
+ const routes = RouteRegistry_1.routeRegistry.getAll();
86
+ const sections = [];
87
+ const grouped = new Map();
88
+ const untagged = [];
89
+ for (const route of routes) {
90
+ if (route.tags && route.tags.length > 0) {
91
+ for (const tag of route.tags) {
92
+ if (!grouped.has(tag))
93
+ grouped.set(tag, []);
94
+ grouped.get(tag).push(route);
104
95
  }
105
- grouped.get(tag).push(route);
96
+ }
97
+ else {
98
+ untagged.push(route);
106
99
  }
107
100
  }
108
- else {
109
- untagged.push(route);
101
+ for (const [tag, tagRoutes] of grouped.entries()) {
102
+ const items = yield Promise.all(tagRoutes.map((route) => this.convertRouteToSection(route)));
103
+ sections.push({
104
+ name: tag.toLowerCase().replace(/\s+/g, "-"),
105
+ label: tag,
106
+ type: "GROUP",
107
+ items,
108
+ });
110
109
  }
111
- }
112
- // Create sections for each tag
113
- for (const [tag, tagRoutes] of grouped.entries()) {
114
- sections.push({
115
- name: tag.toLowerCase().replace(/\s+/g, "-"),
116
- label: tag,
117
- type: "GROUP",
118
- items: tagRoutes.map((route) => this.convertRouteToSection(route)),
119
- });
120
- }
121
- // Add untagged routes
122
- if (untagged.length > 0) {
123
- sections.push({
124
- name: "other",
125
- label: "Other Endpoints",
126
- type: "GROUP",
127
- items: untagged.map((route) => this.convertRouteToSection(route)),
128
- });
129
- }
130
- return sections;
110
+ if (untagged.length > 0) {
111
+ const items = yield Promise.all(untagged.map((route) => this.convertRouteToSection(route)));
112
+ sections.push({
113
+ name: "other",
114
+ label: "Other Endpoints",
115
+ type: "GROUP",
116
+ items,
117
+ });
118
+ }
119
+ return sections;
120
+ });
131
121
  }
132
- /**
133
- * Convert single route to section format
134
- */
135
122
  convertRouteToSection(route) {
136
- if (route === null || route === void 0 ? void 0 : route.description) {
137
- const parsed = marked_1.marked.parse(route.description.toString());
138
- route.description = '<div class="md__code">' + parsed + "</div>";
139
- }
140
- if (route === null || route === void 0 ? void 0 : route.summary) {
141
- const parsed = marked_1.marked.parse(route.summary.toString());
142
- route.summary = '<div class="md__code">' + parsed + "</div>";
143
- }
144
- if (route === null || route === void 0 ? void 0 : route.note) {
145
- const parsed = marked_1.marked.parse(route.note.toString());
146
- route.note = '<div class="md__code">' + parsed + "</div>";
147
- }
148
- const section = {
149
- name: `${route.method}${route.path}`
150
- .replace(/[/:]/g, "-")
151
- .replace("--", "-"),
152
- label: `${route.method.toUpperCase()} ${route.path}`,
153
- type: "ITEM",
154
- content: route.description,
155
- api: {
156
- method: route.method,
157
- path: route.path,
158
- summary: route.summary,
159
- description: route.description,
160
- deprecated: route.deprecated,
161
- note: route.note,
162
- tags: route.tags,
163
- auth: route.auth,
164
- },
165
- };
166
- if (route.params && route.params.length > 0) {
167
- section.api.params = route.params;
168
- }
169
- if (route.query && route.query.length > 0) {
170
- section.api.query = route.query;
171
- }
172
- if (route.headers && route.headers.length > 0) {
173
- section.api.headers = route.headers;
174
- }
175
- if (route.body) {
176
- section.api.body = {
177
- contentType: route.body.contentType,
178
- required: route.body.required,
179
- description: route.body.description,
180
- schema: route.body.schema,
181
- example: route.body.example,
123
+ return __awaiter(this, void 0, void 0, function* () {
124
+ var _a, _b, _c, _d;
125
+ if (route === null || route === void 0 ? void 0 : route.description) {
126
+ const parsed = yield marked_1.marked.parse(route.description.toString());
127
+ route.description = '<div class="md__code">' + parsed + "</div>";
128
+ }
129
+ if (route === null || route === void 0 ? void 0 : route.summary) {
130
+ const parsed = yield marked_1.marked.parse(route.summary.toString());
131
+ route.summary = '<div class="md__code">' + parsed + "</div>";
132
+ }
133
+ if (route === null || route === void 0 ? void 0 : route.note) {
134
+ const parsed = yield marked_1.marked.parse(route.note.toString());
135
+ route.note = '<div class="md__code">' + parsed + "</div>";
136
+ }
137
+ const section = {
138
+ name: `${route.method}${route.path}`
139
+ .replace(/[/:]/g, "-")
140
+ .replace("--", "-"),
141
+ label: `${route.method.toUpperCase()} ${route.path}`,
142
+ type: "ITEM",
143
+ content: route.description,
144
+ api: {
145
+ method: route.method,
146
+ path: route.path,
147
+ summary: route.summary,
148
+ description: route.description,
149
+ deprecated: route.deprecated,
150
+ note: route.note,
151
+ tags: route.tags,
152
+ auth: route.auth,
153
+ },
182
154
  };
183
- }
184
- if (route.responses && route.responses.length > 0) {
185
- section.api.responses = {};
186
- for (const response of route.responses) {
187
- section.api.responses[response.statusCode] = {
188
- description: response.description,
189
- contentType: response.contentType,
190
- schema: response.schema,
191
- example: response.example,
155
+ if ((_a = route.params) === null || _a === void 0 ? void 0 : _a.length)
156
+ section.api.params = route.params;
157
+ if ((_b = route.query) === null || _b === void 0 ? void 0 : _b.length)
158
+ section.api.query = route.query;
159
+ if ((_c = route.headers) === null || _c === void 0 ? void 0 : _c.length)
160
+ section.api.headers = route.headers;
161
+ if (route.body) {
162
+ section.api.body = {
163
+ contentType: route.body.contentType,
164
+ required: route.body.required,
165
+ description: route.body.description,
166
+ schema: route.body.schema,
167
+ example: route.body.example,
192
168
  };
193
169
  }
194
- }
195
- return section;
170
+ if ((_d = route.responses) === null || _d === void 0 ? void 0 : _d.length) {
171
+ section.api.responses = {};
172
+ for (const response of route.responses) {
173
+ section.api.responses[response.statusCode] = {
174
+ description: response.description,
175
+ contentType: response.contentType,
176
+ schema: response.schema,
177
+ example: response.example,
178
+ };
179
+ }
180
+ }
181
+ return section;
182
+ });
196
183
  }
197
- /**
198
- * Get Express middleware - automatically mounts at /api-docs
199
- * Usage: app.use(apiDoc.middleware())
200
- */
184
+ // ══════════════════════════════════════════════════════════════
185
+ // FIXED: Static file serving with correct MIME types
186
+ // ══════════════════════════════════════════════════════════════
201
187
  middleware() {
202
188
  const router = (0, express_1.Router)();
189
+ // ── 1. Serve config.json (MUST come before static assets) ────
203
190
  router.get(`${this.DOCS_PATH}/config.json`, this.serveConfig.bind(this));
204
- router.use(`${this.DOCS_PATH}/assets`, (0, express_1.static)(path.join(this.clientPath, "assets"), {
191
+ // ── 2. Serve static assets (CSS, JS, etc.) ───────────────────
192
+ // CRITICAL: Use absolute path match to prevent wildcard route from catching it
193
+ router.use(`${this.DOCS_PATH}/assets`, (req, res, next) => {
194
+ // Manually set correct MIME types before expressStatic processes
195
+ const ext = path.extname(req.path).toLowerCase();
196
+ if (ext === ".js") {
197
+ res.setHeader("Content-Type", "application/javascript; charset=utf-8");
198
+ }
199
+ else if (ext === ".css") {
200
+ res.setHeader("Content-Type", "text/css; charset=utf-8");
201
+ }
202
+ else if (ext === ".map") {
203
+ res.setHeader("Content-Type", "application/json; charset=utf-8");
204
+ }
205
+ next();
206
+ }, (0, express_1.static)(path.join(this.clientPath, "assets"), {
205
207
  maxAge: "1d",
206
208
  etag: true,
207
- fallthrough: false,
208
- setHeaders: (res, path) => {
209
- if (path.endsWith(".js")) {
210
- res.setHeader("Content-Type", "application/javascript");
211
- }
212
- else if (path.endsWith(".css")) {
213
- res.setHeader("Content-Type", "text/css");
214
- }
215
- },
209
+ index: false, // Don't serve index.html from assets
210
+ fallthrough: false, // Return 404 if file not found
216
211
  }));
212
+ // ── 3. Serve React app (catch-all, MUST come last) ───────────
213
+ // router.get(`${this.DOCS_PATH}*`, this.serveApp.bind(this));
217
214
  router.use(this.serveApp.bind(this));
218
215
  return router;
219
216
  }
220
- /**
221
- * Serve configuration
222
- */
223
217
  serveConfig(req, res) {
224
218
  return __awaiter(this, void 0, void 0, function* () {
225
- // Wait for config to be ready
226
219
  yield this.configReady;
227
- // Auto-generate sections from decorated routes
228
- const sections = this.convertRoutesToSections();
229
- const runtimeConfig = {
220
+ const sections = yield this.convertRoutesToSections();
221
+ res.json({
230
222
  config: Object.assign(Object.assign({}, this.config), { sections }),
231
223
  basePath: this.DOCS_PATH,
232
- };
233
- res.json(runtimeConfig);
224
+ });
234
225
  });
235
226
  }
236
- /**
237
- * Serve the React app
238
- */
239
227
  serveApp(req, res) {
240
228
  const indexPath = path.join(this.clientPath, "index.html");
241
229
  if (fs.existsSync(indexPath)) {
242
230
  let html = fs.readFileSync(indexPath, "utf-8");
243
- // Inject base tag with trailing slash for proper asset resolution
244
231
  const baseTag = `<base href="${this.DOCS_PATH}/">`;
245
- // Insert base tag right after <head> or before any other tags
246
232
  if (html.includes("<base")) {
247
- // Replace existing base tag
248
233
  html = html.replace(/<base[^>]*>/i, baseTag);
249
234
  }
250
235
  else {
251
- // Insert new base tag
252
236
  html = html.replace(/<head>/i, `<head>${baseTag}`);
253
237
  }
254
- res.setHeader("Content-Type", "text/html");
238
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
255
239
  res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
256
240
  res.send(html);
257
241
  }
258
242
  else {
259
243
  res.status(404).send(`
260
- <html>
261
- <head>
262
- <title>API Docs - Not Built</title>
263
- <style>
264
- body {
265
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
266
- max-width: 600px;
267
- margin: 100px auto;
268
- padding: 20px;
269
- text-align: center;
270
- }
271
- h1 { color: #e53e3e; }
272
- code {
273
- background: #f7fafc;
274
- padding: 2px 6px;
275
- border-radius: 4px;
276
- }
277
- </style>
278
- </head>
279
- <body>
280
- <h1>⚠️ Client Not Built</h1>
281
- <p>Please run <code>npm run build</code> first.</p>
282
- <p><small>Looking at: ${this.clientPath}</small></p>
283
- </body>
284
- </html>
285
- `);
244
+ <html>
245
+ <head>
246
+ <title>API Docs - Not Built</title>
247
+ <style>
248
+ body {
249
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
250
+ max-width: 600px;
251
+ margin: 100px auto;
252
+ padding: 20px;
253
+ text-align: center;
254
+ }
255
+ h1 { color: #e53e3e; }
256
+ code { background: #f7fafc; padding: 2px 6px; border-radius: 4px; }
257
+ </style>
258
+ </head>
259
+ <body>
260
+ <h1>⚠️ Client Not Built</h1>
261
+ <p>Run <code>npm run build</code> first.</p>
262
+ <small>Looking at: ${this.clientPath}</small>
263
+ </body>
264
+ </html>
265
+ `);
286
266
  }
287
267
  }
288
- /**
289
- * Get all registered routes
290
- */
291
268
  getRoutes() {
292
269
  return RouteRegistry_1.routeRegistry.getAll();
293
270
  }
294
- /**
295
- * Clear all routes (useful for testing)
296
- */
297
271
  clearRoutes() {
298
272
  RouteRegistry_1.routeRegistry.clear();
299
273
  }
300
- /**
301
- * Get the documentation path
302
- */
303
274
  getDocsPath() {
304
275
  return this.DOCS_PATH;
305
276
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hasancode-api-docs",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
4
4
  "description": "A simple and easy to use API documentation generator for Express.js",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",