nest-scramble 1.4.9 → 1.5.0

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
@@ -149,7 +149,7 @@ You'll see a beautiful dashboard in your terminal:
149
149
 
150
150
  Then open your browser:
151
151
 
152
- - **📖 Interactive API Docs (Scalar UI)**: http://localhost:3000/docs
152
+ - **📖 Professional API Docs (Modal UI)**: http://localhost:3000/docs
153
153
  - **📄 OpenAPI JSON Spec**: http://localhost:3000/docs-json
154
154
  - **🎭 Mock Endpoints**: http://localhost:3000/scramble-mock/*
155
155
 
@@ -164,20 +164,25 @@ Then open your browser:
164
164
 
165
165
  ```typescript
166
166
  NestScrambleModule.forRoot({
167
+ // Documentation path served by the module
168
+ path: '/docs', // default: '/docs'
169
+
167
170
  // Source directory to scan for controllers
168
171
  sourcePath: 'src', // default: 'src'
169
172
 
170
173
  // API base URL for OpenAPI spec
171
174
  baseUrl: 'http://localhost:3000', // default: 'http://localhost:3000'
172
175
 
173
- // Enable live mocking middleware
176
+ // Enable live mocking endpoints under /scramble-mock/*
174
177
  enableMock: true, // default: true
175
178
 
176
- // Auto-export Postman collection on startup
177
- autoExportPostman: false, // default: false
179
+ // Auto-export a Postman collection when generating
180
+ autoExportPostman: true,
181
+ postmanOutputPath: 'collection.json',
178
182
 
179
- // Postman collection output path
180
- postmanOutputPath: 'collection.json', // default: 'collection.json'
183
+ // API metadata
184
+ apiTitle: 'My API', // API documentation title
185
+ apiVersion: '1.0.0', // API version number
181
186
  })
182
187
  ```
183
188
 
@@ -185,11 +190,14 @@ NestScrambleModule.forRoot({
185
190
 
186
191
  | Option | Type | Default | Description |
187
192
  |--------|------|---------|-------------|
193
+ | `path` | `string` | `'/docs'` | Documentation route path |
188
194
  | `sourcePath` | `string` | `'src'` | Directory where your NestJS controllers are located |
189
195
  | `baseUrl` | `string` | `'http://localhost:3000'` | Base URL for your API (used in OpenAPI spec) |
190
196
  | `enableMock` | `boolean` | `true` | Enable `/scramble-mock/*` endpoints for testing |
191
197
  | `autoExportPostman` | `boolean` | `false` | Automatically generate Postman collection file |
192
198
  | `postmanOutputPath` | `string` | `'collection.json'` | Output path for Postman collection |
199
+ | `apiTitle` | `string` | Auto-detected | API documentation title |
200
+ | `apiVersion` | `string` | Auto-detected | API version number |
193
201
 
194
202
  ## 🎭 Live Mocking Guide
195
203
 
@@ -346,94 +354,71 @@ jobs:
346
354
 
347
355
  ## 🎨 Documentation UI - Professional API Dashboard
348
356
 
349
- ### ✨ Elite Dashboard Design (NEW!)
357
+ ### ✨ Professional Dashboard Design (NEW!)
350
358
 
351
- Nest-Scramble features a **professional, high-end API dashboard** inspired by Stripe and Postman, where each request is displayed on a separate page for focused documentation!
359
+ Nest-Scramble features a **modern, professional API dashboard** where each request is displayed on a separate page with focused documentation and beautiful interactions!
352
360
 
353
361
  **🚀 Key Features:**
354
- - **Sidebar-Only Navigation** - Fixed 320px sidebar with controller grouping
355
- - **Single-Request Per Page** - Each endpoint gets its own dedicated view
356
- - **Three-Column Elite Layout** - Information | Request Editor | Test Panel
357
- - **Deep Black Background** - Professional `#000000` and `#0B0E14` theme
358
- - **Cyber-Cyan Accents** - `#00f2ff` for active states and primary actions
359
- - **Vibrant HTTP Method Badges** - Color-coded with glow effects:
360
- - GET = Royal Blue (`#3B82F6`)
361
- - POST = Emerald Green (`#10B981`)
362
- - PUT = Amber Orange (`#F59E0B`)
363
- - PATCH = Violet Purple (`#8B5CF6`)
364
- - DELETE = Vibrant Red (`#EF4444`)
365
- - **Glassmorphism Effects** - Backdrop blur on request/response panels
366
- - **40px Spacious Padding** - Premium whitespace throughout
367
- - **Terminal-Style Response** - Black box with green text for API responses
368
- - **High-Contrast Labels** - Required, Type, and Default badges
369
- - **Custom Scrollbars** - Gradient cyan-to-purple styling
370
- - **Plus Jakarta Sans Typography** - Modern, professional font family
371
- - **Powered by Badge** - Animated branding with pulse effect
372
-
373
- ### 📐 Three-Column Elite Interface
374
-
375
- The dashboard uses a professional three-column layout for each endpoint:
376
-
377
- **Column 1 (Left) - Information Panel:**
378
- - Endpoint title with large, bold typography
379
- - HTTP method badge with vibrant colors and glow
380
- - Endpoint description and documentation
381
- - Clean parameters table with high-contrast labels
382
- - Type information and required field indicators
383
-
384
- **Column 2 (Middle) - Request Body Editor:**
385
- - Glassmorphism design with backdrop blur
386
- - Auto-filled mock data examples
387
- - JSON editor with syntax highlighting
388
- - Copy-to-clipboard functionality
389
- - Real-time validation
390
-
391
- **Column 3 (Right) - Test Request Panel:**
392
- - Enhanced glassmorphism with cyan border glow
393
- - Large "Send Request" button with gradient animation
394
- - Terminal-style response viewer (black background, green text)
395
- - Status code indicators
396
- - Response headers display
397
-
398
- ### 🎨 Theme Customization
399
-
400
- **Futuristic Theme (Default):**
401
- ```typescript
402
- NestScrambleModule.forRoot({
403
- theme: 'futuristic', // Professional dark dashboard
404
- primaryColor: '#00f2ff', // Cyber-Cyan (default)
405
- customDomainIcon: '/logo.png', // Your brand favicon
406
- apiTitle: 'My Awesome API',
407
- })
408
- ```
409
-
410
- **Classic Theme:**
411
- ```typescript
412
- NestScrambleModule.forRoot({
413
- theme: 'classic', // Clean, light, professional
414
- primaryColor: '#0066cc', // Corporate blue
415
- apiTitle: 'Enterprise API',
416
- })
417
- ```
418
-
419
- **Custom Color Branding:**
420
- ```typescript
421
- // One line changes the entire UI color scheme!
422
- NestScrambleModule.forRoot({
423
- primaryColor: '#a855f7', // Electric Purple
424
- // or '#10b981' for Emerald Green
425
- // or '#f59e0b' for Amber Orange
426
- // or any hex color you want!
427
- })
428
- ```
362
+ - **Separate Pages Per Endpoint** - Each request opens in its own modal with dedicated view
363
+ - **Smart Search & Filtering** - Real-time search across all endpoints
364
+ - **Controller Grouping** - Endpoints organized by tags with counters
365
+ - **Gradient Backgrounds** - Dynamic colors based on your brand
366
+ - **Smooth Animations** - Professional transitions and hover effects
367
+ - **Color-Coded HTTP Methods** - Visual distinction for different request types:
368
+ - GET = Green (`#c6f6d5`)
369
+ - POST = Blue (`#bee3f8`)
370
+ - PUT = Orange (`#feebc8`)
371
+ - PATCH = Purple (`#e9d8fd`)
372
+ - DELETE = Red (`#fed7d7`)
373
+ - **Professional Typography** - Clean, readable fonts with proper hierarchy
374
+ - **Responsive Design** - Works perfectly on desktop, tablet, and mobile
375
+ - **Modal-Based Navigation** - Clean overlay system for endpoint details
376
+ - **Parameter Documentation** - Clear display of request parameters and types
377
+ - **Response Examples** - Formatted JSON responses with syntax highlighting
378
+
379
+ ### 📐 Single-Request Interface
380
+
381
+ Each endpoint gets its own dedicated modal view with organized sections:
382
+
383
+ **Header Section:**
384
+ - Large HTTP method badge with color coding
385
+ - Full endpoint path in monospace font
386
+ - Summary and description of the endpoint
387
+ - Close button with rotation animation
388
+
389
+ **Information Sections:**
390
+ - **Description** - Clear documentation of what the endpoint does
391
+ - **Parameters** - Detailed breakdown of path, query, and body parameters
392
+ - **Request Body** - JSON schema examples with syntax highlighting
393
+ - **Responses** - All possible response codes with examples
394
+
395
+ **Interactive Features:**
396
+ - Click any endpoint card to open detailed view
397
+ - Search across all endpoints in real-time
398
+ - Smooth transitions and micro-interactions
399
+ - Click outside modal or press X to close
400
+ - Keyboard-friendly navigation
401
+
402
+ ### 🎨 Professional Design Features
403
+
404
+ **Current Design System:**
405
+ - **Beautiful Gradient Background** - Purple to blue gradient (`#667eea` to `#764ba2`)
406
+ - **Modal-Based Navigation** - Each endpoint opens in its own modal overlay
407
+ - **Smart Search Functionality** - Real-time filtering of endpoints
408
+ - **Controller Grouping** - Endpoints organized by tags with counters
409
+ - **Responsive Design** - Works perfectly on all devices
410
+ - **Smooth Animations** - Professional transitions and hover effects
429
411
 
430
412
  ### 🎭 UI Configuration Options
431
413
 
432
414
  | Option | Type | Default | Description |
433
415
  |--------|------|---------|-------------|
434
- | `theme` | `'classic' \| 'futuristic'` | `'futuristic'` | UI theme selection |
435
- | `primaryColor` | `string` | `'#00f2ff'` | Primary accent color (hex) |
436
- | `customDomainIcon` | `string` | `''` | Custom favicon URL |
416
+ | `path` | `string` | `'/docs'` | Documentation route path |
417
+ | `sourcePath` | `string` | `'src'` | Directory where your NestJS controllers are located |
418
+ | `baseUrl` | `string` | `'http://localhost:3000'` | Base URL for your API (used in OpenAPI spec) |
419
+ | `enableMock` | `boolean` | `true` | Enable `/scramble-mock/*` endpoints for testing |
420
+ | `autoExportPostman` | `boolean` | `false` | Automatically generate Postman collection file |
421
+ | `postmanOutputPath` | `string` | `'collection.json'` | Output path for Postman collection |
437
422
  | `apiTitle` | `string` | Auto-detected | API documentation title |
438
423
  | `apiVersion` | `string` | Auto-detected | API version number |
439
424
 
@@ -441,16 +426,15 @@ NestScrambleModule.forRoot({
441
426
 
442
427
  When you visit `http://localhost:3000/docs`, you'll experience:
443
428
 
444
- - 🎯 **Single-Request Navigation** - Each endpoint on its own dedicated page
445
- - 📂 **Controller Grouping** - Organized sidebar with uppercase section headers
446
- - 🎨 **Active State Glow** - Cyber-cyan highlight for selected endpoints
447
- - 📝 **Auto-generated Examples** - Pre-filled mock data in request editor
448
- - 🧪 **Live Testing** - Send requests directly from the three-column interface
449
- - 💻 **Terminal Response** - Black box with green text for authentic feel
450
- - 🔍 **Quick Search** - Press 'K' to search endpoints instantly
451
- - 📱 **Responsive Design** - Adapts to mobile, tablet, and desktop
452
- - **Animated Branding** - Pulsing "Powered by Nest-Scramble" badge
453
- - 🎭 **Developer Easter Eggs** - Check your browser console for surprises!
429
+ - 🎯 **Modal-Based Navigation** - Each endpoint opens in its own modal
430
+ - 📂 **Smart Grouping** - Endpoints organized by tags with counters
431
+ - 🔍 **Real-Time Search** - Instant filtering as you type
432
+ - 🎨 **Beautiful Gradients** - Professional purple to blue background
433
+ - 📱 **Responsive Design** - Perfect on desktop, tablet, and mobile
434
+ - **Smooth Animations** - Professional transitions and hover effects
435
+ - 📋 **Parameter Details** - Clear documentation of request parameters
436
+ - 💻 **Code Examples** - Formatted JSON with syntax highlighting
437
+ - 🔄 **Interactive Elements** - Click to explore, search to filter
454
438
 
455
439
  ### 🖥️ Terminal Dashboard
456
440
 
@@ -487,7 +471,7 @@ For complete UI customization guide, see:
487
471
 
488
472
  | Endpoint | Description |
489
473
  |----------|-------------|
490
- | `GET /docs` | Interactive Scalar UI documentation |
474
+ | `GET /docs` | Professional API documentation with modal-based endpoint pages |
491
475
  | `GET /docs-json` | OpenAPI 3.0 JSON specification |
492
476
  | `GET /docs/json` | Legacy endpoint (same as above) |
493
477
  | `GET /docs/spec` | OpenAPI spec as JSON response |
@@ -60,398 +60,398 @@ let DocsController = class DocsController {
60
60
  }
61
61
  generateMainPage(endpoints) {
62
62
  const groupedEndpoints = this.groupByTag(endpoints);
63
- return `<!DOCTYPE html>
64
- <html lang="en">
65
- <head>
66
- <meta charset="UTF-8">
67
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
68
- <title>${this.openApiSpec?.info?.title || 'API Documentation'}</title>
69
- <style>
70
- * {
71
- margin: 0;
72
- padding: 0;
73
- box-sizing: border-box;
74
- }
75
-
76
- body {
77
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
78
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
79
- min-height: 100vh;
80
- color: #1a202c;
81
- }
82
-
83
- .container {
84
- max-width: 1400px;
85
- margin: 0 auto;
86
- padding: 2rem;
87
- }
88
-
89
- .header {
90
- text-align: center;
91
- color: white;
92
- margin-bottom: 3rem;
93
- animation: fadeInDown 0.6s ease-out;
94
- }
95
-
96
- .header h1 {
97
- font-size: 2.5rem;
98
- font-weight: 700;
99
- margin-bottom: 0.5rem;
100
- text-shadow: 0 2px 10px rgba(0,0,0,0.2);
101
- }
102
-
103
- .header p {
104
- font-size: 1.1rem;
105
- opacity: 0.95;
106
- }
107
-
108
- .search-box {
109
- max-width: 600px;
110
- margin: 0 auto 3rem;
111
- animation: fadeIn 0.8s ease-out;
112
- }
113
-
114
- .search-input {
115
- width: 100%;
116
- padding: 1rem 1.5rem;
117
- font-size: 1rem;
118
- border: none;
119
- border-radius: 12px;
120
- background: white;
121
- box-shadow: 0 10px 30px rgba(0,0,0,0.2);
122
- transition: all 0.3s ease;
123
- }
124
-
125
- .search-input:focus {
126
- outline: none;
127
- box-shadow: 0 15px 40px rgba(0,0,0,0.3);
128
- transform: translateY(-2px);
129
- }
130
-
131
- .endpoints-grid {
132
- display: grid;
133
- gap: 1.5rem;
134
- animation: fadeInUp 0.8s ease-out;
135
- }
136
-
137
- .tag-section {
138
- background: white;
139
- border-radius: 16px;
140
- padding: 2rem;
141
- box-shadow: 0 10px 30px rgba(0,0,0,0.15);
142
- transition: all 0.3s ease;
143
- }
144
-
145
- .tag-section:hover {
146
- box-shadow: 0 15px 40px rgba(0,0,0,0.2);
147
- transform: translateY(-4px);
148
- }
149
-
150
- .tag-title {
151
- font-size: 1.5rem;
152
- font-weight: 600;
153
- margin-bottom: 1.5rem;
154
- color: #2d3748;
155
- display: flex;
156
- align-items: center;
157
- gap: 0.5rem;
158
- }
159
-
160
- .tag-badge {
161
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
162
- color: white;
163
- padding: 0.25rem 0.75rem;
164
- border-radius: 20px;
165
- font-size: 0.875rem;
166
- font-weight: 500;
167
- }
168
-
169
- .endpoint-list {
170
- display: grid;
171
- gap: 1rem;
172
- }
173
-
174
- .endpoint-card {
175
- background: #f7fafc;
176
- border-radius: 12px;
177
- padding: 1.25rem;
178
- cursor: pointer;
179
- transition: all 0.3s ease;
180
- border: 2px solid transparent;
181
- text-decoration: none;
182
- display: block;
183
- }
184
-
185
- .endpoint-card:hover {
186
- background: white;
187
- border-color: #667eea;
188
- transform: translateX(8px);
189
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
190
- }
191
-
192
- .endpoint-header {
193
- display: flex;
194
- align-items: center;
195
- gap: 1rem;
196
- margin-bottom: 0.75rem;
197
- }
198
-
199
- .method-badge {
200
- padding: 0.375rem 0.875rem;
201
- border-radius: 6px;
202
- font-weight: 600;
203
- font-size: 0.875rem;
204
- text-transform: uppercase;
205
- letter-spacing: 0.5px;
206
- min-width: 70px;
207
- text-align: center;
208
- }
209
-
210
- .method-get { background: #c6f6d5; color: #22543d; }
211
- .method-post { background: #bee3f8; color: #2c5282; }
212
- .method-put { background: #feebc8; color: #7c2d12; }
213
- .method-patch { background: #e9d8fd; color: #44337a; }
214
- .method-delete { background: #fed7d7; color: #742a2a; }
215
-
216
- .endpoint-path {
217
- font-family: 'Courier New', monospace;
218
- font-size: 1rem;
219
- color: #2d3748;
220
- font-weight: 500;
221
- flex: 1;
222
- }
223
-
224
- .endpoint-summary {
225
- color: #718096;
226
- font-size: 0.95rem;
227
- line-height: 1.5;
228
- }
229
-
230
- .endpoint-detail {
231
- display: none;
232
- position: fixed;
233
- top: 0;
234
- left: 0;
235
- right: 0;
236
- bottom: 0;
237
- background: rgba(0,0,0,0.5);
238
- z-index: 1000;
239
- overflow-y: auto;
240
- animation: fadeIn 0.3s ease-out;
241
- }
242
-
243
- .endpoint-detail.active {
244
- display: flex;
245
- align-items: center;
246
- justify-content: center;
247
- padding: 2rem;
248
- }
249
-
250
- .detail-content {
251
- background: white;
252
- border-radius: 16px;
253
- max-width: 900px;
254
- width: 100%;
255
- max-height: 90vh;
256
- overflow-y: auto;
257
- box-shadow: 0 20px 60px rgba(0,0,0,0.3);
258
- animation: slideUp 0.4s ease-out;
259
- }
260
-
261
- .detail-header {
262
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
- color: white;
264
- padding: 2rem;
265
- border-radius: 16px 16px 0 0;
266
- position: sticky;
267
- top: 0;
268
- z-index: 10;
269
- }
270
-
271
- .detail-close {
272
- float: right;
273
- background: rgba(255,255,255,0.2);
274
- border: none;
275
- color: white;
276
- font-size: 1.5rem;
277
- width: 40px;
278
- height: 40px;
279
- border-radius: 50%;
280
- cursor: pointer;
281
- transition: all 0.3s ease;
282
- display: flex;
283
- align-items: center;
284
- justify-content: center;
285
- }
286
-
287
- .detail-close:hover {
288
- background: rgba(255,255,255,0.3);
289
- transform: rotate(90deg);
290
- }
291
-
292
- .detail-body {
293
- padding: 2rem;
294
- }
295
-
296
- .section {
297
- margin-bottom: 2rem;
298
- }
299
-
300
- .section-title {
301
- font-size: 1.25rem;
302
- font-weight: 600;
303
- color: #2d3748;
304
- margin-bottom: 1rem;
305
- padding-bottom: 0.5rem;
306
- border-bottom: 2px solid #e2e8f0;
307
- }
308
-
309
- .param-item, .response-item {
310
- background: #f7fafc;
311
- padding: 1rem;
312
- border-radius: 8px;
313
- margin-bottom: 0.75rem;
314
- border-left: 3px solid #667eea;
315
- }
316
-
317
- .param-name, .response-code {
318
- font-weight: 600;
319
- color: #2d3748;
320
- font-family: 'Courier New', monospace;
321
- }
322
-
323
- .param-type {
324
- color: #667eea;
325
- font-size: 0.875rem;
326
- font-weight: 500;
327
- margin-left: 0.5rem;
328
- }
329
-
330
- .param-desc {
331
- color: #718096;
332
- margin-top: 0.5rem;
333
- font-size: 0.95rem;
334
- }
335
-
336
- .code-block {
337
- background: #1a202c;
338
- color: #e2e8f0;
339
- padding: 1.5rem;
340
- border-radius: 8px;
341
- overflow-x: auto;
342
- font-family: 'Courier New', monospace;
343
- font-size: 0.9rem;
344
- line-height: 1.6;
345
- }
346
-
347
- .no-results {
348
- text-align: center;
349
- padding: 3rem;
350
- color: white;
351
- font-size: 1.1rem;
352
- }
353
-
354
- @keyframes fadeIn {
355
- from { opacity: 0; }
356
- to { opacity: 1; }
357
- }
358
-
359
- @keyframes fadeInDown {
360
- from { opacity: 0; transform: translateY(-20px); }
361
- to { opacity: 1; transform: translateY(0); }
362
- }
363
-
364
- @keyframes fadeInUp {
365
- from { opacity: 0; transform: translateY(20px); }
366
- to { opacity: 1; transform: translateY(0); }
367
- }
368
-
369
- @keyframes slideUp {
370
- from { opacity: 0; transform: translateY(40px); }
371
- to { opacity: 1; transform: translateY(0); }
372
- }
373
-
374
- @media (max-width: 768px) {
375
- .container { padding: 1rem; }
376
- .header h1 { font-size: 2rem; }
377
- .detail-content { margin: 1rem; }
378
- }
379
- </style>
380
- </head>
381
- <body>
382
- <div class="container">
383
- <div class="header">
384
- <h1>${this.openApiSpec?.info?.title || 'API Documentation'}</h1>
385
- <p>${this.openApiSpec?.info?.description || ''} ${this.openApiSpec?.info?.version ? `v${this.openApiSpec.info.version}` : ''}</p>
386
- </div>
387
-
388
- <div class="search-box">
389
- <input type="text" class="search-input" id="searchInput" placeholder="🔍 Search endpoints...">
390
- </div>
391
-
392
- <div class="endpoints-grid" id="endpointsGrid">
393
- ${this.renderGroupedEndpoints(groupedEndpoints)}
394
- </div>
395
-
396
- <div class="no-results" id="noResults" style="display: none;">
397
- No endpoints found matching your search.
398
- </div>
399
- </div>
400
-
401
- ${this.renderEndpointModals(endpoints)}
402
-
403
- <script>
404
- const searchInput = document.getElementById('searchInput');
405
- const endpointsGrid = document.getElementById('endpointsGrid');
406
- const noResults = document.getElementById('noResults');
407
-
408
- searchInput.addEventListener('input', (e) => {
409
- const query = e.target.value.toLowerCase();
410
- const sections = endpointsGrid.querySelectorAll('.tag-section');
411
- let hasResults = false;
412
-
413
- sections.forEach(section => {
414
- const cards = section.querySelectorAll('.endpoint-card');
415
- let sectionHasResults = false;
416
-
417
- cards.forEach(card => {
418
- const text = card.textContent.toLowerCase();
419
- if (text.includes(query)) {
420
- card.style.display = 'block';
421
- sectionHasResults = true;
422
- hasResults = true;
423
- } else {
424
- card.style.display = 'none';
425
- }
426
- });
427
-
428
- section.style.display = sectionHasResults ? 'block' : 'none';
429
- });
430
-
431
- noResults.style.display = hasResults ? 'none' : 'block';
432
- endpointsGrid.style.display = hasResults ? 'grid' : 'none';
433
- });
434
-
435
- function openEndpoint(id) {
436
- document.getElementById('detail-' + id).classList.add('active');
437
- document.body.style.overflow = 'hidden';
438
- }
439
-
440
- function closeEndpoint(id) {
441
- document.getElementById('detail-' + id).classList.remove('active');
442
- document.body.style.overflow = 'auto';
443
- }
444
-
445
- document.querySelectorAll('.endpoint-detail').forEach(modal => {
446
- modal.addEventListener('click', (e) => {
447
- if (e.target === modal) {
448
- modal.classList.remove('active');
449
- document.body.style.overflow = 'auto';
450
- }
451
- });
452
- });
453
- </script>
454
- </body>
63
+ return `<!DOCTYPE html>
64
+ <html lang="en">
65
+ <head>
66
+ <meta charset="UTF-8">
67
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
68
+ <title>${this.openApiSpec?.info?.title || 'API Documentation'}</title>
69
+ <style>
70
+ * {
71
+ margin: 0;
72
+ padding: 0;
73
+ box-sizing: border-box;
74
+ }
75
+
76
+ body {
77
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
78
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
79
+ min-height: 100vh;
80
+ color: #1a202c;
81
+ }
82
+
83
+ .container {
84
+ max-width: 1400px;
85
+ margin: 0 auto;
86
+ padding: 2rem;
87
+ }
88
+
89
+ .header {
90
+ text-align: center;
91
+ color: white;
92
+ margin-bottom: 3rem;
93
+ animation: fadeInDown 0.6s ease-out;
94
+ }
95
+
96
+ .header h1 {
97
+ font-size: 2.5rem;
98
+ font-weight: 700;
99
+ margin-bottom: 0.5rem;
100
+ text-shadow: 0 2px 10px rgba(0,0,0,0.2);
101
+ }
102
+
103
+ .header p {
104
+ font-size: 1.1rem;
105
+ opacity: 0.95;
106
+ }
107
+
108
+ .search-box {
109
+ max-width: 600px;
110
+ margin: 0 auto 3rem;
111
+ animation: fadeIn 0.8s ease-out;
112
+ }
113
+
114
+ .search-input {
115
+ width: 100%;
116
+ padding: 1rem 1.5rem;
117
+ font-size: 1rem;
118
+ border: none;
119
+ border-radius: 12px;
120
+ background: white;
121
+ box-shadow: 0 10px 30px rgba(0,0,0,0.2);
122
+ transition: all 0.3s ease;
123
+ }
124
+
125
+ .search-input:focus {
126
+ outline: none;
127
+ box-shadow: 0 15px 40px rgba(0,0,0,0.3);
128
+ transform: translateY(-2px);
129
+ }
130
+
131
+ .endpoints-grid {
132
+ display: grid;
133
+ gap: 1.5rem;
134
+ animation: fadeInUp 0.8s ease-out;
135
+ }
136
+
137
+ .tag-section {
138
+ background: white;
139
+ border-radius: 16px;
140
+ padding: 2rem;
141
+ box-shadow: 0 10px 30px rgba(0,0,0,0.15);
142
+ transition: all 0.3s ease;
143
+ }
144
+
145
+ .tag-section:hover {
146
+ box-shadow: 0 15px 40px rgba(0,0,0,0.2);
147
+ transform: translateY(-4px);
148
+ }
149
+
150
+ .tag-title {
151
+ font-size: 1.5rem;
152
+ font-weight: 600;
153
+ margin-bottom: 1.5rem;
154
+ color: #2d3748;
155
+ display: flex;
156
+ align-items: center;
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ .tag-badge {
161
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
162
+ color: white;
163
+ padding: 0.25rem 0.75rem;
164
+ border-radius: 20px;
165
+ font-size: 0.875rem;
166
+ font-weight: 500;
167
+ }
168
+
169
+ .endpoint-list {
170
+ display: grid;
171
+ gap: 1rem;
172
+ }
173
+
174
+ .endpoint-card {
175
+ background: #f7fafc;
176
+ border-radius: 12px;
177
+ padding: 1.25rem;
178
+ cursor: pointer;
179
+ transition: all 0.3s ease;
180
+ border: 2px solid transparent;
181
+ text-decoration: none;
182
+ display: block;
183
+ }
184
+
185
+ .endpoint-card:hover {
186
+ background: white;
187
+ border-color: #667eea;
188
+ transform: translateX(8px);
189
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.15);
190
+ }
191
+
192
+ .endpoint-header {
193
+ display: flex;
194
+ align-items: center;
195
+ gap: 1rem;
196
+ margin-bottom: 0.75rem;
197
+ }
198
+
199
+ .method-badge {
200
+ padding: 0.375rem 0.875rem;
201
+ border-radius: 6px;
202
+ font-weight: 600;
203
+ font-size: 0.875rem;
204
+ text-transform: uppercase;
205
+ letter-spacing: 0.5px;
206
+ min-width: 70px;
207
+ text-align: center;
208
+ }
209
+
210
+ .method-get { background: #c6f6d5; color: #22543d; }
211
+ .method-post { background: #bee3f8; color: #2c5282; }
212
+ .method-put { background: #feebc8; color: #7c2d12; }
213
+ .method-patch { background: #e9d8fd; color: #44337a; }
214
+ .method-delete { background: #fed7d7; color: #742a2a; }
215
+
216
+ .endpoint-path {
217
+ font-family: 'Courier New', monospace;
218
+ font-size: 1rem;
219
+ color: #2d3748;
220
+ font-weight: 500;
221
+ flex: 1;
222
+ }
223
+
224
+ .endpoint-summary {
225
+ color: #718096;
226
+ font-size: 0.95rem;
227
+ line-height: 1.5;
228
+ }
229
+
230
+ .endpoint-detail {
231
+ display: none;
232
+ position: fixed;
233
+ top: 0;
234
+ left: 0;
235
+ right: 0;
236
+ bottom: 0;
237
+ background: rgba(0,0,0,0.5);
238
+ z-index: 1000;
239
+ overflow-y: auto;
240
+ animation: fadeIn 0.3s ease-out;
241
+ }
242
+
243
+ .endpoint-detail.active {
244
+ display: flex;
245
+ align-items: center;
246
+ justify-content: center;
247
+ padding: 2rem;
248
+ }
249
+
250
+ .detail-content {
251
+ background: white;
252
+ border-radius: 16px;
253
+ max-width: 900px;
254
+ width: 100%;
255
+ max-height: 90vh;
256
+ overflow-y: auto;
257
+ box-shadow: 0 20px 60px rgba(0,0,0,0.3);
258
+ animation: slideUp 0.4s ease-out;
259
+ }
260
+
261
+ .detail-header {
262
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
263
+ color: white;
264
+ padding: 2rem;
265
+ border-radius: 16px 16px 0 0;
266
+ position: sticky;
267
+ top: 0;
268
+ z-index: 10;
269
+ }
270
+
271
+ .detail-close {
272
+ float: right;
273
+ background: rgba(255,255,255,0.2);
274
+ border: none;
275
+ color: white;
276
+ font-size: 1.5rem;
277
+ width: 40px;
278
+ height: 40px;
279
+ border-radius: 50%;
280
+ cursor: pointer;
281
+ transition: all 0.3s ease;
282
+ display: flex;
283
+ align-items: center;
284
+ justify-content: center;
285
+ }
286
+
287
+ .detail-close:hover {
288
+ background: rgba(255,255,255,0.3);
289
+ transform: rotate(90deg);
290
+ }
291
+
292
+ .detail-body {
293
+ padding: 2rem;
294
+ }
295
+
296
+ .section {
297
+ margin-bottom: 2rem;
298
+ }
299
+
300
+ .section-title {
301
+ font-size: 1.25rem;
302
+ font-weight: 600;
303
+ color: #2d3748;
304
+ margin-bottom: 1rem;
305
+ padding-bottom: 0.5rem;
306
+ border-bottom: 2px solid #e2e8f0;
307
+ }
308
+
309
+ .param-item, .response-item {
310
+ background: #f7fafc;
311
+ padding: 1rem;
312
+ border-radius: 8px;
313
+ margin-bottom: 0.75rem;
314
+ border-left: 3px solid #667eea;
315
+ }
316
+
317
+ .param-name, .response-code {
318
+ font-weight: 600;
319
+ color: #2d3748;
320
+ font-family: 'Courier New', monospace;
321
+ }
322
+
323
+ .param-type {
324
+ color: #667eea;
325
+ font-size: 0.875rem;
326
+ font-weight: 500;
327
+ margin-left: 0.5rem;
328
+ }
329
+
330
+ .param-desc {
331
+ color: #718096;
332
+ margin-top: 0.5rem;
333
+ font-size: 0.95rem;
334
+ }
335
+
336
+ .code-block {
337
+ background: #1a202c;
338
+ color: #e2e8f0;
339
+ padding: 1.5rem;
340
+ border-radius: 8px;
341
+ overflow-x: auto;
342
+ font-family: 'Courier New', monospace;
343
+ font-size: 0.9rem;
344
+ line-height: 1.6;
345
+ }
346
+
347
+ .no-results {
348
+ text-align: center;
349
+ padding: 3rem;
350
+ color: white;
351
+ font-size: 1.1rem;
352
+ }
353
+
354
+ @keyframes fadeIn {
355
+ from { opacity: 0; }
356
+ to { opacity: 1; }
357
+ }
358
+
359
+ @keyframes fadeInDown {
360
+ from { opacity: 0; transform: translateY(-20px); }
361
+ to { opacity: 1; transform: translateY(0); }
362
+ }
363
+
364
+ @keyframes fadeInUp {
365
+ from { opacity: 0; transform: translateY(20px); }
366
+ to { opacity: 1; transform: translateY(0); }
367
+ }
368
+
369
+ @keyframes slideUp {
370
+ from { opacity: 0; transform: translateY(40px); }
371
+ to { opacity: 1; transform: translateY(0); }
372
+ }
373
+
374
+ @media (max-width: 768px) {
375
+ .container { padding: 1rem; }
376
+ .header h1 { font-size: 2rem; }
377
+ .detail-content { margin: 1rem; }
378
+ }
379
+ </style>
380
+ </head>
381
+ <body>
382
+ <div class="container">
383
+ <div class="header">
384
+ <h1>${this.openApiSpec?.info?.title || 'API Documentation'}</h1>
385
+ <p>${this.openApiSpec?.info?.description || ''} ${this.openApiSpec?.info?.version ? `v${this.openApiSpec.info.version}` : ''}</p>
386
+ </div>
387
+
388
+ <div class="search-box">
389
+ <input type="text" class="search-input" id="searchInput" placeholder="🔍 Search endpoints...">
390
+ </div>
391
+
392
+ <div class="endpoints-grid" id="endpointsGrid">
393
+ ${this.renderGroupedEndpoints(groupedEndpoints)}
394
+ </div>
395
+
396
+ <div class="no-results" id="noResults" style="display: none;">
397
+ No endpoints found matching your search.
398
+ </div>
399
+ </div>
400
+
401
+ ${this.renderEndpointModals(endpoints)}
402
+
403
+ <script>
404
+ const searchInput = document.getElementById('searchInput');
405
+ const endpointsGrid = document.getElementById('endpointsGrid');
406
+ const noResults = document.getElementById('noResults');
407
+
408
+ searchInput.addEventListener('input', (e) => {
409
+ const query = e.target.value.toLowerCase();
410
+ const sections = endpointsGrid.querySelectorAll('.tag-section');
411
+ let hasResults = false;
412
+
413
+ sections.forEach(section => {
414
+ const cards = section.querySelectorAll('.endpoint-card');
415
+ let sectionHasResults = false;
416
+
417
+ cards.forEach(card => {
418
+ const text = card.textContent.toLowerCase();
419
+ if (text.includes(query)) {
420
+ card.style.display = 'block';
421
+ sectionHasResults = true;
422
+ hasResults = true;
423
+ } else {
424
+ card.style.display = 'none';
425
+ }
426
+ });
427
+
428
+ section.style.display = sectionHasResults ? 'block' : 'none';
429
+ });
430
+
431
+ noResults.style.display = hasResults ? 'none' : 'block';
432
+ endpointsGrid.style.display = hasResults ? 'grid' : 'none';
433
+ });
434
+
435
+ function openEndpoint(id) {
436
+ document.getElementById('detail-' + id).classList.add('active');
437
+ document.body.style.overflow = 'hidden';
438
+ }
439
+
440
+ function closeEndpoint(id) {
441
+ document.getElementById('detail-' + id).classList.remove('active');
442
+ document.body.style.overflow = 'auto';
443
+ }
444
+
445
+ document.querySelectorAll('.endpoint-detail').forEach(modal => {
446
+ modal.addEventListener('click', (e) => {
447
+ if (e.target === modal) {
448
+ modal.classList.remove('active');
449
+ document.body.style.overflow = 'auto';
450
+ }
451
+ });
452
+ });
453
+ </script>
454
+ </body>
455
455
  </html>`;
456
456
  }
457
457
  groupByTag(endpoints) {
@@ -466,90 +466,90 @@ let DocsController = class DocsController {
466
466
  return grouped;
467
467
  }
468
468
  renderGroupedEndpoints(grouped) {
469
- return Object.entries(grouped).map(([tag, endpoints]) => `
470
- <div class="tag-section">
471
- <div class="tag-title">
472
- ${tag}
473
- <span class="tag-badge">${endpoints.length}</span>
474
- </div>
475
- <div class="endpoint-list">
476
- ${endpoints.map(ep => `
477
- <div class="endpoint-card" onclick="openEndpoint('${ep.id}')">
478
- <div class="endpoint-header">
479
- <span class="method-badge method-${ep.method.toLowerCase()}">${ep.method}</span>
480
- <span class="endpoint-path">${ep.path}</span>
481
- </div>
482
- ${ep.summary ? `<div class="endpoint-summary">${ep.summary}</div>` : ''}
483
- </div>
484
- `).join('')}
485
- </div>
486
- </div>
469
+ return Object.entries(grouped).map(([tag, endpoints]) => `
470
+ <div class="tag-section">
471
+ <div class="tag-title">
472
+ ${tag}
473
+ <span class="tag-badge">${endpoints.length}</span>
474
+ </div>
475
+ <div class="endpoint-list">
476
+ ${endpoints.map(ep => `
477
+ <div class="endpoint-card" onclick="openEndpoint('${ep.id}')">
478
+ <div class="endpoint-header">
479
+ <span class="method-badge method-${ep.method.toLowerCase()}">${ep.method}</span>
480
+ <span class="endpoint-path">${ep.path}</span>
481
+ </div>
482
+ ${ep.summary ? `<div class="endpoint-summary">${ep.summary}</div>` : ''}
483
+ </div>
484
+ `).join('')}
485
+ </div>
486
+ </div>
487
487
  `).join('');
488
488
  }
489
489
  renderEndpointModals(endpoints) {
490
- return endpoints.map(ep => `
491
- <div class="endpoint-detail" id="detail-${ep.id}">
492
- <div class="detail-content">
493
- <div class="detail-header">
494
- <button class="detail-close" onclick="closeEndpoint('${ep.id}')">&times;</button>
495
- <div style="clear: both;">
496
- <div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
497
- <span class="method-badge method-${ep.method.toLowerCase()}">${ep.method}</span>
498
- <span style="font-family: 'Courier New', monospace; font-size: 1.25rem;">${ep.path}</span>
499
- </div>
500
- ${ep.summary ? `<p style="opacity: 0.95; font-size: 1.1rem;">${ep.summary}</p>` : ''}
501
- </div>
502
- </div>
503
-
504
- <div class="detail-body">
505
- ${ep.description ? `
506
- <div class="section">
507
- <div class="section-title">Description</div>
508
- <p style="color: #4a5568; line-height: 1.6;">${ep.description}</p>
509
- </div>
510
- ` : ''}
511
-
512
- ${ep.parameters && ep.parameters.length > 0 ? `
513
- <div class="section">
514
- <div class="section-title">Parameters</div>
515
- ${ep.parameters.map((param) => `
516
- <div class="param-item">
517
- <div>
518
- <span class="param-name">${param.name}</span>
519
- <span class="param-type">${param.schema?.type || param.type || 'string'}</span>
520
- ${param.required ? '<span style="color: #e53e3e; font-size: 0.875rem; margin-left: 0.5rem;">*required</span>' : ''}
521
- </div>
522
- <div style="color: #a0aec0; font-size: 0.875rem; margin-top: 0.25rem;">in: ${param.in}</div>
523
- ${param.description ? `<div class="param-desc">${param.description}</div>` : ''}
524
- </div>
525
- `).join('')}
526
- </div>
527
- ` : ''}
528
-
529
- ${ep.requestBody ? `
530
- <div class="section">
531
- <div class="section-title">Request Body</div>
532
- <div class="code-block">${this.formatJson(ep.requestBody.content?.['application/json']?.schema || ep.requestBody)}</div>
533
- </div>
534
- ` : ''}
535
-
536
- ${ep.responses && Object.keys(ep.responses).length > 0 ? `
537
- <div class="section">
538
- <div class="section-title">Responses</div>
539
- ${Object.entries(ep.responses).map(([code, response]) => `
540
- <div class="response-item">
541
- <div class="response-code">Status ${code}</div>
542
- ${response.description ? `<div class="param-desc">${response.description}</div>` : ''}
543
- ${response.content?.['application/json']?.schema ? `
544
- <div class="code-block" style="margin-top: 0.75rem;">${this.formatJson(response.content['application/json'].schema)}</div>
545
- ` : ''}
546
- </div>
547
- `).join('')}
548
- </div>
549
- ` : ''}
550
- </div>
551
- </div>
552
- </div>
490
+ return endpoints.map(ep => `
491
+ <div class="endpoint-detail" id="detail-${ep.id}">
492
+ <div class="detail-content">
493
+ <div class="detail-header">
494
+ <button class="detail-close" onclick="closeEndpoint('${ep.id}')">&times;</button>
495
+ <div style="clear: both;">
496
+ <div style="display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem;">
497
+ <span class="method-badge method-${ep.method.toLowerCase()}">${ep.method}</span>
498
+ <span style="font-family: 'Courier New', monospace; font-size: 1.25rem;">${ep.path}</span>
499
+ </div>
500
+ ${ep.summary ? `<p style="opacity: 0.95; font-size: 1.1rem;">${ep.summary}</p>` : ''}
501
+ </div>
502
+ </div>
503
+
504
+ <div class="detail-body">
505
+ ${ep.description ? `
506
+ <div class="section">
507
+ <div class="section-title">Description</div>
508
+ <p style="color: #4a5568; line-height: 1.6;">${ep.description}</p>
509
+ </div>
510
+ ` : ''}
511
+
512
+ ${ep.parameters && ep.parameters.length > 0 ? `
513
+ <div class="section">
514
+ <div class="section-title">Parameters</div>
515
+ ${ep.parameters.map((param) => `
516
+ <div class="param-item">
517
+ <div>
518
+ <span class="param-name">${param.name}</span>
519
+ <span class="param-type">${param.schema?.type || param.type || 'string'}</span>
520
+ ${param.required ? '<span style="color: #e53e3e; font-size: 0.875rem; margin-left: 0.5rem;">*required</span>' : ''}
521
+ </div>
522
+ <div style="color: #a0aec0; font-size: 0.875rem; margin-top: 0.25rem;">in: ${param.in}</div>
523
+ ${param.description ? `<div class="param-desc">${param.description}</div>` : ''}
524
+ </div>
525
+ `).join('')}
526
+ </div>
527
+ ` : ''}
528
+
529
+ ${ep.requestBody ? `
530
+ <div class="section">
531
+ <div class="section-title">Request Body</div>
532
+ <div class="code-block">${this.formatJson(ep.requestBody.content?.['application/json']?.schema || ep.requestBody)}</div>
533
+ </div>
534
+ ` : ''}
535
+
536
+ ${ep.responses && Object.keys(ep.responses).length > 0 ? `
537
+ <div class="section">
538
+ <div class="section-title">Responses</div>
539
+ ${Object.entries(ep.responses).map(([code, response]) => `
540
+ <div class="response-item">
541
+ <div class="response-code">Status ${code}</div>
542
+ ${response.description ? `<div class="param-desc">${response.description}</div>` : ''}
543
+ ${response.content?.['application/json']?.schema ? `
544
+ <div class="code-block" style="margin-top: 0.75rem;">${this.formatJson(response.content['application/json'].schema)}</div>
545
+ ` : ''}
546
+ </div>
547
+ `).join('')}
548
+ </div>
549
+ ` : ''}
550
+ </div>
551
+ </div>
552
+ </div>
553
553
  `).join('');
554
554
  }
555
555
  formatJson(obj) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nest-scramble",
3
- "version": "1.4.9",
3
+ "version": "1.5.0",
4
4
  "description": "A next-generation, decorator-free API documentation engine and intelligent mock server for NestJS, engineered by Mohamed Mustafa",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",