UNPKG

creevey

Version:

Cross-browser screenshot testing tool for Storybook with fancy UI Runner

230 lines 9.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.start = start; const fs_1 = __importDefault(require("fs")); const path_1 = __importDefault(require("path")); const http_1 = require("http"); const ws_1 = require("ws"); const url_1 = require("url"); const utils_js_1 = require("../utils.js"); const messages_js_1 = require("../messages.js"); const types_js_1 = require("../../types.js"); const logger_js_1 = require("../logger.js"); const index_js_1 = require("./handlers/index.js"); const tests_handler_js_1 = require("./handlers/tests-handler.js"); function json(handler, defaultValue) { return (request, response) => { const chunks = []; request.on('data', (chunk) => { chunks.push(chunk); }); request.on('end', () => { try { const body = Buffer.concat(chunks); const value = body.length === 0 ? defaultValue : JSON.parse(body.toString('utf-8')); handler(value); response.end(); } catch (error) { (0, logger_js_1.logger)().error('Failed to parse JSON', error); const errorMessage = error instanceof Error ? error.message : String(error); response.statusCode = 500; response.setHeader('Content-Type', 'text/plain'); response.end(`Failed to parse JSON: ${errorMessage}`); } }); request.on('error', (error) => { (0, logger_js_1.logger)().error('Failed to parse JSON', error); const errorMessage = error instanceof Error ? error.message : String(error); response.statusCode = 500; response.setHeader('Content-Type', 'text/plain'); response.end(`Failed to parse JSON: ${errorMessage}`); }); }; } function file(handler) { return (request, response) => { const parsedUrl = (0, url_1.parse)(request.url ?? '/', true); const requestedPath = decodeURIComponent(parsedUrl.pathname ?? '/'); try { const filePath = handler(requestedPath); if (filePath) { const stat = fs_1.default.statSync(filePath); // Set appropriate MIME type const ext = path_1.default.extname(filePath).toLowerCase(); const mimeTypes = { '.html': 'text/html', '.js': 'application/javascript', '.css': 'text/css', '.json': 'application/json', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', }; const contentType = mimeTypes[ext] || 'application/octet-stream'; response.statusCode = 200; response.setHeader('Content-Type', contentType); response.setHeader('Content-Length', stat.size); // Stream the file const stream = fs_1.default.createReadStream(filePath); stream.pipe(response); stream.on('error', (error) => { (0, logger_js_1.logger)().error('Error streaming file', error); if (!response.headersSent) { const errorMessage = error instanceof Error ? error.message : String(error); response.statusCode = 500; response.setHeader('Content-Type', 'text/plain'); response.end(`Internal server error: ${errorMessage}`); } }); } else { (0, logger_js_1.logger)().error('File not found', requestedPath); response.statusCode = 404; response.setHeader('Content-Type', 'text/plain'); response.end('File not found'); } } catch (error) { (0, logger_js_1.logger)().error('Failed to serve file', error); const errorMessage = error instanceof Error ? error.message : String(error); response.statusCode = 500; response.setHeader('Content-Type', 'text/plain'); response.end(`Failed to serve file: ${errorMessage}`); } }; } const importMetaUrl = (0, url_1.pathToFileURL)(__filename).href; function start(reportDir, port, ui = false, host) { let wss = null; let creeveyApi = null; let resolveApi = types_js_1.noop; const webDir = path_1.default.join(path_1.default.dirname((0, url_1.fileURLToPath)(importMetaUrl)), '../../client/web'); const server = (0, http_1.createServer)(); const routes = [ { path: '/ping', method: 'GET', handler: index_js_1.pingHandler, }, ...(ui ? [ { path: '/tests', method: 'POST', handler: json(tests_handler_js_1.testsHandler, { tests: {} }), }, ] : []), { path: '/stories', method: 'POST', handler: json(index_js_1.storiesHandler, { stories: [] }), }, { path: '/capture', method: 'POST', handler: json(index_js_1.captureHandler, { workerId: 0, options: undefined }), }, { path: '/report/', method: 'GET', handler: file((0, index_js_1.staticHandler)(reportDir, '/report/')), }, { path: '/', method: 'GET', handler: file((0, index_js_1.staticHandler)(webDir)), }, ]; const router = (request, response) => { const parsedUrl = (0, url_1.parse)(request.url ?? '/', true); const path = parsedUrl.pathname ?? '/'; const method = request.method ?? 'GET'; try { const route = routes.find((route) => path.startsWith(route.path) && route.method === method); if (route) { route.handler(request, response); } else { response.statusCode = 404; response.setHeader('Content-Type', 'text/plain'); response.end('Not Found'); } } catch (error) { (0, logger_js_1.logger)().error('Request handling error', error); response.statusCode = 500; response.setHeader('Content-Type', 'text/plain'); response.end('Internal Server Error'); } }; server.on('request', (request, response) => { response.setHeader('Access-Control-Allow-Origin', '*'); response.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); response.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept'); if (request.method === 'OPTIONS') { response.statusCode = 200; response.end(); return; } router(request, response); }); if (ui) { wss = new ws_1.WebSocketServer({ server }); wss.on('connection', (ws) => { ws.on('message', (message, isBinary) => { if (creeveyApi) { // NOTE Text messages are passed as Buffer https://github.com/websockets/ws/releases/tag/8.0.0 // eslint-disable-next-line @typescript-eslint/no-base-to-string creeveyApi.handleMessage(ws, isBinary ? message : message.toString('utf-8')); return; } }); ws.on('error', (error) => { (0, logger_js_1.logger)().error('WebSocket error', error); }); }); wss.on('error', (error) => { (0, logger_js_1.logger)().error('WebSocket error', error); }); } (0, messages_js_1.subscribeOn)('shutdown', () => { if (wss) { wss.clients.forEach((ws) => { ws.close(); }); wss.close(() => { server.close(); }); } else { server.close(); } }); server .listen(port, host, () => { (0, logger_js_1.logger)().info(`Server starting on port ${port}`); }) .on('error', (error) => { (0, logger_js_1.logger)().error('Failed to start server', error); process.exit(1); }); void new Promise((resolve) => (resolveApi = resolve)) .then((api) => { creeveyApi = api; if (wss) { creeveyApi.subscribe(wss); } }) .catch(utils_js_1.shutdownOnException); // Return the function to resolve the API return resolveApi; } //# sourceMappingURL=server.js.map