mirror of
https://github.com/zoriya/elysia-swagger.git
synced 2025-12-06 00:36:10 +00:00
🧹 chore: merge main
This commit is contained in:
150
src/index.ts
150
src/index.ts
@@ -1,118 +1,118 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
import { Elysia, type InternalRoute } from "elysia";
|
||||
import { Elysia, type InternalRoute } from 'elysia'
|
||||
|
||||
import { SwaggerUIRender } from "./swagger";
|
||||
import { ScalarRender } from "./scalar";
|
||||
import { SwaggerUIRender } from './swagger'
|
||||
import { ScalarRender } from './scalar'
|
||||
|
||||
import { filterPaths, registerSchemaPath } from "./utils";
|
||||
import { filterPaths, registerSchemaPath } from './utils'
|
||||
|
||||
import type { OpenAPIV3 } from "openapi-types";
|
||||
import type { ReferenceConfiguration } from "./scalar/types";
|
||||
import type { ElysiaSwaggerConfig } from "./types";
|
||||
import type { OpenAPIV3 } from 'openapi-types'
|
||||
import type { ReferenceConfiguration } from './scalar/types'
|
||||
import type { ElysiaSwaggerConfig } from './types'
|
||||
|
||||
/**
|
||||
* Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate Swagger page.
|
||||
*
|
||||
* @see https://github.com/elysiajs/elysia-swagger
|
||||
*/
|
||||
export const swagger = async <Path extends string = "/swagger">(
|
||||
export const swagger = async <Path extends string = '/swagger'>(
|
||||
{
|
||||
provider = "scalar",
|
||||
scalarVersion = "latest",
|
||||
scalarCDN = "",
|
||||
provider = 'scalar',
|
||||
scalarVersion = 'latest',
|
||||
scalarCDN = '',
|
||||
scalarConfig = {},
|
||||
documentation = {},
|
||||
version = "5.9.0",
|
||||
version = '5.9.0',
|
||||
excludeStaticFile = true,
|
||||
path = "/swagger" as Path,
|
||||
path = '/swagger' as Path,
|
||||
exclude = [],
|
||||
swaggerOptions = {},
|
||||
theme = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`,
|
||||
autoDarkMode = true,
|
||||
excludeMethods = ["OPTIONS"],
|
||||
excludeTags = [],
|
||||
excludeMethods = ['OPTIONS'],
|
||||
excludeTags = []
|
||||
}: ElysiaSwaggerConfig<Path> = {
|
||||
provider: "scalar",
|
||||
scalarVersion: "latest",
|
||||
scalarCDN: "",
|
||||
provider: 'scalar',
|
||||
scalarVersion: 'latest',
|
||||
scalarCDN: '',
|
||||
scalarConfig: {},
|
||||
documentation: {},
|
||||
version: "5.9.0",
|
||||
version: '5.9.0',
|
||||
excludeStaticFile: true,
|
||||
path: "/swagger" as Path,
|
||||
path: '/swagger' as Path,
|
||||
exclude: [],
|
||||
swaggerOptions: {},
|
||||
autoDarkMode: true,
|
||||
excludeMethods: ["OPTIONS"],
|
||||
excludeTags: [],
|
||||
},
|
||||
excludeMethods: ['OPTIONS'],
|
||||
excludeTags: []
|
||||
}
|
||||
) => {
|
||||
const schema = {};
|
||||
let totalRoutes = 0;
|
||||
const schema = {}
|
||||
let totalRoutes = 0
|
||||
|
||||
if (!version)
|
||||
version = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`;
|
||||
version = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`
|
||||
|
||||
const info = {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info,
|
||||
};
|
||||
title: 'Elysia Documentation',
|
||||
description: 'Development documentation',
|
||||
version: '0.0.0',
|
||||
...documentation.info
|
||||
}
|
||||
|
||||
const relativePath = path.startsWith("/") ? path.slice(1) : path;
|
||||
const relativePath = path.startsWith('/') ? path.slice(1) : path
|
||||
|
||||
const app = new Elysia({ name: "@elysiajs/swagger" });
|
||||
const app = new Elysia({ name: '@elysiajs/swagger' })
|
||||
|
||||
app.get(path, function documentation() {
|
||||
const combinedSwaggerOptions = {
|
||||
url: `${relativePath}/json`,
|
||||
dom_id: "#swagger-ui",
|
||||
...swaggerOptions,
|
||||
};
|
||||
dom_id: '#swagger-ui',
|
||||
...swaggerOptions
|
||||
}
|
||||
|
||||
const stringifiedSwaggerOptions = JSON.stringify(
|
||||
combinedSwaggerOptions,
|
||||
(key, value) => {
|
||||
if (typeof value == "function") return undefined;
|
||||
if (typeof value == 'function') return undefined
|
||||
|
||||
return value;
|
||||
},
|
||||
);
|
||||
return value
|
||||
}
|
||||
)
|
||||
|
||||
const scalarConfiguration: ReferenceConfiguration = {
|
||||
spec: {
|
||||
...scalarConfig.spec,
|
||||
url: `${relativePath}/json`,
|
||||
url: `${relativePath}/json`
|
||||
},
|
||||
...scalarConfig,
|
||||
};
|
||||
...scalarConfig
|
||||
}
|
||||
|
||||
return new Response(
|
||||
provider === "swagger-ui"
|
||||
provider === 'swagger-ui'
|
||||
? SwaggerUIRender(
|
||||
info,
|
||||
version,
|
||||
theme,
|
||||
stringifiedSwaggerOptions,
|
||||
autoDarkMode,
|
||||
autoDarkMode
|
||||
)
|
||||
: ScalarRender(scalarVersion, scalarConfiguration, scalarCDN),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf8",
|
||||
},
|
||||
},
|
||||
);
|
||||
}).get(path === "/" ? "/json" : `${path}/json`, function openAPISchema() {
|
||||
'content-type': 'text/html; charset=utf8'
|
||||
}
|
||||
}
|
||||
)
|
||||
}).get(path === '/' ? '/json' : `${path}/json`, function openAPISchema() {
|
||||
// @ts-expect-error Private property
|
||||
const routes = app.getGlobalRoutes() as InternalRoute[];
|
||||
const routes = app.getGlobalRoutes() as InternalRoute[]
|
||||
|
||||
if (routes.length !== totalRoutes) {
|
||||
totalRoutes = routes.length;
|
||||
totalRoutes = routes.length
|
||||
|
||||
routes.forEach((route: InternalRoute) => {
|
||||
if (excludeMethods.includes(route.method)) return;
|
||||
if (excludeMethods.includes(route.method)) return
|
||||
|
||||
registerSchemaPath({
|
||||
schema,
|
||||
@@ -121,45 +121,45 @@ export const swagger = async <Path extends string = "/swagger">(
|
||||
path: route.path,
|
||||
// @ts-ignore
|
||||
models: app.definitions?.type,
|
||||
contentType: route.hooks.type,
|
||||
});
|
||||
});
|
||||
contentType: route.hooks.type
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
openapi: "3.0.3",
|
||||
openapi: '3.0.3',
|
||||
...{
|
||||
...documentation,
|
||||
tags: documentation.tags?.filter(
|
||||
(tag) => !excludeTags?.includes(tag?.name),
|
||||
(tag) => !excludeTags?.includes(tag?.name)
|
||||
),
|
||||
info: {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info,
|
||||
},
|
||||
title: 'Elysia Documentation',
|
||||
description: 'Development documentation',
|
||||
version: '0.0.0',
|
||||
...documentation.info
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
...filterPaths(schema, {
|
||||
...filterPaths(schema, relativePath, {
|
||||
excludeStaticFile,
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude],
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude]
|
||||
}),
|
||||
...documentation.paths,
|
||||
...documentation.paths
|
||||
},
|
||||
components: {
|
||||
...documentation.components,
|
||||
schemas: {
|
||||
// @ts-ignore
|
||||
...app.definitions?.type,
|
||||
...documentation.components?.schemas,
|
||||
},
|
||||
},
|
||||
} satisfies OpenAPIV3.Document;
|
||||
});
|
||||
...documentation.components?.schemas
|
||||
}
|
||||
}
|
||||
} satisfies OpenAPIV3.Document
|
||||
})
|
||||
|
||||
// This is intentional to prevent deeply nested type
|
||||
return app;
|
||||
};
|
||||
return app
|
||||
}
|
||||
|
||||
export default swagger;
|
||||
export type { ElysiaSwaggerConfig }
|
||||
export default swagger
|
||||
|
||||
47
src/utils.ts
47
src/utils.ts
@@ -1,5 +1,6 @@
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import path from 'path'
|
||||
import type { HTTPMethod, LocalHook } from 'elysia'
|
||||
|
||||
import { Kind, type TSchema } from '@sinclair/typebox'
|
||||
@@ -295,30 +296,36 @@ export const registerSchemaPath = ({
|
||||
}
|
||||
|
||||
export const filterPaths = (
|
||||
paths: Record<string, any>,
|
||||
{
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}: {
|
||||
excludeStaticFile: boolean
|
||||
exclude: (string | RegExp)[]
|
||||
}
|
||||
paths: Record<string, any>,
|
||||
docsPath: string,
|
||||
{
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}: {
|
||||
excludeStaticFile: boolean
|
||||
exclude: (string | RegExp)[]
|
||||
}
|
||||
) => {
|
||||
const newPaths: Record<string, any> = {}
|
||||
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (
|
||||
!exclude.some((x) => {
|
||||
if (typeof x === 'string') return key === x
|
||||
// exclude docs path and OpenAPI json path
|
||||
const excludePaths = [`/${docsPath}`, `/${docsPath}/json`].map((p) =>
|
||||
path.normalize(p)
|
||||
)
|
||||
|
||||
return x.test(key)
|
||||
}) &&
|
||||
!key.includes('/swagger') &&
|
||||
!key.includes('*') &&
|
||||
(excludeStaticFile ? !key.includes('.') : true)
|
||||
) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method]
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (
|
||||
!exclude.some((x) => {
|
||||
if (typeof x === 'string') return key === x
|
||||
|
||||
return x.test(key)
|
||||
}) &&
|
||||
!excludePaths.includes(key) &&
|
||||
!key.includes('*') &&
|
||||
(excludeStaticFile ? !key.includes('.') : true)
|
||||
) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method]
|
||||
|
||||
if (key.includes('{')) {
|
||||
if (!schema.parameters) schema.parameters = []
|
||||
|
||||
Reference in New Issue
Block a user