UNPKG

@compodoc/compodoc

Version:

The missing documentation tool for your Angular application

884 lines (880 loc) 440 kB
'use strict'; var logger = require('./logger-kbUbohEP.js'); var path = require('path'); var fs = require('fs-extra'); var http = require('http'); var crypto = require('crypto'); var os = require('os'); var child_process = require('child_process'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path); var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http); var crypto__namespace = /*#__PURE__*/_interopNamespaceDefault(crypto); var os__namespace = /*#__PURE__*/_interopNamespaceDefault(os); var polka = require('polka'); var sirv = require('sirv'); var _a = require('body-parser'), json = _a.json, urlencoded = _a.urlencoded; var send = require('@polka/send-type'); var archiver = require('archiver'); var TemplatePlaygroundServer = /** @class */ (function () { function TemplatePlaygroundServer(port) { this.sessions = new Map(); this.ipToSessionId = new Map(); this.debounceTimers = new Map(); this.signalHandlers = new Map(); this.port = port || parseInt(process.env.PLAYGROUND_PORT || process.env.PORT || '3001', 10); this.app = polka(); this.setupPaths(); this.initializeHandlebars(); this.setupMiddleware(); this.setupRoutes(); this.startSessionCleanup(); this.setupSignalHandlers(); } /** * Get the underlying HTTP server instance for testing purposes * @returns HTTP server instance or null if not started */ TemplatePlaygroundServer.prototype.getHttpServer = function () { var _a; // Polka stores the actual HTTP server in the .server property // This is needed for Supertest compatibility which expects a Node.js HTTP server return ((_a = this.server) === null || _a === void 0 ? void 0 : _a.server) || null; }; TemplatePlaygroundServer.prototype.setupSignalHandlers = function () { var _this = this; // Skip signal handlers entirely in test environment to prevent memory leaks if (process.env.NODE_ENV === 'test') { return; } // Handle CTRL+C (SIGINT) and other termination signals var signals = ['SIGINT', 'SIGTERM', 'SIGUSR2']; signals.forEach(function (signal) { var handler = function () { return logger.__awaiter(_this, void 0, void 0, function () { var error_1; return logger.__generator(this, function (_a) { switch (_a.label) { case 0: logger.logger.info("Received ".concat(signal, ", shutting down Template Playground server gracefully...")); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.stop()]; case 2: _a.sent(); logger.logger.info('Server shutdown complete'); process.exit(0); return [3 /*break*/, 4]; case 3: error_1 = _a.sent(); logger.logger.error('Error during server shutdown:', error_1); process.exit(1); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }; _this.signalHandlers.set(signal, handler); process.on(signal, handler); }); // Handle uncaught exceptions (only if not already handled) if (process.listenerCount('uncaughtException') === 0) { var uncaughtHandler = function (error) { return logger.__awaiter(_this, void 0, void 0, function () { var stopError_1; return logger.__generator(this, function (_a) { switch (_a.label) { case 0: logger.logger.error('Uncaught exception:', error); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.stop()]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: stopError_1 = _a.sent(); logger.logger.error('Error during emergency shutdown:', stopError_1); return [3 /*break*/, 4]; case 4: process.exit(1); return [2 /*return*/]; } }); }); }; this.signalHandlers.set('uncaughtException', uncaughtHandler); process.on('uncaughtException', uncaughtHandler); } // Handle unhandled promise rejections (only if not already handled) if (process.listenerCount('unhandledRejection') === 0) { var rejectionHandler = function (reason, promise) { return logger.__awaiter(_this, void 0, void 0, function () { var stopError_2; return logger.__generator(this, function (_a) { switch (_a.label) { case 0: logger.logger.error('Unhandled rejection at:', promise, 'reason:', reason); _a.label = 1; case 1: _a.trys.push([1, 3, , 4]); return [4 /*yield*/, this.stop()]; case 2: _a.sent(); return [3 /*break*/, 4]; case 3: stopError_2 = _a.sent(); logger.logger.error('Error during emergency shutdown:', stopError_2); return [3 /*break*/, 4]; case 4: process.exit(1); return [2 /*return*/]; } }); }); }; this.signalHandlers.set('unhandledRejection', rejectionHandler); process.on('unhandledRejection', rejectionHandler); } }; TemplatePlaygroundServer.prototype.setupPaths = function () { // Try to find paths for distributed package first, then fall back to development paths // For playground-demo: check resources/playground-demo first, then src directory var distributedFakeProjectPath = path__namespace.join(__dirname, 'resources', 'playground-demo'); var devFakeProjectPath = path__namespace.join(process.cwd(), 'src', 'playground-demo'); if (fs__namespace.existsSync(distributedFakeProjectPath)) { this.fakeProjectPath = distributedFakeProjectPath; } else if (fs__namespace.existsSync(devFakeProjectPath)) { this.fakeProjectPath = devFakeProjectPath; } else { throw new Error('playground-demo directory not found. Please ensure it exists.'); } // For templates: check if we're running from dist (distributed) or development var distributedTemplatesPath = path__namespace.join(__dirname, 'templates'); // When running from dist/, this is dist/templates var devTemplatesPath = path__namespace.join(process.cwd(), 'src', 'templates'); var legacyTemplatesPath = path__namespace.join(process.cwd(), 'hbs-templates-copy'); if (fs__namespace.existsSync(distributedTemplatesPath)) { this.originalTemplatesPath = distributedTemplatesPath; } else if (fs__namespace.existsSync(devTemplatesPath)) { this.originalTemplatesPath = devTemplatesPath; } else if (fs__namespace.existsSync(legacyTemplatesPath)) { // Keep legacy support for existing hbs-templates-copy this.originalTemplatesPath = legacyTemplatesPath; } else { throw new Error('Templates directory not found. Please ensure src/templates or dist/templates exists.'); } }; TemplatePlaygroundServer.prototype.getClientIP = function (req) { var _a; // Get IP address from various headers (handles proxies, load balancers, etc.) var forwarded = req.headers['x-forwarded-for']; var realIP = req.headers['x-real-ip']; var remoteAddr = ((_a = req.socket) === null || _a === void 0 ? void 0 : _a.remoteAddress) || 'unknown'; var ip = (forwarded === null || forwarded === void 0 ? void 0 : forwarded.split(',')[0]) || realIP || remoteAddr || 'unknown'; // Clean up IPv6 localhost if (ip === '::1' || ip === '::ffff:127.0.0.1') { ip = '127.0.0.1'; } return ip; }; TemplatePlaygroundServer.prototype.generateSessionIdFromIP = function (ip) { // Create a consistent hash from IP address return crypto__namespace.createHash('md5').update(ip + 'template-playground-salt').digest('hex'); }; TemplatePlaygroundServer.prototype.createOrGetSessionByIP = function (ip) { // Check if session already exists for this IP var existingSessionId = this.ipToSessionId.get(ip); if (existingSessionId && this.sessions.has(existingSessionId)) { var session_1 = this.sessions.get(existingSessionId); // Update last activity session_1.lastActivity = Date.now(); logger.logger.info("\u267B\uFE0F Reusing existing session for IP ".concat(ip, ": ").concat(existingSessionId)); return session_1; } // Create new session var sessionId = this.generateSessionIdFromIP(ip); var templateDir = path__namespace.join(os__namespace.tmpdir(), "hbs-templates-copy-".concat(sessionId)); var documentationDir = path__namespace.join(os__namespace.tmpdir(), "generated-documentation-".concat(sessionId)); // Clean up any existing directories from previous sessions if (fs__namespace.existsSync(templateDir)) { fs__namespace.removeSync(templateDir); } if (fs__namespace.existsSync(documentationDir)) { fs__namespace.removeSync(documentationDir); } // Copy original templates to session directory fs__namespace.copySync(this.originalTemplatesPath, templateDir); fs__namespace.ensureDirSync(documentationDir); var session = { id: sessionId, templateDir: templateDir, documentationDir: documentationDir, lastActivity: Date.now(), config: { hideGenerator: false, disableSourceCode: false, disableGraph: false, disableCoverage: false, disablePrivate: false, disableProtected: false, disableInternal: false } }; this.sessions.set(sessionId, session); this.ipToSessionId.set(ip, sessionId); logger.logger.info("\uD83C\uDD95 Created new session for IP ".concat(ip, ": ").concat(sessionId)); // Generate initial documentation (skip in test mode to avoid template issues) if (process.env.NODE_ENV !== 'test') { this.generateDocumentation(sessionId); } return session; }; TemplatePlaygroundServer.prototype.createNewSession = function (ip) { // Generate a unique session ID (not based on IP) var sessionId = crypto__namespace.randomBytes(16).toString('hex'); var templateDir = path__namespace.join(os__namespace.tmpdir(), "hbs-templates-copy-".concat(sessionId)); var documentationDir = path__namespace.join(os__namespace.tmpdir(), "generated-documentation-".concat(sessionId)); // Clean up any existing directories from previous sessions if (fs__namespace.existsSync(templateDir)) { fs__namespace.removeSync(templateDir); } if (fs__namespace.existsSync(documentationDir)) { fs__namespace.removeSync(documentationDir); } // Copy original templates to session directory fs__namespace.copySync(this.originalTemplatesPath, templateDir); fs__namespace.ensureDirSync(documentationDir); var session = { id: sessionId, templateDir: templateDir, documentationDir: documentationDir, lastActivity: Date.now(), config: { hideGenerator: false, disableSourceCode: false, disableGraph: false, disableCoverage: false, disablePrivate: false, disableProtected: false, disableInternal: false, disableFilePath: false, disableOverview: false } }; this.sessions.set(sessionId, session); // Don't update ipToSessionId mapping for new sessions to allow multiple sessions per IP logger.logger.info("\uD83C\uDD95 Created new session for IP ".concat(ip, ": ").concat(sessionId)); // Generate initial documentation (skip in test mode to avoid template issues) if (process.env.NODE_ENV !== 'test') { this.generateDocumentation(sessionId); } return session; }; TemplatePlaygroundServer.prototype.updateSessionActivity = function (sessionId) { var session = this.sessions.get(sessionId); if (session) { session.lastActivity = Date.now(); } }; TemplatePlaygroundServer.prototype.generateDocumentation = function (sessionId, debounce) { var _this = this; if (debounce === void 0) { debounce = false; } if (debounce) { // Clear existing timer var existingTimer = this.debounceTimers.get(sessionId); if (existingTimer) { clearTimeout(existingTimer); } // Set new timer for 300ms var timer = setTimeout(function () { _this.runCompoDocForSession(sessionId); _this.debounceTimers.delete(sessionId); }, 300); this.debounceTimers.set(sessionId, timer); } else { // Generate immediately this.runCompoDocForSession(sessionId); } }; TemplatePlaygroundServer.prototype.runCompoDocForSession = function (sessionId) { return logger.__awaiter(this, void 0, void 0, function () { var session, fakeProjectTsConfigPath, cliPath, cmd, config, booleanFlags, valueFlags, booleanFlags_1, booleanFlags_1_1, flag, valueFlags_1, valueFlags_1_1, flag, value, fullCmd; var e_1, _a, e_2, _b; return logger.__generator(this, function (_c) { session = this.sessions.get(sessionId); if (!session) { logger.logger.error("Session ".concat(sessionId, " not found")); return [2 /*return*/]; } try { logger.logger.info("\uD83D\uDE80 Generating documentation for session ".concat(sessionId)); fakeProjectTsConfigPath = path__namespace.join(this.fakeProjectPath, 'tsconfig.json'); cliPath = path__namespace.resolve(process.cwd(), 'bin', 'index-cli.js'); // In test mode, check if CLI exists before proceeding if (process.env.NODE_ENV === 'test' && !fs__namespace.existsSync(cliPath)) { logger.logger.warn("CLI not found in test environment: ".concat(cliPath, ". Skipping documentation generation.")); session.documentationGenerated = true; // Mark as generated to avoid retries return [2 /*return*/]; } cmd = [ "node \"".concat(cliPath, "\""), "-p \"".concat(fakeProjectTsConfigPath, "\""), "-d \"".concat(session.documentationDir, "\""), "--templates \"".concat(session.templateDir, "\"") ]; config = session.config || {}; booleanFlags = [ 'hideGenerator', 'disableSourceCode', 'disableGraph', 'disableCoverage', 'disablePrivate', 'disableProtected', 'disableInternal', 'disableLifeCycleHooks', 'disableConstructors', 'disableRoutesGraph', 'disableSearch', 'disableDependencies', 'disableProperties', 'disableDomTree', 'disableTemplateTab', 'disableStyleTab', 'disableMainGraph', 'disableFilePath', 'disableOverview', 'hideDarkModeToggle', 'minimal', 'serve', 'open', 'watch', 'silent', 'coverageTest', 'coverageTestThresholdFail', 'coverageTestShowOnlyFailed' ]; valueFlags = [ 'theme', 'language', 'base', 'customFavicon', 'customLogo', 'assetsFolder', 'extTheme', 'includes', 'includesName', 'output', 'port', 'hostname', 'exportFormat', 'coverageTestThreshold', 'coverageMinimumPerFile', 'unitTestCoverage', 'gaID', 'gaSite', 'maxSearchResults', 'toggleMenuItems', 'navTabConfig' ]; try { for (booleanFlags_1 = logger.__values(booleanFlags), booleanFlags_1_1 = booleanFlags_1.next(); !booleanFlags_1_1.done; booleanFlags_1_1 = booleanFlags_1.next()) { flag = booleanFlags_1_1.value; if (config[flag] === true) { cmd.push("--".concat(flag)); } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (booleanFlags_1_1 && !booleanFlags_1_1.done && (_a = booleanFlags_1.return)) _a.call(booleanFlags_1); } finally { if (e_1) throw e_1.error; } } try { for (valueFlags_1 = logger.__values(valueFlags), valueFlags_1_1 = valueFlags_1.next(); !valueFlags_1_1.done; valueFlags_1_1 = valueFlags_1.next()) { flag = valueFlags_1_1.value; if (config[flag] !== undefined && config[flag] !== "") { value = config[flag]; // For arrays/objects, stringify if (Array.isArray(value) || typeof value === 'object') { value = JSON.stringify(value); } cmd.push("--".concat(flag, " \"").concat(value, "\"")); } } } catch (e_2_1) { e_2 = { error: e_2_1 }; } finally { try { if (valueFlags_1_1 && !valueFlags_1_1.done && (_b = valueFlags_1.return)) _b.call(valueFlags_1); } finally { if (e_2) throw e_2.error; } } fullCmd = cmd.join(' '); logger.logger.info("\uD83D\uDE80 Executing CompoDoc command: ".concat(fullCmd)); // Log the command to a file for debugging require('fs').appendFileSync('server-commands.log', "".concat(new Date().toISOString(), " - ").concat(fullCmd, "\n")); // Execute with proper error handling (inherit stdio to see errors) child_process.execSync(fullCmd, { cwd: process.cwd(), stdio: 'inherit' // Show output/errors instead of hiding them }); this.updateSessionActivity(sessionId); logger.logger.info("\u2705 Documentation generated successfully for session ".concat(sessionId)); } catch (error) { logger.logger.error("\u274C Error generating documentation for session ".concat(sessionId, ":"), error); } return [2 /*return*/]; }); }); }; TemplatePlaygroundServer.prototype.startSessionCleanup = function () { var _this = this; // Clean up sessions older than 1 hour every 10 minutes this.cleanupInterval = setInterval(function () { var e_3, _a; var cutoffTime = Date.now() - (60 * 60 * 1000); // 1 hour ago try { for (var _b = logger.__values(_this.sessions.entries()), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = logger.__read(_c.value, 2), sessionId = _d[0], session = _d[1]; if (session.lastActivity < cutoffTime) { _this.cleanupSession(sessionId); } } } catch (e_3_1) { e_3 = { error: e_3_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_3) throw e_3.error; } } }, 10 * 60 * 1000); // Every 10 minutes }; TemplatePlaygroundServer.prototype.cleanupSession = function (sessionId) { var e_4, _a; var session = this.sessions.get(sessionId); if (session) { try { // Remove directories if (fs__namespace.existsSync(session.templateDir)) { fs__namespace.removeSync(session.templateDir); } if (fs__namespace.existsSync(session.documentationDir)) { fs__namespace.removeSync(session.documentationDir); } // Clear timer if exists var timer = this.debounceTimers.get(sessionId); if (timer) { clearTimeout(timer); this.debounceTimers.delete(sessionId); } try { // Remove IP mapping for (var _b = logger.__values(this.ipToSessionId.entries()), _c = _b.next(); !_c.done; _c = _b.next()) { var _d = logger.__read(_c.value, 2), ip = _d[0], id = _d[1]; if (id === sessionId) { this.ipToSessionId.delete(ip); break; } } } catch (e_4_1) { e_4 = { error: e_4_1 }; } finally { try { if (_c && !_c.done && (_a = _b.return)) _a.call(_b); } finally { if (e_4) throw e_4.error; } } this.sessions.delete(sessionId); logger.logger.info("\uD83E\uDDF9 Cleaned up session: ".concat(sessionId)); } catch (error) { logger.logger.error("Error cleaning up session ".concat(sessionId, ":"), error); } } }; TemplatePlaygroundServer.prototype.initializeHandlebars = function () { this.handlebars = require('handlebars'); this.registerHandlebarsHelpers(this.handlebars, {}); }; TemplatePlaygroundServer.prototype.registerAvailablePartials = function () { return logger.__awaiter(this, void 0, void 0, function () { var partialsDir, partialFiles, partialFiles_1, partialFiles_1_1, file, partialName, partialPath, partialContent; var e_5, _a; return logger.__generator(this, function (_b) { try { partialsDir = path__namespace.join(process.cwd(), 'dist/templates/partials'); logger.logger.info("\uD83D\uDD0D Looking for partials in: ".concat(partialsDir)); logger.logger.info("\uD83D\uDD0D Partials directory exists: ".concat(fs__namespace.existsSync(partialsDir))); if (fs__namespace.existsSync(partialsDir)) { partialFiles = fs__namespace.readdirSync(partialsDir).filter(function (file) { return file.endsWith('.hbs'); }); logger.logger.info("\uD83D\uDCC1 Found ".concat(partialFiles.length, " partial files: ").concat(JSON.stringify(partialFiles))); try { for (partialFiles_1 = logger.__values(partialFiles), partialFiles_1_1 = partialFiles_1.next(); !partialFiles_1_1.done; partialFiles_1_1 = partialFiles_1.next()) { file = partialFiles_1_1.value; partialName = file.replace('.hbs', ''); partialPath = path__namespace.join(partialsDir, file); partialContent = fs__namespace.readFileSync(partialPath, 'utf8'); // Register the partial this.handlebars.registerPartial(partialName, partialContent); logger.logger.info("\u2705 Registered partial: ".concat(partialName)); } } catch (e_5_1) { e_5 = { error: e_5_1 }; } finally { try { if (partialFiles_1_1 && !partialFiles_1_1.done && (_a = partialFiles_1.return)) _a.call(partialFiles_1); } finally { if (e_5) throw e_5.error; } } } else { logger.logger.warn("\u26A0\uFE0F Partials directory not found at: ".concat(partialsDir)); } } catch (error) { logger.logger.error("\u274C Error registering partials:", error); } return [2 /*return*/]; }); }); }; TemplatePlaygroundServer.prototype.setupMiddleware = function () { // Add request logging for debugging this.app.use(function (req, res, next) { var headers = req.headers; logger.logger.info("\uD83D\uDD0D REQUEST: ".concat(req.method, " ").concat(req.url, " - User-Agent: ").concat(headers['user-agent'] || 'unknown')); next(); }); // Enable CORS for development this.app.use(function (req, res, next) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization'); if (req.method === 'OPTIONS') { res.statusCode = 200; res.end(); } else { next(); } }); // Serve Compodoc resources at root level for relative path compatibility // Try dist/resources first (production), then src/resources (development/testing) var compodocResourcesPathDist = path__namespace.join(process.cwd(), 'dist/resources'); var compodocResourcesPathSrc = path__namespace.join(process.cwd(), 'src/resources'); var compodocResourcesPath = fs__namespace.existsSync(compodocResourcesPathDist) ? compodocResourcesPathDist : compodocResourcesPathSrc; logger.logger.info("\uD83D\uDCC1 Setting up root-level static files from: ".concat(compodocResourcesPath)); logger.logger.info("\uD83D\uDCC1 Compodoc resources path exists: ".concat(fs__namespace.existsSync(compodocResourcesPath))); // Serve styles, js, images, and other resources at root level using sirv this.app.use('/styles', sirv(path__namespace.join(compodocResourcesPath, 'styles'), { dev: true })); this.app.use('/js', sirv(path__namespace.join(compodocResourcesPath, 'js'), { dev: true })); this.app.use('/images', sirv(path__namespace.join(compodocResourcesPath, 'images'), { dev: true })); this.app.use('/fonts', sirv(path__namespace.join(compodocResourcesPath, 'fonts'), { dev: true })); // Serve Compodoc resources under /resources path as well (for backward compatibility) this.app.use('/resources', sirv(compodocResourcesPath, { dev: true })); // Serve static files from template playground directory (index.html, app.js) // Try dist/resources first (production), then src/resources (development/testing) var playgroundStaticPathDist = path__namespace.join(process.cwd(), 'dist/resources/template-playground-app'); var playgroundStaticPathSrc = path__namespace.join(process.cwd(), 'src/resources/template-playground-app'); var playgroundStaticPath = fs__namespace.existsSync(playgroundStaticPathDist) ? playgroundStaticPathDist : playgroundStaticPathSrc; logger.logger.info("\uD83D\uDCC1 Setting up playground static files from: ".concat(playgroundStaticPath)); logger.logger.info("\uD83D\uDCC1 Playground static path exists: ".concat(fs__namespace.existsSync(playgroundStaticPath))); this.app.use(sirv(playgroundStaticPath, { dev: true })); // Parse JSON bodies and form data using body-parser this.app.use(json({ limit: '10mb' })); this.app.use(urlencoded({ extended: true, limit: '10mb' })); }; TemplatePlaygroundServer.prototype.setupRoutes = function () { var _this = this; // API route to get available templates this.app.get('/api/templates', this.getTemplates.bind(this)); // API route to get template content this.app.get('/api/templates/:templateName', this.getTemplate.bind(this)); // API route to get example data this.app.get('/api/example-data/:dataType', this.getExampleData.bind(this)); // API route to render template with custom data this.app.post('/api/render', this.renderTemplate.bind(this)); // API route to render complete page with template this.app.post('/api/render-page', this.renderCompletePage.bind(this)); // API route to generate documentation with CompoDoc CLI this.app.post('/api/generate-docs', this.generateDocs.bind(this)); // API route to download template package this.app.post('/api/download-template', this.downloadTemplatePackage.bind(this)); // API route to download template ZIP (server-side creation) this.app.post('/api/session/:sessionId/download-zip', this.downloadSessionTemplateZip.bind(this)); this.app.post('/api/session/:sessionId/download-all-templates', this.downloadAllSessionTemplates.bind(this)); this.app.get('/api/session/:sessionId/download/all', this.downloadAllSessionTemplates.bind(this)); // Alias for compatibility // Session management API routes this.app.post('/api/session', this.createSessionAPI.bind(this)); this.app.post('/api/session/create', this.createSessionAPI.bind(this)); this.app.get('/api/session/:sessionId/templates', this.getSessionTemplates.bind(this)); this.app.get('/api/session/:sessionId/template/*', this.getSessionTemplate.bind(this)); this.app.post('/api/session/:sessionId/template/*', this.saveSessionTemplate.bind(this)); this.app.get('/api/session/:sessionId/template-data/*', this.getSessionTemplateData.bind(this)); this.app.post('/api/session/:sessionId/generate-docs', this.generateSessionDocs.bind(this)); this.app.post('/api/session/:sessionId/generate', this.generateSessionDocs.bind(this)); // Alias for compatibility this.app.get('/api/session/:sessionId/config', this.getSessionConfig.bind(this)); this.app.post('/api/session/:sessionId/config', this.updateSessionConfig.bind(this)); // Serve session-specific generated documentation this.app.get('/api/session/:sessionId/docs/*', this.serveSessionDocs.bind(this)); // Serve session-specific generated documentation at the expected URL pattern // These routes MUST come before the catch-all route this.app.get('/docs/:sessionId/index.html', function (req, res) { logger.logger.info("\uD83D\uDD0D Docs index route hit: /docs/".concat(req.params.sessionId, "/index.html")); var sessionId = req.params.sessionId; var session = _this.sessions.get(sessionId); if (!session) { logger.logger.error("\u274C Session not found: ".concat(sessionId)); send(res, 404, { success: false, message: 'Session not found' }); return; } _this.updateSessionActivity(sessionId); var fullPath = path__namespace.join(session.documentationDir, 'index.html'); logger.logger.info("\uD83D\uDCC2 Looking for file: ".concat(fullPath)); if (fs__namespace.existsSync(fullPath)) { logger.logger.info("\u2705 Serving file: ".concat(fullPath)); var content = fs__namespace.readFileSync(fullPath); res.setHeader('Content-Type', 'text/html'); res.end(content); } else { logger.logger.error("\u274C File not found: ".concat(fullPath)); res.statusCode = 404; res.end('Documentation file not found'); } }); // Serve any file within session documentation using dynamic sirv middleware this.app.get('/docs/:sessionId/*', function (req, res) { var sessionId = req.params.sessionId; var session = _this.sessions.get(sessionId); if (!session) { logger.logger.error("\u274C Session not found: ".concat(sessionId)); send(res, 404, { success: false, message: 'Session not found' }); return; } _this.updateSessionActivity(sessionId); // Use sirv to serve files from the session documentation directory var sessionSirv = sirv(session.documentationDir, { dev: true, single: false, setHeaders: function (res, pathname) { logger.logger.info("\u2705 Serving file via sirv: ".concat(pathname)); } }); // Remove the session prefix from the URL for sirv var originalUrl = req.url; var sessionPrefix = "/docs/".concat(sessionId); if (originalUrl && originalUrl.startsWith(sessionPrefix)) { req.url = originalUrl.substring(sessionPrefix.length) || '/'; logger.logger.info("\uD83D\uDD0D Sirv serving: ".concat(req.url, " from ").concat(session.documentationDir)); } sessionSirv(req, res, function () { // If sirv doesn't handle it, restore original URL and return 404 req.url = originalUrl; logger.logger.error("\u274C File not found in session docs: ".concat(req.url)); res.statusCode = 404; res.end('Documentation file not found'); }); }); // Handle direct access to session documentation root (index.html) this.app.get('/docs/:sessionId', function (req, res) { logger.logger.info("\uD83D\uDD0D Docs root route hit: /docs/".concat(req.params.sessionId)); var sessionId = req.params.sessionId; var session = _this.sessions.get(sessionId); if (!session) { logger.logger.error("\u274C Session not found: ".concat(sessionId)); send(res, 404, { success: false, message: 'Session not found' }); return; } _this.updateSessionActivity(sessionId); var fullPath = path__namespace.join(session.documentationDir, 'index.html'); logger.logger.info("\uD83D\uDCC2 Looking for file: ".concat(fullPath)); if (fs__namespace.existsSync(fullPath)) { logger.logger.info("\u2705 Serving file: ".concat(fullPath)); var content = fs__namespace.readFileSync(fullPath); res.setHeader('Content-Type', 'text/html'); res.end(content); } else { logger.logger.error("\u274C File not found: ".concat(fullPath)); res.statusCode = 404; res.end('Documentation file not found'); } }); // Serve generated documentation files (legacy) - MUST come after session-specific routes // TEMPORARILY COMMENTED OUT TO TEST SESSION ROUTES // this.app.use('/docs', express.static(this.fakeProjectPath)); // Serve generated docs from playground-demo // Serve the main playground app for root path only this.app.get('/', function (req, res) { // Try dist/resources first (production), then src/resources (development/testing) var indexPathDist = path__namespace.join(process.cwd(), 'dist/resources/template-playground-app/index.html'); var indexPathSrc = path__namespace.join(process.cwd(), 'src/resources/template-playground-app/index.html'); var indexPath = fs__namespace.existsSync(indexPathDist) ? indexPathDist : indexPathSrc; if (fs__namespace.existsSync(indexPath)) { var content = fs__namespace.readFileSync(indexPath); res.setHeader('Content-Type', 'text/html'); res.end(content); } else { res.statusCode = 404; res.end('Template Playground not built. Please run the build process.'); } }); // Handle any remaining non-API routes by serving the main app (for SPA routing) // Note: This catch-all route should be last and will handle all unmatched routes this.app.get('*', function (req, res) { // Skip API, resources, and docs routes as they are handled above if (req.url.startsWith('/api') || req.url.startsWith('/resources') || req.url.startsWith('/docs')) { res.statusCode = 404; res.end('Not Found'); return; } logger.logger.warn("\u26A0\uFE0F CATCH-ALL ROUTE HIT: ".concat(req.method, " ").concat(req.url)); // Try dist/resources first (production), then src/resources (development/testing) var indexPathDist = path__namespace.join(process.cwd(), 'dist/resources/template-playground-app/index.html'); var indexPathSrc = path__namespace.join(process.cwd(), 'src/resources/template-playground-app/index.html'); var indexPath = fs__namespace.existsSync(indexPathDist) ? indexPathDist : indexPathSrc; if (fs__namespace.existsSync(indexPath)) { var content = fs__namespace.readFileSync(indexPath); res.setHeader('Content-Type', 'text/html'); res.end(content); } else { res.statusCode = 404; res.end('Template Playground not built. Please run the build process.'); } }); }; TemplatePlaygroundServer.prototype.getTemplates = function (req, res) { return logger.__awaiter(this, void 0, void 0, function () { var templatesDir_1, files, templates, error_2; return logger.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 2, , 3]); templatesDir_1 = path__namespace.join(process.cwd(), 'dist/templates/partials'); return [4 /*yield*/, fs__namespace.readdir(templatesDir_1)]; case 1: files = _a.sent(); templates = files .filter(function (file) { return file.endsWith('.hbs'); }) .map(function (file) { return ({ name: file.replace('.hbs', ''), filename: file, path: path__namespace.join(templatesDir_1, file) }); }); send(res, 200, templates); return [3 /*break*/, 3]; case 2: error_2 = _a.sent(); logger.logger.error('Error reading templates:', error_2); send(res, 500, { error: 'Failed to read templates' }); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; TemplatePlaygroundServer.prototype.getTemplate = function (req, res) { return logger.__awaiter(this, void 0, void 0, function () { var templateName, templatePath, content, error_3; return logger.__generator(this, function (_a) { switch (_a.label) { case 0: _a.trys.push([0, 3, , 4]); templateName = req.params.templateName; templatePath = path__namespace.join(process.cwd(), 'dist/templates/partials', "".concat(templateName, ".hbs")); return [4 /*yield*/, fs__namespace.pathExists(templatePath)]; case 1: if (!(_a.sent())) { send(res, 404, { error: 'Template not found' }); return [2 /*return*/]; } return [4 /*yield*/, fs__namespace.readFile(templatePath, 'utf-8')]; case 2: content = _a.sent(); send(res, 200, { name: templateName, content: content, path: templatePath }); return [3 /*break*/, 4]; case 3: error_3 = _a.sent(); logger.logger.error('Error reading template:', error_3); send(res, 500, { error: 'Failed to read template' }); return [3 /*break*/, 4]; case 4: return [2 /*return*/]; } }); }); }; TemplatePlaygroundServer.prototype.getExampleData = function (req, res) { return logger.__awaiter(this, void 0, void 0, function () { var dataType, _a, EXAMPLE_DATA, TEMPLATE_CONTEXT, wrappedData, error_4; var _b; return logger.__generator(this, function (_c) { switch (_c.label) { case 0: _c.trys.push([0, 2, , 3]); dataType = req.params.dataType; return [4 /*yield*/, Promise.resolve().then(function () { return require('./example-data-DR3xY3Rr.js'); })]; case 1: _a = _c.sent(), EXAMPLE_DATA = _a.EXAMPLE_DATA, TEMPLATE_CONTEXT = _a.TEMPLATE_CONTEXT; if (!EXAMPLE_DATA[dataType]) { send(res, 404, { error: 'Example data type not found' }); return [2 /*return*/]; } wrappedData = dataType === 'component' || dataType === 'directive' || dataType === 'pipe' || dataType === 'guard' || dataType === 'interceptor' || dataType === 'injectable' || dataType === 'class' || dataType === 'interface' || dataType === 'entity' ? logger.__assign((_b = {}, _b[dataType] = EXAMPLE_DATA[dataType], _b), EXAMPLE_DATA[dataType]) : EXAMPLE_DATA[dataType]; send(res, 200, { data: wrappedData, context: TEMPLATE_CONTEXT }); return [3 /*break*/, 3]; case 2: error_4 = _c.sent(); logger.logger.error('Error getting example data:', error_4); send(res, 500, { error: 'Failed to get example data' }); return [3 /*break*/, 3]; case 3: return [2 /*return*/]; } }); }); }; TemplatePlaygroundServer.prototype.renderTemplate = function (req, res) { return logger.__awaiter(this, void 0, void 0, function () { var _a, templateContent, templateData, templateContext, template, rendered; return logger.__generator(this, function (_b) { try { _a = req.body, templateContent = _a.templateContent, templateData = _a.templateData, templateContext = _a.templateContext; if (!templateContent) { send(res, 400, { error: 'Template content is required' }); return [2 /*return*/]; } template = this.handlebars.compile(templateContent); rendered = template(templateData || {}); send(res, 200, { rendered: rendered }); } catch (error) { logger.logger.error('Error rendering template:', error); send(res, 500, { error: 'Failed to render template', details: error.message }); } return [2 /*return*/]; }); }); }; TemplatePlaygroundServer.prototype.renderCompletePage = function (req, res) { return logger.__awaiter(this, void 0, void 0, function () { var _a, templateContent, templateData, templateContext, renderedContent, completePage; return logger.__generator(this, function (_b) { try { _a = req.body, templateContent = _a.templateContent, templateData = _a.templateData, templateContext = _a.templateContext; // Handle form data by parsing JSON strings if (typeof templateData === 'string') { try { templateData = JSON.parse(templateData); } catch (e) { templateData = {}; } } if (typeof templateContext === 'string') { try { templateContext = JSON.parse(templateContext); } catch (e) { templateContext = {}; } } if (!templateContent) { send(res, 400, { error: 'Template content is required' }); return [2 /*return*/]; } renderedContent = this.generateCompodocHtml(templateData || {}); completePage = "<!doctype html>\n<html class=\"no-js\" lang=\"\">\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n <title>Template Preview - Compodoc</title>\n <meta name=\"description\" content=\"\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n\n <link rel=\"icon\" type=\"image/x-icon\" href=\"/resources/images/favicon.ico\">\n <link rel=\"stylesheet\" href=\"/resources/styles/bootstrap.min.css\">\n <link rel=\"stylesheet\" href=\"/resources/styles/compodoc.css\">\n <link rel=\"stylesheet\" href=\"/resources/styles/prism.css\">\n <link rel=\"stylesheet\" href=\"/resources/styles/dark.css\">\n <link rel=\"stylesheet\" href=\"/resources/styles/style.css\">\n </head>\n <body>\n <script>\n // Blocking script to avoid flickering dark mode\n var useDark = window.matchMedia('(prefers-color-scheme: dark)');\n var darkModeState = useDark.matches;\n var darkModeStateLocal = localStorage.getItem('compodoc_darkmode-state');\n if (darkModeStateLocal) {\n darkModeState = darkModeStateLocal === 'true';\n }\n if (darkModeState) {\n document.body.classList.add('dark');\n }\n </script>\n\n <div class=\"container-fluid main\">\n <!-- START CONTENT -->\n <div class=\"content component\">\n <div class=\"content-data\">\n ".concat(renderedContent, "\n </div>\n </div>\n <!-- END CONTENT -->\n </div>\n\n <script>\n var COMPODOC_CURRENT_PA