From 11d968ccd412e448baeef4b6d631d0c68fb2c161 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 12 Aug 2024 23:46:21 +0700 Subject: [PATCH 1/6] :tada: release: 1.1.1 --- CHANGELOG.md | 3 + generated/index.d.ts | 40 ++ generated/plugin.d.ts | 107 ++++ package.json | 4 +- src/codegen.ts | 23 + src/index.d.ts | 30 ++ src/scalar/index.d.ts | 2 + src/scalar/theme.d.ts | 2 + src/scalar/types/index.d.ts | 34 ++ src/scalar/types/unjead.d.ts | 981 +++++++++++++++++++++++++++++++++++ src/swagger/index.d.ts | 5 + src/swagger/types.d.ts | 274 ++++++++++ src/types.d.ts | 96 ++++ src/utils.d.ts | 26 + 14 files changed, 1625 insertions(+), 2 deletions(-) create mode 100644 generated/index.d.ts create mode 100644 generated/plugin.d.ts create mode 100644 src/codegen.ts create mode 100644 src/index.d.ts create mode 100644 src/scalar/index.d.ts create mode 100644 src/scalar/theme.d.ts create mode 100644 src/scalar/types/index.d.ts create mode 100644 src/scalar/types/unjead.d.ts create mode 100644 src/swagger/index.d.ts create mode 100644 src/swagger/types.d.ts create mode 100644 src/types.d.ts create mode 100644 src/utils.d.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f5953c..6947c0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.1.1 - 12 Aug 2024 +Feature: +- add hide flag # 1.1.0 - 16 Jul 2024 Change: diff --git a/generated/index.d.ts b/generated/index.d.ts new file mode 100644 index 0000000..4934851 --- /dev/null +++ b/generated/index.d.ts @@ -0,0 +1,40 @@ +import { Elysia } from 'elysia'; +declare const app: Elysia<"", false, { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + type: {}; + error: {}; +}, { + schema: {}; + macro: {}; + macroFn: {}; +}, { + id: { + ":id?": { + get: { + body: unknown; + params: { + id?: string; + }; + query: unknown; + headers: unknown; + response: { + 200: "a"; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; +}>; +export type app = typeof app; +export {}; diff --git a/generated/plugin.d.ts b/generated/plugin.d.ts new file mode 100644 index 0000000..53e61ac --- /dev/null +++ b/generated/plugin.d.ts @@ -0,0 +1,107 @@ +import { Elysia } from 'elysia'; +export declare const plugin: Elysia<"/a", false, { + decorator: {}; + store: {}; + derive: {}; + resolve: {}; +}, { + type: { + readonly sign: { + username: string; + password: string; + }; + readonly number: number; + }; + error: {}; +}, { + schema: {}; + macro: {}; + macroFn: {}; +}, { + a: { + index: { + get: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: string; + }; + }; + }; + }; +} & { + a: { + unpath: { + ":id": { + get: { + body: unknown; + params: { + id: string; + }; + query: unknown; + headers: unknown; + response: { + 200: string; + }; + }; + }; + }; + }; +} & { + a: { + json: { + post: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: unknown; + }; + }; + }; + }; +} & { + a: { + json: { + ":id": { + post: { + body: unknown; + params: { + id: string; + }; + query: unknown; + headers: unknown; + response: { + 200: unknown; + 418: unknown; + }; + }; + }; + }; + }; +} & { + a: { + file: { + post: { + body: unknown; + params: {}; + query: unknown; + headers: unknown; + response: { + 200: unknown; + }; + }; + }; + }; +}, { + derive: {}; + resolve: {}; + schema: {}; +}, { + derive: {}; + resolve: {}; + schema: {}; +}>; diff --git a/package.json b/package.json index 9031870..65701d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/swagger", - "version": "1.1.0", + "version": "1.1.1", "description": "Plugin for Elysia to auto-generate Swagger page", "author": { "name": "saltyAom", @@ -78,4 +78,4 @@ "lodash.clonedeep": "^4.5.0", "openapi-types": "^12.1.3" } -} \ No newline at end of file +} diff --git a/src/codegen.ts b/src/codegen.ts new file mode 100644 index 0000000..65d7d1e --- /dev/null +++ b/src/codegen.ts @@ -0,0 +1,23 @@ +// import { TypeScriptToModel, ModelToJsonSchema } from '@sinclair/typebox-codegen' +// import { cwd } from 'process' +// import { join } from 'path' + +// // await Bun.$` tsc example/index.ts --declaration --emitDeclarationOnly --esModuleInterop --skipLibCheck --declarationDir generated --rootDir example` + +// // Bun.file(join(cwd(), 'generated/index.d.ts')) + +// const type = TypeScriptToModel.Generate(` +// type A = { +// body: unknown; +// params: { +// id?: string; +// }; +// query: unknown; +// headers: unknown; +// response: { +// 200: "a"; +// }; +// } +// `) + +// console.dir(type, { depth: null }) diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..77a71b9 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,30 @@ +import { Elysia } from 'elysia'; +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 declare const swagger: ({ provider, scalarVersion, scalarCDN, scalarConfig, documentation, version, excludeStaticFile, path, exclude, swaggerOptions, theme, autoDarkMode, excludeMethods, excludeTags }?: ElysiaSwaggerConfig) => Promise>; +export type { ElysiaSwaggerConfig }; +export default swagger; diff --git a/src/scalar/index.d.ts b/src/scalar/index.d.ts new file mode 100644 index 0000000..7bcb294 --- /dev/null +++ b/src/scalar/index.d.ts @@ -0,0 +1,2 @@ +import type { ReferenceConfiguration } from './types'; +export declare const ScalarRender: (version: string, config: ReferenceConfiguration, cdn: string) => string; diff --git a/src/scalar/theme.d.ts b/src/scalar/theme.d.ts new file mode 100644 index 0000000..cab26bb --- /dev/null +++ b/src/scalar/theme.d.ts @@ -0,0 +1,2 @@ +declare const _default: "\n/* basic theme */\n.light-mode {\n --theme-color-1: #2a2f45;\n --theme-color-2: #757575;\n --theme-color-3: #8e8e8e;\n --theme-color-accent: #f06292;\n\n --theme-background-1: #fff;\n --theme-background-2: #f6f6f6;\n --theme-background-3: #e7e7e7;\n --theme-background-accent: #f062921f;\n\n --theme-border-color: rgba(0, 0, 0, 0.1);\n}\n.dark-mode {\n --theme-color-1: rgba(255, 255, 255, 0.9);\n --theme-color-2: rgba(156, 163, 175, 1);\n --theme-color-3: rgba(255, 255, 255, 0.44);\n --theme-color-accent: #f06292;\n\n --theme-background-1: #111728;\n --theme-background-2: #1e293b;\n --theme-background-3: #334155;\n --theme-background-accent: #f062921f;\n\n --theme-border-color: rgba(255, 255, 255, 0.1);\n}\n/* Document Sidebar */\n.light-mode .sidebar,\n.dark-mode .sidebar {\n --sidebar-background-1: var(--theme-background-1);\n --sidebar-item-hover-color: currentColor;\n --sidebar-item-hover-background: var(--theme-background-2);\n --sidebar-item-active-background: var(--theme-background-accent);\n --sidebar-border-color: transparent;\n --sidebar-color-1: var(--theme-color-1);\n --sidebar-color-2: var(--theme-color-2);\n --sidebar-color-active: var(--theme-color-accent);\n --sidebar-search-background: transparent;\n --sidebar-search-border-color: var(--theme-border-color);\n --sidebar-search--color: var(--theme-color-3);\n}\n/* Document header only shows on mobile*/\n.dark-mode .t-doc__header,\n.light-mode .t-doc__header {\n --header-background-1: rgba(255, 255, 255, 0.85);\n --header-border-color: transparent;\n --header-color-1: var(--theme-color-1);\n --header-color-2: var(--theme-color-2);\n --header-background-toggle: var(--theme-color-3);\n --header-call-to-action-color: var(--theme-color-accent);\n}\n\n.dark-mode .t-doc__header {\n --header-background-1: rgba(17, 23, 40, 0.75);\n}\n\n/* advanced */\n.light-mode {\n --theme-button-1: rgb(49 53 56);\n --theme-button-1-color: #fff;\n --theme-button-1-hover: rgb(28 31 33);\n\n --theme-color-green: #069061;\n --theme-color-red: #ef0006;\n --theme-color-yellow: #edbe20;\n --theme-color-blue: #0082d0;\n --theme-color-orange: #fb892c;\n --theme-color-purple: #5203d1;\n\n --theme-scrollbar-color: rgba(0, 0, 0, 0.18);\n --theme-scrollbar-color-active: rgba(0, 0, 0, 0.36);\n}\n.dark-mode {\n --theme-button-1: #f6f6f6;\n --theme-button-1-color: #000;\n --theme-button-1-hover: #e7e7e7;\n\n --theme-color-green: #a3ffa9;\n --theme-color-red: #ffa3a3;\n --theme-color-yellow: #fffca3;\n --theme-color-blue: #a5d6ff;\n --theme-color-orange: #e2ae83;\n --theme-color-purple: #d2a8ff;\n\n --theme-scrollbar-color: rgba(255, 255, 255, 0.24);\n --theme-scrollbar-color-active: rgba(255, 255, 255, 0.48);\n}\n/* Elysia Specific */\n.scalar-api-client__send-request-button,\n.show-api-client-button {\n background: #3c82f6 !important;\n}\n.show-api-client-button:before {\n display: none;\n}\n\n.sidebar-search:hover {\n transition: all 0.15s ease-in-out;\n --sidebar-search-border-color: var(--theme-color-accent) !important;\n color: var(--sidebar-color-1) !important;\n}\n.scalar-api-client__container .sidebar {\n --sidebar-border-color: var(--theme-border-color);\n}\n@media (min-width: 1150px) {\n .section-container:has( ~ .footer):before,\n .tag-section-container:before {\n content: \"\";\n position: absolute;\n top: -5px;\n left: 0;\n width: 100%;\n height: 10px;\n background: linear-gradient(90deg, var(--theme-background-1) 3%,transparent 10%);\n }\n}\n.section-flare {\n position: absolute;\n width: 100vw;\n height: 300px;\n --stripes: repeating-linear-gradient(\n 100deg,\n #fff 0%,\n #fff 7%,\n transparent 10%,\n transparent 12%,\n #fff 16%\n );\n --stripesDark: repeating-linear-gradient(\n 100deg,\n #000 0%,\n #000 7%,\n transparent 10%,\n transparent 12%,\n #000 16%\n );\n --rainbow: repeating-linear-gradient(\n 100deg,\n #60a5fa 10%,\n #e879f9 16%,\n #5eead4 22%,\n #60a5fa 30%\n );\n background-image: var(--stripes), var(--rainbow);\n background-size: 300%, 200%;\n background-position: 50% 50%, 50% 50%;\n filter: invert(100%);\n -webkit-mask-image: radial-gradient(\n ellipse at 100% 0%,\n black 40%,\n transparent 70%\n );\n mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%);\n pointer-events: none;\n opacity: 0.15;\n}\n.dark-mode .section-flare {\n background-image: var(--stripesDark), var(--rainbow);\n filter: opacity(50%) saturate(200%);\n opacity: 0.25;\n}\n.section-flare:after {\n content: \"\";\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-image: var(--stripes), var(--rainbow);\n background-size: 200%, 100%;\n background-attachment: fixed;\n mix-blend-mode: difference;\n}\n.dark-mode .section-flare:after {\n background-image: var(--stripesDark), var(--rainbow);\n}\n@keyframes headerbackground {\n from {\n background: transparent;\n backdrop-filter: none;\n }\n to {\n background: var(--header-background-1);\n backdrop-filter: blur(12px);\n }\n}\n.light-mode .t-doc__header,\n.dark-mode .t-doc__header {\n animation: headerbackground forwards;\n animation-timeline: scroll();\n animation-range: 0px 200px;\n --header-border-color: transparent;\n}\n"; +export default _default; diff --git a/src/scalar/types/index.d.ts b/src/scalar/types/index.d.ts new file mode 100644 index 0000000..6b18c23 --- /dev/null +++ b/src/scalar/types/index.d.ts @@ -0,0 +1,34 @@ +import type { MetaFlatInput } from "./unjead"; +export type ReferenceConfiguration = { + /** A string to use one of the color presets */ + theme?: ThemeId; + /** The layout to use for the references */ + layout?: ReferenceLayoutType; + /** The Swagger/OpenAPI spec to render */ + spec?: SpecConfiguration; + /** URL to a request proxy for the API client */ + proxy?: string; + /** Whether the spec input should show */ + isEditable?: boolean; + /** Whether to show the sidebar */ + showSidebar?: boolean; + /** Remove the Scalar branding :( */ + /** Key used with CNTRL/CMD to open the search modal (defaults to 'k' e.g. CMD+k) */ + searchHotKey?: string; + /** If used, passed data will be added to the HTML header. Read more: https://unhead.unjs.io/usage/composables/use-seo-meta */ + metaData?: MetaFlatInput; + /** Custom CSS to be added to the page */ + customCss?: string; + /** onSpecUpdate is fired on spec/swagger content change */ + onSpecUpdate?: (spec: string) => void; +}; +export type SpecConfiguration = { + /** URL to a Swagger/OpenAPI file */ + url?: string; + /** Swagger/Open API spec */ + content?: string | Record | (() => Record); + /** The result of @scalar/swagger-parser */ + preparsedContent?: Record; +}; +export type ReferenceLayoutType = 'modern' | 'classic'; +export type ThemeId = 'alternate' | 'default' | 'moon' | 'purple' | 'solarized' | 'none'; diff --git a/src/scalar/types/unjead.d.ts b/src/scalar/types/unjead.d.ts new file mode 100644 index 0000000..4df2ab1 --- /dev/null +++ b/src/scalar/types/unjead.d.ts @@ -0,0 +1,981 @@ +type Booleanable = boolean | 'false' | 'true' | ''; +type Arrayable = T | Array; +type ReferrerPolicy = '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'; +interface MetaFlatArticle { + /** + * Writers of the article. + * @example ['https://example.com/some.html', 'https://example.com/one.html'] + */ + articleAuthor?: string[]; + /** + * When the article is out of date after. + * @example '1970-01-01T00:00:00.000Z' + */ + articleExpirationTime?: string; + /** + * When the article was last changed. + * @example '1970-01-01T00:00:00.000Z' + */ + articleModifiedTime?: string; + /** + * When the article was first published. + * @example '1970-01-01T00:00:00.000Z' + */ + articlePublishedTime?: string; + /** + * A high-level section name. + * @example 'Technology' + */ + articleSection?: string; + /** + * Tag words associated with this article. + * @example ['Apple', 'Steve Jobs] + */ + articleTag?: string[]; +} +interface MetaFlatBook { + /** + * Who wrote this book. + * @example ['https://example.com/some.html', 'https://example.com/one.html'] + */ + bookAuthor?: string[]; + /** + * The ISBN. + * @example '978-3-16-148410-0' + */ + bookIsbn?: string; + /** + * The date the book was released. + * @example '1970-01-01T00:00:00.000Z' + */ + bookReleaseDate?: string; + /** + * Tag words associated with this book. + * @example ['Apple', 'Steve Jobs] + */ + bookTag?: string[]; +} +interface MetaFlatProfile { + /** + * A name normally given to an individual by a parent or self-chosen. + */ + profileFirstName?: string; + /** + * Their gender. + */ + profileGender?: 'male' | 'female' | string; + /** + * A name inherited from a family or marriage and by which the individual is commonly known. + */ + profileLastName?: string; + /** + * A short unique string to identify them. + */ + profileUsername?: string; +} +interface MetaFlat extends MetaFlatArticle, MetaFlatBook, MetaFlatProfile { + /** + * This attribute declares the document's character encoding. + * If the attribute is present, its value must be an ASCII case-insensitive match for the string "utf-8", + * because UTF-8 is the only valid encoding for HTML5 documents. + * `` elements which declare a character encoding must be located entirely within the first 1024 bytes + * of the document. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset + */ + charset: 'utf-8' | (string & Record); + /** + * Use this tag to provide a short description of the page. + * In some situations, this description is used in the snippet shown in search results. + * + * @see https://developers.google.com/search/docs/advanced/appearance/snippet#meta-descriptions + */ + description: string; + /** + * Specifies one or more color schemes with which the document is compatible. + * The browser will use this information in tandem with the user's browser or device settings to determine what colors + * to use for everything from background and foregrounds to form controls and scrollbars. + * The primary use for `` is to indicate compatibility with—and order of preference + * for—light and dark color modes. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#normal + */ + colorScheme: 'normal' | 'light dark' | 'dark light' | 'only light' | (string & Record); + /** + * The name of the application running in the web page. + * + * Uses: + * - When adding the page to the home screen. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name + */ + applicationName: string; + /** + * The name of the document's author. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name + */ + author: string; + /** + * The name of the creator of the document, such as an organization or institution. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names + */ + creator: string; + /** + * The name of the document's publisher. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names + */ + publisher: string; + /** + * The identifier of the software that generated the page. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_the_html_specification + */ + generator: string; + /** + * Controls the HTTP Referer header of requests sent from the document. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_the_html_specification + */ + referrer: ReferrerPolicy; + /** + * This tag tells the browser how to render a page on a mobile device. + * Presence of this tag indicates to Google that the page is mobile friendly. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_other_specifications + */ + viewport: 'width=device-width, initial-scale=1.0' | string | Partial<{ + /** + * Defines the pixel width of the viewport that you want the web site to be rendered at. + */ + width: number | string | 'device-width'; + /** + * Defines the height of the viewport. Not used by any browser. + */ + height: number | string | 'device-height'; + /** + * Defines the ratio between the device width + * (device-width in portrait mode or device-height in landscape mode) and the viewport size. + * + * @minimum 0 + * @maximum 10 + */ + initialScale: '1.0' | number | (string & Record); + /** + * Defines the maximum amount to zoom in. + * It must be greater or equal to the minimum-scale or the behavior is undefined. + * Browser settings can ignore this rule and iOS10+ ignores it by default. + * + * @minimum 0 + * @maximum 10 + */ + maximumScale: number | string; + /** + * Defines the minimum zoom level. It must be smaller or equal to the maximum-scale or the behavior is undefined. + * Browser settings can ignore this rule and iOS10+ ignores it by default. + * + * @minimum 0 + * @maximum 10 + */ + minimumScale: number | string; + /** + * If set to no, the user is unable to zoom in the webpage. + * The default is yes. Browser settings can ignore this rule, and iOS10+ ignores it by default. + */ + userScalable: 'yes' | 'no'; + /** + * The auto value doesn't affect the initial layout viewport, and the whole web page is viewable. + * + * The contain value means that the viewport is scaled to fit the largest rectangle inscribed within the display. + * + * The cover value means that the viewport is scaled to fill the device display. + * It is highly recommended to make use of the safe area inset variables to ensure that important content + * doesn't end up outside the display. + */ + viewportFit: 'auto' | 'contain' | 'cover'; + }>; + /** + * Control the behavior of search engine crawling and indexing. + * + * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag + */ + robots: 'noindex, nofollow' | 'index, follow' | string | Partial<{ + /** + * Allow search engines to index this page. + * + * Note: This is not officially supported by Google but is used widely. + */ + index: Booleanable; + /** + * Allow search engines to follow links on this page. + * + * Note: This is not officially supported by Google but is used widely. + */ + follow: Booleanable; + /** + * There are no restrictions for indexing or serving. + * This directive is the default value and has no effect if explicitly listed. + */ + all: Booleanable; + /** + * Do not show this page, media, or resource in search results. + * If you don't specify this directive, the page, media, or resource may be indexed and shown in search results. + */ + noindex: Booleanable; + /** + * Do not follow the links on this page. + * If you don't specify this directive, Google may use the links on the page to discover those linked pages. + */ + nofollow: Booleanable; + /** + * Equivalent to noindex, nofollow. + */ + none: Booleanable; + /** + * Do not show a cached link in search results. + * If you don't specify this directive, + * Google may generate a cached page and users may access it through the search results. + */ + noarchive: Booleanable; + /** + * Do not show a sitelinks search box in the search results for this page. + * If you don't specify this directive, Google may generate a search box specific to your site in search results, + * along with other direct links to your site. + */ + nositelinkssearchbox: Booleanable; + /** + * + * Do not show a text snippet or video preview in the search results for this page. + * A static image thumbnail (if available) may still be visible, when it results in a better user experience. + */ + nosnippet: Booleanable; + /** + * Google is allowed to index the content of a page if it's embedded in another + * page through iframes or similar HTML tags, in spite of a noindex directive. + * + * indexifembedded only has an effect if it's accompanied by noindex. + */ + indexifembedded: Booleanable; + /** + * Use a maximum of [number] characters as a textual snippet for this search result. + */ + maxSnippet: number | string; + /** + * Set the maximum size of an image preview for this page in a search results. + */ + maxImagePreview: 'none' | 'standard' | 'large'; + /** + * Use a maximum of [number] seconds as a video snippet for videos on this page in search results. + */ + maxVideoPreview: number | string; + /** + * Don't offer translation of this page in search results. + */ + notranslate: Booleanable; + /** + * Do not show this page in search results after the specified date/time. + */ + unavailable_after: string; + /** + * Do not index images on this page. + */ + noimageindex: Booleanable; + }>; + /** + * Special meta tag for controlling Google's indexing behavior. + * + * @see https://developers.google.com/search/docs/crawling-indexing/special-tags + */ + google: + /** + * When users search for your site, Google Search results sometimes display a search box specific to your site, + * along with other direct links to your site. This tag tells Google not to show the sitelinks search box. + */ + 'nositelinkssearchbox' | + /** + * Prevents various Google text-to-speech services from reading aloud web pages using text-to-speech (TTS). + */ + 'nopagereadaloud'; + /** + * Control how Google indexing works specifically for the googlebot crawler. + * + * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag + */ + googlebot: + /** + * When Google recognizes that the contents of a page aren't in the language that the user likely wants to read, + * Google may provide a translated title link and snippet in search results. + */ + 'notranslate' | 'noimageindex' | 'noarchive' | 'nosnippet' | 'max-snippet' | 'max-image-preview' | 'max-video-preview'; + /** + * Control how Google indexing works specifically for the googlebot-news crawler. + * + * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag + */ + googlebotNews: 'noindex' | 'nosnippet' | 'notranslate' | 'noimageindex'; + /** + * You can use this tag on the top-level page of your site to verify ownership for Search Console. + * + * @see https://developers.google.com/search/docs/crawling-indexing/special-tags + */ + googleSiteVerification: string; + /** + * Labels a page as containing adult content, to signal that it be filtered by SafeSearch results + * . + * @see https://developers.google.com/search/docs/advanced/guidelines/safesearch + */ + rating: 'adult'; + /** + * The canonical URL for your page. + * + * This should be the undecorated URL, without session variables, user identifying parameters, or counters. + * Likes and Shares for this URL will aggregate at this URL. + * + * For example: mobile domain URLs should point to the desktop version of the URL as the canonical URL to aggregate + * Likes and Shares across different versions of the page. + * + * @see https://ogp.me/#metadata + */ + ogUrl: string; + /** + * The title of your page without any branding such as your site name. + * + * @see https://ogp.me/#metadata + */ + ogTitle: string; + /** + * A brief description of the content, usually between 2 and 4 sentences. + * + * @see https://ogp.me/#optional + */ + ogDescription: string; + /** + * The type of media of your content. This tag impacts how your content shows up in Feed. If you don't specify a type, + * the default is website. + * Each URL should be a single object, so multiple og:type values are not possible. + * + * @see https://ogp.me/#metadata + */ + ogType: 'website' | 'article' | 'book' | 'profile' | 'music.song' | 'music.album' | 'music.playlist' | 'music.radio_status' | 'video.movie' | 'video.episode' | 'video.tv_show' | 'video.other'; + /** + * The locale of the resource. Defaults to en_US. + * + * @see https://ogp.me/#optional + */ + ogLocale: string; + /** + * An array of other locales this page is available in. + * + * @see https://ogp.me/#optional + */ + ogLocaleAlternate: Arrayable; + /** + * The word that appears before this object's title in a sentence. + * An enum of (a, an, the, "", auto). + * If auto is chosen, the consumer of your data should choose between "a" or "an". + * Default is "" (blank). + * + * @see https://ogp.me/#optional + */ + ogDeterminer: 'a' | 'an' | 'the' | '' | 'auto'; + /** + * If your object is part of a larger website, the name which should be displayed for the overall site. e.g., "IMDb". + * + * @see https://ogp.me/#optional + */ + ogSiteName: string; + /** + * The URL for the video. If you want the video to play in-line in Feed, you should use the https:// URL if possible. + * + * @see https://ogp.me/#type_video + */ + ogVideo: string | Arrayable<{ + /** + * Equivalent to og:video + */ + url: string; + /** + * + * Secure URL for the video. Include this even if you set the secure URL in og:video. + */ + secureUrl?: string; + /** + * MIME type of the video. + */ + type?: 'application/x-shockwave-flash' | 'video/mp4'; + /** + * Width of video in pixels. This property is required for videos. + */ + width?: string | number; + /** + * Height of video in pixels. This property is required for videos. + */ + height?: string | number; + /** + * A text description of the video. + */ + alt?: string; + }>; + /** + * Equivalent to og:video + * + * @see https://ogp.me/#type_video + */ + ogVideoUrl: string; + /** + * + * Secure URL for the video. Include this even if you set the secure URL in og:video. + * + * @see https://ogp.me/#type_video + */ + ogVideoSecureUrl: string; + /** + * MIME type of the video. + * + * @see https://ogp.me/#type_video + */ + ogVideoType: 'application/x-shockwave-flash' | 'video/mp4'; + /** + * Width of video in pixels. This property is required for videos. + * + * @see https://ogp.me/#type_video + */ + ogVideoWidth: string | number; + /** + * Height of video in pixels. This property is required for videos. + * + * @see https://ogp.me/#type_video + */ + ogVideoHeight: string | number; + /** + * A text description of the video. + * + * @see https://ogp.me/#type_video + */ + ogVideoAlt: string; + /** + * The URL of the image that appears when someone shares the content. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImage: string | Arrayable<{ + /** + * Equivalent to og:image + */ + url: string; + /** + * + * https:// URL for the image + */ + secureUrl?: string; + /** + * MIME type of the image. + */ + type?: 'image/jpeg' | 'image/gif' | 'image/png'; + /** + * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + */ + width?: '1200' | string | number; + /** + * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + */ + height?: '630' | string | number; + /** + * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. + */ + alt?: string; + }>; + /** + * Equivalent to og:image + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageUrl: string; + /** + * + * https:// URL for the image + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageSecureUrl: string; + /** + * MIME type of the image. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageType: 'image/jpeg' | 'image/gif' | 'image/png'; + /** + * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageWidth: '1200' | string | number; + /** + * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageHeight: '630' | string | number; + /** + * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#images + */ + ogImageAlt: string; + /** + * The URL for an audio file to accompany this object. + * + * @see https://ogp.me/#optional + */ + ogAudio: string | Arrayable<{ + /** + * Equivalent to og:audio + */ + url: string; + /** + * Secure URL for the audio. Include this even if you set the secure URL in og:audio. + */ + secureUrl?: string; + /** + * MIME type of the audio. + */ + type?: 'audio/mpeg' | 'audio/ogg' | 'audio/wav'; + }>; + /** + * Equivalent to og:audio + * + * @see https://ogp.me/#optional + */ + ogAudioUrl: string; + /** + * Secure URL for the audio. Include this even if you set the secure URL in og:audio. + * + * @see https://ogp.me/#optional + */ + ogAudioSecureUrl: string; + /** + * MIME type of the audio. + * + * @see https://ogp.me/#optional + */ + ogAudioType: 'audio/mpeg' | 'audio/ogg' | 'audio/wav'; + /** + * Your Facebook app ID. + * + * In order to use Facebook Insights you must add the app ID to your page. + * Insights lets you view analytics for traffic to your site from Facebook. Find the app ID in your App Dashboard. + * + * @see https://developers.facebook.com/docs/sharing/webmasters#basic + */ + fbAppId: string | number; + /** + * The card type + * + * Used with all cards + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterCard: 'summary' | 'summary_large_image' | 'app' | 'player'; + /** + * Title of content (max 70 characters) + * + * Used with summary, summary_large_image, player cards + * + * Same as `og:title` + * + * @maxLength 70 + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterTitle: string; + /** + * Description of content (maximum 200 characters) + * + * Used with summary, summary_large_image, player cards. + * + * Same as `og:description` + * + * @maxLength 200 + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterDescription: string; + /** + * URL of image to use in the card. + * Images must be less than 5MB in size. + * JPG, PNG, WEBP and GIF formats are supported. + * Only the first frame of an animated GIF will be used. SVG is not supported. + * + * Used with summary, summary_large_image, player cards + * + * Same as `og:image`. + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterImage: string | Arrayable<{ + /** + * Equivalent to twitter:image + */ + url: string; + /** + * MIME type of the image. + * @deprecated Twitter removed this property from their card specification. + */ + type?: 'image/jpeg' | 'image/gif' | 'image/png'; + /** + * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + * @deprecated Twitter removed this property from their card specification. + */ + width?: '1200' | string | number; + /** + * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. + * @deprecated Twitter removed this property from their card specification. + */ + height?: '630' | string | number; + /** + * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. + */ + alt?: string; + }>; + /** + * The width of the image in pixels. + * + * Note: This is not officially documented. + * + * Same as `og:image:width` + * + * @deprecated Twitter removed this property from their card specification. + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterImageWidth: string | number; + /** + * The height of the image in pixels. + * + * Note: This is not officially documented. + * + * Same as `og:image:height` + * + * @deprecated Twitter removed this property from their card specification. + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterImageHeight: string | number; + /** + * The type of the image. + * + * Note: This is not officially documented. + * + * Same as `og:image:type` + * + * @deprecated Twitter removed this property from their card specification. + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterImageType: 'image/jpeg' | 'image/gif' | 'image/png'; + /** + * A text description of the image conveying the essential nature of an image to users who are visually impaired. + * Maximum 420 characters. + * + * Used with summary, summary_large_image, player cards + * + * Same as `og:image:alt`. + * + * @maxLength 420 + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards + */ + twitterImageAlt: string; + /** + * The @username of website. Either twitter:site or twitter:site:id is required. + * + * Used with summary, summary_large_image, app, player cards + * + * @example @harlan_zw + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterSite: string; + /** + * Same as twitter:site, but the user’s Twitter ID. Either twitter:site or twitter:site:id is required. + * + * Used with summary, summary_large_image, player cards + * + * @example 1296047337022742529 + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterSiteId: string | number; + /** + * The @username who created the pages content. + * + * Used with summary_large_image cards + * + * @example harlan_zw + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterCreator: string; + /** + * Twitter user ID of content creator + * + * Used with summary, summary_large_image cards + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterCreatorId: string | number; + /** + * HTTPS URL of player iframe + * + * Used with player card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterPlayer: string; + /** + * + * Width of iframe in pixels + * + * Used with player card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterPlayerWidth: string | number; + /** + * Height of iframe in pixels + * + * Used with player card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterPlayerHeight: string | number; + /** + * URL to raw video or audio stream + * + * Used with player card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterPlayerStream: string; + /** + * Name of your iPhone app + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppNameIphone: string; + /** + * Your app ID in the iTunes App Store + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppIdIphone: string; + /** + * Your app’s custom URL scheme (you must include ”://” after your scheme name) + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppUrlIphone: string; + /** + * Name of your iPad optimized app + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppNameIpad: string; + /** + * Your app ID in the iTunes App Store + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppIdIpad: string; + /** + * Your app’s custom URL scheme + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppUrlIpad: string; + /** + * Name of your Android app + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppNameGoogleplay: string; + /** + * Your app ID in the Google Play Store + * + * Used with app card + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppIdGoogleplay: string; + /** + * Your app’s custom URL scheme + * + * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup + */ + twitterAppUrlGoogleplay: string; + /** + * Top customizable data field, can be a relatively short string (ie “$3.99”) + * + * Used by Slack. + * + * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl + */ + twitterData1: string; + /** + * Customizable label or units for the information in twitter:data1 (best practice: use all caps) + * + * Used by Slack. + * + * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl + */ + twitterLabel1: string; + /** + * Bottom customizable data field, can be a relatively short string (ie “Seattle, WA”) + * + * Used by Slack. + * + * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl + */ + twitterData2: string; + /** + * Customizable label or units for the information in twitter:data2 (best practice: use all caps) + * + * Used by Slack. + * + * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl + */ + twitterLabel2: string; + /** + * Indicates a suggested color that user agents should use to customize the display of the page or + * of the surrounding user interface. + * + * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name + * @example `#4285f4` or `{ color: '#4285f4', media: '(prefers-color-scheme: dark)'}` + */ + themeColor: string | Arrayable<{ + /** + * A valid CSS color value that matches the value used for the `theme-color` CSS property. + * + * @example `#4285f4` + */ + content: string; + /** + * A valid media query that defines when the value should be used. + * + * @example `(prefers-color-scheme: dark)` + */ + media: '(prefers-color-scheme: dark)' | '(prefers-color-scheme: light)' | string; + }>; + /** + * Sets whether a web application runs in full-screen mode. + */ + mobileWebAppCapable: 'yes'; + /** + * Sets whether a web application runs in full-screen mode. + * + * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html + */ + appleMobileWebAppCapable: 'yes'; + /** + * Sets the style of the status bar for a web application. + * + * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html + */ + appleMobileWebAppStatusBarStyle: 'default' | 'black' | 'black-translucent'; + /** + * Make the app title different from the page title. + */ + appleMobileWebAppTitle: string; + /** + * Promoting Apps with Smart App Banners + * + * @see https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners + */ + appleItunesApp: string | { + /** + * Your app’s unique identifier. + */ + appId: string; + /** + * A URL that provides context to your native app. + */ + appArgument?: string; + }; + /** + * Enables or disables automatic detection of possible phone numbers in a webpage in Safari on iOS. + * + * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html + */ + formatDetection: 'telephone=no'; + /** + * Tile image for windows. + * + * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) + */ + msapplicationTileImage: string; + /** + * Tile colour for windows + * + * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) + */ + msapplicationTileColor: string; + /** + * URL of a config for windows tile. + * + * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) + */ + msapplicationConfig: string; + contentSecurityPolicy: string | Partial<{ + childSrc: string; + connectSrc: string; + defaultSrc: string; + fontSrc: string; + imgSrc: string; + manifestSrc: string; + mediaSrc: string; + objectSrc: string; + prefetchSrc: string; + scriptSrc: string; + scriptSrcElem: string; + scriptSrcAttr: string; + styleSrc: string; + styleSrcElem: string; + styleSrcAttr: string; + workerSrc: string; + baseUri: string; + sandbox: string; + formAction: string; + frameAncestors: string; + reportUri: string; + reportTo: string; + requireSriFor: string; + requireTrustedTypesFor: string; + trustedTypes: string; + upgradeInsecureRequests: string; + }>; + contentType: 'text/html; charset=utf-8'; + defaultStyle: string; + xUaCompatible: 'IE=edge'; + refresh: string | { + seconds: number | string; + url: string; + }; + /** + * A comma-separated list of keywords - relevant to the page (Legacy tag used to tell search engines what the page is about). + * @deprecated the "keywords" metatag is no longer used. + * @see https://web.dev/learn/html/metadata/#keywords + */ + keywords: string; +} +type MetaFlatNullable = { + [K in keyof MetaFlat]: MetaFlat[K] | null; +}; +export type MetaFlatInput = Partial; +export {}; diff --git a/src/swagger/index.d.ts b/src/swagger/index.d.ts new file mode 100644 index 0000000..1a0fed3 --- /dev/null +++ b/src/swagger/index.d.ts @@ -0,0 +1,5 @@ +import type { OpenAPIV3 } from 'openapi-types'; +export declare const SwaggerUIRender: (info: OpenAPIV3.InfoObject, version: string, theme: string | { + light: string; + dark: string; +}, stringifiedSwaggerOptions: string, autoDarkMode?: boolean) => string; diff --git a/src/swagger/types.d.ts b/src/swagger/types.d.ts new file mode 100644 index 0000000..a82f415 --- /dev/null +++ b/src/swagger/types.d.ts @@ -0,0 +1,274 @@ +/** + * Swagger UI type because swagger-ui doesn't export an interface so here's copy paste + * + * @see swagger-ui/index.d.ts + **/ +export interface SwaggerUIOptions { + /** + * URL to fetch external configuration document from. + */ + configUrl?: string | undefined; + /** + * REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface. + */ + dom_id?: string | undefined; + /** + * A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them + */ + spec?: { + [propName: string]: any; + } | undefined; + /** + * The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used. + */ + url?: string | undefined; + /** + * An array of API definition objects ([{url: "", name: ""},{url: "", name: ""}]) + * used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed. + * Names and URLs must be unique among all items in this array, since they're used as identifiers. + */ + urls?: Array<{ + url: string; + name: string; + }> | undefined; + /** + * The name of a component available via the plugin system to use as the top-level layout + * for Swagger UI. + */ + layout?: string | undefined; + /** + * A Javascript object to configure plugin integration and behaviors + */ + pluginsOptions?: PluginsOptions; + /** + * An array of plugin functions to use in Swagger UI. + */ + plugins?: SwaggerUIPlugin[] | undefined; + /** + * An array of presets to use in Swagger UI. + * Usually, you'll want to include ApisPreset if you use this option. + */ + presets?: SwaggerUIPlugin[] | undefined; + /** + * If set to true, enables deep linking for tags and operations. + * See the Deep Linking documentation for more information. + */ + deepLinking?: boolean | undefined; + /** + * Controls the display of operationId in operations list. The default is false. + */ + displayOperationId?: boolean | undefined; + /** + * The default expansion depth for models (set to -1 completely hide the models). + */ + defaultModelsExpandDepth?: number | undefined; + /** + * The default expansion depth for the model on the model-example section. + */ + defaultModelExpandDepth?: number | undefined; + /** + * Controls how the model is shown when the API is first rendered. + * (The user can always switch the rendering for a given model by clicking the + * 'Model' and 'Example Value' links.) + */ + defaultModelRendering?: 'example' | 'model' | undefined; + /** + * Controls the display of the request duration (in milliseconds) for "Try it out" requests. + */ + displayRequestDuration?: boolean | undefined; + /** + * Controls the default expansion setting for the operations and tags. + * It can be 'list' (expands only the tags), 'full' (expands the tags and operations) + * or 'none' (expands nothing). + */ + docExpansion?: 'list' | 'full' | 'none' | undefined; + /** + * If set, enables filtering. + * The top bar will show an edit box that you can use to filter the tagged operations that are shown. + * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled + * using that string as the filter expression. + * Filtering is case sensitive matching the filter expression anywhere inside the tag. + */ + filter?: boolean | string | undefined; + /** + * If set, limits the number of tagged operations displayed to at most this many. + * The default is to show all operations. + */ + maxDisplayedTags?: number | undefined; + /** + * Apply a sort to the operation list of each API. + * It can be 'alpha' (sort by paths alphanumerically), + * 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). + * Default is the order returned by the server unchanged. + */ + operationsSorter?: SorterLike | undefined; + /** + * Controls the display of vendor extension (x-) fields and values for Operations, + * Parameters, Responses, and Schema. + */ + showExtensions?: boolean | undefined; + /** + * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields + * and values for Parameters. + */ + showCommonExtensions?: boolean | undefined; + /** + * Apply a sort to the tag list of each API. + * It can be 'alpha' (sort by paths alphanumerically) + * or a function (see Array.prototype.sort() to learn how to write a sort function). + * Two tag name strings are passed to the sorter for each pass. + * Default is the order determined by Swagger UI. + */ + tagsSorter?: SorterLike | undefined; + /** + * When enabled, sanitizer will leave style, class and data-* attributes untouched + * on all HTML Elements declared inside markdown strings. + * This parameter is Deprecated and will be removed in 4.0.0. + * @deprecated + */ + useUnsafeMarkdown?: boolean | undefined; + /** + * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. + */ + onComplete?: (() => any) | undefined; + /** + * Set to false to deactivate syntax highlighting of payloads and cURL command, + * can be otherwise an object with the activate and theme properties. + */ + syntaxHighlight?: false | { + /** + * Whether syntax highlighting should be activated or not. + */ + activate?: boolean | undefined; + /** + * Highlight.js syntax coloring theme to use. (Only these 6 styles are available.) + */ + theme?: 'agate' | 'arta' | 'idea' | 'monokai' | 'nord' | 'obsidian' | 'tomorrow-night' | undefined; + } | undefined; + /** + * Controls whether the "Try it out" section should be enabled by default. + */ + tryItOutEnabled?: boolean | undefined; + /** + * This is the default configuration section for the the requestSnippets plugin. + */ + requestSnippets?: { + generators?: { + [genName: string]: { + title: string; + syntax: string; + }; + } | undefined; + defaultExpanded?: boolean | undefined; + /** + * e.g. only show curl bash = ["curl_bash"] + */ + languagesMask?: string[] | undefined; + } | undefined; + /** + * OAuth redirect URL. + */ + oauth2RedirectUrl?: string | undefined; + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 requests. + * Accepts one argument requestInterceptor(request) and must return the modified request, + * or a Promise that resolves to the modified request. + */ + requestInterceptor?: ((a: Request) => Request | Promise) | undefined; + /** + * MUST be a function. Function to intercept remote definition, + * "Try it out", and OAuth 2.0 responses. + * Accepts one argument responseInterceptor(response) and must return the modified response, + * or a Promise that resolves to the modified response. + */ + responseInterceptor?: ((a: Response) => Response | Promise) | undefined; + /** + * If set to true, uses the mutated request returned from a requestInterceptor + * to produce the curl command in the UI, otherwise the request + * beforethe requestInterceptor was applied is used. + */ + showMutatedRequest?: boolean | undefined; + /** + * List of HTTP methods that have the "Try it out" feature enabled. + * An empty array disables "Try it out" for all operations. + * This does not filter the operations from the display. + */ + supportedSubmitMethods?: SupportedHTTPMethods[] | undefined; + /** + * By default, Swagger UI attempts to validate specs against swagger.io's online validator. + * You can use this parameter to set a different validator URL, + * for example for locally deployed validators (Validator Badge). + * Setting it to either none, 127.0.0.1 or localhost will disable validation. + */ + validatorUrl?: string | undefined; + /** + * If set to true, enables passing credentials, as defined in the Fetch standard, + * in CORS requests that are sent by the browser. + * Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163) + * - as a result, you will have to rely on browser-supplied + * cookies (which this setting enables sending) that Swagger UI cannot control. + */ + withCredentials?: boolean | undefined; + /** + * Function to set default values to each property in model. + * Accepts one argument modelPropertyMacro(property), property is immutable + */ + modelPropertyMacro?: ((propName: Readonly) => any) | undefined; + /** + * Function to set default value to parameters. + * Accepts two arguments parameterMacro(operation, parameter). + * Operation and parameter are objects passed for context, both remain immutable + */ + parameterMacro?: ((operation: Readonly, parameter: Readonly) => any) | undefined; + /** + * If set to true, it persists authorization data and it would not be lost on browser close/refresh + */ + persistAuthorization?: boolean | undefined; +} +interface PluginsOptions { + /** + * Control behavior of plugins when targeting the same component with wrapComponent.
+ * - `legacy` (default) : last plugin takes precedence over the others
+ * - `chain` : chain wrapComponents when targeting the same core component, + * allowing multiple plugins to wrap the same component + * @default 'legacy' + */ + pluginLoadType?: PluginLoadType; +} +type PluginLoadType = 'legacy' | 'chain'; +type SupportedHTTPMethods = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'; +type SorterLike = 'alpha' | 'method' | { + (name1: string, name2: string): number; +}; +interface Request { + [prop: string]: any; +} +interface Response { + [prop: string]: any; +} +/** + * See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/ + */ +interface SwaggerUIPlugin { + (system: any): { + statePlugins?: { + [stateKey: string]: { + actions?: Indexable | undefined; + reducers?: Indexable | undefined; + selectors?: Indexable | undefined; + wrapActions?: Indexable | undefined; + wrapSelectors?: Indexable | undefined; + }; + } | undefined; + components?: Indexable | undefined; + wrapComponents?: Indexable | undefined; + rootInjects?: Indexable | undefined; + afterLoad?: ((system: any) => any) | undefined; + fn?: Indexable | undefined; + }; +} +interface Indexable { + [index: string]: any; +} +export {}; diff --git a/src/types.d.ts b/src/types.d.ts new file mode 100644 index 0000000..5dc906c --- /dev/null +++ b/src/types.d.ts @@ -0,0 +1,96 @@ +import type { OpenAPIV3 } from 'openapi-types'; +import type { ReferenceConfiguration } from './scalar/types'; +import type { SwaggerUIOptions } from './swagger/types'; +export interface ElysiaSwaggerConfig { + /** + * Customize Swagger config, refers to Swagger 2.0 config + * + * @see https://swagger.io/specification/v2/ + */ + documentation?: Omit, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>; + /** + * Choose your provider, Scalar or Swagger UI + * + * @default 'scalar' + * @see https://github.com/scalar/scalar + * @see https://github.com/swagger-api/swagger-ui + */ + provider?: 'scalar' | 'swagger-ui'; + /** + * Version to use for Scalar cdn bundle + * + * @default 'latest' + * @see https://github.com/scalar/scalar + */ + scalarVersion?: string; + /** + * Optional override to specifying the path for the Scalar bundle + * + * Custom URL or path to locally hosted Scalar bundle + * + * Lease blank to use default jsdeliver.net CDN + * + * @default '' + * @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js' + * @example '/public/standalone.js' + * @see https://github.com/scalar/scalar + */ + scalarCDN?: string; + /** + * Scalar configuration to customize scalar + *' + * @see https://github.com/scalar/scalar + */ + scalarConfig?: ReferenceConfiguration; + /** + * Version to use for swagger cdn bundle + * + * @see unpkg.com/swagger-ui-dist + * + * @default 4.18.2 + */ + version?: string; + /** + * Determine if Swagger should exclude static files. + * + * @default true + */ + excludeStaticFile?: boolean; + /** + * The endpoint to expose Swagger + * + * @default '/swagger' + */ + path?: Path; + /** + * Paths to exclude from Swagger endpoint + * + * @default [] + */ + exclude?: string | RegExp | (string | RegExp)[]; + /** + * Options to send to SwaggerUIBundle + * Currently, options that are defined as functions such as requestInterceptor + * and onComplete are not supported. + */ + swaggerOptions?: Omit, 'dom_id' | 'dom_node' | 'spec' | 'url' | 'urls' | 'layout' | 'pluginsOptions' | 'plugins' | 'presets' | 'onComplete' | 'requestInterceptor' | 'responseInterceptor' | 'modelPropertyMacro' | 'parameterMacro'>; + /** + * Custom Swagger CSS + */ + theme?: string | { + light: string; + dark: string; + }; + /** + * Using poor man dark mode 😭 + */ + autoDarkMode?: boolean; + /** + * Exclude methods from Swagger + */ + excludeMethods?: string[]; + /** + * Exclude tags from Swagger or Scalar + */ + excludeTags?: string[]; +} diff --git a/src/utils.d.ts b/src/utils.d.ts new file mode 100644 index 0000000..6c2ff66 --- /dev/null +++ b/src/utils.d.ts @@ -0,0 +1,26 @@ +import type { HTTPMethod, LocalHook } from 'elysia'; +import { type TSchema } from '@sinclair/typebox'; +import type { OpenAPIV3 } from 'openapi-types'; +export declare const toOpenAPIPath: (path: string) => string; +export declare const mapProperties: (name: string, schema: TSchema | string | undefined, models: Record) => { + description: any; + examples: any; + schema: any; + in: string; + name: string; + required: any; +}[]; +export declare const capitalize: (word: string) => string; +export declare const generateOperationId: (method: string, paths: string) => string; +export declare const registerSchemaPath: ({ schema, path, method, hook, models }: { + schema: Partial; + contentType?: string | string[]; + path: string; + method: HTTPMethod; + hook?: LocalHook; + models: Record; +}) => void; +export declare const filterPaths: (paths: Record, docsPath: string, { excludeStaticFile, exclude }: { + excludeStaticFile: boolean; + exclude: (string | RegExp)[]; +}) => Record; From 40c1267dbd4d486e85bb4cddfe70e53078fe7032 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Mon, 12 Aug 2024 23:47:27 +0700 Subject: [PATCH 2/6] :tada: release: 1.1.1 --- generated/index.d.ts | 40 -- generated/plugin.d.ts | 107 ---- src/codegen.ts | 23 - src/index.d.ts | 30 -- src/scalar/index.d.ts | 2 - src/scalar/theme.d.ts | 2 - src/scalar/types/index.d.ts | 34 -- src/scalar/types/unjead.d.ts | 981 ----------------------------------- src/swagger/index.d.ts | 5 - src/swagger/types.d.ts | 274 ---------- src/types.d.ts | 96 ---- src/utils.d.ts | 26 - 12 files changed, 1620 deletions(-) delete mode 100644 generated/index.d.ts delete mode 100644 generated/plugin.d.ts delete mode 100644 src/codegen.ts delete mode 100644 src/index.d.ts delete mode 100644 src/scalar/index.d.ts delete mode 100644 src/scalar/theme.d.ts delete mode 100644 src/scalar/types/index.d.ts delete mode 100644 src/scalar/types/unjead.d.ts delete mode 100644 src/swagger/index.d.ts delete mode 100644 src/swagger/types.d.ts delete mode 100644 src/types.d.ts delete mode 100644 src/utils.d.ts diff --git a/generated/index.d.ts b/generated/index.d.ts deleted file mode 100644 index 4934851..0000000 --- a/generated/index.d.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { Elysia } from 'elysia'; -declare const app: Elysia<"", false, { - decorator: {}; - store: {}; - derive: {}; - resolve: {}; -}, { - type: {}; - error: {}; -}, { - schema: {}; - macro: {}; - macroFn: {}; -}, { - id: { - ":id?": { - get: { - body: unknown; - params: { - id?: string; - }; - query: unknown; - headers: unknown; - response: { - 200: "a"; - }; - }; - }; - }; -}, { - derive: {}; - resolve: {}; - schema: {}; -}, { - derive: {}; - resolve: {}; - schema: {}; -}>; -export type app = typeof app; -export {}; diff --git a/generated/plugin.d.ts b/generated/plugin.d.ts deleted file mode 100644 index 53e61ac..0000000 --- a/generated/plugin.d.ts +++ /dev/null @@ -1,107 +0,0 @@ -import { Elysia } from 'elysia'; -export declare const plugin: Elysia<"/a", false, { - decorator: {}; - store: {}; - derive: {}; - resolve: {}; -}, { - type: { - readonly sign: { - username: string; - password: string; - }; - readonly number: number; - }; - error: {}; -}, { - schema: {}; - macro: {}; - macroFn: {}; -}, { - a: { - index: { - get: { - body: unknown; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: string; - }; - }; - }; - }; -} & { - a: { - unpath: { - ":id": { - get: { - body: unknown; - params: { - id: string; - }; - query: unknown; - headers: unknown; - response: { - 200: string; - }; - }; - }; - }; - }; -} & { - a: { - json: { - post: { - body: unknown; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: unknown; - }; - }; - }; - }; -} & { - a: { - json: { - ":id": { - post: { - body: unknown; - params: { - id: string; - }; - query: unknown; - headers: unknown; - response: { - 200: unknown; - 418: unknown; - }; - }; - }; - }; - }; -} & { - a: { - file: { - post: { - body: unknown; - params: {}; - query: unknown; - headers: unknown; - response: { - 200: unknown; - }; - }; - }; - }; -}, { - derive: {}; - resolve: {}; - schema: {}; -}, { - derive: {}; - resolve: {}; - schema: {}; -}>; diff --git a/src/codegen.ts b/src/codegen.ts deleted file mode 100644 index 65d7d1e..0000000 --- a/src/codegen.ts +++ /dev/null @@ -1,23 +0,0 @@ -// import { TypeScriptToModel, ModelToJsonSchema } from '@sinclair/typebox-codegen' -// import { cwd } from 'process' -// import { join } from 'path' - -// // await Bun.$` tsc example/index.ts --declaration --emitDeclarationOnly --esModuleInterop --skipLibCheck --declarationDir generated --rootDir example` - -// // Bun.file(join(cwd(), 'generated/index.d.ts')) - -// const type = TypeScriptToModel.Generate(` -// type A = { -// body: unknown; -// params: { -// id?: string; -// }; -// query: unknown; -// headers: unknown; -// response: { -// 200: "a"; -// }; -// } -// `) - -// console.dir(type, { depth: null }) diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 77a71b9..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { Elysia } from 'elysia'; -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 declare const swagger: ({ provider, scalarVersion, scalarCDN, scalarConfig, documentation, version, excludeStaticFile, path, exclude, swaggerOptions, theme, autoDarkMode, excludeMethods, excludeTags }?: ElysiaSwaggerConfig) => Promise>; -export type { ElysiaSwaggerConfig }; -export default swagger; diff --git a/src/scalar/index.d.ts b/src/scalar/index.d.ts deleted file mode 100644 index 7bcb294..0000000 --- a/src/scalar/index.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import type { ReferenceConfiguration } from './types'; -export declare const ScalarRender: (version: string, config: ReferenceConfiguration, cdn: string) => string; diff --git a/src/scalar/theme.d.ts b/src/scalar/theme.d.ts deleted file mode 100644 index cab26bb..0000000 --- a/src/scalar/theme.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -declare const _default: "\n/* basic theme */\n.light-mode {\n --theme-color-1: #2a2f45;\n --theme-color-2: #757575;\n --theme-color-3: #8e8e8e;\n --theme-color-accent: #f06292;\n\n --theme-background-1: #fff;\n --theme-background-2: #f6f6f6;\n --theme-background-3: #e7e7e7;\n --theme-background-accent: #f062921f;\n\n --theme-border-color: rgba(0, 0, 0, 0.1);\n}\n.dark-mode {\n --theme-color-1: rgba(255, 255, 255, 0.9);\n --theme-color-2: rgba(156, 163, 175, 1);\n --theme-color-3: rgba(255, 255, 255, 0.44);\n --theme-color-accent: #f06292;\n\n --theme-background-1: #111728;\n --theme-background-2: #1e293b;\n --theme-background-3: #334155;\n --theme-background-accent: #f062921f;\n\n --theme-border-color: rgba(255, 255, 255, 0.1);\n}\n/* Document Sidebar */\n.light-mode .sidebar,\n.dark-mode .sidebar {\n --sidebar-background-1: var(--theme-background-1);\n --sidebar-item-hover-color: currentColor;\n --sidebar-item-hover-background: var(--theme-background-2);\n --sidebar-item-active-background: var(--theme-background-accent);\n --sidebar-border-color: transparent;\n --sidebar-color-1: var(--theme-color-1);\n --sidebar-color-2: var(--theme-color-2);\n --sidebar-color-active: var(--theme-color-accent);\n --sidebar-search-background: transparent;\n --sidebar-search-border-color: var(--theme-border-color);\n --sidebar-search--color: var(--theme-color-3);\n}\n/* Document header only shows on mobile*/\n.dark-mode .t-doc__header,\n.light-mode .t-doc__header {\n --header-background-1: rgba(255, 255, 255, 0.85);\n --header-border-color: transparent;\n --header-color-1: var(--theme-color-1);\n --header-color-2: var(--theme-color-2);\n --header-background-toggle: var(--theme-color-3);\n --header-call-to-action-color: var(--theme-color-accent);\n}\n\n.dark-mode .t-doc__header {\n --header-background-1: rgba(17, 23, 40, 0.75);\n}\n\n/* advanced */\n.light-mode {\n --theme-button-1: rgb(49 53 56);\n --theme-button-1-color: #fff;\n --theme-button-1-hover: rgb(28 31 33);\n\n --theme-color-green: #069061;\n --theme-color-red: #ef0006;\n --theme-color-yellow: #edbe20;\n --theme-color-blue: #0082d0;\n --theme-color-orange: #fb892c;\n --theme-color-purple: #5203d1;\n\n --theme-scrollbar-color: rgba(0, 0, 0, 0.18);\n --theme-scrollbar-color-active: rgba(0, 0, 0, 0.36);\n}\n.dark-mode {\n --theme-button-1: #f6f6f6;\n --theme-button-1-color: #000;\n --theme-button-1-hover: #e7e7e7;\n\n --theme-color-green: #a3ffa9;\n --theme-color-red: #ffa3a3;\n --theme-color-yellow: #fffca3;\n --theme-color-blue: #a5d6ff;\n --theme-color-orange: #e2ae83;\n --theme-color-purple: #d2a8ff;\n\n --theme-scrollbar-color: rgba(255, 255, 255, 0.24);\n --theme-scrollbar-color-active: rgba(255, 255, 255, 0.48);\n}\n/* Elysia Specific */\n.scalar-api-client__send-request-button,\n.show-api-client-button {\n background: #3c82f6 !important;\n}\n.show-api-client-button:before {\n display: none;\n}\n\n.sidebar-search:hover {\n transition: all 0.15s ease-in-out;\n --sidebar-search-border-color: var(--theme-color-accent) !important;\n color: var(--sidebar-color-1) !important;\n}\n.scalar-api-client__container .sidebar {\n --sidebar-border-color: var(--theme-border-color);\n}\n@media (min-width: 1150px) {\n .section-container:has( ~ .footer):before,\n .tag-section-container:before {\n content: \"\";\n position: absolute;\n top: -5px;\n left: 0;\n width: 100%;\n height: 10px;\n background: linear-gradient(90deg, var(--theme-background-1) 3%,transparent 10%);\n }\n}\n.section-flare {\n position: absolute;\n width: 100vw;\n height: 300px;\n --stripes: repeating-linear-gradient(\n 100deg,\n #fff 0%,\n #fff 7%,\n transparent 10%,\n transparent 12%,\n #fff 16%\n );\n --stripesDark: repeating-linear-gradient(\n 100deg,\n #000 0%,\n #000 7%,\n transparent 10%,\n transparent 12%,\n #000 16%\n );\n --rainbow: repeating-linear-gradient(\n 100deg,\n #60a5fa 10%,\n #e879f9 16%,\n #5eead4 22%,\n #60a5fa 30%\n );\n background-image: var(--stripes), var(--rainbow);\n background-size: 300%, 200%;\n background-position: 50% 50%, 50% 50%;\n filter: invert(100%);\n -webkit-mask-image: radial-gradient(\n ellipse at 100% 0%,\n black 40%,\n transparent 70%\n );\n mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%);\n pointer-events: none;\n opacity: 0.15;\n}\n.dark-mode .section-flare {\n background-image: var(--stripesDark), var(--rainbow);\n filter: opacity(50%) saturate(200%);\n opacity: 0.25;\n}\n.section-flare:after {\n content: \"\";\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n background-image: var(--stripes), var(--rainbow);\n background-size: 200%, 100%;\n background-attachment: fixed;\n mix-blend-mode: difference;\n}\n.dark-mode .section-flare:after {\n background-image: var(--stripesDark), var(--rainbow);\n}\n@keyframes headerbackground {\n from {\n background: transparent;\n backdrop-filter: none;\n }\n to {\n background: var(--header-background-1);\n backdrop-filter: blur(12px);\n }\n}\n.light-mode .t-doc__header,\n.dark-mode .t-doc__header {\n animation: headerbackground forwards;\n animation-timeline: scroll();\n animation-range: 0px 200px;\n --header-border-color: transparent;\n}\n"; -export default _default; diff --git a/src/scalar/types/index.d.ts b/src/scalar/types/index.d.ts deleted file mode 100644 index 6b18c23..0000000 --- a/src/scalar/types/index.d.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type { MetaFlatInput } from "./unjead"; -export type ReferenceConfiguration = { - /** A string to use one of the color presets */ - theme?: ThemeId; - /** The layout to use for the references */ - layout?: ReferenceLayoutType; - /** The Swagger/OpenAPI spec to render */ - spec?: SpecConfiguration; - /** URL to a request proxy for the API client */ - proxy?: string; - /** Whether the spec input should show */ - isEditable?: boolean; - /** Whether to show the sidebar */ - showSidebar?: boolean; - /** Remove the Scalar branding :( */ - /** Key used with CNTRL/CMD to open the search modal (defaults to 'k' e.g. CMD+k) */ - searchHotKey?: string; - /** If used, passed data will be added to the HTML header. Read more: https://unhead.unjs.io/usage/composables/use-seo-meta */ - metaData?: MetaFlatInput; - /** Custom CSS to be added to the page */ - customCss?: string; - /** onSpecUpdate is fired on spec/swagger content change */ - onSpecUpdate?: (spec: string) => void; -}; -export type SpecConfiguration = { - /** URL to a Swagger/OpenAPI file */ - url?: string; - /** Swagger/Open API spec */ - content?: string | Record | (() => Record); - /** The result of @scalar/swagger-parser */ - preparsedContent?: Record; -}; -export type ReferenceLayoutType = 'modern' | 'classic'; -export type ThemeId = 'alternate' | 'default' | 'moon' | 'purple' | 'solarized' | 'none'; diff --git a/src/scalar/types/unjead.d.ts b/src/scalar/types/unjead.d.ts deleted file mode 100644 index 4df2ab1..0000000 --- a/src/scalar/types/unjead.d.ts +++ /dev/null @@ -1,981 +0,0 @@ -type Booleanable = boolean | 'false' | 'true' | ''; -type Arrayable = T | Array; -type ReferrerPolicy = '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url'; -interface MetaFlatArticle { - /** - * Writers of the article. - * @example ['https://example.com/some.html', 'https://example.com/one.html'] - */ - articleAuthor?: string[]; - /** - * When the article is out of date after. - * @example '1970-01-01T00:00:00.000Z' - */ - articleExpirationTime?: string; - /** - * When the article was last changed. - * @example '1970-01-01T00:00:00.000Z' - */ - articleModifiedTime?: string; - /** - * When the article was first published. - * @example '1970-01-01T00:00:00.000Z' - */ - articlePublishedTime?: string; - /** - * A high-level section name. - * @example 'Technology' - */ - articleSection?: string; - /** - * Tag words associated with this article. - * @example ['Apple', 'Steve Jobs] - */ - articleTag?: string[]; -} -interface MetaFlatBook { - /** - * Who wrote this book. - * @example ['https://example.com/some.html', 'https://example.com/one.html'] - */ - bookAuthor?: string[]; - /** - * The ISBN. - * @example '978-3-16-148410-0' - */ - bookIsbn?: string; - /** - * The date the book was released. - * @example '1970-01-01T00:00:00.000Z' - */ - bookReleaseDate?: string; - /** - * Tag words associated with this book. - * @example ['Apple', 'Steve Jobs] - */ - bookTag?: string[]; -} -interface MetaFlatProfile { - /** - * A name normally given to an individual by a parent or self-chosen. - */ - profileFirstName?: string; - /** - * Their gender. - */ - profileGender?: 'male' | 'female' | string; - /** - * A name inherited from a family or marriage and by which the individual is commonly known. - */ - profileLastName?: string; - /** - * A short unique string to identify them. - */ - profileUsername?: string; -} -interface MetaFlat extends MetaFlatArticle, MetaFlatBook, MetaFlatProfile { - /** - * This attribute declares the document's character encoding. - * If the attribute is present, its value must be an ASCII case-insensitive match for the string "utf-8", - * because UTF-8 is the only valid encoding for HTML5 documents. - * `` elements which declare a character encoding must be located entirely within the first 1024 bytes - * of the document. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta#attr-charset - */ - charset: 'utf-8' | (string & Record); - /** - * Use this tag to provide a short description of the page. - * In some situations, this description is used in the snippet shown in search results. - * - * @see https://developers.google.com/search/docs/advanced/appearance/snippet#meta-descriptions - */ - description: string; - /** - * Specifies one or more color schemes with which the document is compatible. - * The browser will use this information in tandem with the user's browser or device settings to determine what colors - * to use for everything from background and foregrounds to form controls and scrollbars. - * The primary use for `` is to indicate compatibility with—and order of preference - * for—light and dark color modes. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#normal - */ - colorScheme: 'normal' | 'light dark' | 'dark light' | 'only light' | (string & Record); - /** - * The name of the application running in the web page. - * - * Uses: - * - When adding the page to the home screen. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name - */ - applicationName: string; - /** - * The name of the document's author. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name - */ - author: string; - /** - * The name of the creator of the document, such as an organization or institution. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names - */ - creator: string; - /** - * The name of the document's publisher. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#other_metadata_names - */ - publisher: string; - /** - * The identifier of the software that generated the page. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_the_html_specification - */ - generator: string; - /** - * Controls the HTTP Referer header of requests sent from the document. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_the_html_specification - */ - referrer: ReferrerPolicy; - /** - * This tag tells the browser how to render a page on a mobile device. - * Presence of this tag indicates to Google that the page is mobile friendly. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name#standard_metadata_names_defined_in_other_specifications - */ - viewport: 'width=device-width, initial-scale=1.0' | string | Partial<{ - /** - * Defines the pixel width of the viewport that you want the web site to be rendered at. - */ - width: number | string | 'device-width'; - /** - * Defines the height of the viewport. Not used by any browser. - */ - height: number | string | 'device-height'; - /** - * Defines the ratio between the device width - * (device-width in portrait mode or device-height in landscape mode) and the viewport size. - * - * @minimum 0 - * @maximum 10 - */ - initialScale: '1.0' | number | (string & Record); - /** - * Defines the maximum amount to zoom in. - * It must be greater or equal to the minimum-scale or the behavior is undefined. - * Browser settings can ignore this rule and iOS10+ ignores it by default. - * - * @minimum 0 - * @maximum 10 - */ - maximumScale: number | string; - /** - * Defines the minimum zoom level. It must be smaller or equal to the maximum-scale or the behavior is undefined. - * Browser settings can ignore this rule and iOS10+ ignores it by default. - * - * @minimum 0 - * @maximum 10 - */ - minimumScale: number | string; - /** - * If set to no, the user is unable to zoom in the webpage. - * The default is yes. Browser settings can ignore this rule, and iOS10+ ignores it by default. - */ - userScalable: 'yes' | 'no'; - /** - * The auto value doesn't affect the initial layout viewport, and the whole web page is viewable. - * - * The contain value means that the viewport is scaled to fit the largest rectangle inscribed within the display. - * - * The cover value means that the viewport is scaled to fill the device display. - * It is highly recommended to make use of the safe area inset variables to ensure that important content - * doesn't end up outside the display. - */ - viewportFit: 'auto' | 'contain' | 'cover'; - }>; - /** - * Control the behavior of search engine crawling and indexing. - * - * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag - */ - robots: 'noindex, nofollow' | 'index, follow' | string | Partial<{ - /** - * Allow search engines to index this page. - * - * Note: This is not officially supported by Google but is used widely. - */ - index: Booleanable; - /** - * Allow search engines to follow links on this page. - * - * Note: This is not officially supported by Google but is used widely. - */ - follow: Booleanable; - /** - * There are no restrictions for indexing or serving. - * This directive is the default value and has no effect if explicitly listed. - */ - all: Booleanable; - /** - * Do not show this page, media, or resource in search results. - * If you don't specify this directive, the page, media, or resource may be indexed and shown in search results. - */ - noindex: Booleanable; - /** - * Do not follow the links on this page. - * If you don't specify this directive, Google may use the links on the page to discover those linked pages. - */ - nofollow: Booleanable; - /** - * Equivalent to noindex, nofollow. - */ - none: Booleanable; - /** - * Do not show a cached link in search results. - * If you don't specify this directive, - * Google may generate a cached page and users may access it through the search results. - */ - noarchive: Booleanable; - /** - * Do not show a sitelinks search box in the search results for this page. - * If you don't specify this directive, Google may generate a search box specific to your site in search results, - * along with other direct links to your site. - */ - nositelinkssearchbox: Booleanable; - /** - * - * Do not show a text snippet or video preview in the search results for this page. - * A static image thumbnail (if available) may still be visible, when it results in a better user experience. - */ - nosnippet: Booleanable; - /** - * Google is allowed to index the content of a page if it's embedded in another - * page through iframes or similar HTML tags, in spite of a noindex directive. - * - * indexifembedded only has an effect if it's accompanied by noindex. - */ - indexifembedded: Booleanable; - /** - * Use a maximum of [number] characters as a textual snippet for this search result. - */ - maxSnippet: number | string; - /** - * Set the maximum size of an image preview for this page in a search results. - */ - maxImagePreview: 'none' | 'standard' | 'large'; - /** - * Use a maximum of [number] seconds as a video snippet for videos on this page in search results. - */ - maxVideoPreview: number | string; - /** - * Don't offer translation of this page in search results. - */ - notranslate: Booleanable; - /** - * Do not show this page in search results after the specified date/time. - */ - unavailable_after: string; - /** - * Do not index images on this page. - */ - noimageindex: Booleanable; - }>; - /** - * Special meta tag for controlling Google's indexing behavior. - * - * @see https://developers.google.com/search/docs/crawling-indexing/special-tags - */ - google: - /** - * When users search for your site, Google Search results sometimes display a search box specific to your site, - * along with other direct links to your site. This tag tells Google not to show the sitelinks search box. - */ - 'nositelinkssearchbox' | - /** - * Prevents various Google text-to-speech services from reading aloud web pages using text-to-speech (TTS). - */ - 'nopagereadaloud'; - /** - * Control how Google indexing works specifically for the googlebot crawler. - * - * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag - */ - googlebot: - /** - * When Google recognizes that the contents of a page aren't in the language that the user likely wants to read, - * Google may provide a translated title link and snippet in search results. - */ - 'notranslate' | 'noimageindex' | 'noarchive' | 'nosnippet' | 'max-snippet' | 'max-image-preview' | 'max-video-preview'; - /** - * Control how Google indexing works specifically for the googlebot-news crawler. - * - * @see https://developers.google.com/search/docs/crawling-indexing/robots-meta-tag - */ - googlebotNews: 'noindex' | 'nosnippet' | 'notranslate' | 'noimageindex'; - /** - * You can use this tag on the top-level page of your site to verify ownership for Search Console. - * - * @see https://developers.google.com/search/docs/crawling-indexing/special-tags - */ - googleSiteVerification: string; - /** - * Labels a page as containing adult content, to signal that it be filtered by SafeSearch results - * . - * @see https://developers.google.com/search/docs/advanced/guidelines/safesearch - */ - rating: 'adult'; - /** - * The canonical URL for your page. - * - * This should be the undecorated URL, without session variables, user identifying parameters, or counters. - * Likes and Shares for this URL will aggregate at this URL. - * - * For example: mobile domain URLs should point to the desktop version of the URL as the canonical URL to aggregate - * Likes and Shares across different versions of the page. - * - * @see https://ogp.me/#metadata - */ - ogUrl: string; - /** - * The title of your page without any branding such as your site name. - * - * @see https://ogp.me/#metadata - */ - ogTitle: string; - /** - * A brief description of the content, usually between 2 and 4 sentences. - * - * @see https://ogp.me/#optional - */ - ogDescription: string; - /** - * The type of media of your content. This tag impacts how your content shows up in Feed. If you don't specify a type, - * the default is website. - * Each URL should be a single object, so multiple og:type values are not possible. - * - * @see https://ogp.me/#metadata - */ - ogType: 'website' | 'article' | 'book' | 'profile' | 'music.song' | 'music.album' | 'music.playlist' | 'music.radio_status' | 'video.movie' | 'video.episode' | 'video.tv_show' | 'video.other'; - /** - * The locale of the resource. Defaults to en_US. - * - * @see https://ogp.me/#optional - */ - ogLocale: string; - /** - * An array of other locales this page is available in. - * - * @see https://ogp.me/#optional - */ - ogLocaleAlternate: Arrayable; - /** - * The word that appears before this object's title in a sentence. - * An enum of (a, an, the, "", auto). - * If auto is chosen, the consumer of your data should choose between "a" or "an". - * Default is "" (blank). - * - * @see https://ogp.me/#optional - */ - ogDeterminer: 'a' | 'an' | 'the' | '' | 'auto'; - /** - * If your object is part of a larger website, the name which should be displayed for the overall site. e.g., "IMDb". - * - * @see https://ogp.me/#optional - */ - ogSiteName: string; - /** - * The URL for the video. If you want the video to play in-line in Feed, you should use the https:// URL if possible. - * - * @see https://ogp.me/#type_video - */ - ogVideo: string | Arrayable<{ - /** - * Equivalent to og:video - */ - url: string; - /** - * - * Secure URL for the video. Include this even if you set the secure URL in og:video. - */ - secureUrl?: string; - /** - * MIME type of the video. - */ - type?: 'application/x-shockwave-flash' | 'video/mp4'; - /** - * Width of video in pixels. This property is required for videos. - */ - width?: string | number; - /** - * Height of video in pixels. This property is required for videos. - */ - height?: string | number; - /** - * A text description of the video. - */ - alt?: string; - }>; - /** - * Equivalent to og:video - * - * @see https://ogp.me/#type_video - */ - ogVideoUrl: string; - /** - * - * Secure URL for the video. Include this even if you set the secure URL in og:video. - * - * @see https://ogp.me/#type_video - */ - ogVideoSecureUrl: string; - /** - * MIME type of the video. - * - * @see https://ogp.me/#type_video - */ - ogVideoType: 'application/x-shockwave-flash' | 'video/mp4'; - /** - * Width of video in pixels. This property is required for videos. - * - * @see https://ogp.me/#type_video - */ - ogVideoWidth: string | number; - /** - * Height of video in pixels. This property is required for videos. - * - * @see https://ogp.me/#type_video - */ - ogVideoHeight: string | number; - /** - * A text description of the video. - * - * @see https://ogp.me/#type_video - */ - ogVideoAlt: string; - /** - * The URL of the image that appears when someone shares the content. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImage: string | Arrayable<{ - /** - * Equivalent to og:image - */ - url: string; - /** - * - * https:// URL for the image - */ - secureUrl?: string; - /** - * MIME type of the image. - */ - type?: 'image/jpeg' | 'image/gif' | 'image/png'; - /** - * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - */ - width?: '1200' | string | number; - /** - * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - */ - height?: '630' | string | number; - /** - * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. - */ - alt?: string; - }>; - /** - * Equivalent to og:image - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageUrl: string; - /** - * - * https:// URL for the image - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageSecureUrl: string; - /** - * MIME type of the image. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageType: 'image/jpeg' | 'image/gif' | 'image/png'; - /** - * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageWidth: '1200' | string | number; - /** - * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageHeight: '630' | string | number; - /** - * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#images - */ - ogImageAlt: string; - /** - * The URL for an audio file to accompany this object. - * - * @see https://ogp.me/#optional - */ - ogAudio: string | Arrayable<{ - /** - * Equivalent to og:audio - */ - url: string; - /** - * Secure URL for the audio. Include this even if you set the secure URL in og:audio. - */ - secureUrl?: string; - /** - * MIME type of the audio. - */ - type?: 'audio/mpeg' | 'audio/ogg' | 'audio/wav'; - }>; - /** - * Equivalent to og:audio - * - * @see https://ogp.me/#optional - */ - ogAudioUrl: string; - /** - * Secure URL for the audio. Include this even if you set the secure URL in og:audio. - * - * @see https://ogp.me/#optional - */ - ogAudioSecureUrl: string; - /** - * MIME type of the audio. - * - * @see https://ogp.me/#optional - */ - ogAudioType: 'audio/mpeg' | 'audio/ogg' | 'audio/wav'; - /** - * Your Facebook app ID. - * - * In order to use Facebook Insights you must add the app ID to your page. - * Insights lets you view analytics for traffic to your site from Facebook. Find the app ID in your App Dashboard. - * - * @see https://developers.facebook.com/docs/sharing/webmasters#basic - */ - fbAppId: string | number; - /** - * The card type - * - * Used with all cards - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterCard: 'summary' | 'summary_large_image' | 'app' | 'player'; - /** - * Title of content (max 70 characters) - * - * Used with summary, summary_large_image, player cards - * - * Same as `og:title` - * - * @maxLength 70 - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterTitle: string; - /** - * Description of content (maximum 200 characters) - * - * Used with summary, summary_large_image, player cards. - * - * Same as `og:description` - * - * @maxLength 200 - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterDescription: string; - /** - * URL of image to use in the card. - * Images must be less than 5MB in size. - * JPG, PNG, WEBP and GIF formats are supported. - * Only the first frame of an animated GIF will be used. SVG is not supported. - * - * Used with summary, summary_large_image, player cards - * - * Same as `og:image`. - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterImage: string | Arrayable<{ - /** - * Equivalent to twitter:image - */ - url: string; - /** - * MIME type of the image. - * @deprecated Twitter removed this property from their card specification. - */ - type?: 'image/jpeg' | 'image/gif' | 'image/png'; - /** - * Width of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - * @deprecated Twitter removed this property from their card specification. - */ - width?: '1200' | string | number; - /** - * Height of image in pixels. Specify height and width for your image to ensure that the image loads properly the first time it's shared. - * @deprecated Twitter removed this property from their card specification. - */ - height?: '630' | string | number; - /** - * A description of what is in the image (not a caption). If the page specifies an og:image, it should specify og:image:alt. - */ - alt?: string; - }>; - /** - * The width of the image in pixels. - * - * Note: This is not officially documented. - * - * Same as `og:image:width` - * - * @deprecated Twitter removed this property from their card specification. - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterImageWidth: string | number; - /** - * The height of the image in pixels. - * - * Note: This is not officially documented. - * - * Same as `og:image:height` - * - * @deprecated Twitter removed this property from their card specification. - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterImageHeight: string | number; - /** - * The type of the image. - * - * Note: This is not officially documented. - * - * Same as `og:image:type` - * - * @deprecated Twitter removed this property from their card specification. - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterImageType: 'image/jpeg' | 'image/gif' | 'image/png'; - /** - * A text description of the image conveying the essential nature of an image to users who are visually impaired. - * Maximum 420 characters. - * - * Used with summary, summary_large_image, player cards - * - * Same as `og:image:alt`. - * - * @maxLength 420 - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/abouts-cards - */ - twitterImageAlt: string; - /** - * The @username of website. Either twitter:site or twitter:site:id is required. - * - * Used with summary, summary_large_image, app, player cards - * - * @example @harlan_zw - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterSite: string; - /** - * Same as twitter:site, but the user’s Twitter ID. Either twitter:site or twitter:site:id is required. - * - * Used with summary, summary_large_image, player cards - * - * @example 1296047337022742529 - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterSiteId: string | number; - /** - * The @username who created the pages content. - * - * Used with summary_large_image cards - * - * @example harlan_zw - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterCreator: string; - /** - * Twitter user ID of content creator - * - * Used with summary, summary_large_image cards - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterCreatorId: string | number; - /** - * HTTPS URL of player iframe - * - * Used with player card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterPlayer: string; - /** - * - * Width of iframe in pixels - * - * Used with player card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterPlayerWidth: string | number; - /** - * Height of iframe in pixels - * - * Used with player card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterPlayerHeight: string | number; - /** - * URL to raw video or audio stream - * - * Used with player card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterPlayerStream: string; - /** - * Name of your iPhone app - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppNameIphone: string; - /** - * Your app ID in the iTunes App Store - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppIdIphone: string; - /** - * Your app’s custom URL scheme (you must include ”://” after your scheme name) - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppUrlIphone: string; - /** - * Name of your iPad optimized app - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppNameIpad: string; - /** - * Your app ID in the iTunes App Store - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppIdIpad: string; - /** - * Your app’s custom URL scheme - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppUrlIpad: string; - /** - * Name of your Android app - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppNameGoogleplay: string; - /** - * Your app ID in the Google Play Store - * - * Used with app card - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppIdGoogleplay: string; - /** - * Your app’s custom URL scheme - * - * @see https://developer.twitter.com/en/docs/twitter-for-websites/cards/overview/markup - */ - twitterAppUrlGoogleplay: string; - /** - * Top customizable data field, can be a relatively short string (ie “$3.99”) - * - * Used by Slack. - * - * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl - */ - twitterData1: string; - /** - * Customizable label or units for the information in twitter:data1 (best practice: use all caps) - * - * Used by Slack. - * - * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl - */ - twitterLabel1: string; - /** - * Bottom customizable data field, can be a relatively short string (ie “Seattle, WA”) - * - * Used by Slack. - * - * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl - */ - twitterData2: string; - /** - * Customizable label or units for the information in twitter:data2 (best practice: use all caps) - * - * Used by Slack. - * - * @see https://api.slack.com/reference/messaging/link-unfurling#classic_unfurl - */ - twitterLabel2: string; - /** - * Indicates a suggested color that user agents should use to customize the display of the page or - * of the surrounding user interface. - * - * @see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name - * @example `#4285f4` or `{ color: '#4285f4', media: '(prefers-color-scheme: dark)'}` - */ - themeColor: string | Arrayable<{ - /** - * A valid CSS color value that matches the value used for the `theme-color` CSS property. - * - * @example `#4285f4` - */ - content: string; - /** - * A valid media query that defines when the value should be used. - * - * @example `(prefers-color-scheme: dark)` - */ - media: '(prefers-color-scheme: dark)' | '(prefers-color-scheme: light)' | string; - }>; - /** - * Sets whether a web application runs in full-screen mode. - */ - mobileWebAppCapable: 'yes'; - /** - * Sets whether a web application runs in full-screen mode. - * - * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html - */ - appleMobileWebAppCapable: 'yes'; - /** - * Sets the style of the status bar for a web application. - * - * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html - */ - appleMobileWebAppStatusBarStyle: 'default' | 'black' | 'black-translucent'; - /** - * Make the app title different from the page title. - */ - appleMobileWebAppTitle: string; - /** - * Promoting Apps with Smart App Banners - * - * @see https://developer.apple.com/documentation/webkit/promoting_apps_with_smart_app_banners - */ - appleItunesApp: string | { - /** - * Your app’s unique identifier. - */ - appId: string; - /** - * A URL that provides context to your native app. - */ - appArgument?: string; - }; - /** - * Enables or disables automatic detection of possible phone numbers in a webpage in Safari on iOS. - * - * @see https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariHTMLRef/Articles/MetaTags.html - */ - formatDetection: 'telephone=no'; - /** - * Tile image for windows. - * - * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) - */ - msapplicationTileImage: string; - /** - * Tile colour for windows - * - * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) - */ - msapplicationTileColor: string; - /** - * URL of a config for windows tile. - * - * @see https://learn.microsoft.com/en-us/previous-versions/windows/internet-explorer/ie-developer/platform-apis/dn320426(v=vs.85) - */ - msapplicationConfig: string; - contentSecurityPolicy: string | Partial<{ - childSrc: string; - connectSrc: string; - defaultSrc: string; - fontSrc: string; - imgSrc: string; - manifestSrc: string; - mediaSrc: string; - objectSrc: string; - prefetchSrc: string; - scriptSrc: string; - scriptSrcElem: string; - scriptSrcAttr: string; - styleSrc: string; - styleSrcElem: string; - styleSrcAttr: string; - workerSrc: string; - baseUri: string; - sandbox: string; - formAction: string; - frameAncestors: string; - reportUri: string; - reportTo: string; - requireSriFor: string; - requireTrustedTypesFor: string; - trustedTypes: string; - upgradeInsecureRequests: string; - }>; - contentType: 'text/html; charset=utf-8'; - defaultStyle: string; - xUaCompatible: 'IE=edge'; - refresh: string | { - seconds: number | string; - url: string; - }; - /** - * A comma-separated list of keywords - relevant to the page (Legacy tag used to tell search engines what the page is about). - * @deprecated the "keywords" metatag is no longer used. - * @see https://web.dev/learn/html/metadata/#keywords - */ - keywords: string; -} -type MetaFlatNullable = { - [K in keyof MetaFlat]: MetaFlat[K] | null; -}; -export type MetaFlatInput = Partial; -export {}; diff --git a/src/swagger/index.d.ts b/src/swagger/index.d.ts deleted file mode 100644 index 1a0fed3..0000000 --- a/src/swagger/index.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { OpenAPIV3 } from 'openapi-types'; -export declare const SwaggerUIRender: (info: OpenAPIV3.InfoObject, version: string, theme: string | { - light: string; - dark: string; -}, stringifiedSwaggerOptions: string, autoDarkMode?: boolean) => string; diff --git a/src/swagger/types.d.ts b/src/swagger/types.d.ts deleted file mode 100644 index a82f415..0000000 --- a/src/swagger/types.d.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Swagger UI type because swagger-ui doesn't export an interface so here's copy paste - * - * @see swagger-ui/index.d.ts - **/ -export interface SwaggerUIOptions { - /** - * URL to fetch external configuration document from. - */ - configUrl?: string | undefined; - /** - * REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface. - */ - dom_id?: string | undefined; - /** - * A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them - */ - spec?: { - [propName: string]: any; - } | undefined; - /** - * The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used. - */ - url?: string | undefined; - /** - * An array of API definition objects ([{url: "", name: ""},{url: "", name: ""}]) - * used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed. - * Names and URLs must be unique among all items in this array, since they're used as identifiers. - */ - urls?: Array<{ - url: string; - name: string; - }> | undefined; - /** - * The name of a component available via the plugin system to use as the top-level layout - * for Swagger UI. - */ - layout?: string | undefined; - /** - * A Javascript object to configure plugin integration and behaviors - */ - pluginsOptions?: PluginsOptions; - /** - * An array of plugin functions to use in Swagger UI. - */ - plugins?: SwaggerUIPlugin[] | undefined; - /** - * An array of presets to use in Swagger UI. - * Usually, you'll want to include ApisPreset if you use this option. - */ - presets?: SwaggerUIPlugin[] | undefined; - /** - * If set to true, enables deep linking for tags and operations. - * See the Deep Linking documentation for more information. - */ - deepLinking?: boolean | undefined; - /** - * Controls the display of operationId in operations list. The default is false. - */ - displayOperationId?: boolean | undefined; - /** - * The default expansion depth for models (set to -1 completely hide the models). - */ - defaultModelsExpandDepth?: number | undefined; - /** - * The default expansion depth for the model on the model-example section. - */ - defaultModelExpandDepth?: number | undefined; - /** - * Controls how the model is shown when the API is first rendered. - * (The user can always switch the rendering for a given model by clicking the - * 'Model' and 'Example Value' links.) - */ - defaultModelRendering?: 'example' | 'model' | undefined; - /** - * Controls the display of the request duration (in milliseconds) for "Try it out" requests. - */ - displayRequestDuration?: boolean | undefined; - /** - * Controls the default expansion setting for the operations and tags. - * It can be 'list' (expands only the tags), 'full' (expands the tags and operations) - * or 'none' (expands nothing). - */ - docExpansion?: 'list' | 'full' | 'none' | undefined; - /** - * If set, enables filtering. - * The top bar will show an edit box that you can use to filter the tagged operations that are shown. - * Can be Boolean to enable or disable, or a string, in which case filtering will be enabled - * using that string as the filter expression. - * Filtering is case sensitive matching the filter expression anywhere inside the tag. - */ - filter?: boolean | string | undefined; - /** - * If set, limits the number of tagged operations displayed to at most this many. - * The default is to show all operations. - */ - maxDisplayedTags?: number | undefined; - /** - * Apply a sort to the operation list of each API. - * It can be 'alpha' (sort by paths alphanumerically), - * 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works). - * Default is the order returned by the server unchanged. - */ - operationsSorter?: SorterLike | undefined; - /** - * Controls the display of vendor extension (x-) fields and values for Operations, - * Parameters, Responses, and Schema. - */ - showExtensions?: boolean | undefined; - /** - * Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields - * and values for Parameters. - */ - showCommonExtensions?: boolean | undefined; - /** - * Apply a sort to the tag list of each API. - * It can be 'alpha' (sort by paths alphanumerically) - * or a function (see Array.prototype.sort() to learn how to write a sort function). - * Two tag name strings are passed to the sorter for each pass. - * Default is the order determined by Swagger UI. - */ - tagsSorter?: SorterLike | undefined; - /** - * When enabled, sanitizer will leave style, class and data-* attributes untouched - * on all HTML Elements declared inside markdown strings. - * This parameter is Deprecated and will be removed in 4.0.0. - * @deprecated - */ - useUnsafeMarkdown?: boolean | undefined; - /** - * Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition. - */ - onComplete?: (() => any) | undefined; - /** - * Set to false to deactivate syntax highlighting of payloads and cURL command, - * can be otherwise an object with the activate and theme properties. - */ - syntaxHighlight?: false | { - /** - * Whether syntax highlighting should be activated or not. - */ - activate?: boolean | undefined; - /** - * Highlight.js syntax coloring theme to use. (Only these 6 styles are available.) - */ - theme?: 'agate' | 'arta' | 'idea' | 'monokai' | 'nord' | 'obsidian' | 'tomorrow-night' | undefined; - } | undefined; - /** - * Controls whether the "Try it out" section should be enabled by default. - */ - tryItOutEnabled?: boolean | undefined; - /** - * This is the default configuration section for the the requestSnippets plugin. - */ - requestSnippets?: { - generators?: { - [genName: string]: { - title: string; - syntax: string; - }; - } | undefined; - defaultExpanded?: boolean | undefined; - /** - * e.g. only show curl bash = ["curl_bash"] - */ - languagesMask?: string[] | undefined; - } | undefined; - /** - * OAuth redirect URL. - */ - oauth2RedirectUrl?: string | undefined; - /** - * MUST be a function. Function to intercept remote definition, - * "Try it out", and OAuth 2.0 requests. - * Accepts one argument requestInterceptor(request) and must return the modified request, - * or a Promise that resolves to the modified request. - */ - requestInterceptor?: ((a: Request) => Request | Promise) | undefined; - /** - * MUST be a function. Function to intercept remote definition, - * "Try it out", and OAuth 2.0 responses. - * Accepts one argument responseInterceptor(response) and must return the modified response, - * or a Promise that resolves to the modified response. - */ - responseInterceptor?: ((a: Response) => Response | Promise) | undefined; - /** - * If set to true, uses the mutated request returned from a requestInterceptor - * to produce the curl command in the UI, otherwise the request - * beforethe requestInterceptor was applied is used. - */ - showMutatedRequest?: boolean | undefined; - /** - * List of HTTP methods that have the "Try it out" feature enabled. - * An empty array disables "Try it out" for all operations. - * This does not filter the operations from the display. - */ - supportedSubmitMethods?: SupportedHTTPMethods[] | undefined; - /** - * By default, Swagger UI attempts to validate specs against swagger.io's online validator. - * You can use this parameter to set a different validator URL, - * for example for locally deployed validators (Validator Badge). - * Setting it to either none, 127.0.0.1 or localhost will disable validation. - */ - validatorUrl?: string | undefined; - /** - * If set to true, enables passing credentials, as defined in the Fetch standard, - * in CORS requests that are sent by the browser. - * Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163) - * - as a result, you will have to rely on browser-supplied - * cookies (which this setting enables sending) that Swagger UI cannot control. - */ - withCredentials?: boolean | undefined; - /** - * Function to set default values to each property in model. - * Accepts one argument modelPropertyMacro(property), property is immutable - */ - modelPropertyMacro?: ((propName: Readonly) => any) | undefined; - /** - * Function to set default value to parameters. - * Accepts two arguments parameterMacro(operation, parameter). - * Operation and parameter are objects passed for context, both remain immutable - */ - parameterMacro?: ((operation: Readonly, parameter: Readonly) => any) | undefined; - /** - * If set to true, it persists authorization data and it would not be lost on browser close/refresh - */ - persistAuthorization?: boolean | undefined; -} -interface PluginsOptions { - /** - * Control behavior of plugins when targeting the same component with wrapComponent.
- * - `legacy` (default) : last plugin takes precedence over the others
- * - `chain` : chain wrapComponents when targeting the same core component, - * allowing multiple plugins to wrap the same component - * @default 'legacy' - */ - pluginLoadType?: PluginLoadType; -} -type PluginLoadType = 'legacy' | 'chain'; -type SupportedHTTPMethods = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace'; -type SorterLike = 'alpha' | 'method' | { - (name1: string, name2: string): number; -}; -interface Request { - [prop: string]: any; -} -interface Response { - [prop: string]: any; -} -/** - * See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/ - */ -interface SwaggerUIPlugin { - (system: any): { - statePlugins?: { - [stateKey: string]: { - actions?: Indexable | undefined; - reducers?: Indexable | undefined; - selectors?: Indexable | undefined; - wrapActions?: Indexable | undefined; - wrapSelectors?: Indexable | undefined; - }; - } | undefined; - components?: Indexable | undefined; - wrapComponents?: Indexable | undefined; - rootInjects?: Indexable | undefined; - afterLoad?: ((system: any) => any) | undefined; - fn?: Indexable | undefined; - }; -} -interface Indexable { - [index: string]: any; -} -export {}; diff --git a/src/types.d.ts b/src/types.d.ts deleted file mode 100644 index 5dc906c..0000000 --- a/src/types.d.ts +++ /dev/null @@ -1,96 +0,0 @@ -import type { OpenAPIV3 } from 'openapi-types'; -import type { ReferenceConfiguration } from './scalar/types'; -import type { SwaggerUIOptions } from './swagger/types'; -export interface ElysiaSwaggerConfig { - /** - * Customize Swagger config, refers to Swagger 2.0 config - * - * @see https://swagger.io/specification/v2/ - */ - documentation?: Omit, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>; - /** - * Choose your provider, Scalar or Swagger UI - * - * @default 'scalar' - * @see https://github.com/scalar/scalar - * @see https://github.com/swagger-api/swagger-ui - */ - provider?: 'scalar' | 'swagger-ui'; - /** - * Version to use for Scalar cdn bundle - * - * @default 'latest' - * @see https://github.com/scalar/scalar - */ - scalarVersion?: string; - /** - * Optional override to specifying the path for the Scalar bundle - * - * Custom URL or path to locally hosted Scalar bundle - * - * Lease blank to use default jsdeliver.net CDN - * - * @default '' - * @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js' - * @example '/public/standalone.js' - * @see https://github.com/scalar/scalar - */ - scalarCDN?: string; - /** - * Scalar configuration to customize scalar - *' - * @see https://github.com/scalar/scalar - */ - scalarConfig?: ReferenceConfiguration; - /** - * Version to use for swagger cdn bundle - * - * @see unpkg.com/swagger-ui-dist - * - * @default 4.18.2 - */ - version?: string; - /** - * Determine if Swagger should exclude static files. - * - * @default true - */ - excludeStaticFile?: boolean; - /** - * The endpoint to expose Swagger - * - * @default '/swagger' - */ - path?: Path; - /** - * Paths to exclude from Swagger endpoint - * - * @default [] - */ - exclude?: string | RegExp | (string | RegExp)[]; - /** - * Options to send to SwaggerUIBundle - * Currently, options that are defined as functions such as requestInterceptor - * and onComplete are not supported. - */ - swaggerOptions?: Omit, 'dom_id' | 'dom_node' | 'spec' | 'url' | 'urls' | 'layout' | 'pluginsOptions' | 'plugins' | 'presets' | 'onComplete' | 'requestInterceptor' | 'responseInterceptor' | 'modelPropertyMacro' | 'parameterMacro'>; - /** - * Custom Swagger CSS - */ - theme?: string | { - light: string; - dark: string; - }; - /** - * Using poor man dark mode 😭 - */ - autoDarkMode?: boolean; - /** - * Exclude methods from Swagger - */ - excludeMethods?: string[]; - /** - * Exclude tags from Swagger or Scalar - */ - excludeTags?: string[]; -} diff --git a/src/utils.d.ts b/src/utils.d.ts deleted file mode 100644 index 6c2ff66..0000000 --- a/src/utils.d.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { HTTPMethod, LocalHook } from 'elysia'; -import { type TSchema } from '@sinclair/typebox'; -import type { OpenAPIV3 } from 'openapi-types'; -export declare const toOpenAPIPath: (path: string) => string; -export declare const mapProperties: (name: string, schema: TSchema | string | undefined, models: Record) => { - description: any; - examples: any; - schema: any; - in: string; - name: string; - required: any; -}[]; -export declare const capitalize: (word: string) => string; -export declare const generateOperationId: (method: string, paths: string) => string; -export declare const registerSchemaPath: ({ schema, path, method, hook, models }: { - schema: Partial; - contentType?: string | string[]; - path: string; - method: HTTPMethod; - hook?: LocalHook; - models: Record; -}) => void; -export declare const filterPaths: (paths: Record, docsPath: string, { excludeStaticFile, exclude }: { - excludeStaticFile: boolean; - exclude: (string | RegExp)[]; -}) => Record; From a9f07368ae454a8a0223c483c55433512e5eb222 Mon Sep 17 00:00:00 2001 From: Michael Kassabov <35264484+Mkassabov@users.noreply.github.com> Date: Sat, 31 Aug 2024 20:55:21 -0400 Subject: [PATCH 3/6] move to pathe to support working in environments without "path" --- bun.lockb | Bin 373111 -> 373135 bytes package.json | 3 ++- src/utils.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/bun.lockb b/bun.lockb index 833d9fec569df120aded5db42ee865e5469b6ceb..6ebdda90d5a07b3b3c6b7a6375bddd3514979631 100755 GIT binary patch delta 51824 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 delta 52074 zcmeFa2XqzH+xC4*0y$`a&_jqcsTw*Yl;nu05HNJ52@)Vc=nz^^Ot2C#A`Y;rA~rxo zu>vY$LkSuYE2yYg5KvUCAfn&zn%M`UFa95&=Xuw9*ZP*TxX!ijYu~%?*|TR(&amLK zD!+VM<@WCWFBkS`yYTi}c`*T>FXZ#(=S&+uB{L_>cM`i2_N447nNzcJeB)>N+~fVA z&vy>`)X`ZJvPb6nUa)pC9E*O0baC*gaTA9r!K!GVuL^b{Tpk`+%1htF@{G~h!$$|Dgc4gS-IDfR%I@V@#;7}dqVc4%$%HDbk(^lA4<0xE(=ex zdP*6u%nM-E?R+wk|1a45%ioPHeup?p?FxB^yzbNLXblUf-p$qO;zaBoa1FSHwd3Jw#21P1kQazd5z`ymZ!rEOa3sJ z-p%iBxt-dlwS2MVW{y9)L0*NX zULQwU{sh(uojDrzd^B9gL*L;$SncQ?hc#Wlxyu^Q~n(>Y*3Fnqp%u z2VqsOH>@d`2CKP^Em!e)VE$jv>e9?~w^v(x{S?XQhvwekCMA3EEELmKv3syJsYZ^= zoH{jYgzq!!H^K3jsv18m#T&cGgq+{jtA2H-d8w*-MeV$al{tLklnLaOImXS>cVBy- zuR3`ygjMMsi$)ZEJ6a)hQcb&5+fWkEeQd{1(>4U4*VWMkek&Y}z;` zKi>=F7lggWjmw&@die6N)d}M)PluJ!h^(whN!dBR={DWS>bmc~a82 ziNjgQ;q39*Q+>mo=A~=;yE?r}SIuj2iC0!UtdSW$apKtQET1n1I}W=WR#=T5YxN89)5M^pV-))P*yz^c)7kELdC(CQchQny`}|YLGYECV#QNSL~1h zUibHfHHF&3TB2_rXotu009eJFL0+%d!B4}!nS3;?55gLy$zHYcPhREu|1{XURP2Mf z1mxw9B~S~;?pJ#iSclyldo`?nJ2lihnI43zV1H)q7vS31i(wUfEgTQ;V8PL~)FavydFA*t)VM`^VFhg1XSQ~ z>)6BE7sIjW^T<>coRm2wCyRA;2DVy$A@NE-3|pse+3B8N6s(?G39FzkeZ8sPdxlq$ zG;GDU=u7{r>o1(?Wmu0071%h}OLzdi683Ib=R|4ry6{idE_IF9qVHyT6)8`8Rix&% zUV-B#&KQ?HYV_1EICoTzpLm^f$FS8yU-Y5>74bGg99;T3FM|=-YVjaA1dqrXInBq> z1Aooxsa(FaVeN=5KNhQ`5n1E2Kkw}2*KCeg@mjD7x)48AtlITnMJr-!vhHq(pbGp( z!5Z6}$v_F3P_SD3HM;C_PF!r&?2AxU{{=UB)w=O!kEg?`#?-9wljtJfWw&_!xENOd zrNU}PgyomSu4OWvDLp4^{PZkl$D}C}$4{D?O3#?NLaZKgjLrbusY#V zxD5R6eCPex`qh8D)vH{;LeD+j^3g)4dfD^p+e4Rxq2`!^V{6#)%0_O3$e>%Ye9P%TLso4K?67jt_Huh)*G6>>%0~}jxGJ?1zyF* zuJ;Oxe!!dbU!ZF!UWGLS-i6D-eKz=ft<{pqeaxc>a<~*$mrfcxO0(7%*|rSt=*3TP z)|XH2c+G>}h_=UD(=z#CuiwUMlQVIouOYUI$Em8bIpZ>OSnPdEn20)UDxqtRjmjRCH5Gko<`h1CzRk|_ zaml50KbqxpevWHb`|oZEBDbuwoL&_g_-8t~6>2uRit|F%?E8#2%vlqrXHOV8(bofA z(;#wJc#-pbg{pBaP}Mc{Y;2Xs4p-#H@W9@)@EPJk&{nZu{Dd;GbZbEMj3p+O{4DG-yPv4oxQfycx+F655NCDr*=Y9|3ar%LbAWCQ;-n$XE{ZL zUv>O78U%jt>@==%S=@J|QKk36s?^iYoEo=0Rz7ibTuhT2o<4s-olmMhHnU0JbDVSA zbqMV9IbGYO`IDT2cHz)szc=w?oP{r@IHC4ozvHCySAsqN3=ydIn z=5Oj0ba3my=RHm+m53;(YigQ5>=dMiLytxKe0`~b-`O)H*i=E;j?L)yBpDzVH=d7BgJ z91hiG$>@qk|FumHjmJ_i`;~$JF{cO*&G9o;8iaK@9*h)4ti@_(3IqNQF@!V@0cT-{ zyXSGNly<)&t(-I|ySuXaPrPKS;Y^0K>vW!sB=oRC+6SkAS6O5q>j z6l8=$E3wz`HI}Nw1fkFN zVW}C>nikQqb-70B?1&D#p~6iaVhE^H-SawB=6rA7xV;m)97|P*ax=cwTAt@?SZX1K z_Dl|yspqxHJu#y%!(!rhY#+)eq?UXA@FbQhN)EG9{Pmn&{nDa;Z9w@>aliJ_T^cg8 z-O!@bp-*h6LsX+kreIL%jlDmK#=3E-RTVH%yoAXhDZ1In*61 z-OX(eE3U!n=~>LJFR|FFI6vqtEg!j4ACEmX?+>ZozL_PbPbhWUj)Y(P95ECw+7{w4EjGD*U|DDuK>WFZ!H?y^=#?v2T*##fW@4$)s3Sx6+u3k z$H(~YDcUg(bd6rmxQ6^_8I!)6Q!qK~ALtbE`J@w?5)OQSsnc~z8XZ|M zCG5|0iul~&gmS|EQcgOb?VN&~aOg(z_EvL7&A-bDO$|qPP6&NR zh=r6;V@{dN6belu%UzFPH|r5d@8-;#5st#{;-)Bnd1O9wC5i4W%PCH4 zAH9fBij&c2$1$PN1^J4Lzd_Ih}|%9XNba^OG@r|YaV^}(!g=mt&!4Tjg@ zuVC4^%BHlslYULu-_I%F^ERjGnsDgTUZ=g881;L5rMr{RKg21xHXM2o%~qBHJcOkl zW^J019Gct5+cdEPaj{z7*Be}KT2*8L?C-Wjd&;p`%pTgM3Aho9#XwUoEje@$OXoft z30B5NS9(*+J%vIyVJRJx3C~xshItmbH0tMd0+W*~`Cu&02YFJ@RW{BmVUM-EK8dDU znrhypZj04S;}*&$#B8M=z0y)*Fc_x%SU2;@1H84*Yua`!b&0nzIEgjH%}z~Q!HlMl zbdF;k{gV|Qg~bW1?&6N2 z;$ZT0SKew=V#4YCZpIpV+B%3e8jB9#JRW+rd(o%K4-%T^d9gm@e|oM&SIuN?bu-Q( z#L(J`z9Rj!RhQuybJ{AzVu(D?=+CeQx;Y#eUa~SBN0dyn0qaaf&&ev0Ln!l4)@CfS z^m6ydJE4NG|0*ZFARM}VWXbF2Cs-Zu^A@09qr5Av+g+heShg+9l6v%sHxtyc1F-Bh zleo25I*;6CG5RYk@BXI(&7JDi;*yjYj5E#uj-ag|8T1~@ZoqTIdI^g>RC%lm$MK*& zVg>FU=j$`fNAW>0S@jo9Oe=0d}W#(iELE&A-RmFr(ck!i-mUw~B>?RCTWxFV6LHck_#0j^#Em`UoMf(QRgN zV!DmKmyk+xMEnOU2`vqWj#>{+ zY8HZ)vvpl6?$F+UjZ;MIHf#-{w{q6F&g(6&&J(aSn;5vvWdEa1(Xw!8V4k-fVaP5? zj-H2==47O{_wRHHmWM;X$|eQVgB|U)PH06qbTHprFFfrMrvUA?IbPqmTHuX2PMMWy z(P`IH6(@FO`_NoMo!w?EY?mDP;Cg4*%Cu0`8@zRw{nv=(&_JyAxNvb~o4FV(jKvix zH97G14bHArY5rPH=+3Zzu#gQR7H+zi=2v_87KMt@7E=&o?|gt@xbYii$5h-V!< zx;aY7o5FEplKr)vqBY@AW`Q@e7%Q%EtFT&-le^;#eT?Oet?E*Bp3m2WIA$_C)wx)z z0oPlWjlEc2tr>BD4X5CqaOkS}r#E_dEXUFec3Tqq983P(bNrDKb*sC3d%uGMY}Wg< zk6uEE`__BI(HG)HE6E{P=*>Ik=$PbC7M3lM3&(m{nq#5kgft&GZ#duD+~&1_Tkk%} z{s~Ue{o&9Cv^ID!j@&ODxXtOhHZ7E}AkuJ62X4=P3t;DE!DihE7XV3 znJZ->7QMKpeP}x&^;WdI>;;$Exe;nlNTaCHPl>`vb7Irl`-_~=L*dXl%Og`GjjgVWY=PIv? z3=pTnJS+_cgUYiJZXX{BhkD)VEvHn9VSflK#r4orxPx~(yB79;+D^SA~8l zF&N&0q>IR;T98p2&#CIJC_yT|@SneTK zhIF}`A>Hd9VinNFGylX&Pd~UvNtQoT#ZAWuxQAH&9c=lUt3K7}VxWRA1xm=wbPuug zbkF<~%a2pl_3vim#q#fN^&VCiOYd#%-d_7l2$2Zuc!jmas&PMSmt<9-KTyVlY`j?d zRn{)a8qx_?&x-{9AMudWH7QwvC~7yw*6?hsp_*yq#nFH*rrSf29u@n|;9CBw0|@!9 z*9V7K9dm=_8(|${b@0tV@wWgSV(A4y`aGb+WwWi8zh-Tj^FatKlEG1u{pJ+4Fz43x z$LOP4UR4}eW!>Zd0T26M7p#tW$g980F@#xb+zw);@%c<*9e>q4_1jZ2ImEJ`k-;IZ z13r?$QIh5NiA0W1+|RSKCj4hW)%hGK-9exO_GUr}!FBwSNI4z`N_Yh5D9IYhA6(UA z`Tb~ZvC{u+ZL#8ivGy5m@12srT8ER?L97Y>hqcefOx(KunsF*8pv*1%%_`))QkZ3| zESB53u%uY4|0kAzIqN4@u)O6s%M~PYlw?U2{VbBopt5!3`*QaXt70{*o@n*6vER%- z-_DQP*1e8(7b{qok1B9mSjDx6m3>E8y>}_hKi_5guq?kW))s4ibc3a5SndI9x#;V+ z^MinUoQ*XP1{1H0hFZUpEGd%@@h}@NR&Y2UDrmITOS1AC>-Xj-0W4SW3Yvf|X`oWt@u(d?1gEm?BN36S8**$LUlC12Xw0cRFw3!d_7Ctn>&%o!xJFQ-1^|hE<`f5!0ILtNJRDXAqhK8+S^i_t)#cM+xy-bFf91Tt3bIx6TsRiK*(MZAzXeu> zZ?n2sEm{IgTFQqSunLy{omRgKR>khIe80zezV!suH4nqeXfvz~o`IFYHkg0D?fS5+ z_?K+_4$CjY%I7ufCsu*Gt$lW8zp4mzcRuo7W++MBY)NAQoi6r$qw7- zk}T;ftBd_+Kgse`onuxOEBJ$=;UBI2vqTQD&dERE8gPOV|Ake2O?0KNWx2NXI}3ZO zRLKaHRo5ms8><2r+W51v(l@aMH-%+4w{{E5EnyukgOsmu8$QHsZG>1YXlLz`tPDF? z{cK#B_+G@zr7s`l;NjN)|6sTOt%H0^L=df1J?drB8OP{98tXH|JOF6Buo014^`wl z%g109_=6%XEB;3t|C80l^83ZwXJbYE%11~UoRnpbCUG0_Tav$;snNtA6Q?Ey)gH@! zliGy!LN!0v$|YF^mbJQA`Idv_U)k#aiM7loTK~E>{rQ&b+4OLp+p-2$_$RCiBw2s4 z+S&}3)XM5&tt+Y4F3D<0n$^>yOuMH37Vb&g$!cOX{!&=Jms$NkvHZJOzmhDytJTHY za9sgQ?+0s4u2Lr`kcprIhrv3;vWHv8(bmqk@n>WCkFoJ$SSe-X3 zib<=8YY=pZWnXJ~Hmn-w*?4hH>^p7zYODVfmg_y%|1{GD?jb?@flbyHt0IqBTdWp6 zY3+Z)ev{PP-yl!hiDzxHv#~D1yNFkDuUbE`g1fCPRzK{qwpjje!jj&#x>$aDtqp5g z_>!DM3dRul6Sm}Ggno9j(6)+IiQIh39$m(MG4Y{0v${1#Oq>ae7_IOws=fFC| zDsY;$OS0dzZpnzcQnkR8}XWrcpX+pziIV%Ebq1P?^}DnwLiA@r`G=5@3n%{cKEpo-frprdda^mYGYfU6PgIW!As5jTcMr z0_$qo)9NKz73yPkvHbhGHsh~=&fZMxa5h#K54Qt)9i7_@_EPWiThbq^@ z72(CODzHSX70nRPAyx(}VAc3etDlY4L-$#~b=FU;bQ@qvkMN;_9+hEP_G8#8XEW?x zjGiW-gwI-j9_F9#6>IN;)sok&y&Kl--D~X+EPn*6VqaMM5Ufdh6ju6gVI5*sR&QY#`VZh1vju6mt+;#(8f20HFQaqn_2&}vGQwS<0D)z5-7Cl$9YJ^drY)R~v$4J^Im@?SI#vGs zHJA!&0Nf6-+z8fjHdcFhir^k%Rq&sG`{kAYf8pyduL2z)Rp?@%hFl8NkjsD$vGjBq z9O<@vSCC^@Anpcqh~2J-rS||j#L~4B(ea;Het-AvS6)9ix%YLL3izLX{Z;ZyCuR8$ z?Yw{L_&@T~NjF8c;W!(Y(oFmJ>o2!|HoBkxt*_ID{ZA8~PNy07@7G^m&;R@Nm)8&f ze*NY3!@pmDu{!A5Rq|^ub&YIY#={o{?gZAJb}_y;oq;nyngui>#u*m{`&XpFMSQB zRpH;SzyAID>;KPRf2IDfzWy?~SNf-#OZ)k|nUsG1ShI?dKWGm3^Y=4{BxLkQh&Idm zBP{KYa7sdHlRf~U%K(Ip0}w*ygoNV~1`k9y$E+WSux=ni>>z|#Ghh%x|3QSdNhoJR zS0Th)g)sgqggCQR!WIdMgApp4?7;}52O|_qsBGegAXFQIFnb6>RZ}Ekr-Y=d5#mkm z)d(}MM%XVQ!896*&~PY1;ZTG`vroca38|R~HBCV#!rV-RqY`SHw!;uoh9RsPhEUfW zmT*Wy#&CpsX4!CrrNa?UNoZiwM<8?=fv|A|LL+lR!f^?Mvk)#Y>$4EnWg*0lL`X6N zMk4ediLgyVQxh765Hkv4{3wLxW~+oP5)wxvv^3eH5k`+jD3;LL#AhQ^%SM=;jgV}L zBAry39h@I13@$oRDx_!r*HV#+vol zAgsFvA@*8?@n*oa2>q``*d}433C%``nT;@hHo|1HRl*htiPs_InC$BiMqh_eEMc07 z&qJt|hcG)2VTLJ^uv0=(K0>a^%}1D-kFa0DHKx%V`ubWkM>N~)6J2MTUk~M(0#Uv> zAev*^-T+;17K&~#hebD#@S7+ru+EMc*UpNCLw9>VN-2*wmi z*eM}tKEg7SJ0D@@e1!cHR+vV&A~d`eq3~9ORc4=ry%JIj5muXmLWH@62uCHXF>P-{ zNVyGR)olp(n8OkdNyu1$aGzPW0Ac9@gi{jMn)KTdy4;Si@pgpu=7faf5(Y0s*kIN# zL|C^FA@&Z0jb^|d2>tIs*e2m&6LJt@9E9->!Xsv@ge?*h7a=@mvKJwYUW8CA;RzGJ z7@^u?gxQM`Hk%>|J0&D7LD*_?mmthsg0Nr0)25L@XlM`$4Z=3FPr_aasY?;In}VeX zbC)6 zI4)uEN`zO<`jrUlRwBf%LMS!^Rw49Xg|JP+ZWFo_A?8km@pmHZF@&G*5N57H*e~G&)97x5hIb5{^q4{3yaXX8ofG>mEgjeGDPi z40sHo|6_!X~IvBP@Ly;gp01CjA+NF3%urdr2A*|bm5c@1bk{R$ULjPwGwn=DeLfa8ywj+$+j?mm}m9RxZ;&TWsP4;sLqn|@4 zmeAV7KaWuDd4$=|BP5$52|Fbuy@1fxk z9ii}bgu!N?guN0{cOzVF3U(vR-HmWmLZ)f^213dk2&>*e7;X+rI3yus4?>n%wg+M9 z9)wd8Mw#?C5xTsIu<=cVY;!`waS4OpLKth-zlE^wEri&&5yqPVZzJ@78)2J-i6-<8 zLd-h||+A|dfzgdCIoF2d+{5sD>DGx6^sRC^C$_In63Op%105|Z{JQ z2s8I0?3ZwjX|xZa;XZ`IeF(G7J_&mzq`r@kXA0g&nEO7$Q3-QQ+Yb;@K0sLY0m2RD zu!KVrGCoAO$t?R2Vd;kmrzG5B()S~D*^jVsKSF^yA>p`$!5<;aH|sw_SoaY^?8gX& zX28b?{Xa(7CSidIeS#443BveK5Eh!P61GT4Jb>Vs>;nj+4|t3xv5}ARLvj z#wZLt{RyGi4EPD5|4#_pBXv3mnQvpgf71$Z2TSJusI>&xP-xfAbe%k|ADaX4}a|9*Z}*F#RCHU7WWUt zF5ZUqtqJ)9^o2h_U-%J@nXMAGNJtDI{9v*J2%`fC#S(rp@j--YL4?^sgkMaNgq;$S zq7Y7)+$e;ZQ3(4boHUK15gJA#6h1|ixki$Pc#gK$bhX_Fp8=n_KM7(xh{6B3R~7+eP79J9U*!n!gD zvF9MfngQn^^gjn-n}l*EbS^^7xd`LWMTj$7C2Wz9s1B@XvSSfO$08I5RZ}Ekr-Y<(2=OMj9Ky_U2>T@@m`3Fh8kR>WERT?A_DR?)AvF%6rYVR+m>Y+1 zR6=djwgN&*1%y=<5bB!45)MhosEAO{EUSpHv?9VO2@OnoC4?@O5H?mqXk<=EI4)su zWrPdN`pO9FDkH>JK}a$Ksvz{Qf^hUof49w>sswJ$H9c<+T*xa^%u#zG+uUit>8;Jg< z9)GfZI_7LyHk5ZL^53NJ2J&y){@JD~M zobK0(s)YrwcA|$bl{aTUXlOTM*M#}7}eD7&0Ss( z?2I`xKL>4Q8om8cs@{~NKM>5|uL{NbOCK(>pzq%w%OT%I2O)lycLL zn7Ua_zuZ@Dm4M!Vpksv9^oxJLd0trrkaXzx`*N=eqO3N`YSqx9tv1?f@o42u++4D# zW~0X1s05fd6m`lrU2Ef7Wh^p z%aLQX+Jt{Hk0^^1Hfp+!s!LQ)tIe?5`DpQ0n`t%HOL^cq`C^f z8MazOY~Dbp|1igQt=064mWxa;WzmtK4*hylmDb?3vm=skwF}Yongg|e4$MDa5-?_s zvN%sOmE$JsuHVCSvD(d6Yle2cZOAQX>NEXb=0PB7XuugI~aLkX+jQUKp&Br{BEQ2Q9%te7^*TfL`L1 z4aNZd-t7W#A<&QXw8+*0i-^*QRZZj1EzuL`YGZ} z0=eKCa2?QF9j4$P2eE-1n9kP`Yp`E;1Tc`cpPv= z_AN0376b>y&L?;)CZI3Z?;FS!RHV;3_c099s|!yFc;Lf^iR1cZ z26aGPpr7^i0D2{k-lC(|XpIBofqrt>9O$QrO+Zti-&0lvLx{f`=Vf(|zlqfQ#U_BBK#QUu1VI#t0q25ppggDq zzNWEzz&qeo@G{s09ssMr-9Wz|)+-70ml(QcriL3MDHj@O%)^p>VQ;7#xf zC<6LF$v1&V!DHZY@C0}gtOfUi)nGIz8=&LL5hxETfUR`WQ{Y~(0BGqd59?=81>i8q z2Uh@X33Lh2m81dCFLe`ve!;8vE%XNiz~^+p5}-feNd`TE-dmsxcBXz7JPaWXguz9i zBj^M!0eY3kbwF?27y_;a+P-O@rcFkFFaT)Nr45EQT0?<$P}(tRhm-}h(f9#qSJ4e* zfF7WyhNTzi3-rbmy+!3B&=Fh=^vakjpc>FtqdG_cH9$E~33Q-$R@WVQ>-X2=r5Hy&yyT z9BrNSbA0`A>R2!ij0Y1yCYS~EUKedzw6)MyLR$yzRJ1eEPNWcIgE2wQzu$58MVqoq zf)lvmpS>g)uGfUJnu2DaIcN!5f!3f6NCEB4fhEDtNxzegUM}(x_!xWw^uzjmFb8N4 zbpyBw++waU!9jT!5WEm1g7ZL4P#b7Nqz#ZZJO{y-;1GBp6oJ>kD?l59mY_9g4)kiT znqVP4ya-GOGe9O72Ks<_P#v__`PG3yYtRNH177gwzRPbPydU(WLwW&iC$gx}L@*UB zrx0zSRs!vpsxS=Yf!=8G1NZ>weIQqZukb$v4uj8u-t=-mSg*e%-A7qQiUKxr~44=RAysiZc*+DEn~t_|o7 zh7tD@gE@%mfp_l|a`W?ErMX zxE&AEUGBRt{`kwG?Oh8{PP>D$1VSJNlm?NFbQ!{tjdcRynxF=V2XUZ0s0b>6YM=_J z1S*57KxwLj$X2d4;aZ>xs0Y}Z`07%=JN4>dG_(moEIjjZIiZ7k-i#FcpSJIi~;>Y zKadTEgP}li+I|fJ1Hn~bFc<qS`&O}H=Mr293{aUv!D67Y76AvSX{yT-AWh-rK$GPuunX)3w}Z_B3*G?A z>t3)M>;xK)R{+(^^Sw-92Y3NI51s?tz;^HuP{*tX>%cwWZm{`s`%}iEUNL7K(&4pYywLBFn9z!4juzffURH)P-PT;7O1k% zfTw{b!%N^r3$^?;@H!|4uLAW}5s>~W<-375;XU9j@HTh{D3gX@A9x>p3_b$;!H2*P zG__9Q^Z>}~gQ3N58t@}z!dgvr!*`fWw10{v ztee0;uzv@Kz;8hD$H3R%7w|1O0#vDQVChG}S2ip=68?@b>GNEtNG9JS{tSKsKY|~C z5=oPN5*!E8<*Q60X@4br0w}H(I0aNewNef1OITf|2Iwv|3Iz3{BHiM~0Nu8h2D)`s z0wva)u(dUhbffOa)pd6O`Q!s#bmdzX)~)olK)2Ppy`BkltF3z?-B9YrQuqA*z(t^u z5+?$6cR8SYYHb&F9}o$Pg&LqbhzEL(q30R8Cs$k*pgX`wSeyVFf(Ag(HzN1&5v@L2 zo-(WrYJr+Sm55||KH<8cjzv9K84Jp^F_5N=WlK*2$}Z~;(0@>gXQCjUIw zL531Gu@R}T&f!RAZLnJdRj37M1(dNWr;J+yHKI9)6rxJVH)2Ovnku2Z>S_EVh3Hv| zo~fvyc0i9qA_b-pP6m;}N){xaNam4B{++E#mGnRVsuGFy5J%l8WPr;+BhV2M%80<^Q#gf2T(}P;FNSN7^L4m}(g*z;(cmWU9DPHeM&;NFX0oR(2$h(S##;NE60bR>f2aX=+$rB;k0BNb6OQ zYOfkAzA{jP$zT%50qT({U?Pb0U?g5~aUfE-(nQivBRmyYE$_6GY9?_8Q1@!CDv>Ia zYr|@(e69lutB~8lZJ-d$2L<3}a3i=L%mKOx`(Z7_H^4UmU4{Db~}0+P!YZiz7$L#+zEaIz6h=dYJn{z z+YIgn_kg>=YM}kaQrLhc;7+g#tOU!za!Sss^B`{c(g(>YUOg`8R}L z!7tzk@ICk%d<71Htw4kDC43MlgD*f4cpJPA-UIIf<+l@l5o`y~0$l>O!OwuF!3*Gd z@LV1rkxbqq{0ev(>;TG087T2fu)-0j0EOkF^x7}%f?or#T3ZFY0d|98@H*H7-UN|+ z@>-IxHFyUxlBp`P59|f1h%!=(l_=6G@#o+(upfK?)Upr3r{Dni1bhTO2CAU^BIT(k z$urNVz!7lR6I{DwVd_@-s4~(OK5E0q;7G51OZYqR4N!inxGJPH@==_8WJemYUE{A- zmCW>KYpZ2H0?h#>Qei4szCVFTW&X-acihIGfaPD(=Ongn6EvY?DBl;uOw_XuA2?;5 z|A2o7=b$Ox4+0cl-L)tLh3^WBzKz)z|E(DFh1)wo# z02&6kJC{hn5Gv& zTAVb49}G6AHxrM=NFzZO&EI($Txw$71#~5=DiMoZ<=okcFT*LM>>JmBdfBJLe=hv z&>pb*M)*OX$I({+`L6{^qXk2DBtKPJ4SttA6fgTR>noqf33J*7d{6Nw^1%!8O2+>+ z97+FotTo|maXTqPc`2@l@U9ZJ^vG#ntcS8^%Ro-*+`q%z2bhxFOudjd5&}^=U>Frwf{RkQt3#+^3(ZMvTOh3!*4i(E%^ap z>f^ztN!r3Ba!%{pp6WnTR2Tes!qtGDC7+MaISQL)j|V&DMV_?ii=$FNYnyg`1wfm{ z!Dx{uDL119z#TvjPfNn%6qwZo8w>g*)(ld-Edu(pBkd%P1ARxQr|i86>sim$K#Nc*?4g9UA(SRxeWxcL1?zh~`N%TmV03pHcrh-7Mb{tdHW`ynRcsrk_8zKCv~p zyM_jtB|E>zi5H)ECTiI7fPYAp>r-TbEJ}g2+4I+H+fG7GyJnNIno3v%OX})?U7|&mGuYv;a2hEmef{jCZzJD(M zZfr~cBvW%+Furwev{!n~z^6lcee=yEa%oEgT5*Fy%sHx8`K!i_yV1XrnC4BI^Uqgk z#*;K*6%JK!c;MW3fB&P?JFQ%YW=)zglgt_%`~%I?+k!0v2TGamwgoTuKWo?SoTcZ8A)Oki62m5+ zpL)?bC>xYz$4)=c#S7=}|Q*KA|VW zlz`ufxU8|iw<=zSU-KsPNi+UBbvAyj|IVS6@8>wLE^+ZYOtm*s-X&?W*JT~dtIxAa zrkg)Ri%rWHf-Mv7=G0S3Pgkl^Veo=w*GKs&CPh`>X6C%W+}~HxtbBovK91vgIF_3F z=7@2x|G?iRM;xn|-8i;x&4|{-A?3SkdXz1H%bqh1eTk_<%=`tXR&K5vcjQdWbd&fZ z-Leb^<$bVYy_lamUGmZyhs~xJ4uSV7nQ<>tiIdi`??>C$-16g`>Sr9|&1xK5w_x5S zl2hWW9{w+`eJB5nLl0tV5L52cJ{A;d|>{ zdqZ4x`mZHJ#G2669D9i&DmLfr2*%ggj~}Cyzx~1$@3+6AT32_7S~O|NWj4`Vw1ZXV zc%r#IHmaJrbw{vzLK^K;wpR@O`j204&-@v8oYl`ynCHnaaPmC!;f`Q6et`DV4#p*= zmN$%LRvmc%#?qg)bp5q*FfN^5W=ShH3ty%-*O?7a!mirf%#eNinq7v>x}$41TN_T` zCFbLo$!~I9ul#qrU$*wE1qZa!X#O+vW|^c{$f$>zTt7++PNu9I%{5TM?WEGWFuh-k z#KLR4?;@35n^&5L<+t7xy%NkwXjb37i^%`zqwT9UwAj`*%FncJ-h{j4i%s*L`1Lj2 zpoEdu@A)qluB%wJ^Dk%oW|}-4TF=8l7lUROFTZ+osdCJ+NJgt{%-!p+8}m&}y*JOq zY%;|pZS$gah>1(PV2gjzYiAtZC5DNUzq52tbJ?NsFPw=vQs2}lVqX1jJznsA)4BfP zz(r>~DwrNc)T@32FN?+%8n*P$|7^_}hjun*Se4&Gq&3pYv;8 zd+!y_8Hed+4-Wjs>?`@L!B5wM*s7NtzVo5!ublDQY-;Xe#uZzKaYOI^Y)~VM*Q|XhYJk+7c5w|{hW%h@6esgA0 zR50^jrO2};buVoPn{9sX)nFSxKi%8Cn?_7Gam7%+xd8ePtMWW^wfx-6Tb1*epGP^5 zwC>({^V6Nyoho}jb81()H)n1No7m>&I?^TtDP6Ph%ZG9o z_WN}5FQnD9Cv6q;$m^8V*t`lQq~WKDb=#UpuQ)Y(_Z4USdYeCRXgwAOEo?c@T(RMy zK)GYCgS*}r*qC*7p>i6OwI)Ly8Z@JJbE*G=1x4%gWV2#7L+bT*>#i&% zI^73s|0a6nM@JjFshc-R=HVhSXWs;rHi>VLZ<0xVgXQBMbJH8a75-tS_MX$(`X`v? zp{PpchCRWWHRiM8>4LeTcK^G7z3GqhZ3XEAwq?)mVU|@l?77WWG%`AHEr^7C4@2%D;7*TlmMBkKYQ$o2%Ypshl46 zma6Bm54UTI)?QVnTzx z6~3wy@Ux~jY0kinHU4)=HPs|Q30v{gqz_H(@bZyf%b%tNt(&xH!=`eo>6OA+adT&{ z51Ku-YIWx4O;6b2VOA|M+dgtTe3deK+WI{oZ1>V@d(XSqwvE1e)$G9`u&=ZE`dvoi z^Uh0xQI$=?d%@%y-;qd@=)GfCJr!KPeZB2qI^_4xX2N^H7XGqk&3nO72{pQS>;CKK zmcF*?++9@yehowmYSF}mGr62xWK#E1nck*Q)^Iaf)?_n(FBgpyB#xt$%iBFaW<{$z zGXnmW3{p$xOc}FaAAXfhER;|`-8;*ceRstb4$>@!KRn~N+Qhw2el}Z6e-Z=VcU|&fu$me2KI3Tf`>*O(V?j5s_5P<$%^LF3b;HjT zc(~h=@=;aH@%Mu@0zY>%*L}e05;Su@JY9)^=Mk0ci$o7M<^v{==k*`5_A2^ccAmNX zLt1jb@w1(b6y4LTPz~0ZCqCrVw}Vm9jNMNs*XZdD`E5_de!2XfyW-sWlerL9AByO=v=ba3QyK{QkMQWSeZ*k;>WBkgG~5ivLC_ku<5QTrVy*m+vK33ziiKY z)mlAqQu}2s*v!K(h|!7E>*FWSd9UpET~uxE@Hn}T^)VlQ%((c?Pvq3Pd|&UT<>Ol` zCBN|_KS$H`!d>(05u=rGW~XZ(3k);g5~KY;XG04!_!Igr4F@eZ!>8os4xD)2vpCo- zcNen|2YwK{L4KpGU($2yKDe)H^KAU=Wo5GYSbnq3iBD+MJX7vh&dg8ynu|_wW`5Jx zY&rm+>}#$S|5cCJmd$Smg5CLx&u*UvGa|n9n*TEjm}!bXJ(IIlBTlB(XH1y~%^rDf zHqF0G%Wf>pQ^fhxl@fhnzcRUehyOIK=cg zMQ(1xT1Lv_EePSSQ74;kKZkBM&&#r8%$&nqKtH9+G<@|B{8sK%<4^Mg{>JWoJ>Qu8 zZH!m-gp*gff9sTga%QWe`Oh5G&9~k0dU=~8r>MH;e7Z_w&FUkJzw)n?#iugq>P^iFFDNQ`crEnqhLkbd@rkr zr(1z|-fXq;%QKxZ$sG8ST6vXAxOS-fS9AH3KkxX-b1R-a_e`OSP0eGZvKc>UCVqwW zf_dZ+WQ*}SJK$9>;WM(*gM^n-(z~DMKd6hm?IUkU1E+?X?tg7ZOJ;(b^Pd~{XUn>H zT^*^BwQM_5hkN&G-Me;doZtObJ@~PO4>U)QP-WYk$)>j2G1s&g{i$(n?j;Xyeez#j z9lho1vbVi-CXXG&XlU-8JmTWmyglpA#CUD5@%L>%Tcb}G{O4x=xyS!$T+2u+{+z%z z?N4GtpOM}iymU(6n)ml_fBl&=z@DO(YM9J#=$^*r8j)R4I+};SVd7Yq<>u5GD>CI1 zqDFfw|Jp_ggCBWkfL4CH@KrE*S_ACK@b`1wTd&N)Z_k`hcFjuYn9WyJjKcG!uAf)8 z(vcNsMj_ku_>L320Ec=wY^hXZW7X>GE>YU~=2f$bqzPZ+ zzM>D_A{bR>%Pfzt8KDbw~(&mC6>B{rQdnZeo zQX@C^IQ-jlXS%YTxmph0anN9t&A)EQ!ygZM=8S_osUp`4)8r>M@PA*C|I@j3;skH{ zg%vvas&hE4T0M(?KftkHE?FFiTc?u;7DFokRm{#1jO{y$ynx#FKX z8RWk1*E9PEuVH5SVH)}8>Yj0qY(q|W$*hyitElPT{^G&6dd9qc)sbFjYBJ3{e3FKi z#X!Dj2H{`_z{~n{cFlgHaNBCHKK}o&l53Bvy2#@9cNs*`<^8(+xNy-_ zG!}%*Gc?UW7Zr(!SV>W96J3pmnxeLd7%e?alIYmSM~@Fm*~iV&Pgl#2%BLmG$44+% zEz5VgTBt~^+veha=Qs1a4+LxZXYS0LIdkUBIcLr}GyK-kvRhb;D@87hK@_8KFGeWK!oQI{V`#;HVN~q++Z;8D=)%m8Q(70|7U7LaIOCBD+SLX3?py`MC@j?Y zQw3c|9eyw0XVqh|_UNbTQqq8eagkkStWE04FdBKAClG~u*b6#|Mt3H4;slNQoZZH% z*O!Qt)VlS4$JSnL=T{1= zI7Q@W&Zi)H|FWCAdZ!0pDLr^eyzc+%)Ob*w5ya`uy6*i@NtH$xn$%-BqD4BlD#gu4 z?SVEw8evkB< z2-}O|kxBse9J0DGt4vZ+l^dHCu(wLYwKt31H@_Zs=&L|{2^UA~z1O5CEJ<-(8<@e$vonq@5{BsE3B&C_M|@RRwP6Q%oD!Rmh^~LvP?QaM$l=A9bInAwgOJF&%n1d3n`0{<-hv z_N_dn0d6A=H=3q;FsphFmxi3OTMnna zp)Xr2QfpSENk`oWmOV?$JW-U>NaWn*69 z6fcY!NvanFsep34SaDM33&JECs&~J$f9$-^G;mFrd$}0Hj^@z!K%^F8DBs1#{Kn0% zA1p-n0`3#cC9ZfWvL9wzKu@8dYA;YHejAU!NVWYSHQFsfFtzn#bBsT}MECbcTNah~ z$EX5|FGt)DcC@b)8x?gbu4qV6)FqL_;uE}0d-{VX8|bm$L90GmCDu?sl#cEEW+ax} zgchFOls70zJ#RVqV%Q5vE(}?I^%#w^l(Ra?~1Y)Th5$a2}e%987nj zf!_4P@%Cc_#Rj1iH0e|j^JmGjf-JG7(DqUiZf_PjG`AFkoNp6#`11u3^$&nRj;B!p zm{+u_y%SwaD*@M2G9QY>E!TKz4uInKaD^-~NrpA#6KLn8PHr$bdVr>)L)_|y(93}= z+;t7XZm9;zo_*v4E`?uME4W@-S)ci3)3C!1-b#jr_r4GzI>C6PAQ>mNI0RWn*9LQn zA#lAA_0Pfsy4GLz3DxIGH4LU^m?$*^0Ngy3t~ugUSv!#ckQkf5Z!BM>_+XZuqGpII?L-pocwATd18cJ=*emeC!1M5;G zc?2mE3WOaIXzsEVj`=jr9ZY_z(>$NiIaFQ6UcK0e2c4k*_@tsero$!tnoDYvFFgP? zRFWx&FGbR>s|VdMu`sy;{%ry&EMkoDVk1ongAU1!N5891vs8WK?W>i>#+77sdy|L| zZcbcvE&k#_Y4{Rxp=5`LP=r8k4gr$u{-yn=Uz?X7HAr7aiUD@62QFw6h1i*ji{Q9$ zCteBkPFuto+O4W+5*dOWnnSmp2CMFyg{yJW`ibr6tO%onmb;==56VL$b^L*=32VbD zFpl5m#B5B+f%%i4A2z&{ZM#4*orVd_9p+OdwZO65kR$4GaF*RA3)!3!b*A|Gi z=0gNizbLj-+y`p+Y;V(d1_0dfAFt)F>ZH;5e3VslNgnksy z{=*bs`Xz}SL2)n{BX^QD1wMmz{beO%DoYf|NKLA%m0%N#n92Fj#QCokrDH>~oU z7S=@>Y=cI^c3@HlBt)Aj$UOm9qM9ZIQO1MpVZEESKFHST6-Y{Ph7dVzs0JVCp~d$& zG_GW@1P&UJsW~bgW5q1+P=cvC6I&*X9S7lUwkw&ej{-aF1jrqnZq;r|t{H8CLHC9v z!rrR7mHbVE$Uh64?ENuqZq{hyTcg-}tB%cve$B7IEBL%2Pz^V$zrM>ALwM)Z)A{nlKHV0XlO z4k&H<&|Js#_TyALZ~4&#w&mJ&gL9(?@|^b5X)8Feiv~#dyqj=JuRKt6z}Xpf4m&u@ zL_h$_G>l?JH2qPQ=rq|~xJbbTnp$H8y7UUE?rBN9?e5CKiEx@&8-Uo*Dy6`w%ZXfl zl@4vv!aVMAJDaG<8mpaVa8f_9ZXFTrm<*$;B|@_K%Ypj~*-YGg#Z86$?=ECvYTpcT zVnwbuo=x=Mj-Z;qAWcDV*I?@`WM%~!9lt3!C0V?qR!>T_r(B6M!>#sqy_a7#k_v7remqcK5 zdy1v=C*gR*WyL4d=a;a{@Lr~t@=Y;QMJbyY{v@vRcnDAtKD+f?ZP5)Z9UH{+3CI(i zEd`Iot2Od3gJ6iTkH(gzXs0X8dt+sjQ?(p=M zHOQZF{9Y?sE<7(hsF zH&vnTg<%&KRL}PPQ`V^$sH1`{!;_Qp#Vpd~Rxf&~%Q|N6)(QSQ77KQrp?4RvleTS3 W*vB7{rJ6CSS;FFMCGqZ2RsREpLGg_M diff --git a/package.json b/package.json index 65701d7..634ef50 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ }, "dependencies": { "lodash.clonedeep": "^4.5.0", - "openapi-types": "^12.1.3" + "openapi-types": "^12.1.3", + "pathe": "^1.1.2" } } diff --git a/src/utils.ts b/src/utils.ts index cb55b24..7f9753c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/no-unused-vars */ -import path from 'path' +import { normalize } from 'pathe' import type { HTTPMethod, LocalHook } from 'elysia' import { Kind, type TSchema } from '@sinclair/typebox' @@ -310,7 +310,7 @@ export const filterPaths = ( // exclude docs path and OpenAPI json path const excludePaths = [`/${docsPath}`, `/${docsPath}/json`].map((p) => - path.normalize(p) + normalize(p) ) for (const [key, value] of Object.entries(paths)) From bfce798a56ed858cbd96f83e75767508f0b2b649 Mon Sep 17 00:00:00 2001 From: Christian Llontop Date: Tue, 3 Sep 2024 15:53:42 -0500 Subject: [PATCH 4/6] Fix nested path issue in Swagger and Scalar config --- src/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/index.ts b/src/index.ts index 7fdb2c9..bfbd905 100644 --- a/src/index.ts +++ b/src/index.ts @@ -66,7 +66,7 @@ export const swagger = async ( app.get(path, function documentation() { const combinedSwaggerOptions = { - url: `${relativePath}/json`, + url: `/${relativePath}/json`, dom_id: '#swagger-ui', ...swaggerOptions } @@ -83,7 +83,7 @@ export const swagger = async ( const scalarConfiguration: ReferenceConfiguration = { spec: { ...scalarConfig.spec, - url: `${relativePath}/json` + url: `/${relativePath}/json` }, ...scalarConfig } @@ -111,7 +111,7 @@ export const swagger = async ( if (routes.length !== totalRoutes) { const ALLOWED_METHODS = ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS', 'HEAD', 'PATCH', 'TRACE'] totalRoutes = routes.length - + routes.forEach((route: InternalRoute) => { if (route.hooks?.detail?.hide === true) return // TODO: route.hooks?.detail?.hide !== false add ability to hide: false to prevent excluding From 7d005527b0e9a86a90fad837f4645bbf778aa515 Mon Sep 17 00:00:00 2001 From: saltyaom Date: Thu, 5 Sep 2024 17:03:37 +0700 Subject: [PATCH 5/6] :tada: feat: provenance publish --- CHANGELOG.md | 10 +++++++--- package.json | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6947c0b..c571d13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 1.1.2 - 5 Sep 2024 +Feature: +- add provenance publish + # 1.1.1 - 12 Aug 2024 Feature: - add hide flag @@ -62,9 +66,9 @@ Change: Change: - Add support for Elysia 0.8 -# 0.7.5 +# 0.7.5 Improvement: -- #[59](https://github.com/elysiajs/elysia-swagger/pull/59) use relative path to swagger json #59 +- #[59](https://github.com/elysiajs/elysia-swagger/pull/59) use relative path to swagger json #59 # 0.7.4 - 27 Oct 2023 Improvement: @@ -81,7 +85,7 @@ Bug fix: # 0.7.3 - 26 Sep 2023 Feature: - [#19](https://github.com/elysiajs/elysia-swagger/pull/19) feat: handle nullish response types -- [#18](https://github.com/elysiajs/elysia-swagger/pull/18) swagger ui options +- [#18](https://github.com/elysiajs/elysia-swagger/pull/18) swagger ui options Improvement: diff --git a/package.json b/package.json index 65701d7..0fb94fa 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@elysiajs/swagger", - "version": "1.1.1", + "version": "1.1.2", "description": "Plugin for Elysia to auto-generate Swagger page", "author": { "name": "saltyAom", From 3d0e07f22a5dcf33d80ce33c2bcb2745b09f84a3 Mon Sep 17 00:00:00 2001 From: Abraxas-365 Date: Tue, 10 Sep 2024 23:25:28 -0500 Subject: [PATCH 6/6] fix: swagger date time --- bun.lockb | Bin 373135 -> 373135 bytes src/swagger/index.ts | 71 ++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 4 deletions(-) diff --git a/bun.lockb b/bun.lockb index 6ebdda90d5a07b3b3c6b7a6375bddd3514979631..a5473bc1fe2af444638b18245cc68d6e6c2333f9 100755 GIT binary patch delta 30 mcmeBwEY|;6tf7Umg=q`(`<>JERarROf9z!5{$nT0^zQ)CC=Mt9 delta 33 mcmeBwEY|;6tf7Umg=q`(`9!VNb7 diff --git a/src/swagger/index.ts b/src/swagger/index.ts index 062556f..60a0144 100644 --- a/src/swagger/index.ts +++ b/src/swagger/index.ts @@ -1,4 +1,52 @@ -import type { OpenAPIV3 } from 'openapi-types' +import { OpenAPIV3 } from 'openapi-types'; + +type DateTimeSchema = { + type: 'string'; + format: 'date-time'; + default?: string; +}; + +type SchemaObject = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject; + +function isSchemaObject(schema: SchemaObject): schema is OpenAPIV3.SchemaObject { + return 'type' in schema || 'properties' in schema || 'items' in schema; +} + +function isDateTimeProperty(key: string, schema: OpenAPIV3.SchemaObject): boolean { + return (key === 'createdAt' || key === 'updatedAt') && + 'anyOf' in schema && + Array.isArray(schema.anyOf); +} + +function transformDateProperties(schema: SchemaObject): SchemaObject { + if (!isSchemaObject(schema) || typeof schema !== 'object' || schema === null) { + return schema; + } + + const newSchema: OpenAPIV3.SchemaObject = { ...schema }; + + Object.entries(newSchema).forEach(([key, value]) => { + if (isSchemaObject(value)) { + if (isDateTimeProperty(key, value)) { + const dateTimeFormat = value.anyOf?.find((item): item is OpenAPIV3.SchemaObject => + isSchemaObject(item) && item.format === 'date-time' + ); + if (dateTimeFormat) { + const dateTimeSchema: DateTimeSchema = { + type: 'string', + format: 'date-time', + default: dateTimeFormat.default + }; + (newSchema as Record)[key] = dateTimeSchema; + } + } else { + (newSchema as Record)[key] = transformDateProperties(value); + } + } + }); + + return newSchema; +} export const SwaggerUIRender = ( info: OpenAPIV3.InfoObject, @@ -11,7 +59,21 @@ export const SwaggerUIRender = ( }, stringifiedSwaggerOptions: string, autoDarkMode?: boolean -) => ` +): string => { + const swaggerOptions: OpenAPIV3.Document = JSON.parse(stringifiedSwaggerOptions); + + if (swaggerOptions.components && swaggerOptions.components.schemas) { + swaggerOptions.components.schemas = Object.fromEntries( + Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [ + key, + transformDateProperties(schema) + ]) + ); + } + + const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions); + + return ` @@ -57,8 +119,9 @@ export const SwaggerUIRender = ( -` +`; +};