generate-ai.mjs 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises';
  2. import path from 'node:path';
  3. const root = process.cwd();
  4. const docsDir = path.join(root, 'src/content/docs');
  5. const rawDir = path.join(root, 'public/raw');
  6. const publicDir = path.join(root, 'public');
  7. async function collectMarkdown(directory) {
  8. const entries = await readdir(directory, { withFileTypes: true });
  9. const files = [];
  10. for (const entry of entries) {
  11. const entryPath = path.join(directory, entry.name);
  12. if (entry.isDirectory()) files.push(...(await collectMarkdown(entryPath)));
  13. if (entry.isFile() && entry.name.endsWith('.md')) files.push(entryPath);
  14. }
  15. return files.sort();
  16. }
  17. function parseFrontmatter(source) {
  18. const match = source.match(/^---\n([\s\S]*?)\n---\n?/);
  19. if (!match) return { body: source, description: '', title: '' };
  20. const value = (key) => {
  21. const line = match[1].match(new RegExp(`^${key}:\\s*["']?(.*?)["']?\\s*$`, 'm'));
  22. return line?.[1] ?? '';
  23. };
  24. return {
  25. body: source.slice(match[0].length),
  26. description: value('description'),
  27. title: value('title'),
  28. };
  29. }
  30. await rm(rawDir, { recursive: true, force: true });
  31. await mkdir(rawDir, { recursive: true });
  32. const files = await collectMarkdown(docsDir);
  33. const pages = [];
  34. for (const file of files) {
  35. const relative = path.relative(docsDir, file);
  36. const route = relative === 'index.md' ? '/' : `/${relative.replace(/(?:\/index)?\.md$/, '/')}`;
  37. const source = await readFile(file, 'utf8');
  38. const { body, description, title } = parseFrontmatter(source);
  39. const rawPath = path.join(rawDir, relative);
  40. await mkdir(path.dirname(rawPath), { recursive: true });
  41. await writeFile(rawPath, source);
  42. pages.push({ body, description, route, source, title });
  43. }
  44. const index = [
  45. '# database.pizza',
  46. '',
  47. '> End-user documentation for database.pizza, PizzaSQL, and its PostgreSQL and HTTP interfaces.',
  48. '',
  49. 'Use the canonical Markdown links below when supplying context to an AI tool. The full documentation corpus is available at https://docs.database.pizza/llms-full.txt.',
  50. '',
  51. '## Documentation',
  52. '',
  53. ...pages.map(({ description, route, title }) => `- [${title || route}](https://docs.database.pizza/raw${route === '/' ? '/index.md' : `${route.slice(0, -1)}.md`})${description ? `: ${description}` : ''}`),
  54. '',
  55. '## Important context',
  56. '',
  57. '- PizzaSQL uses SQLite-style type affinity and exposes a PostgreSQL-compatible wire interface. It is not PostgreSQL itself.',
  58. '- Prefer the compatibility page before generating production SQL.',
  59. '- Never place a real API key in prompts, source code, or logs.',
  60. '',
  61. ].join('\n');
  62. const full = pages
  63. .map(({ body, route, title }) => `# ${title}\n\nCanonical URL: https://docs.database.pizza${route}\n\n${body.trim()}\n`)
  64. .join('\n---\n\n');
  65. await mkdir(publicDir, { recursive: true });
  66. await writeFile(path.join(publicDir, 'llms.txt'), index);
  67. await writeFile(path.join(publicDir, 'llms-full.txt'), full);