@compodoc/compodoc
Version:
The missing documentation tool for your Angular application
884 lines (880 loc) • 440 kB
JavaScript
'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