UNPKG

vitepress-openapi

Version:

Generate VitePress API Documentation from OpenAPI Specification.

912 lines (759 loc) 25.1 kB
import type { Ref, UnwrapNestedRefs } from 'vue' import type { OARequest } from '../lib/codeSamples/request' import type { OperationSlot, ParsedOperation } from '../types' import vitesseDark from '@shikijs/themes/vitesse-dark' import vitesseLight from '@shikijs/themes/vitesse-light' import { useDark } from '@vueuse/core' import { ref } from 'vue' import { generateCodeSample } from '../lib/codeSamples/generateCodeSample' import { deepUnref } from '../lib/deepUnref' import { locales } from '../locales' export interface ThemeConfig { highlighterTheme: { light: any dark: any } } export interface PathConfig { showBaseURL: Ref<boolean> } export interface RequestConfig { defaultView: Ref<'schema' | 'contentType' | 'autogenerated'> } export interface JsonViewerConfig { deep: Ref<number> renderer: Ref<'vue-json-pretty' | 'shiki' | string> } export interface SchemaViewerConfig { deep: Ref<number> } export interface HeadingLevels { h1: number h2: number h3: number h4: number h5: number h6: number } export interface ResponseConfig { responseCodeSelector: Ref<'tabs' | 'select'> maxTabs: Ref<number> body: { defaultView: Ref<'schema' | 'contentType' | 'autogenerated'> } } type PlaygroundJsonEditorMode = 'text' | 'tree' | 'table' export interface PlaygroundConfig { jsonEditor: { mode: Ref<PlaygroundJsonEditorMode> mainMenuBar: Ref<boolean> navigationBar: Ref<boolean> } } export interface SecurityConfig { defaultScheme: Ref<string | null> } type OperationBadges = 'deprecated' | 'operationId' export interface OperationConfig { badges?: Ref<OperationBadges[]> slots?: Ref<OperationSlot[]> /** @deprecated Use server.getServers instead */ getServers?: GetServersFunction | null hiddenSlots?: Ref<OperationSlot[]> cols?: Ref<1 | 2> defaultBaseUrl?: string } export type Languages = 'es' | 'en' | 'ja' | 'pt-BR' | string export type Messages = Record<Languages, Record<string, string>> export interface AvailableLocale { code: string label: string } export interface I18nConfig { locale: Ref<Languages> fallbackLocale: Ref<Languages> messages: Messages availableLocales?: AvailableLocale[] } export interface SpecConfig { groupByTags?: Ref<boolean> collapsePaths?: Ref<boolean> showPathsSummary?: Ref<boolean> avoidCirculars?: Ref<boolean> lazyRendering?: Ref<boolean> defaultTag?: string defaultTagDescription?: string wrapExamples?: boolean } export interface ServerConfig { allowCustomServer: boolean getServers: GetServersFunction | null } export interface UseThemeConfig { theme?: Partial<ThemeConfig> path?: Partial<PathConfig> requestBody?: Partial<RequestConfig> jsonViewer?: Partial<JsonViewerConfig> schemaViewer?: Partial<SchemaViewerConfig> headingLevels?: Partial<HeadingLevels> response?: Partial<ResponseConfig> playground?: Partial<PlaygroundConfig> security?: Partial<SecurityConfig> operation?: Partial<OperationConfig> i18n?: Partial<I18nConfig> spec?: Partial<SpecConfig> codeSamples?: Partial<CodeSamplesConfig> linksPrefixes?: Partial<LinksPrefixesConfig> server?: Partial<ServerConfig> } export interface CodeSamplesConfig { langs: string[] defaultLang: string availableLanguages: LanguageConfig[] generator: GeneratorFunction defaultHeaders: Record<string, string> } export interface LinksPrefixesConfig { tags: string operations: string } interface LanguageConfig { lang: string label: string highlighter: string icon?: string } export type PartialUseThemeConfig = Partial<UnwrapNestedRefs<UseThemeConfig>> type GeneratorFunction = (lang: string, request: OARequest) => Promise<string> type GetServersFunction = ({ method, path, operation }: { method: string, path: string, operation: ParsedOperation }) => string[] | null export const DEFAULT_OPERATION_SLOTS: OperationSlot[] = [ 'header', 'path', 'description', 'security', 'parameters', 'request-body', 'responses', 'playground', 'code-samples', 'branding', 'footer', ] export const DEFAULT_BASE_URL = 'http://localhost' export const availableLanguages: LanguageConfig[] = [ { lang: 'curl', label: 'cURL', highlighter: 'bash', icon: 'curl', }, { lang: 'javascript', label: 'JavaScript', highlighter: 'javascript', icon: '.js', }, { lang: 'php', label: 'PHP', highlighter: 'php', icon: '.php', }, { lang: 'python', label: 'Python', highlighter: 'python', icon: '.py', }, ] const themeConfig: UseThemeConfig = { theme: { highlighterTheme: { light: vitesseLight, dark: vitesseDark, }, }, path: { showBaseURL: ref<boolean>(false), }, requestBody: { defaultView: ref<'schema' | 'contentType' | 'autogenerated'>('contentType'), }, jsonViewer: { deep: ref<number>(Number.POSITIVE_INFINITY), renderer: ref<'vue-json-pretty' | 'shiki' | string>('vue-json-pretty'), }, schemaViewer: { deep: ref<number>(Number.POSITIVE_INFINITY), }, headingLevels: { h1: 1, h2: 2, h3: 3, h4: 4, h5: 5, h6: 6, }, response: { responseCodeSelector: ref<'tabs' | 'select'>('tabs'), maxTabs: ref<number>(5), body: { defaultView: ref<'schema' | 'contentType' | 'autogenerated'>('contentType'), }, }, playground: { jsonEditor: { mode: ref<PlaygroundJsonEditorMode>('tree'), mainMenuBar: ref<boolean>(false), navigationBar: ref<boolean>(false), }, }, security: { defaultScheme: ref<string | null>(null), }, operation: { badges: ref<OperationBadges[]>(['deprecated']), slots: ref(DEFAULT_OPERATION_SLOTS), hiddenSlots: ref([]), cols: ref(2), defaultBaseUrl: DEFAULT_BASE_URL, getServers: null, }, i18n: { locale: ref<Languages>('en'), fallbackLocale: ref<Languages>('en'), messages: locales, availableLocales: [ { code: 'en', label: 'English', }, { code: 'es', label: 'Español', }, { code: 'ja', label: 'Japanese', }, { code: 'pt-BR', label: 'Português (Brasil)', }, ], }, spec: { groupByTags: ref(true), collapsePaths: ref(false), showPathsSummary: ref(true), avoidCirculars: ref(false), lazyRendering: ref(false), defaultTag: 'Default', defaultTagDescription: '', wrapExamples: true, }, codeSamples: { langs: [ 'curl', 'javascript', 'php', 'python', ], defaultLang: 'curl', availableLanguages, generator: (lang: string, request: OARequest) => generateCodeSample(lang, request), defaultHeaders: {}, }, linksPrefixes: { tags: '/tags/', operations: '/operations/', }, server: { allowCustomServer: false, getServers: null, }, } const defaultThemeConfig: UnwrapNestedRefs<UseThemeConfig> = { ...deepUnref(themeConfig) } as PartialUseThemeConfig const isDark = useDark({ storageKey: 'vitepress-theme-appearance', }) export function useTheme(initialConfig: PartialUseThemeConfig = {}) { setConfig(initialConfig) function setConfig(config: PartialUseThemeConfig) { if (!config || !Object.keys(config).length) { return } if (config?.theme?.highlighterTheme) { setHighlighterTheme(config.theme.highlighterTheme) } if (config?.requestBody?.defaultView !== undefined) { setRequestBodyDefaultView(config.requestBody.defaultView) } if (config?.path?.showBaseURL !== undefined) { setShowBaseURL(config.path.showBaseURL) } if (config?.jsonViewer?.deep !== undefined) { setJsonViewerDeep(config.jsonViewer.deep) } if (config?.jsonViewer?.renderer !== undefined) { setJsonViewerRenderer(config.jsonViewer.renderer) } if (config?.schemaViewer?.deep !== undefined) { setSchemaViewerDeep(config.schemaViewer.deep) } if (config?.headingLevels !== undefined) { setHeadingLevels(config.headingLevels) } if (config?.response?.responseCodeSelector !== undefined) { setResponseCodeSelector(config.response.responseCodeSelector) } if (config?.response?.maxTabs !== undefined) { setResponseCodeMaxTabs(config.response.maxTabs) } if (config?.response?.body?.defaultView !== undefined) { setResponseBodyDefaultView(config.response.body.defaultView) } if (config?.playground?.jsonEditor?.mode !== undefined) { setPlaygroundJsonEditorMode(config.playground.jsonEditor.mode) } if (config?.playground?.jsonEditor?.mainMenuBar !== undefined) { setPlaygroundJsonEditorMainMenuBar(config.playground.jsonEditor.mainMenuBar) } if (config?.playground?.jsonEditor?.navigationBar !== undefined) { setPlaygroundJsonEditorNavigationBar(config.playground.jsonEditor.navigationBar) } if (config?.security?.defaultScheme !== undefined) { setSecurityDefaultScheme(config.security.defaultScheme) } if (config?.operation?.badges !== undefined) { setOperationBadges(config.operation.badges) } if (config?.operation?.slots !== undefined) { setOperationSlots(config.operation.slots) } if (config?.operation?.hiddenSlots !== undefined) { setOperationHiddenSlots(config.operation.hiddenSlots) } if (config?.operation?.cols !== undefined) { setOperationCols(config.operation.cols) } if (config?.operation?.defaultBaseUrl !== undefined) { setOperationDefaultBaseUrl(config.operation.defaultBaseUrl) } if (config?.operation?.getServers !== undefined) { setOperationServers(config.operation.getServers) } if (config?.i18n !== undefined) { setI18nConfig(config.i18n) } if (config?.spec !== undefined) { setSpecConfig(config.spec) } if (config?.codeSamples !== undefined) { setCodeSamplesConfig(config.codeSamples) } if (config?.linksPrefixes !== undefined) { setLinksPrefixesConfig(config.linksPrefixes) } if (config?.server !== undefined) { setServerConfig(config.server) } } function reset() { setConfig(defaultThemeConfig) } function getState() { return deepUnref(themeConfig) } function getLocale(): Languages { return themeConfig?.i18n?.locale?.value || 'en' } /** * @deprecated Use `setI18nConfig({ locale: value })` instead. */ function setLocale(value: Languages) { console.warn('`setLocale` is deprecated. Use `setI18nConfig({ locale: value })` instead.') // @ts-expect-error: This is a valid expression. themeConfig.i18n.locale.value = value } function getHighlighterTheme() { return themeConfig?.theme?.highlighterTheme } function setHighlighterTheme(value: ThemeConfig['highlighterTheme']) { // @ts-expect-error: This is a valid expression. themeConfig.theme.highlighterTheme = { ...themeConfig?.theme?.highlighterTheme, ...value, } } function getRequestBodyDefaultView(): 'schema' | 'contentType' | 'autogenerated' | undefined { return themeConfig?.requestBody?.defaultView?.value } function setRequestBodyDefaultView(value: 'schema' | 'contentType' | 'autogenerated') { // @ts-expect-error: This is a valid expression. themeConfig.requestBody.defaultView.value = value } function getShowBaseURL(): boolean | undefined { return themeConfig?.path?.showBaseURL?.value } function setShowBaseURL(value: boolean) { // @ts-expect-error: This is a valid expression. themeConfig.path.showBaseURL.value = value } function getJsonViewerDeep(): number | undefined { return themeConfig?.jsonViewer?.deep?.value } function setJsonViewerDeep(value: number) { // @ts-expect-error: This is a valid expression. themeConfig.jsonViewer.deep.value = value } function getJsonViewerRenderer(): 'vue-json-pretty' | 'shiki' | string { return themeConfig?.jsonViewer?.renderer?.value || 'vue-json-pretty' } function setJsonViewerRenderer(value: 'vue-json-pretty' | 'shiki' | string) { // @ts-expect-error: This is a valid expression. themeConfig.jsonViewer.renderer.value = value } function getSchemaViewerDeep(): number | undefined { return themeConfig?.schemaViewer?.deep?.value } function setSchemaViewerDeep(value: number) { // @ts-expect-error: This is a valid expression. themeConfig.schemaViewer.deep.value = value } function getHeadingLevels() { return themeConfig.headingLevels } function getHeadingLevel(level: keyof HeadingLevels): `h${1 | 2 | 3 | 4 | 5 | 6}` { if (!themeConfig.headingLevels) { return `h${level}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' } const headingLevel = themeConfig.headingLevels[level] as number if (headingLevel < 1 || headingLevel > 6) { throw new Error(`Heading level for ${level} must be between 1 and 6.`) } return `h${headingLevel}` as 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' } function setHeadingLevels(levels: Partial<UnwrapNestedRefs<HeadingLevels>>) { if (!themeConfig.headingLevels) { themeConfig.headingLevels = { } } for (const key of Object.keys(levels)) { const value = levels[key as keyof HeadingLevels] as number if (value < 1 || value > 6) { throw new Error(`Heading level for ${key} must be between 1 and 6.`) } } Object.assign(themeConfig.headingLevels, levels) } function getResponseCodeSelector(): 'tabs' | 'select' | undefined { return themeConfig?.response?.responseCodeSelector?.value } function setResponseCodeSelector(value: 'tabs' | 'select') { // @ts-expect-error: This is a valid expression. themeConfig.response.responseCodeSelector.value = value } function getResponseCodeMaxTabs(): number | undefined { return themeConfig?.response?.maxTabs?.value } function setResponseCodeMaxTabs(value: number) { // @ts-expect-error: This is a valid expression. themeConfig.response.maxTabs.value = value } function getResponseBodyDefaultView(): 'schema' | 'contentType' | 'autogenerated' | undefined { return themeConfig?.response?.body?.defaultView?.value } function setResponseBodyDefaultView(value: 'schema' | 'contentType' | 'autogenerated') { // @ts-expect-error: This is a valid expression. themeConfig.response.body.defaultView.value = value } function getPlaygroundJsonEditorMode(): PlaygroundJsonEditorMode | undefined { return themeConfig?.playground?.jsonEditor?.mode.value } function setPlaygroundJsonEditorMode(value: PlaygroundJsonEditorMode) { // @ts-expect-error: This is a valid expression. themeConfig.playground.jsonEditor.mode.value = value } function getPlaygroundJsonEditorMainMenuBar(): boolean | undefined { return themeConfig?.playground?.jsonEditor?.mainMenuBar?.value } function setPlaygroundJsonEditorMainMenuBar(value: boolean) { // @ts-expect-error: This is a valid expression. themeConfig.playground.jsonEditor.mainMenuBar.value = value } function getPlaygroundJsonEditorNavigationBar(): boolean | undefined { return themeConfig?.playground?.jsonEditor?.navigationBar.value } function setPlaygroundJsonEditorNavigationBar(value: boolean) { // @ts-expect-error: This is a valid expression. themeConfig.playground.jsonEditor.navigationBar.value = value } function getSecurityDefaultScheme(): string | null | undefined { return themeConfig?.security?.defaultScheme?.value } function setSecurityDefaultScheme(value: string | null) { // @ts-expect-error: This is a valid expression. themeConfig.security.defaultScheme.value = value } function getOperationBadges(): OperationBadges[] { return [...(themeConfig?.operation?.badges?.value || [])] } function setOperationBadges(value: OperationBadges[]) { // @ts-expect-error: This is a valid expression. themeConfig.operation.badges.value = value } function getOperationSlots(): OperationSlot[] | undefined { return themeConfig?.operation?.slots?.value } function setOperationSlots(value: OperationSlot[]) { // @ts-expect-error: This is a valid expression. themeConfig.operation.slots.value = value } function getOperationHiddenSlots(): OperationSlot[] | undefined { return themeConfig?.operation?.hiddenSlots?.value } function setOperationHiddenSlots(value: OperationSlot[]) { // @ts-expect-error: This is a valid expression. themeConfig.operation.hiddenSlots.value = value } function getOperationCols(): 1 | 2 | undefined { return themeConfig?.operation?.cols?.value } function setOperationCols(value: number) { // @ts-expect-error: This is a valid expression. themeConfig.operation.cols.value = value } function getOperationDefaultBaseUrl(): string { return themeConfig?.operation?.defaultBaseUrl || DEFAULT_BASE_URL } function setOperationDefaultBaseUrl(value: string) { // @ts-expect-error: This is a valid expression. themeConfig.operation.defaultBaseUrl = value } function getOperationServers(): GetServersFunction | null { if (themeConfig?.operation?.getServers) { console.warn('operation.getServers is deprecated. Use server.getServers instead.') return themeConfig.operation.getServers } return themeConfig?.server?.getServers || null } function setOperationServers(value: GetServersFunction | null) { // @ts-expect-error: This is a valid expression. themeConfig.operation.getServers = value } function getI18nConfig(): I18nConfig { return themeConfig.i18n as I18nConfig } function setI18nConfig(config: Partial<UnwrapNestedRefs<I18nConfig>>) { if (config.locale) { // @ts-expect-error: This is a valid expression. themeConfig.i18n.locale.value = config.locale } if (config.fallbackLocale) { // @ts-expect-error: This is a valid expression. themeConfig.i18n.fallbackLocale.value = config.fallbackLocale } if (config.messages) { // @ts-expect-error: This is a valid expression. themeConfig.i18n.messages = config.messages } if (config.availableLocales) { // @ts-expect-error: This is a valid expression. themeConfig.i18n.availableLocales = config.availableLocales } } function getSpecConfig() { return themeConfig.spec } function getWrapExamples() { return themeConfig?.spec?.wrapExamples } function setSpecConfig(config: Partial<UnwrapNestedRefs<SpecConfig>>) { if (!themeConfig.spec) { themeConfig.spec = {} } if (config.groupByTags !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.spec.groupByTags.value = config.groupByTags } if (config.collapsePaths !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.spec.collapsePaths.value = config.collapsePaths } if (config.showPathsSummary !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.spec.showPathsSummary.value = config.showPathsSummary } if (config.avoidCirculars !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.spec.avoidCirculars.value = config.avoidCirculars } if (config.lazyRendering !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.spec.lazyRendering.value = config.lazyRendering } if (config.defaultTag !== undefined) { themeConfig.spec.defaultTag = config.defaultTag } if (config.defaultTagDescription !== undefined) { themeConfig.spec.defaultTagDescription = config.defaultTagDescription } if (config.wrapExamples !== undefined) { themeConfig.spec.wrapExamples = config.wrapExamples } } function getCodeSamplesLangs() { return themeConfig?.codeSamples?.langs?.filter((lang, index, self) => self.indexOf(lang) === index) } function getCodeSamplesDefaultLang() { const availableLangs = getCodeSamplesLangs() || [] const defaultLang = themeConfig?.codeSamples?.defaultLang if (defaultLang && availableLangs.includes(defaultLang)) { return defaultLang } return availableLangs[0] } function getCodeSamplesAvailableLanguages() { return themeConfig?.codeSamples?.availableLanguages } function getCodeSamplesGenerator() { return themeConfig?.codeSamples?.generator } function getCodeSamplesDefaultHeaders() { return themeConfig?.codeSamples?.defaultHeaders } function setCodeSamplesConfig(config: Partial<UnwrapNestedRefs<CodeSamplesConfig>>) { if (config.langs) { // @ts-expect-error: This is a valid expression. themeConfig.codeSamples.langs = config.langs.filter((lang, index, self) => self.indexOf(lang) === index) } if (config.defaultLang) { // @ts-expect-error: This is a valid expression. themeConfig.codeSamples.defaultLang = config.defaultLang } if (config.availableLanguages) { setCodeSamplesAvailableLanguages(config.availableLanguages) } if (config.generator) { // @ts-expect-error: This is a valid expression. themeConfig.codeSamples.generator = config.generator } if (config.defaultHeaders) { // @ts-expect-error: This is a valid expression. themeConfig.codeSamples.defaultHeaders = config.defaultHeaders } } function setCodeSamplesAvailableLanguages(languages: LanguageConfig[]) { if (!themeConfig.codeSamples) { return } const uniqueLanguages = [...new Set(languages.map(({ lang }) => lang))] themeConfig.codeSamples.availableLanguages = uniqueLanguages.map((lang) => { const language = languages.find(l => l.lang === lang) ?? availableLanguages.find(l => l.lang === lang) return language || { lang, label: lang, highlighter: 'plaintext' } }) } function getLinksPrefixesConfig() { return themeConfig.linksPrefixes } function setLinksPrefixesConfig(config: Partial<UnwrapNestedRefs<LinksPrefixesConfig>>) { if (config.tags) { (themeConfig.linksPrefixes as LinksPrefixesConfig).tags = config.tags } if (config.operations) { (themeConfig.linksPrefixes as LinksPrefixesConfig).operations = config.operations } } function getTagsLinkPrefix() { return themeConfig?.linksPrefixes?.tags } function getOperationsLinkPrefix() { return themeConfig?.linksPrefixes?.operations } function getServerConfig(): ServerConfig { return themeConfig.server as ServerConfig } function setServerConfig(config: Partial<UnwrapNestedRefs<ServerConfig>>) { if (config.allowCustomServer !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.server.allowCustomServer = config.allowCustomServer } if (config.getServers !== undefined) { // @ts-expect-error: This is a valid expression. themeConfig.server.getServers = config.getServers } } function getServerAllowCustomServer(): boolean { return themeConfig.server?.allowCustomServer || false } return { isDark, schemaConfig: themeConfig.requestBody, reset, getState, getLocale, setLocale, getHighlighterTheme, /** @deprecated Use `getRequestBodyDefaultView` instead. */ getSchemaDefaultView: getRequestBodyDefaultView, getRequestBodyDefaultView, /** @deprecated Use `setRequestBodyDefaultView` instead. */ setSchemaDefaultView: setRequestBodyDefaultView, setRequestBodyDefaultView, getShowBaseURL, setShowBaseURL, getJsonViewerDeep, setJsonViewerDeep, getJsonViewerRenderer, setJsonViewerRenderer, getSchemaViewerDeep, setSchemaViewerDeep, getHeadingLevels, getHeadingLevel, setHeadingLevels, getResponseCodeSelector, setResponseCodeSelector, getResponseCodeMaxTabs, setResponseCodeMaxTabs, getResponseBodyDefaultView, setResponseBodyDefaultView, getPlaygroundJsonEditorMode, setPlaygroundJsonEditorMode, getPlaygroundJsonEditorMainMenuBar, setPlaygroundJsonEditorMainMenuBar, getPlaygroundJsonEditorNavigationBar, setPlaygroundJsonEditorNavigationBar, getSecurityDefaultScheme, setSecurityDefaultScheme, getOperationBadges, setOperationBadges, getOperationSlots, setOperationSlots, getOperationHiddenSlots, setOperationHiddenSlots, getOperationCols, setOperationCols, getOperationDefaultBaseUrl, getOperationServers, getI18nConfig, setI18nConfig, getSpecConfig, getWrapExamples, setSpecConfig, getCodeSamplesLangs, getCodeSamplesDefaultLang, getCodeSamplesAvailableLanguages, getCodeSamplesGenerator, getCodeSamplesDefaultHeaders, setCodeSamplesConfig, getLinksPrefixesConfig, setLinksPrefixesConfig, getTagsLinkPrefix, getOperationsLinkPrefix, getServerConfig, setServerConfig, getServerAllowCustomServer, } }