'use strict'; var Joi = require('joi'); var fp = require('fastify-plugin'); var ms = require('ms'); var femtocolor = require('femtocolor'); var Fastify = require('fastify'); var proxyPlugin = require('@fastify/http-proxy'); var staticPlugin = require('@fastify/static'); var path = require('path'); var timing = require('fastify-request-timing'); var getPort = require('get-port'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var Joi__default = /*#__PURE__*/_interopDefaultLegacy(Joi); var fp__default = /*#__PURE__*/_interopDefaultLegacy(fp); var ms__default = /*#__PURE__*/_interopDefaultLegacy(ms); var Fastify__default = /*#__PURE__*/_interopDefaultLegacy(Fastify); var proxyPlugin__default = /*#__PURE__*/_interopDefaultLegacy(proxyPlugin); var staticPlugin__default = /*#__PURE__*/_interopDefaultLegacy(staticPlugin); var timing__default = /*#__PURE__*/_interopDefaultLegacy(timing); var getPort__default = /*#__PURE__*/_interopDefaultLegacy(getPort); // this is a modified version of https://github.com/sindresorhus/date-time // but they don't provide a CJS build function dateTime() { let date = new Date(); let end = ''; // Offset the date so it will return the correct value when getting the ISO string. date = new Date(date.getTime() - (date.getTimezoneOffset() * 60000)); return date .toISOString() .replace(/T/, ' ') .replace(/\..+/, end) } const EOL = '\n'; const colorCodes = { 5: femtocolor.red, 4: femtocolor.yellow, 3: femtocolor.cyan, 2: femtocolor.green }; const getColor = status => colorCodes[Math.trunc(status / 100)]; const responseLogger = fp__default["default"](async server => { const logResponse = async (req, reply) => { const status = reply.statusCode; const c = getColor(status); const timing = ms__default["default"](parseInt(reply.getResponseTime())) || ''; server.log.info(femtocolor.bold(c(req.method)) + ' ' + req.url + c(' • ') + femtocolor.dim(status + ' • ' + timing)); }; server.addHook('onResponse', logResponse); }); const header = femtocolor.blue('⚡︎dev-server'); const prettifier = () => entry => { if (entry.reqId) return if (entry.msg) { if (entry.msg.includes('Server listening')) { const startup = entry.msg.replace('Server listening', header).split('at'); entry.msg = startup[0] + 'listening on' + femtocolor.bold(femtocolor.green(startup[1])); } const output = []; output.push(`[${dateTime()}]`, entry.msg); return output.join(' ') + EOL } }; const logDir = (server) => (dir) => server.log.info(header + ' serving ' + femtocolor.bold(dir)); const logSpa = (server, spaFile) => server.log.info(header + ' using fallback file ' + femtocolor.bold(spaFile)); const logProxy = (server, from, to) => server.log.info(header + ' proxying from ' + femtocolor.bold(femtocolor.yellow(from)) + ' to ' + femtocolor.bold(femtocolor.yellow(to))); const proxyItem = Joi__default["default"].object({ from: Joi__default["default"].string().uri({ relativeOnly: true }), to: Joi__default["default"].string().uri(), opts: Joi__default["default"].object() }); const schema = Joi__default["default"].alternatives().try( Joi__default["default"].string(), Joi__default["default"].object({ silent: Joi__default["default"].boolean(), force: Joi__default["default"].boolean(), proxy: Joi__default["default"].array().items(proxyItem), dirs: Joi__default["default"].array().items(Joi__default["default"].string()), dirname: Joi__default["default"].string(), spa: [Joi__default["default"].boolean(), Joi__default["default"].string()], port: Joi__default["default"].number().port(), forcePort: Joi__default["default"].boolean(), host: [Joi__default["default"].string().ip(), Joi__default["default"].string().hostname()], basePath: Joi__default["default"].string().uri({ relativeOnly: true }), extend: Joi__default["default"].function(), server: Joi__default["default"].object(), onListen: Joi__default["default"].function() }) ); const serverDefaults = Object.freeze({ ignoreTrailingSlash: true, disableRequestLogging: true }); const pluginServer = Object.freeze({ logger: { prettyPrint: { suppressFlushSyncWarning: true }, prettifier } }); const defaults = { proxy: [], dirs: ['.'], port: 8080, host: 'localhost', spa: false, silent: false, force: false, server: { ...pluginServer, ...serverDefaults }, basePath: undefined, extend: undefined, dirname: undefined, onListen: undefined }; const normalize = (rollupOptions = {}) => { const parsed = Joi__default["default"].attempt(rollupOptions, schema); const normalized = (typeof parsed === 'string') ? { dirs: [parsed] } : parsed; const serverConfig = Object.assign({}, defaults.server, normalized.server); const config = Object.assign({}, defaults, normalized); config.server = serverConfig; if (config.silent) config.server.logger = false; return config }; const createProxy = (server) => ({ from, to, opts }) => { logProxy(server, from, to); const prefix = from.endsWith('/') ? from.slice(0, -1) : from; const url = new URL(to); const upstream = url.origin; const rewritePrefix = url.pathname; server.register(proxyPlugin__default["default"], { prefix, upstream, rewritePrefix, undici: false, ...opts }); }; var proxy = fp__default["default"](async (server, { proxy = [] }) => proxy.forEach(createProxy(server))); var dirs = fp__default["default"](async (server, { basePath, dirs, dirname = '' }) => { const prefix = basePath; const root = dirs.map(dir => path.resolve(dirname, dir)); root.forEach(logDir(server)); server.register(staticPlugin__default["default"], { prefix, root }); }); var spa = fp__default["default"](async (server, { spa }) => { if (!spa) return const fallbackFile = (typeof spa === 'boolean') ? 'index.html' : spa; logSpa(server, fallbackFile); const spaHandler = async (_, reply) => reply.sendFile(fallbackFile); server.setNotFoundHandler(spaHandler); }); async function init(config = {}) { const server = Fastify__default["default"](config.server); await server.register(proxy, config); await server.register(dirs, config); await server.register(spa, { ...config, prefix: config.basePath }); await server.register(timing__default["default"]); await server.register(responseLogger); if (config.extend) await server.register(config.extend, config); await server.ready(); return server } async function boot(config) { try { const resolvedPort = config.forcePort ? config.port : await getPort__default["default"]({ host: (config.host ?? '127.0.0.0'), port: [config.port, ...getPort.makeRange(8081, 9000)] }); const server = await init(config); await server.listen(resolvedPort, config.host); if (config.onListen) config.onListen(server); return server } catch (err) { console.error(err); } } var index = (opts = {}) => { let booted = false; let server; return { name: 'dev-server', async closeWatcher() { booted = false; await server.server.close(); }, async writeBundle() { if (booted) return booted = true; try { const config = normalize(opts); if (!this.meta.watchMode) { if (!config.force) return else this.warn(`Starting dev-server even though we're not in watch mode`); } server = await boot(config); } catch (err) { this.error(err); } } } }; module.exports = index;