UNPKG

@strapi/strapi

Version:

An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite

137 lines (133 loc) 5.75 kB
'use strict'; var path = require('node:path'); var http = require('node:http'); var fs = require('node:fs/promises'); var node_net = require('node:net'); var config = require('./config.js'); const HMR_DEFAULT_PORT = 5173; const MAX_PORT_ATTEMPTS = 30; const findAvailablePort = (startingPort, attemptsLeft = MAX_PORT_ATTEMPTS)=>{ return new Promise((resolve, reject)=>{ if (attemptsLeft <= 0) { reject(new Error(`No available ports found after ${MAX_PORT_ATTEMPTS} attempts.`)); return; } const server = new node_net.Server(); server.listen(startingPort, ()=>{ const { port } = server.address(); server.close(()=>resolve(port)); }); server.on('error', (err)=>{ if (err.code === 'EADDRINUSE') { resolve(findAvailablePort(startingPort + 1, attemptsLeft - 1)); } else { reject(err); } }); }); }; const createHMRServer = ()=>{ return http.createServer(// http server request handler. keeps the same with // https://github.com/websockets/ws/blob/45e17acea791d865df6b255a55182e9c42e5877a/lib/websocket-server.js#L88-L96 (_, res)=>{ const body = http.STATUS_CODES[426]; // Upgrade Required res.writeHead(426, { 'Content-Length': body?.length ?? 0, 'Content-Type': 'text/plain' }); res.end(body); }); }; const watch = async (ctx)=>{ const hmrServer = createHMRServer(); // Allowing Vite to find an available port doesn't work, so we'll find an available port manually // and use that. There is therefore a very slight race condition if you start up two servers at the same time // one might fail, or it might start up but listen on the wrong port. const availablePort = await findAvailablePort(HMR_DEFAULT_PORT); ctx.options.hmrServer = hmrServer; ctx.options.hmrClientPort = availablePort; const config$1 = await config.resolveDevelopmentConfig(ctx); const finalConfig = await config.mergeConfigWithUserConfig(config$1, ctx); const hmrConfig = config$1.server?.hmr; // If the server used for Vite hmr is the one we've created (<> no user override) if (typeof hmrConfig === 'object' && hmrConfig.server === hmrServer) { // Only restart the hmr server when Strapi's server is listening strapi.server.httpServer.on('listening', async ()=>{ hmrServer.listen(availablePort); }); } ctx.logger.debug('Vite config', finalConfig); const { createServer } = await import('vite'); const vite = await createServer(finalConfig); const viteMiddlewares = (koaCtx, next)=>{ return new Promise((resolve, reject)=>{ const prefix = ctx.basePath.replace(ctx.adminPath, '').replace(/\/+$/, ''); const originalPath = koaCtx.path; if (!koaCtx.path.startsWith(prefix)) { koaCtx.path = `${prefix}${koaCtx.path}`; } // Set cache-control headers to prevent caching issues during development restarts koaCtx.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate'); koaCtx.set('Pragma', 'no-cache'); koaCtx.set('Expires', '0'); koaCtx.set('Surrogate-Control', 'no-store'); vite.middlewares(koaCtx.req, koaCtx.res, (err)=>{ if (err) { reject(err); } else { if (!koaCtx.res.headersSent) { koaCtx.path = originalPath; } resolve(next()); } }); }); }; const serveAdmin = async (koaCtx, next)=>{ await next(); if (koaCtx.method !== 'HEAD' && koaCtx.method !== 'GET') { return; } if (koaCtx.body != null || koaCtx.status !== 404) { return; } const url = koaCtx.originalUrl; try { let template = await fs.readFile(path.relative(ctx.cwd, '.strapi/client/index.html'), 'utf-8'); template = await vite.transformIndexHtml(url, template); koaCtx.type = 'html'; koaCtx.body = template; } catch (error) { ctx.logger.error('Failed to serve admin panel in development mode:', error); // Don't fallback to other handlers in development mode to prevent MIME type conflicts koaCtx.status = 500; koaCtx.body = 'Admin panel temporarily unavailable during server restart'; } }; const adminRoute = `${ctx.adminPath}/:path*`; // Remove any existing admin routes to prevent conflicts during restart const existingRoutes = ctx.strapi.server.router.stack.filter((layer)=>layer.path === adminRoute); existingRoutes.forEach((route)=>{ const index = ctx.strapi.server.router.stack.indexOf(route); if (index > -1) { ctx.strapi.server.router.stack.splice(index, 1); } }); ctx.strapi.server.router.get(adminRoute, serveAdmin); ctx.strapi.server.router.use(adminRoute, viteMiddlewares); return { async close () { await vite.close(); if (hmrServer.listening) { // Manually close the hmr server // /!\ This operation MUST be done after calling .close() on the vite // instance to avoid flaky behaviors with attached clients await new Promise((resolve, reject)=>{ hmrServer.close((err)=>err ? reject(err) : resolve()); }); } } }; }; exports.watch = watch; //# sourceMappingURL=watch.js.map