// @ts-check /** * PromptJS v1.0.0 — Adapter: Static Export * ============================================================================ * * Enhances the default `pjs build` output for production CDN deployment. * * Adds: * - Asset hashing (prompt.a1b2c3.js) for cache busting * - tags (og:title, og:description, canonical) from config * - sitemap.xml auto-generation from route list * - 404.html fallback for SPA routes * * Zero-dependency. Uses Node.js crypto for content hashing. */ 'use strict'; const crypto = require('crypto'); const path = require('path'); const fs = require('fs'); const { isInsideRoot } = require('../../utils/path-guard'); /** * Generate a short content hash from a string. * * @param {string} content - Content to hash * @param {number} [len=8] - Hash length * @returns {string} Hex hash */ function contentHash(content, len) { len = len || 8; return crypto.createHash('md5').update(content).digest('hex').slice(0, len); } /** * Hash a filename: prompt.js → prompt.a1b2c3.js * * @param {string} filename - Original filename (e.g. "prompt.js") * @param {string} content - File content for hashing * @returns {string} Hashed filename */ function hashFilename(filename, content) { const ext = path.extname(filename); const base = filename.slice(0, -ext.length); const hash = contentHash(content, 8); return base + '.' + hash + ext; } /** * Enhance HTML with tags from config. * * @param {string} html - Original HTML * @param {Object} meta - Meta config { title, description, ogImage, ... } * @param {Object} [opts] - Options * @param {string} [opts.siteUrl] - Base URL for canonical * @param {string} [opts.route] - Current page route (for canonical) * @returns {string} HTML with meta tags injected */ function injectMetaTags(html, meta, opts) { opts = opts || {}; const tags = []; if (meta && meta.title) { tags.push(' '); } if (meta && meta.description) { tags.push(' '); tags.push(' '); } if (meta && meta.ogImage) { tags.push(' '); } if (meta && meta.ogType) { tags.push(' '); } if (opts.siteUrl && opts.route) { const canonical = opts.siteUrl.replace(/\/$/, '') + opts.route; tags.push(' '); } if (tags.length === 0) return html; const metaBlock = tags.join('\n') + '\n'; return html.replace('', metaBlock + ''); } /** * Generate sitemap.xml from route list. * * @param {string[]} routes - Array of route paths (e.g. ["/", "/about"]) * @param {string} siteUrl - Base URL (e.g. "https://example.com") * @returns {string} sitemap.xml content */ function generateSitemap(routes, siteUrl) { const urls = routes .map(function (route) { return ( ' \n ' + escapeXml(siteUrl.replace(/\/$/, '') + route) + '\n ' ); }) .join('\n'); return ( '\n' + '\n' + urls + '\n\n' ); } /** * Generate 404.html fallback page for SPA. * Redirects to the SPA shell so client-side router handles 404. * * @param {string} htmlShell - The SPA index.html content (will be reused as 404) * @returns {string} 404.html content */ function generate404(htmlShell) { // For SPA: the 404 page IS the index.html (client-side router handles it) // For MPA: generate a simple 404 page if (htmlShell && htmlShell.includes('
')) { return htmlShell; // SPA: reuse shell } return ( '\n' + '\n' + '\n' + ' \n' + ' \n' + ' 404 — Tidak Ditemukan\n' + '\n' + '\n' + '

404

\n' + '

Halaman tidak ditemukan.

\n' + ' Kembali ke beranda\n' + '\n' + '\n' ); } /** * Generate a cryptographically random nonce for CSP. * * @param {number} [len=24] - Nonce length in bytes (48 hex chars) * @returns {string} Base64-encoded nonce */ function generateNonce(len) { len = len || 24; return crypto.randomBytes(len).toString('base64'); } /** * Inject CSP meta tag and nonce attributes into HTML. * * Adds: * - tag * - nonce="..." to