UNPKG

rasengan

Version:

The modern React Framework

193 lines (192 loc) 7.66 kB
import chalk from 'chalk'; import ora from 'ora'; import fs from 'fs/promises'; import openBrowser from 'open'; import os from 'node:os'; import readline from 'node:readline'; // Get local IP export default function getIPAddress() { // Get network interfaces const networkInterfaces = os.networkInterfaces(); // Find the IPv4 address for the default network interface let ipAddress = ''; // Loop through the network interfaces for (const interfaceName in networkInterfaces) { // Get the network interface const iface = networkInterfaces[interfaceName]; // Skip when there is no network interface if (!iface) { continue; } // Loop through the interface addresses for (let i = 0; i < iface.length; i++) { const alias = iface[i]; if (alias.family === 'IPv4' && !alias.internal) { ipAddress = alias.address; break; } } if (ipAddress) { break; } } return ipAddress; } /** * Log server info after the server is started * @param {number} port The port the server is running on * @param {boolean} isProduction Whether the server is running in production mode * @param {boolean} open Whether to open the browser automatically */ export async function logServerInfo(port, mode, open = false) { // Constants const arrowRight = '\u2192'; // Spinner console.log(''); const spinner = ora({ text: `Starting server in ${mode} mode...`, color: 'blue', spinner: 'dots', }).start(); // Getting the package.json file let packageJson = await fs.readFile('node_modules/rasengan/package.json', 'utf-8'); // Parsing the package.json file const parsedPackageJson = JSON.parse(packageJson); spinner.succeed(`${chalk.bold.blue(`Rasengan v${parsedPackageJson['version']}`)} is running 🚀`); console.log('\n\n'); process.stdout.write(`${chalk.bold.green(arrowRight)} ${chalk.bold('Local:')} ${chalk.blue(`http://localhost:${chalk.bold(port)}`)}`); console.log(''); // Get the IP address of the machine const ipAddress = getIPAddress(); if (ipAddress) { process.stdout.write(`${chalk.bold.green(arrowRight)} ${chalk.bold('Network:')} ${chalk.blue(`http://${ipAddress}:${chalk.bold(port)}`)}\n`); } // Display options process.stdout.write(`${chalk.bold.green(arrowRight)} ${chalk.gray('Use')} ${chalk.bold('-p <PORT>')} ${chalk.gray('to specify a custom port')}\n`); process.stdout.write(`${chalk.bold.green(arrowRight)} ${chalk.gray('Press')} ${chalk.bold('c')} ${chalk.gray('to clear')}\n`); process.stdout.write(`${chalk.bold.green(arrowRight)} ${chalk.gray('Press')} ${chalk.bold('ctrl + c')} ${chalk.gray('to close the server')}\n`); console.log('\n'); // Open the browser if (open) { openBrowser(`http://localhost:${port}`); } readline.emitKeypressEvents(process.stdin); // Listen on user keyboard input on the terminal process.stdin.on('keypress', (_, key) => { // Check if the key pressed is 'c' if (key) { if (key.name === 'c' && key.ctrl) { console.log(`${chalk.green('ctrl + c')} ${chalk.gray('pressed: ')} ${chalk.blue('Closing server... \n\n')}`); process.exit(0); } else if (key.name === 'c' && !key.ctrl && !key.meta && !key.shift) { // Clear terminal process.stdout.write('\x1Bc'); logServerInfo(port, mode); } } }); // Allow the process to exit when Ctrl+C is pressed if (process.stdin.setRawMode instanceof Function) { process.stdin.setRawMode(true); } process.stdin.resume(); // Set a higher limit for the number of listeners process.stdin.setMaxListeners(100); } /** * This function extracts the meta data from the React Router context * @param context React Router context */ export function extractMetaFromRRContext(context) { const leaf = context.matches[context.matches.length - 1]; // the last match is the leaf (the actual page requested) // Get the loader id const pageLoaderId = leaf.route.id; // The id follows the pattern "[layoutIndex]-[pageIndex]", eg. 0-1, 1-2, etc. const layoutLoaderId = pageLoaderId.split('-')[0]; // Get the meta from the loader based on the loader id const pageLoaderData = context.loaderData[pageLoaderId] ?? { meta: {} }; // This is the loader data specific to the page const layoutLoaderData = context.loaderData[layoutLoaderId] ?? { meta: {} }; // This is the loader data specific to the layout // Get the meta from the loaders data const pageMeta = pageLoaderData.meta; const layoutMeta = layoutLoaderData.meta; return { page: pageMeta, layout: layoutMeta, }; } /** * This function extracts the headers from the React Router context * @param context React Router context */ export function extractHeadersFromRRContext(context) { // Setup headers from action and loaders from deepest match let leaf = context.matches[context.matches.length - 1]; let actionHeaders = context.actionHeaders[leaf.route.id]; let loaderHeaders = context.loaderHeaders[leaf.route.id]; let headers = new Headers(actionHeaders); if (loaderHeaders) { for (let [key, value] of loaderHeaders.entries()) { headers.append(key, value); } } headers.set('Content-Type', 'text/html; charset=utf-8'); return headers; } /** * Check if the request is a document request * @param request Express request object */ export function isDocumentRequest(request) { // Check if the request accepts HTML in header const accept = request.headers['accept'] || ''; return accept.includes('text/html'); } export function isDataRequest(request) { // Check if the request accepts JSON (React Router's fetch requests) const acceptsJson = request.headers['accept']?.includes('application/json'); // Check if the URL path follows the `.data` pattern const isDataPath = request.url?.endsWith('.data'); return acceptsJson || isDataPath; } export function isResourceRequest(request) { const accept = request.headers['accept'] || ''; // Check common resource-related MIME types in the Accept header if (accept.includes('image/') || // Images accept.includes('font/') || // Fonts accept.includes('text/css') || // Stylesheets accept.includes('application/javascript') || // JavaScript accept.includes('application/json') // JSON (if resources include it) ) { return true; } // Check if the URL includes common resource path segments const url = request.originalUrl || ''; if (url.includes('/assets/') || url.includes('/static/') || url.includes('/public/')) { return true; } // Exclude cases that are clearly document or API requests if (isDocumentRequest(request)) { return false; } // Fallback: Treat unknown requests as resource requests return true; } /** * Check if the request is an Redirect request * @param context Response context */ export function isRedirectResponse(context) { return context.status === 302 || context.status === 301; } export async function isStaticRedirectFromConfig(req, redirects) { let redirectFound = false; for (let redirect of redirects) { if (redirect.source === req.originalUrl) { redirectFound = true; break; } } return redirectFound; }