import fs from 'node:fs' import { spawn } from 'node:child_process' import path from 'node:path' import { defineConfig } from 'vite' import { createRegenerationQueue } from './dev-reload.js' import { contentDirectories, contentSources, generatedRoot } from './site.config.js' function generatedPages(directory) { const inputs = {} for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { const filePath = path.join(directory, entry.name) if (entry.isDirectory()) Object.assign(inputs, generatedPages(filePath)) else if (entry.name === 'index.html') { const relativePath = path.relative(generatedRoot, filePath) const key = relativePath.endsWith('/index.html') ? relativePath.slice(0, -'/index.html'.length) : relativePath inputs[key] = path.resolve(filePath) } else if (entry.name === '404.html' && directory === generatedRoot) { inputs['404'] = path.resolve(filePath) } } return inputs } function copyGeneratedFiles() { const files = ['feed.xml', 'sitemap.xml'] return { name: 'copy-generated-files', closeBundle() { const dist = path.resolve('dist') if (!fs.existsSync(dist)) return for (const file of files) { const source = path.resolve(generatedRoot, file) if (fs.existsSync(source)) fs.copyFileSync(source, path.join(dist, file)) } }, } } function contentGenerator() { return { name: 'content-generator', configureServer(server) { const queue = createRegenerationQueue({ generate: () => new Promise((resolve, reject) => { const child = spawn(process.execPath, ['generator.js', '--drafts'], { stdio: 'inherit' }) child.once('error', reject) child.once('close', (code) => code === 0 ? resolve() : reject(new Error(`exit ${code ?? 'unknown'}`))) }), onSuccess: () => server.ws.send({ type: 'full-reload' }), onError: (error) => { console.error(`Content generation failed: ${error.message}`) server.ws.send({ type: 'error', err: { message: `Content generation failed (${error.message}). Check the terminal for details.`, stack: '' }, }) }, }) const sources = new Set(contentSources.map((source) => path.resolve(source))) server.watcher.add([...sources]) server.httpServer?.once('close', () => queue.close()) server.watcher.on('all', (event, file) => { if (!['add', 'change', 'unlink'].includes(event)) return const isMarkdown = file.endsWith('.md') && contentDirectories.some((dir) => file.startsWith(`${path.resolve(dir)}/`)) if (!isMarkdown && !sources.has(file)) return queue.request() }) }, } } export default defineConfig({ plugins: [contentGenerator(), copyGeneratedFiles()], root: generatedRoot, publicDir: '../public', resolve: { alias: { '/src': path.resolve('src'), }, }, server: { watch: { ignored: [`**/${generatedRoot}/**`], }, }, build: { outDir: path.resolve('dist'), emptyOutDir: true, rollupOptions: { input: generatedPages(generatedRoot), }, }, })