11import type { OpenAPIV3 } from 'openapi-types'
2- import type { ApiReferenceConfiguration } from '@scalar/types'
32import { ElysiaOpenAPIConfig } from '../types'
43
54const elysiaCSS = `.light-mode {
@@ -123,10 +122,75 @@ const elysiaCSS = `.light-mode {
123122 filter: opacity(4%) saturate(200%);
124123}`
125124
125+ const serializeArrayWithFunctions = ( arr : unknown [ ] ) : string => {
126+ return `[${ arr . map ( ( item ) => ( typeof item === 'function' ? item . toString ( ) : JSON . stringify ( item ) ) ) . join ( ', ' ) } ]`
127+ }
128+
129+ /**
130+ * Generates the complete HTML script block required for Scalar setup, based on the provided configuration.
131+ *
132+ * This includes:
133+ * 1. The Scalar bundle script.
134+ * 2. An inline script that initializes the Scalar reference with user-provided configuration data.
135+ *
136+ * This function is adapted from the Scalar Core implementation.
137+ * @see https://github.com/scalar/scalar/blob/main/packages/core/src/libs/html-rendering/html-rendering.ts#L93
138+ *
139+ * @param config - The Scalar configuration object.
140+ * @returns A string containing all required <script> tags for embedding Scalar.
141+ */
142+ export function getScriptTags ( {
143+ cdn,
144+ ...configuration
145+ } : NonNullable < ElysiaOpenAPIConfig [ 'scalar' ] > ) {
146+ const restConfig = { ...configuration }
147+
148+ const functionProps : string [ ] = [ ]
149+
150+ for ( const [ key , value ] of Object . entries ( configuration ) as [
151+ keyof typeof configuration ,
152+ unknown
153+ ] [ ] ) {
154+ if ( typeof value === 'function' ) {
155+ functionProps . push ( `"${ key } ": ${ value . toString ( ) } ` )
156+ delete restConfig [ key ]
157+ } else if (
158+ Array . isArray ( value ) &&
159+ value . some ( ( item ) => typeof item === 'function' )
160+ ) {
161+ // Handle arrays that contain functions (like plugins)
162+ functionProps . push (
163+ `"${ key } ": ${ serializeArrayWithFunctions ( value ) } `
164+ )
165+ delete restConfig [ key ]
166+ }
167+ }
168+
169+ // Stringify the rest of the configuration
170+ const configString = JSON . stringify ( restConfig , null , 2 )
171+ . split ( '\n' )
172+ . map ( ( line , index ) => ( index === 0 ? line : ' ' + line ) )
173+ . join ( '\n' )
174+ . replace ( / \s * } $ / , '' ) // Remove the closing brace and any whitespace before it
175+
176+ const functionPropsString = functionProps . length
177+ ? `,\n ${ functionProps . join ( ',\n ' ) } \n }`
178+ : '}'
179+
180+ return `
181+ <!-- Scalar script -->
182+ <script src="${ cdn ?? 'https://cdn.jsdelivr.net/npm/@scalar/api-reference' } "></script>
183+
184+ <!-- Initialize the Scalar API Reference using provided config -->
185+ <script type="text/javascript">
186+ Scalar.createApiReference('#app', ${ configString } ${ functionPropsString } )
187+ </script>`
188+ }
189+
126190export const ScalarRender = (
127- info : OpenAPIV3 . InfoObject ,
128- config : NonNullable < ElysiaOpenAPIConfig [ 'scalar' ] > ,
129- embedSpec ?: string
191+ info : OpenAPIV3 . InfoObject ,
192+ config : NonNullable < ElysiaOpenAPIConfig [ 'scalar' ] > ,
193+ embedSpec ?: string
130194) => `<!doctype html>
131195<html>
132196 <head>
@@ -153,18 +217,7 @@ export const ScalarRender = (
153217 </style>
154218 </head>
155219 <body>
156- <script
157- id="api-reference"
158- data-configuration='${ JSON . stringify (
159- Object . assign (
160- config ,
161- {
162- content : embedSpec
163- }
164- )
165- ) } '
166- >
167- </script>
168- <script src="${ config . cdn } " crossorigin></script>
220+ <div id="app"></div>
221+ ${ getScriptTags ( Object . assign ( config , { content : embedSpec } ) ) }
169222 </body>
170223</html>`
0 commit comments