fragment-ts 1.2.5 → 1.2.6

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.
@@ -37,11 +37,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.DocGenerator = void 0;
40
+ const ejs_1 = __importDefault(require("ejs"));
40
41
  const fs = __importStar(require("fs"));
41
42
  const path = __importStar(require("path"));
42
- const ejs_1 = __importDefault(require("ejs"));
43
- const metadata_storage_1 = require("../../core/metadata/metadata-storage");
44
43
  const metadata_keys_1 = require("../../core/metadata/metadata-keys");
44
+ const metadata_storage_1 = require("../../core/metadata/metadata-storage");
45
45
  const config_utils_1 = require("../../shared/config.utils");
46
46
  const logger_1 = require("../../shared/logger");
47
47
  const logger = logger_1.FragmentLogger.for("docs");
@@ -88,9 +88,7 @@ class DocGenerator {
88
88
  buildSpec(controllers) {
89
89
  const config = config_utils_1.ConfigUtils.getDocsConfig();
90
90
  const controllerList = controllers ||
91
- this.storage
92
- .getAllClasses()
93
- .filter((cls) => cls.type === "controller");
91
+ this.storage.getAllClasses().filter((cls) => cls.type === "controller");
94
92
  const methods = this.storage.getAllMethods();
95
93
  const schemas = {};
96
94
  const paths = {};
@@ -151,7 +149,9 @@ class DocGenerator {
151
149
  : this.buildExampleFromType(response.type);
152
150
  entry.content = {
153
151
  "application/json": {
154
- schema: response.isArray ? { type: "array", items: schema } : schema,
152
+ schema: response.isArray
153
+ ? { type: "array", items: schema }
154
+ : schema,
155
155
  ...(example !== undefined
156
156
  ? {
157
157
  example: response.isArray ? [example] : example,
@@ -202,7 +202,8 @@ class DocGenerator {
202
202
  };
203
203
  }
204
204
  buildSchemaForClass(type, schemas) {
205
- const props = (Reflect.getMetadata(metadata_keys_1.METADATA_KEYS.API_PROPERTIES, type) || {});
205
+ const props = (Reflect.getMetadata(metadata_keys_1.METADATA_KEYS.API_PROPERTIES, type) ||
206
+ {});
206
207
  const properties = {};
207
208
  const required = [];
208
209
  Object.entries(props).forEach(([key, options]) => {
@@ -250,7 +251,8 @@ class DocGenerator {
250
251
  return new Date().toISOString();
251
252
  if (type === Array)
252
253
  return [];
253
- const props = (Reflect.getMetadata(metadata_keys_1.METADATA_KEYS.API_PROPERTIES, type) || {});
254
+ const props = (Reflect.getMetadata(metadata_keys_1.METADATA_KEYS.API_PROPERTIES, type) ||
255
+ {});
254
256
  const keys = Object.keys(props);
255
257
  if (keys.length === 0)
256
258
  return {};
@@ -327,45 +329,686 @@ const DEFAULT_TEMPLATE = `<!doctype html>
327
329
  <meta name="viewport" content="width=device-width, initial-scale=1" />
328
330
  <title><%= spec.info.title %></title>
329
331
  <style>
330
- :root { color-scheme: light; }
331
- body { font-family: "IBM Plex Sans", "Segoe UI", sans-serif; margin: 0; background: #f6f7fb; color: #111; }
332
- header { background: #121826; color: #fff; padding: 32px; }
333
- header h1 { margin: 0 0 8px; font-size: 28px; }
334
- header p { margin: 0; opacity: 0.7; }
335
- main { padding: 24px; max-width: 1100px; margin: 0 auto; }
336
- .endpoint { background: #fff; border-radius: 12px; padding: 16px; margin-bottom: 16px; box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08); }
337
- .endpoint h3 { margin: 0 0 8px; font-size: 18px; }
338
- .badge { display: inline-block; padding: 2px 8px; border-radius: 6px; background: #0f172a; color: #fff; font-size: 12px; margin-right: 8px; }
339
- .path { font-family: "JetBrains Mono", monospace; font-size: 13px; }
340
- .desc { color: #475569; margin: 8px 0; }
341
- pre { background: #0f172a; color: #e2e8f0; padding: 12px; border-radius: 8px; overflow-x: auto; }
332
+ * { box-sizing: border-box; }
333
+ :root {
334
+ --color-bg: #0f172a;
335
+ --color-bg-secondary: #1e293b;
336
+ --color-surface: #334155;
337
+ --color-text: #f1f5f9;
338
+ --color-text-muted: #94a3b8;
339
+ --color-primary: #3b82f6;
340
+ --color-primary-dark: #2563eb;
341
+ --color-success: #10b981;
342
+ --color-warning: #f59e0b;
343
+ --color-error: #ef4444;
344
+ --color-border: #475569;
345
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
346
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1);
347
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.3);
348
+ }
349
+
350
+ body {
351
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
352
+ margin: 0;
353
+ background: var(--color-bg);
354
+ color: var(--color-text);
355
+ line-height: 1.6;
356
+ }
357
+
358
+ .header {
359
+ background: linear-gradient(135deg, #1e293b 0%, #0f172a 100%);
360
+ padding: 2rem 2rem 3rem;
361
+ border-bottom: 1px solid var(--color-border);
362
+ box-shadow: var(--shadow-lg);
363
+ }
364
+
365
+ .header-content {
366
+ max-width: 1400px;
367
+ margin: 0 auto;
368
+ }
369
+
370
+ .header h1 {
371
+ margin: 0 0 0.5rem;
372
+ font-size: 2.25rem;
373
+ font-weight: 700;
374
+ background: linear-gradient(135deg, var(--color-primary) 0%, #60a5fa 100%);
375
+ -webkit-background-clip: text;
376
+ -webkit-text-fill-color: transparent;
377
+ background-clip: text;
378
+ }
379
+
380
+ .header-info {
381
+ display: flex;
382
+ gap: 1.5rem;
383
+ align-items: center;
384
+ margin-top: 1rem;
385
+ }
386
+
387
+ .version-badge {
388
+ display: inline-flex;
389
+ align-items: center;
390
+ gap: 0.5rem;
391
+ padding: 0.375rem 0.75rem;
392
+ background: var(--color-bg-secondary);
393
+ border: 1px solid var(--color-border);
394
+ border-radius: 0.5rem;
395
+ font-size: 0.875rem;
396
+ color: var(--color-text-muted);
397
+ }
398
+
399
+ .openapi-badge {
400
+ display: inline-flex;
401
+ align-items: center;
402
+ padding: 0.375rem 0.75rem;
403
+ background: var(--color-success);
404
+ color: white;
405
+ border-radius: 0.5rem;
406
+ font-size: 0.75rem;
407
+ font-weight: 600;
408
+ text-transform: uppercase;
409
+ letter-spacing: 0.05em;
410
+ }
411
+
412
+ .container {
413
+ max-width: 1400px;
414
+ margin: 0 auto;
415
+ padding: 2rem;
416
+ display: grid;
417
+ grid-template-columns: 280px 1fr;
418
+ gap: 2rem;
419
+ }
420
+
421
+ .sidebar {
422
+ position: sticky;
423
+ top: 2rem;
424
+ height: fit-content;
425
+ max-height: calc(100vh - 4rem);
426
+ overflow-y: auto;
427
+ background: var(--color-bg-secondary);
428
+ border: 1px solid var(--color-border);
429
+ border-radius: 0.75rem;
430
+ padding: 1.5rem;
431
+ }
432
+
433
+ .sidebar::-webkit-scrollbar {
434
+ width: 6px;
435
+ }
436
+
437
+ .sidebar::-webkit-scrollbar-track {
438
+ background: var(--color-bg-secondary);
439
+ }
440
+
441
+ .sidebar::-webkit-scrollbar-thumb {
442
+ background: var(--color-surface);
443
+ border-radius: 3px;
444
+ }
445
+
446
+ .sidebar h3 {
447
+ margin: 0 0 1rem;
448
+ font-size: 0.875rem;
449
+ text-transform: uppercase;
450
+ letter-spacing: 0.05em;
451
+ color: var(--color-text-muted);
452
+ font-weight: 600;
453
+ }
454
+
455
+ .tag-group {
456
+ margin-bottom: 1.5rem;
457
+ }
458
+
459
+ .tag-title {
460
+ font-weight: 600;
461
+ color: var(--color-text);
462
+ margin-bottom: 0.5rem;
463
+ font-size: 0.9rem;
464
+ }
465
+
466
+ .tag-links {
467
+ display: flex;
468
+ flex-direction: column;
469
+ gap: 0.25rem;
470
+ }
471
+
472
+ .tag-link {
473
+ padding: 0.5rem 0.75rem;
474
+ color: var(--color-text-muted);
475
+ text-decoration: none;
476
+ border-radius: 0.375rem;
477
+ font-size: 0.875rem;
478
+ transition: all 0.2s;
479
+ display: block;
480
+ }
481
+
482
+ .tag-link:hover {
483
+ background: var(--color-surface);
484
+ color: var(--color-text);
485
+ transform: translateX(4px);
486
+ }
487
+
488
+ .main-content {
489
+ min-width: 0;
490
+ }
491
+
492
+ .section-title {
493
+ font-size: 1.5rem;
494
+ font-weight: 700;
495
+ margin: 0 0 1.5rem;
496
+ color: var(--color-text);
497
+ }
498
+
499
+ .endpoints-section {
500
+ margin-bottom: 3rem;
501
+ }
502
+
503
+ .endpoint {
504
+ background: var(--color-bg-secondary);
505
+ border: 1px solid var(--color-border);
506
+ border-radius: 0.75rem;
507
+ margin-bottom: 1rem;
508
+ overflow: hidden;
509
+ transition: all 0.3s;
510
+ }
511
+
512
+ .endpoint:hover {
513
+ border-color: var(--color-primary);
514
+ box-shadow: var(--shadow-md);
515
+ }
516
+
517
+ .endpoint-header {
518
+ padding: 1.25rem;
519
+ cursor: pointer;
520
+ display: flex;
521
+ align-items: center;
522
+ gap: 1rem;
523
+ transition: background 0.2s;
524
+ }
525
+
526
+ .endpoint-header:hover {
527
+ background: rgba(59, 130, 246, 0.05);
528
+ }
529
+
530
+ .method-badge {
531
+ display: inline-flex;
532
+ align-items: center;
533
+ justify-content: center;
534
+ min-width: 4rem;
535
+ padding: 0.375rem 0.75rem;
536
+ border-radius: 0.375rem;
537
+ font-size: 0.75rem;
538
+ font-weight: 700;
539
+ text-transform: uppercase;
540
+ letter-spacing: 0.05em;
541
+ }
542
+
543
+ .method-get { background: var(--color-primary); color: white; }
544
+ .method-post { background: var(--color-success); color: white; }
545
+ .method-put { background: var(--color-warning); color: white; }
546
+ .method-delete { background: var(--color-error); color: white; }
547
+ .method-patch { background: #8b5cf6; color: white; }
548
+
549
+ .endpoint-path {
550
+ font-family: "JetBrains Mono", "Fira Code", "Courier New", monospace;
551
+ font-size: 0.9rem;
552
+ color: var(--color-text);
553
+ font-weight: 500;
554
+ flex: 1;
555
+ }
556
+
557
+ .expand-icon {
558
+ width: 20px;
559
+ height: 20px;
560
+ transition: transform 0.3s;
561
+ color: var(--color-text-muted);
562
+ }
563
+
564
+ .endpoint.expanded .expand-icon {
565
+ transform: rotate(180deg);
566
+ }
567
+
568
+ .endpoint-body {
569
+ max-height: 0;
570
+ overflow: hidden;
571
+ transition: max-height 0.3s ease-out;
572
+ }
573
+
574
+ .endpoint.expanded .endpoint-body {
575
+ max-height: 5000px;
576
+ transition: max-height 0.5s ease-in;
577
+ }
578
+
579
+ .endpoint-content {
580
+ padding: 0 1.25rem 1.25rem;
581
+ border-top: 1px solid var(--color-border);
582
+ }
583
+
584
+ .endpoint-summary {
585
+ color: var(--color-text-muted);
586
+ margin: 1rem 0;
587
+ font-size: 0.95rem;
588
+ }
589
+
590
+ .endpoint-description {
591
+ color: var(--color-text-muted);
592
+ margin: 0.5rem 0 1rem;
593
+ font-size: 0.9rem;
594
+ }
595
+
596
+ .tags {
597
+ display: flex;
598
+ gap: 0.5rem;
599
+ flex-wrap: wrap;
600
+ margin: 1rem 0;
601
+ }
602
+
603
+ .tag {
604
+ padding: 0.25rem 0.75rem;
605
+ background: var(--color-surface);
606
+ color: var(--color-primary);
607
+ border-radius: 0.25rem;
608
+ font-size: 0.75rem;
609
+ font-weight: 500;
610
+ }
611
+
612
+ .subsection {
613
+ margin: 1.5rem 0;
614
+ }
615
+
616
+ .subsection-title {
617
+ font-size: 0.875rem;
618
+ font-weight: 700;
619
+ text-transform: uppercase;
620
+ letter-spacing: 0.05em;
621
+ color: var(--color-text-muted);
622
+ margin-bottom: 0.75rem;
623
+ }
624
+
625
+ .code-block {
626
+ background: var(--color-bg);
627
+ border: 1px solid var(--color-border);
628
+ border-radius: 0.5rem;
629
+ overflow: hidden;
630
+ }
631
+
632
+ .code-header {
633
+ display: flex;
634
+ justify-content: space-between;
635
+ align-items: center;
636
+ padding: 0.75rem 1rem;
637
+ background: rgba(0, 0, 0, 0.2);
638
+ border-bottom: 1px solid var(--color-border);
639
+ }
640
+
641
+ .code-language {
642
+ font-size: 0.75rem;
643
+ color: var(--color-text-muted);
644
+ text-transform: uppercase;
645
+ font-weight: 600;
646
+ }
647
+
648
+ .copy-btn {
649
+ padding: 0.25rem 0.75rem;
650
+ background: var(--color-surface);
651
+ color: var(--color-text);
652
+ border: none;
653
+ border-radius: 0.25rem;
654
+ font-size: 0.75rem;
655
+ cursor: pointer;
656
+ transition: all 0.2s;
657
+ }
658
+
659
+ .copy-btn:hover {
660
+ background: var(--color-primary);
661
+ color: white;
662
+ }
663
+
664
+ pre {
665
+ margin: 0;
666
+ padding: 1rem;
667
+ overflow-x: auto;
668
+ font-family: "JetBrains Mono", "Fira Code", "Courier New", monospace;
669
+ font-size: 0.85rem;
670
+ line-height: 1.6;
671
+ }
672
+
673
+ pre::-webkit-scrollbar {
674
+ height: 8px;
675
+ }
676
+
677
+ pre::-webkit-scrollbar-track {
678
+ background: var(--color-bg);
679
+ }
680
+
681
+ pre::-webkit-scrollbar-thumb {
682
+ background: var(--color-surface);
683
+ border-radius: 4px;
684
+ }
685
+
686
+ .json-key { color: #60a5fa; }
687
+ .json-string { color: #34d399; }
688
+ .json-number { color: #f59e0b; }
689
+ .json-boolean { color: #a78bfa; }
690
+ .json-null { color: #94a3b8; }
691
+
692
+ .response-status {
693
+ display: inline-flex;
694
+ align-items: center;
695
+ gap: 0.5rem;
696
+ padding: 0.5rem 0.75rem;
697
+ background: var(--color-surface);
698
+ border-radius: 0.375rem;
699
+ margin-bottom: 0.75rem;
700
+ font-family: "JetBrains Mono", monospace;
701
+ font-size: 0.875rem;
702
+ }
703
+
704
+ .status-2xx { background: rgba(16, 185, 129, 0.1); color: var(--color-success); }
705
+ .status-3xx { background: rgba(59, 130, 246, 0.1); color: var(--color-primary); }
706
+ .status-4xx { background: rgba(245, 158, 11, 0.1); color: var(--color-warning); }
707
+ .status-5xx { background: rgba(239, 68, 68, 0.1); color: var(--color-error); }
708
+
709
+ .schemas-section {
710
+ margin-top: 3rem;
711
+ padding-top: 2rem;
712
+ border-top: 2px solid var(--color-border);
713
+ }
714
+
715
+ .schema-card {
716
+ background: var(--color-bg-secondary);
717
+ border: 1px solid var(--color-border);
718
+ border-radius: 0.75rem;
719
+ padding: 1.5rem;
720
+ margin-bottom: 1rem;
721
+ }
722
+
723
+ .schema-title {
724
+ font-size: 1.125rem;
725
+ font-weight: 700;
726
+ color: var(--color-text);
727
+ margin-bottom: 1rem;
728
+ font-family: "JetBrains Mono", monospace;
729
+ }
730
+
731
+ .try-it {
732
+ margin-top: 1.5rem;
733
+ padding: 1rem;
734
+ background: rgba(59, 130, 246, 0.05);
735
+ border: 1px solid rgba(59, 130, 246, 0.2);
736
+ border-radius: 0.5rem;
737
+ }
738
+
739
+ .try-it-title {
740
+ font-size: 0.875rem;
741
+ font-weight: 700;
742
+ color: var(--color-primary);
743
+ margin-bottom: 0.75rem;
744
+ text-transform: uppercase;
745
+ letter-spacing: 0.05em;
746
+ }
747
+
748
+ .try-it-input {
749
+ width: 100%;
750
+ padding: 0.75rem;
751
+ background: var(--color-bg);
752
+ border: 1px solid var(--color-border);
753
+ border-radius: 0.375rem;
754
+ color: var(--color-text);
755
+ font-family: "JetBrains Mono", monospace;
756
+ font-size: 0.875rem;
757
+ margin-bottom: 0.75rem;
758
+ }
759
+
760
+ .try-it-btn {
761
+ padding: 0.75rem 1.5rem;
762
+ background: var(--color-primary);
763
+ color: white;
764
+ border: none;
765
+ border-radius: 0.375rem;
766
+ font-weight: 600;
767
+ cursor: pointer;
768
+ transition: all 0.2s;
769
+ width: 100%;
770
+ }
771
+
772
+ .try-it-btn:hover {
773
+ background: var(--color-primary-dark);
774
+ transform: translateY(-1px);
775
+ box-shadow: var(--shadow-md);
776
+ }
777
+
778
+ @media (max-width: 1024px) {
779
+ .container {
780
+ grid-template-columns: 1fr;
781
+ }
782
+ .sidebar {
783
+ position: static;
784
+ max-height: none;
785
+ }
786
+ }
342
787
  </style>
343
788
  </head>
344
789
  <body>
345
- <header>
346
- <h1><%= spec.info.title %></h1>
347
- <p>Version <%= spec.info.version %></p>
348
- </header>
349
- <main>
350
- <% const paths = spec.paths || {}; %>
351
- <% Object.entries(paths).forEach(([pathKey, ops]) => { %>
352
- <% Object.entries(ops).forEach(([verb, operation]) => { %>
353
- <div class="endpoint">
354
- <h3><span class="badge"><%= verb.toUpperCase() %></span><span class="path"><%= pathKey %></span></h3>
355
- <% if (operation.summary) { %><div class="desc"><%= operation.summary %></div><% } %>
356
- <% if (operation.description) { %><div class="desc"><%= operation.description %></div><% } %>
357
- <% if (operation.requestBody) { %>
358
- <div class="desc"><strong>Request</strong></div>
359
- <pre><%= JSON.stringify(operation.requestBody || {}, null, 2) %></pre>
360
- <% } %>
361
- <div class="desc"><strong>Responses</strong></div>
362
- <pre><%= JSON.stringify(operation.responses || {}, null, 2) %></pre>
790
+ <div class="header">
791
+ <div class="header-content">
792
+ <h1><%= spec.info.title %></h1>
793
+ <div class="header-info">
794
+ <span class="version-badge">
795
+ <svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
796
+ <path d="M8 1L10.5 6L16 7L12 11L13 16L8 13.5L3 16L4 11L0 7L5.5 6L8 1Z" fill="currentColor"/>
797
+ </svg>
798
+ Version <%= spec.info.version %>
799
+ </span>
800
+ <span class="openapi-badge">OpenAPI 3.0.0</span>
801
+ </div>
802
+ </div>
803
+ </div>
804
+
805
+ <div class="container">
806
+ <aside class="sidebar">
807
+ <h3>Endpoints</h3>
808
+ <%
809
+ const paths = spec.paths || {};
810
+ const groupedByTag = {};
811
+
812
+ Object.entries(paths).forEach(([pathKey, ops]) => {
813
+ Object.entries(ops).forEach(([verb, operation]) => {
814
+ const tags = operation.tags || ['General'];
815
+ tags.forEach(tag => {
816
+ if (!groupedByTag[tag]) groupedByTag[tag] = [];
817
+ groupedByTag[tag].push({ path: pathKey, method: verb, operation });
818
+ });
819
+ });
820
+ });
821
+ %>
822
+
823
+ <% Object.entries(groupedByTag).forEach(([tag, endpoints]) => { %>
824
+ <div class="tag-group">
825
+ <div class="tag-title"><%= tag %></div>
826
+ <div class="tag-links">
827
+ <% endpoints.forEach(({ path, method }) => { %>
828
+ <a href="#<%= method %>-<%= path.replace(/\//g, '-').replace(/[{}:]/g, '') %>" class="tag-link">
829
+ <%= method.toUpperCase() %> <%= path %>
830
+ </a>
831
+ <% }) %>
832
+ </div>
363
833
  </div>
364
834
  <% }) %>
365
- <% }) %>
366
- <h2>Schemas</h2>
367
- <pre><%= JSON.stringify(spec.components?.schemas || {}, null, 2) %></pre>
368
- </main>
835
+ </aside>
836
+
837
+ <main class="main-content">
838
+ <div class="endpoints-section">
839
+ <h2 class="section-title">API Endpoints</h2>
840
+
841
+ <% Object.entries(paths).forEach(([pathKey, ops]) => { %>
842
+ <% Object.entries(ops).forEach(([verb, operation]) => { %>
843
+ <div class="endpoint" id="<%= verb %>-<%= pathKey.replace(/\//g, '-').replace(/[{}:]/g, '') %>">
844
+ <div class="endpoint-header" onclick="this.parentElement.classList.toggle('expanded')">
845
+ <span class="method-badge method-<%= verb %>"><%= verb.toUpperCase() %></span>
846
+ <span class="endpoint-path"><%= pathKey %></span>
847
+ <svg class="expand-icon" viewBox="0 0 20 20" fill="currentColor">
848
+ <path fill-rule="evenodd" d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z" clip-rule="evenodd"/>
849
+ </svg>
850
+ </div>
851
+
852
+ <div class="endpoint-body">
853
+ <div class="endpoint-content">
854
+ <% if (operation.summary) { %>
855
+ <div class="endpoint-summary"><strong>Summary:</strong> <%= operation.summary %></div>
856
+ <% } %>
857
+
858
+ <% if (operation.description) { %>
859
+ <div class="endpoint-description"><%= operation.description %></div>
860
+ <% } %>
861
+
862
+ <% if (operation.tags && operation.tags.length > 0) { %>
863
+ <div class="tags">
864
+ <% operation.tags.forEach(tag => { %>
865
+ <span class="tag"><%= tag %></span>
866
+ <% }) %>
867
+ </div>
868
+ <% } %>
869
+
870
+ <% if (operation.requestBody) { %>
871
+ <div class="subsection">
872
+ <div class="subsection-title">Request Body</div>
873
+ <div class="code-block">
874
+ <div class="code-header">
875
+ <span class="code-language">JSON</span>
876
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
877
+ </div>
878
+ <pre><%= syntaxHighlight(JSON.stringify(operation.requestBody.content?.['application/json']?.example || operation.requestBody, null, 2)) %></pre>
879
+ </div>
880
+ </div>
881
+ <% } %>
882
+
883
+ <div class="subsection">
884
+ <div class="subsection-title">Responses</div>
885
+ <% Object.entries(operation.responses || {}).forEach(([status, response]) => { %>
886
+ <div class="response-status status-<%= status.charAt(0) %>xx">
887
+ <strong><%= status %></strong>
888
+ <span><%= response.description || 'Response' %></span>
889
+ </div>
890
+ <% if (response.content?.['application/json']) { %>
891
+ <div class="code-block">
892
+ <div class="code-header">
893
+ <span class="code-language">JSON</span>
894
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
895
+ </div>
896
+ <pre><%= syntaxHighlight(JSON.stringify(response.content['application/json'].example || response.content['application/json'].schema || {}, null, 2)) %></pre>
897
+ </div>
898
+ <% } %>
899
+ <% }) %>
900
+ </div>
901
+
902
+ <div class="try-it">
903
+ <div class="try-it-title">Try it out</div>
904
+ <input type="text" class="try-it-input" placeholder="Base URL (e.g., http://localhost:3000)" value="http://localhost:3000">
905
+ <button class="try-it-btn" onclick="tryEndpoint(this, '<%= verb %>', '<%= pathKey %>')">Send Request</button>
906
+ </div>
907
+ </div>
908
+ </div>
909
+ </div>
910
+ <% }) %>
911
+ <% }) %>
912
+ </div>
913
+
914
+ <% if (spec.components?.schemas && Object.keys(spec.components.schemas).length > 0) { %>
915
+ <div class="schemas-section">
916
+ <h2 class="section-title">Schemas</h2>
917
+ <% Object.entries(spec.components.schemas).forEach(([schemaName, schema]) => { %>
918
+ <div class="schema-card">
919
+ <div class="schema-title"><%= schemaName %></div>
920
+ <div class="code-block">
921
+ <div class="code-header">
922
+ <span class="code-language">Schema</span>
923
+ <button class="copy-btn" onclick="copyCode(this)">Copy</button>
924
+ </div>
925
+ <pre><%= syntaxHighlight(JSON.stringify(schema, null, 2)) %></pre>
926
+ </div>
927
+ </div>
928
+ <% }) %>
929
+ </div>
930
+ <% } %>
931
+ </main>
932
+ </div>
933
+
934
+ <script>
935
+ function syntaxHighlight(json) {
936
+ return json
937
+ .replace(/&/g, '&amp;')
938
+ .replace(/</g, '&lt;')
939
+ .replace(/>/g, '&gt;')
940
+ .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
941
+ .replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>')
942
+ .replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
943
+ .replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
944
+ .replace(/: (null)/g, ': <span class="json-null">$1</span>');
945
+ }
946
+
947
+ function copyCode(button) {
948
+ const codeBlock = button.closest('.code-block').querySelector('pre');
949
+ const text = codeBlock.textContent;
950
+ navigator.clipboard.writeText(text).then(() => {
951
+ button.textContent = 'Copied!';
952
+ setTimeout(() => button.textContent = 'Copy', 2000);
953
+ });
954
+ }
955
+
956
+ async function tryEndpoint(button, method, path) {
957
+ const input = button.previousElementSibling;
958
+ const baseUrl = input.value.trim();
959
+
960
+ if (!baseUrl) {
961
+ alert('Please enter a base URL');
962
+ return;
963
+ }
964
+
965
+ button.textContent = 'Sending...';
966
+ button.disabled = true;
967
+
968
+ try {
969
+ const response = await fetch(baseUrl + path, {
970
+ method: method.toUpperCase(),
971
+ headers: {
972
+ 'Content-Type': 'application/json'
973
+ }
974
+ });
975
+
976
+ const data = await response.json();
977
+ alert('Response (' + response.status + '):\\n' + JSON.stringify(data, null, 2));
978
+ } catch (error) {
979
+ alert('Error: ' + error.message);
980
+ } finally {
981
+ button.textContent = 'Send Request';
982
+ button.disabled = false;
983
+ }
984
+ }
985
+
986
+ // Smooth scroll to anchor
987
+ document.querySelectorAll('a[href^="#"]').forEach(anchor => {
988
+ anchor.addEventListener('click', function (e) {
989
+ e.preventDefault();
990
+ const target = document.querySelector(this.getAttribute('href'));
991
+ if (target) {
992
+ target.scrollIntoView({ behavior: 'smooth', block: 'start' });
993
+ target.classList.add('expanded');
994
+ }
995
+ });
996
+ });
997
+ </script>
998
+
999
+ <%
1000
+ function syntaxHighlight(json) {
1001
+ return json
1002
+ .replace(/&/g, '&amp;')
1003
+ .replace(/</g, '&lt;')
1004
+ .replace(/>/g, '&gt;')
1005
+ .replace(/"([^"]+)":/g, '<span class="json-key">"$1"</span>:')
1006
+ .replace(/: "([^"]*)"/g, ': <span class="json-string">"$1"</span>')
1007
+ .replace(/: (-?\d+\.?\d*)/g, ': <span class="json-number">$1</span>')
1008
+ .replace(/: (true|false)/g, ': <span class="json-boolean">$1</span>')
1009
+ .replace(/: (null)/g, ': <span class="json-null">$1</span>');
1010
+ }
1011
+ %>
369
1012
  </body>
370
1013
  </html>`;
371
1014
  //# sourceMappingURL=doc-generator.js.map