import http from 'node:http'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { createReadStream } from 'node:fs'; import { PUBLIC_PAGES, buildSchema, canonicalUrl, getSiteConfig, isNoIndex, isDocsPath, } from './src/seo-config.js'; import { stripManagedSeoTags } from './src/seo-head.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const DIST_DIR = path.join(__dirname, 'dist'); const SITE_CONFIG = getSiteConfig(process.env); const { siteUrl: SITE_URL, siteName: SITE_NAME, preview: PREVIEW, apiOrigin: API_ORIGIN, } = SITE_CONFIG; const DOCS_CACHE_TTL_MS = 5 * 60 * 1000; const PUBLIC_DOC_CACHE_TTL_MS = 15 * 60 * 1000; let docsSitemapCache = { expiresAt: 0, items: [], }; const publicDocCache = new Map(); function escapeHtml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function escapeJsonLd(value) { return JSON.stringify(value).replaceAll('<', '\\u003c'); } function escapeXml(value) { return String(value) .replaceAll('&', '&') .replaceAll('<', '<') .replaceAll('>', '>') .replaceAll('"', '"') .replaceAll("'", '''); } function buildHead(page, pathname, noindex = false, docs = null) { const url = canonicalUrl(pathname, SITE_URL); const robots = noindex ? 'noindex, nofollow, noarchive' : 'index, follow, max-image-preview:large, max-snippet:-1, max-video-preview:-1'; const schema = page ? buildSchema(page, pathname, SITE_CONFIG, { docs }) : null; const ogImageAlt = docs ? `${docs.title} | ${SITE_NAME}` : `${SITE_NAME} deployment platform`; return ` ${escapeHtml(page?.title || `404 | ${SITE_NAME}`)} ${ page && !noindex ? ` ` : '' } ${ page ? ` ` : '' } ${ schema ? ` ` : '' } `; } function loadTemplate() { const templateCandidates = [ path.join(DIST_DIR, '_template.html'), path.join(DIST_DIR, 'index.html'), ]; for (const templatePath of templateCandidates) { try { return fs.readFileSync(templatePath, 'utf8'); } catch { // Try the next template candidate. } } throw new Error('No built HTML template is available.'); } const PUBLIC_SHELLS = { '/': { eyebrow: 'PaaSDeployer', heading: 'Deploy faster. Manage more. Worry less.', paragraphs: [ 'PaaSDeployer is an application deployment and infrastructure management platform for teams that want a simpler path from configured workload to running application. Create services, choose CPU and memory resources, connect networks, keep persistent storage, or start with a Ready App when the stack is already known.', 'Ready Apps package popular self-hosted software with the supporting services, configuration and runtime behavior described by the platform. For custom workloads, the same control plane exposes the lower-level service workflow without forcing every application through a preset template.', ], sections: [ { heading: 'One place for deployment and infrastructure management', paragraphs: [ 'PaaSDeployer keeps service deployment, resource selection, networking, and persistent storage close together. You can define a service, give it the resources it needs, connect it to the right network, and keep its persistent data available across the service lifecycle.', 'Service management covers the everyday actions required to run an application, while resource controls help you choose CPU, memory, and storage based on the workload.', ], }, { heading: 'From resources to a running service', paragraphs: [ 'Choose the resources that fit the application, create the service and configuration, deploy it through a repeatable process, and then manage the running workload from the same control surface.', ], }, { heading: 'Built in the open for developers', paragraphs: [ 'PaaSDeployer is built as an open-source stack with a Django API and a React frontend. Application deployment, service lifecycle management, networks, and persistent volumes are the core concepts exposed by the platform.', ], }, ], links: [ ['/plans', 'Plans & Pricing'], ['/aboutUs', 'About PaaSDeployer'], ['/signin_or_signup', 'Sign in'], ], }, '/plans': { eyebrow: 'PaaSDeployer plans', heading: 'Choose resources that fit your application.', paragraphs: [ 'PaaSDeployer plans help you choose an appropriate amount of CPU, memory, and persistent storage for the workload you need to run. Start with the resources that fit your current application, then change the plan when traffic, processing requirements, or stored data grows.', 'The public plans page explains resource options before you enter the authenticated control panel. This makes it easier to compare capacity and understand how resource limits relate to day-to-day application deployment and service management.', ], sections: [ { heading: 'Resource plans for different workloads', paragraphs: [ 'CPU and memory capacity affect how much work a service can process, while persistent storage provides a durable place for application data. Choosing resources around the actual workload helps you avoid both unnecessary capacity and restrictive limits.', ], }, { heading: 'Scale the service as needs change', paragraphs: [ 'A deployment plan should not lock an application into the resources it needed on its first day. PaaSDeployer is designed so that resource choices can be revisited as the workload changes.', ], }, ], links: [ ['/', 'Back to PaaSDeployer'], ['/aboutUs', 'About the platform'], ], }, '/docs': { eyebrow: 'PaaSDeployer documentation', heading: 'Documentation for deploying and managing applications.', paragraphs: [ 'Browse published PaaSDeployer guides, references and practical how-tos for deploying, configuring and managing applications.', 'Documentation pages are published individually and linked from the documentation index, so each guide can be discovered, shared and indexed on its own canonical URL.', ], sections: [ { heading: 'Guides and references', paragraphs: [ 'Use the documentation index to find deployment guides, configuration references, service-management instructions, and other practical material for working with PaaSDeployer.', ], }, { heading: 'Published documentation only', paragraphs: [ 'The public documentation API exposes published pages and their public assets. Draft documentation and unattached library files remain in the authenticated administration area.', ], }, ], links: [ ['/plans', 'Plans & Pricing'], ['/aboutUs', 'About PaaSDeployer'], ], }, '/aboutUs': { eyebrow: 'About PaaSDeployer', heading: 'Infrastructure should feel like a product.', paragraphs: [ 'PaaSDeployer is a self-hosted application deployment platform built to make the path from configured workload to running service easier to understand, operate and automate.', 'The platform combines a Django control plane, a React operator workspace and Docker-based runtime management. Ready Apps provide a faster path for common self-hosted software, while regular Services remain available when you need full control over the workload.', ], sections: [ { heading: 'One control plane for deployment and operations', paragraphs: [ 'PaaSDeployer keeps deployments, service lifecycle actions, resource choices, networks and persistent storage close together. The goal is not to hide infrastructure, but to make the important operational decisions visible in the same place.', ], }, { heading: 'Ready Apps when you do not want to assemble everything', paragraphs: [ 'The Ready Apps catalog describes complete application stacks, including supporting services, configuration and runtime behavior. This gives users a shorter path to popular self-hosted applications while preserving the underlying service model for day-to-day management.', ], }, { heading: 'Open-source and automation-ready', paragraphs: [ 'The frontend and backend are maintained as separate open-source projects. The Agent API extends the same control plane to scoped automation clients, so deployment and runtime workflows can be driven programmatically without creating a second operational model.', ], }, ], links: [ ['/', 'Home'], ['/dashboard/ready-apps', 'Explore Ready Apps'], ['/plans', 'Plans & Pricing'], ], }, }; /** * Visible shell shown to users while JS loads. * Neutral — never a marketing SEO page. */ function buildLoadingShell(pathname) { const isPrivate = isNoIndex(pathname); const label = isPrivate ? 'Loading your workspace…' : 'Loading PaaSDeployer…'; return `

${escapeHtml(label)}

`; } function buildDocsNoscriptContent(doc) { if (!doc) return ''; const raw = String(doc.content || '') .replace(/```[\s\S]*?```/g, ''); const blocks = raw .split(/\n\s*\n/) .map((block) => block.trim()) .filter(Boolean) .slice(0, 18); const html = blocks .map((block) => { const heading = block.match( /^(#{1,3})\s+(.+)$/, ); if (heading) { const level = Math.min( 3, heading[1].length, ); return ` ${escapeHtml(heading[2])} `; } const text = block .replace(/^[-*+]\s+/gm, '• ') .replace(/^\d+\.\s+/gm, '• ') .replace(/[*_`]+/g, ''); return `

${escapeHtml(text).replaceAll( '\n', '
', )}

`; }) .join(''); return `
Documentation

${escapeHtml(doc.title)}

${ doc.description ? `

${escapeHtml(doc.description)}

` : '' }
${html}
`; } /** * Rich HTML for