import { compile } from 'handlebars'; import * as colorette from 'colorette'; import * as portfinder from 'portfinder'; import { readFileSync, promises as fsPromises } from 'fs'; import * as path from 'path'; import { startHttpServer, startWsServer, respondWithGzip, mimeTypes } from './server'; import type { IncomingMessage } from 'http'; function getPageHTML( htmlTemplate: string, redocOptions: object = {}, useRedocPro: boolean, wsPort: number, ) { let templateSrc = readFileSync(htmlTemplate, 'utf-8'); // fix template for backward compatibility templateSrc = templateSrc .replace(/{?{{redocHead}}}?/, '{{{redocHead}}}') .replace('{{redocBody}}', '{{{redocHTML}}}'); const template = compile(templateSrc); return template({ redocHead: ` `, redocHTML: `
`, }); } export default async function startPreviewServer( port: number, { getBundle, getOptions, useRedocPro, }: { getBundle: Function; getOptions: Function; useRedocPro: boolean }, ) { const defaultTemplate = path.join(__dirname, 'default.hbs'); const handler = async (request: IncomingMessage, response: any) => { console.time(colorette.dim(`GET ${request.url}`)); const { htmlTemplate } = getOptions() || {}; if (request.url === '/') { respondWithGzip( getPageHTML(htmlTemplate || defaultTemplate, getOptions(), useRedocPro, wsPort), request, response, { 'Content-Type': 'text/html', }, ); } else if (request.url === '/openapi.json') { const bundle = await getBundle(); if (bundle === undefined) { respondWithGzip( JSON.stringify({ openapi: '3.0.0', info: { description: ' Failed to generate bundle: check out console output for more details ', }, paths: {}, }), request, response, { 'Content-Type': 'application/json', }, ); } else { respondWithGzip(JSON.stringify(bundle), request, response, { 'Content-Type': 'application/json', }); } } else { const filePath = // @ts-ignore { '/hot.js': path.join(__dirname, 'hot.js'), '/simplewebsocket.min.js': require.resolve('simple-websocket/simplewebsocket.min.js'), }[request.url || ''] || path.resolve(htmlTemplate ? path.dirname(htmlTemplate) : process.cwd(), `.${request.url}`); const extname = String(path.extname(filePath)).toLowerCase() as keyof typeof mimeTypes; const contentType = mimeTypes[extname] || 'application/octet-stream'; try { respondWithGzip(await fsPromises.readFile(filePath), request, response, { 'Content-Type': contentType, }); } catch (e) { if (e.code === 'ENOENT') { respondWithGzip('404 Not Found', request, response, { 'Content-Type': 'text/html' }, 404); } else { respondWithGzip( `Something went wrong: ${e.code || e.message}...\n`, request, response, {}, 500, ); } } } console.timeEnd(colorette.dim(`GET ${request.url}`)); }; let wsPort = await portfinder.getPortPromise({ port: 32201 }); const server = startHttpServer(port, handler); server.on('listening', () => { process.stdout.write( `\n 🔎 Preview server running at ${colorette.blue(`http://127.0.0.1:${port}\n`)}`, ); }); return startWsServer(wsPort); }