fusion-framework 1.2.2 → 1.2.3
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.
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/index.js
CHANGED
|
@@ -134,7 +134,7 @@ header.fingerprint = () =>
|
|
|
134
134
|
: {
|
|
135
135
|
'X-Powered-By': 'Fusion Framework',
|
|
136
136
|
'X-Framework': 'Fusion',
|
|
137
|
-
['X-Fusion-Version']: '1.2.
|
|
137
|
+
['X-Fusion-Version']: '1.2.3',
|
|
138
138
|
}
|
|
139
139
|
|
|
140
140
|
function isThenable(value) {
|
|
@@ -487,7 +487,9 @@ function readSwaggerSettings() {
|
|
|
487
487
|
const navbar = {
|
|
488
488
|
enabled: truthyEnabled(navbarRaw.enabled, true),
|
|
489
489
|
showUrlInput: truthyEnabled(navbarRaw.showUrlInput, true),
|
|
490
|
+
showUrlInputSet: Object.prototype.hasOwnProperty.call(navbarRaw, 'showUrlInput'),
|
|
490
491
|
urls: Array.isArray(navbarRaw.urls) ? navbarRaw.urls : null,
|
|
492
|
+
urlsSet: Array.isArray(navbarRaw.urls),
|
|
491
493
|
}
|
|
492
494
|
|
|
493
495
|
const ui = {
|
|
@@ -532,6 +534,60 @@ function readSwaggerSettings() {
|
|
|
532
534
|
}
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
const UNVERSIONED_SWAGGER_NAME = 'default'
|
|
538
|
+
|
|
539
|
+
function normalizeVersionLabel(value) {
|
|
540
|
+
return String(value || '')
|
|
541
|
+
.trim()
|
|
542
|
+
.replace(/^\/+|\/+$/g, '')
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function collectRouteVersions() {
|
|
546
|
+
const versions = []
|
|
547
|
+
let hasUnversioned = false
|
|
548
|
+
for (const item of registry) {
|
|
549
|
+
const version = normalizeVersionLabel(item.version_prefix)
|
|
550
|
+
if (!version) {
|
|
551
|
+
hasUnversioned = true
|
|
552
|
+
continue
|
|
553
|
+
}
|
|
554
|
+
if (!versions.includes(version)) versions.push(version)
|
|
555
|
+
}
|
|
556
|
+
return { versions, hasUnversioned }
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
function swaggerVersionUrls(prefix) {
|
|
560
|
+
const { versions, hasUnversioned } = collectRouteVersions()
|
|
561
|
+
const urls = versions.map((label) => ({
|
|
562
|
+
url: `${prefix}/${label}/openapi.json`,
|
|
563
|
+
name: label,
|
|
564
|
+
}))
|
|
565
|
+
if (hasUnversioned && urls.length) {
|
|
566
|
+
urls.push({
|
|
567
|
+
url: `${prefix}/${UNVERSIONED_SWAGGER_NAME}/openapi.json`,
|
|
568
|
+
name: UNVERSIONED_SWAGGER_NAME,
|
|
569
|
+
})
|
|
570
|
+
}
|
|
571
|
+
return urls
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function applyVersionNavbar(swagger) {
|
|
575
|
+
const autoUrls = swaggerVersionUrls(swagger.path)
|
|
576
|
+
if (!swagger.navbar.urlsSet && autoUrls.length) {
|
|
577
|
+
swagger.navbar.urls = autoUrls
|
|
578
|
+
if (!swagger.navbar.showUrlInputSet) swagger.navbar.showUrlInput = false
|
|
579
|
+
}
|
|
580
|
+
return autoUrls.map((item) => item.name)
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function routeMatchesVersion(item, filter) {
|
|
584
|
+
const version = normalizeVersionLabel(item.version_prefix)
|
|
585
|
+
if (filter == null) return true
|
|
586
|
+
const label = normalizeVersionLabel(filter)
|
|
587
|
+
if (!label || label.toLowerCase() === UNVERSIONED_SWAGGER_NAME) return !version
|
|
588
|
+
return version.toLowerCase() === label.toLowerCase()
|
|
589
|
+
}
|
|
590
|
+
|
|
535
591
|
function applySwaggerOpenApi(openapi, swagger) {
|
|
536
592
|
openapi.info = { ...asObject(openapi.info), ...swagger.info }
|
|
537
593
|
if (swagger.servers?.length) openapi.servers = swagger.servers
|
|
@@ -546,7 +602,66 @@ function applySwaggerOpenApi(openapi, swagger) {
|
|
|
546
602
|
return openapi
|
|
547
603
|
}
|
|
548
604
|
|
|
549
|
-
function
|
|
605
|
+
function fillOpenApiPaths(openapi, versionFilter = null) {
|
|
606
|
+
const parsePathParams = (pattern) => {
|
|
607
|
+
return String(pattern)
|
|
608
|
+
.split('/')
|
|
609
|
+
.filter((seg) => (seg.startsWith('{') && seg.endsWith('}')) || (seg.startsWith('[') && seg.endsWith(']')))
|
|
610
|
+
.map((seg) => seg.slice(1, -1))
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
for (const item of registry) {
|
|
614
|
+
if (!routeMatchesVersion(item, versionFilter)) continue
|
|
615
|
+
const { path: p, ApiClass, swagger: routeSwagger } = item
|
|
616
|
+
const pathParams = parsePathParams(p)
|
|
617
|
+
const resolvedPath = p.startsWith('/') ? p : `/${p}`
|
|
618
|
+
|
|
619
|
+
if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
|
|
620
|
+
|
|
621
|
+
for (const methodName of HTTP_METHODS) {
|
|
622
|
+
if (!definesMethod(ApiClass, methodName)) continue
|
|
623
|
+
|
|
624
|
+
const methodUpper = String(methodName).toUpperCase()
|
|
625
|
+
const methodLower = String(methodName).toLowerCase()
|
|
626
|
+
|
|
627
|
+
const params = pathParams.map((name) => ({
|
|
628
|
+
name,
|
|
629
|
+
in: 'path',
|
|
630
|
+
required: true,
|
|
631
|
+
schema: { type: 'string' },
|
|
632
|
+
}))
|
|
633
|
+
|
|
634
|
+
openapi.paths[resolvedPath][methodLower] = {
|
|
635
|
+
tags: routeSwagger?.tags?.length ? routeSwagger.tags : [],
|
|
636
|
+
summary: routeSwagger?.title ?? `${ApiClass.name}.${methodUpper}`,
|
|
637
|
+
description: routeSwagger?.description ?? '',
|
|
638
|
+
deprecated: !!routeSwagger?.deprecated,
|
|
639
|
+
operationId: `${ApiClass.name}_${methodLower}`,
|
|
640
|
+
parameters: params,
|
|
641
|
+
responses: { 200: { description: 'OK' } },
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return openapi
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
function buildOpenApi(swagger, version = null) {
|
|
649
|
+
const openapi = applySwaggerOpenApi(
|
|
650
|
+
{
|
|
651
|
+
openapi: '3.0.3',
|
|
652
|
+
info: { ...swagger.info },
|
|
653
|
+
paths: {},
|
|
654
|
+
},
|
|
655
|
+
swagger,
|
|
656
|
+
)
|
|
657
|
+
const label = normalizeVersionLabel(version)
|
|
658
|
+
if (label && label !== UNVERSIONED_SWAGGER_NAME) {
|
|
659
|
+
openapi.info = { ...openapi.info, version: label }
|
|
660
|
+
}
|
|
661
|
+
return fillOpenApiPaths(openapi, version)
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function swaggerUiHtml(swagger, openapiUrl, primaryName = null) {
|
|
550
665
|
const uiOpts = { ...swagger.ui }
|
|
551
666
|
delete uiOpts.presets
|
|
552
667
|
delete uiOpts.plugins
|
|
@@ -555,9 +670,12 @@ function swaggerUiHtml(swagger, openapiUrl) {
|
|
|
555
670
|
if (swagger.navbar?.urls?.length) {
|
|
556
671
|
delete uiOpts.url
|
|
557
672
|
uiOpts.urls = swagger.navbar.urls
|
|
673
|
+
const name = primaryName || swagger.navbar.urls[0]?.name
|
|
674
|
+
if (name) uiOpts['urls.primaryName'] = name
|
|
558
675
|
} else {
|
|
559
676
|
uiOpts.url = openapiUrl
|
|
560
677
|
delete uiOpts.urls
|
|
678
|
+
delete uiOpts['urls.primaryName']
|
|
561
679
|
}
|
|
562
680
|
uiOpts.dom_id = '#swagger-ui'
|
|
563
681
|
|
|
@@ -573,7 +691,7 @@ function swaggerUiHtml(swagger, openapiUrl) {
|
|
|
573
691
|
const showUrlInput = swagger.navbar?.showUrlInput !== false
|
|
574
692
|
const hideUrlCss =
|
|
575
693
|
navbarEnabled && !showUrlInput
|
|
576
|
-
? `<style>.topbar
|
|
694
|
+
? `<style>.topbar form { display: none !important; }</style>`
|
|
577
695
|
: ''
|
|
578
696
|
const standaloneScript = navbarEnabled
|
|
579
697
|
? `<script src="https://unpkg.com/swagger-ui-dist/swagger-ui-standalone-preset.js"></script>`
|
|
@@ -657,64 +775,26 @@ class FusionApp {
|
|
|
657
775
|
const swagger = readSwaggerSettings()
|
|
658
776
|
if (swagger.enabled) {
|
|
659
777
|
const prefix = swagger.path
|
|
778
|
+
const labels = applyVersionNavbar(swagger)
|
|
779
|
+
const combined = buildOpenApi(swagger)
|
|
660
780
|
|
|
661
|
-
const
|
|
662
|
-
{
|
|
663
|
-
openapi: '3.0.3',
|
|
664
|
-
info: { ...swagger.info },
|
|
665
|
-
paths: {},
|
|
666
|
-
},
|
|
667
|
-
swagger,
|
|
668
|
-
)
|
|
669
|
-
|
|
670
|
-
const parsePathParams = (pattern) => {
|
|
671
|
-
return String(pattern)
|
|
672
|
-
.split('/')
|
|
673
|
-
.filter((seg) => (seg.startsWith('{') && seg.endsWith('}')) || (seg.startsWith('[') && seg.endsWith(']')))
|
|
674
|
-
.map((seg) => seg.slice(1, -1))
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
for (const item of registry) {
|
|
678
|
-
const { path: p, ApiClass, swagger: routeSwagger } = item
|
|
679
|
-
const pathParams = parsePathParams(p)
|
|
680
|
-
const resolvedPath = p.startsWith('/') ? p : `/${p}`
|
|
681
|
-
|
|
682
|
-
if (!openapi.paths[resolvedPath]) openapi.paths[resolvedPath] = {}
|
|
683
|
-
|
|
684
|
-
for (const methodName of HTTP_METHODS) {
|
|
685
|
-
if (!definesMethod(ApiClass, methodName)) continue
|
|
686
|
-
|
|
687
|
-
const methodUpper = String(methodName).toUpperCase()
|
|
688
|
-
const methodLower = String(methodName).toLowerCase()
|
|
689
|
-
|
|
690
|
-
const params = pathParams.map((name) => ({
|
|
691
|
-
name,
|
|
692
|
-
in: 'path',
|
|
693
|
-
required: true,
|
|
694
|
-
schema: { type: 'string' },
|
|
695
|
-
}))
|
|
696
|
-
|
|
697
|
-
openapi.paths[resolvedPath][methodLower] = {
|
|
698
|
-
tags: routeSwagger?.tags?.length ? routeSwagger.tags : [],
|
|
699
|
-
summary: routeSwagger?.title ?? `${ApiClass.name}.${methodUpper}`,
|
|
700
|
-
description: routeSwagger?.description ?? '',
|
|
701
|
-
deprecated: !!routeSwagger?.deprecated,
|
|
702
|
-
operationId: `${ApiClass.name}_${methodLower}`,
|
|
703
|
-
parameters: params,
|
|
704
|
-
responses: { '200': { description: 'OK' } },
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
const uiEnvelope = () => ({
|
|
781
|
+
const htmlEnvelope = (primaryName = null) => ({
|
|
710
782
|
status: 200,
|
|
711
|
-
body: swaggerUiHtml(swagger, `${prefix}/openapi.json
|
|
783
|
+
body: swaggerUiHtml(swagger, `${prefix}/openapi.json`, primaryName),
|
|
712
784
|
headers: { 'content-type': 'text/html' },
|
|
713
785
|
})
|
|
714
|
-
|
|
715
|
-
this.engine.route('GET', prefix
|
|
786
|
+
|
|
787
|
+
this.engine.route('GET', `${prefix}/openapi.json`, () => combined)
|
|
788
|
+
this.engine.route('GET', prefix, () => htmlEnvelope())
|
|
716
789
|
if (prefix !== '/') {
|
|
717
|
-
this.engine.route('GET', `${prefix}/`,
|
|
790
|
+
this.engine.route('GET', `${prefix}/`, () => htmlEnvelope())
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
for (const label of labels) {
|
|
794
|
+
const spec = buildOpenApi(swagger, label)
|
|
795
|
+
this.engine.route('GET', `${prefix}/${label}/openapi.json`, () => spec)
|
|
796
|
+
this.engine.route('GET', `${prefix}/${label}`, () => htmlEnvelope(label))
|
|
797
|
+
this.engine.route('GET', `${prefix}/${label}/`, () => htmlEnvelope(label))
|
|
718
798
|
}
|
|
719
799
|
}
|
|
720
800
|
|