import { defineConfig } from 'astro/config'; import { readFile, writeFile, stat } from 'node:fs/promises'; import path from 'node:path'; import mdx from '@astrojs/mdx'; import sitemap from '@astrojs/sitemap'; const EXCLUDED_PATHS = new Set([ 'https://unique.rzen.dev/grid/', 'https://unique.rzen.dev/index3/', 'https://unique.rzen.dev/404/', ]); const FIND_ROOT = path.resolve('src/content/find'); const REVIEW_ROOT = path.resolve('src/content/reviews'); const PICK_FILE = path.resolve('src/data/pick.json'); const VALID_SLUG = /^[a-z0-9][a-z0-9-]*$/; const VALID_TAG = /^[a-z0-9][a-z0-9-]{0,40}$/i; function readBody(req) { return new Promise((resolve, reject) => { let data = ''; req.setEncoding('utf8'); req.on('data', (c) => { data += c; if (data.length > 8192) reject(new Error('body too large')); }); req.on('end', () => resolve(data)); req.on('error', reject); }); } function send(res, status, payload) { res.statusCode = status; res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(payload)); } function parseEditorialTags(fm) { const inline = fm.match(/^editorialTags:\s*\[([^\]]*)\]\s*$/m); if (inline) { return inline[1] .split(',') .map((s) => s.trim().replace(/^["']|["']$/g, '')) .filter(Boolean); } const list = fm.match(/^editorialTags:\s*\n((?:[ \t]+-[^\n]*\n?)+)/m); if (list) { return list[1] .split('\n') .map((line) => line.replace(/^[ \t]+-[ \t]*/, '').trim().replace(/^["']|["']$/g, '')) .filter(Boolean); } if (/^editorialTags:\s*\[\s*\]\s*$/m.test(fm) || /^editorialTags:\s*$/m.test(fm)) return []; return null; // field absent } function stripEditorialTags(fm) { return fm.replace(/^editorialTags:[^\n]*(?:\n[ \t]+-[^\n]*)*\n?/m, ''); } function formatEditorialTags(tags) { return `editorialTags: [${tags.map((t) => JSON.stringify(t)).join(', ')}]`; } const editFindTagApi = { name: 'edit-find-tag-dev-api', apply: 'serve', configureServer(server) { server.middlewares.use('/api/edit-find-tag', async (req, res, next) => { if (req.method !== 'POST') return next(); try { const raw = await readBody(req); const { slug, tag, mode } = JSON.parse(raw || '{}'); if (typeof slug !== 'string' || !VALID_SLUG.test(slug)) return send(res, 400, { error: 'invalid slug' }); if (typeof tag !== 'string' || !VALID_TAG.test(tag)) return send(res, 400, { error: 'invalid tag' }); const filePath = path.join(FIND_ROOT, slug, 'index.mdx'); if (!filePath.startsWith(FIND_ROOT + path.sep)) return send(res, 400, { error: 'path escape' }); try { await stat(filePath); } catch { return send(res, 404, { error: 'find not found' }); } const text = await readFile(filePath, 'utf8'); const m = text.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/); if (!m) return send(res, 422, { error: 'no frontmatter' }); const fm = m[1]; const body = m[2]; const current = parseEditorialTags(fm) ?? []; const has = current.includes(tag); let next; if (mode === 'add') next = has ? current : [...current, tag]; else if (mode === 'remove') next = current.filter((t) => t !== tag); else next = has ? current.filter((t) => t !== tag) : [...current, tag]; // toggle const stripped = stripEditorialTags(fm).replace(/\n+$/, ''); const newFm = next.length ? `${stripped}\n${formatEditorialTags(next)}` : stripped; const out = `---\n${newFm}\n---\n${body}`; await writeFile(filePath, out, 'utf8'); return send(res, 200, { slug, tag, editorialTags: next }); } catch (err) { return send(res, 500, { error: String(err && err.message ? err.message : err) }); } }); }, }; const setPickApi = { name: 'set-pick-dev-api', apply: 'serve', configureServer(server) { server.middlewares.use('/api/set-pick', async (req, res, next) => { if (req.method !== 'POST') return next(); try { const raw = await readBody(req); const { slug } = JSON.parse(raw || '{}'); if (typeof slug !== 'string' || !VALID_SLUG.test(slug)) return send(res, 400, { error: 'invalid slug' }); const reviewPath = path.join(REVIEW_ROOT, slug, 'index.mdx'); if (!reviewPath.startsWith(REVIEW_ROOT + path.sep)) return send(res, 400, { error: 'path escape' }); try { await stat(reviewPath); } catch { return send(res, 404, { error: 'review not found' }); } await writeFile(PICK_FILE, JSON.stringify({ hero: slug }, null, 2) + '\n', 'utf8'); return send(res, 200, { slug }); } catch (err) { return send(res, 500, { error: String(err && err.message ? err.message : err) }); } }); }, }; export default defineConfig({ site: 'https://unique.rzen.dev', trailingSlash: 'always', build: { format: 'directory' }, vite: { plugins: [editFindTagApi, setPickApi] }, integrations: [ mdx(), sitemap({ filter: (page) => !EXCLUDED_PATHS.has(page), changefreq: 'weekly', }), ], });