mirror of
https://github.com/supabase/supabase.git
synced 2026-10-07 02:15:05 +03:00
## Summary Makes supabase.com machine-readable for LLM agents and AI crawlers. Adds a combined `llms-full.txt` (product overview + docs), curated product overview `.txt` files, auto-generated pricing content, and updates the `llms.txt` index to reference everything. **Why:** supabase.com had `llms.txt` pointing to per-SDK doc files, but no `llms-full.txt` (every competitor has one), no machine-readable marketing content, and no pricing data agents could parse. Agents evaluating Supabase got docs but no product overview or pricing, leading to inaccurate comparisons. ## What's new ### `llms-full.txt` (auto-generated, combines www + docs) - Product Overview section (~500 lines): homepage, all 6 products, pricing - Documentation section (~122K lines): guides, SDK references, CLI reference - 4.4MB total, regenerated on every docs deploy - Reads product `.txt` files from `apps/www/public/llms/` at build time ### Product overview `.txt` files (`apps/www/public/llms/`) - Curated summaries: homepage, database, auth, storage, edge functions, realtime, vector - `pricing.txt` is auto-generated from `packages/shared-data` (plans, pricing, compute add-ons) via `generateLlmsPricing.mjs`, includes full feature comparison matrix - Other product files are hand-maintained (these pages change ~1x/year per git history) - Reminder comments added to all 7 marketing page source files ### `llms.txt` index (auto-generated) - Two sections: Documentation (existing SDK/guide links) and Product Overview (marketing page links) - Links to `llms-full.txt` for bulk ingestion ### Rewrite changes - Added `/llms-full.txt` rewrite to docs app - Scoped `/llms/*.txt` wildcard to only match docs source slugs (guides, js, dart, etc.), so marketing `.txt` files in `www/public/llms/` are served directly ## Changes - `apps/docs/scripts/llms.ts`: generate `llms-full.txt` combining www product content + docs, update `llms.txt` index with marketing links - `apps/www/lib/rewrites.js`: add `llms-full.txt` rewrite, scope docs proxy to known slugs - `apps/www/public/llms/*.txt`: 8 product overview files (7 hand-curated, 1 auto-generated) - `apps/www/scripts/generateLlmsPricing.mjs`: build script generating pricing.txt from shared-data; uses `getPlanValue()` guard in `buildAddOnsSection` and `buildFeatureComparisonSection` to handle missing plan keys defensively - `apps/www/package.json`: add pricing generation to content:build - `apps/www/pages/*.tsx`: reminder comments for LLM content updates ## Testing Tested locally: - [x] `pnpm run build:llms` generates combined `llms-full.txt` (4.4MB) with Product Overview + Documentation sections - [x] `llms.txt` index has Documentation + Product Overview sections with `llms-full.txt` reference - [x] `pricing.txt` auto-generated from shared-data with correct plan tiers, compute add-ons, disk pricing, and feature comparison - [x] Scoped rewrite regex matches docs slugs but not marketing slugs - [x] Marketing `.txt` files served from `public/` Post-deploy verification: - [ ] `curl https://supabase.com/llms-full.txt` returns combined product + docs content - [ ] `curl https://supabase.com/llms/database.txt` returns product overview (not proxied to docs) - [ ] `curl https://supabase.com/llms/guides.txt` still proxies to docs app - [ ] `curl https://supabase.com/llms/pricing.txt` returns auto-generated pricing ## Maintenance | Content | Auto-updates? | Trigger | |---------|--------------|---------| | `llms-full.txt` | Yes | Every docs deploy | | `llms.txt` index | Yes | Every docs deploy | | `pricing.txt` | Yes | Every www build (reads from shared-data) | | Product `.txt` files (7) | No | ~1x/year, reminder comments in source pages | ## Linear - fixes GROWTH-758 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Documentation** * Added comprehensive AI-friendly product overviews (Auth, Database, Edge Functions, Realtime, Storage, Vector) and a detailed pricing document with plan comparisons, add-ons, disk tiers, and feature tables * Added a consolidated "full" markdown output that combines curated product overview content with per-source documentation * **Chores** * Build now generates the richer documentation outputs and pricing automatically * Improved routing so only scoped documentation .txt assets are proxied while others are served directly <!-- end of auto-generated comment: release notes by coderabbit.ai -->
235 lines
6.5 KiB
TypeScript
235 lines
6.5 KiB
TypeScript
import './utils/dotenv.js'
|
|
import 'dotenv/config'
|
|
|
|
import fs from 'node:fs/promises'
|
|
import path from 'node:path'
|
|
import { fileURLToPath } from 'node:url'
|
|
|
|
import { isFeatureEnabled } from '../../../packages/common/enabled-features/index.js'
|
|
import { getCustomContent } from '../lib/custom-content/getCustomContent.js'
|
|
import {
|
|
fetchCliLibReferenceSource,
|
|
fetchCSharpLibReferenceSource,
|
|
fetchDartLibReferenceSource,
|
|
fetchGuideSources,
|
|
fetchJsLibReferenceSource,
|
|
fetchKtLibReferenceSource,
|
|
fetchPythonLibReferenceSource,
|
|
fetchSwiftLibReferenceSource,
|
|
type SearchSource,
|
|
} from './search/sources/index.js'
|
|
|
|
interface Source {
|
|
title: string
|
|
/**
|
|
* Path relative to https://supabase.com. No leading slash
|
|
*/
|
|
relPath: string
|
|
fetch: () => Promise<SearchSource[]>
|
|
enabled: boolean
|
|
}
|
|
|
|
const {
|
|
sdkCsharp: sdkCsharpEnabled,
|
|
sdkDart: sdkDartEnabled,
|
|
sdkKotlin: sdkKotlinEnabled,
|
|
sdkPython: sdkPythonEnabled,
|
|
sdkSwift: sdkSwiftEnabled,
|
|
} = isFeatureEnabled(['sdk:csharp', 'sdk:dart', 'sdk:kotlin', 'sdk:python', 'sdk:swift'])
|
|
|
|
const { metadataTitle } = getCustomContent(['metadata:title'])
|
|
|
|
function toLink(source: Source) {
|
|
return `[${source.title}](https://supabase.com/${source.relPath})`
|
|
}
|
|
|
|
const SOURCES: Source[] = [
|
|
{
|
|
title: 'Supabase Guides',
|
|
relPath: 'llms/guides.txt',
|
|
fetch: fetchGuideSources,
|
|
enabled: true,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (JavaScript)',
|
|
relPath: 'llms/js.txt',
|
|
fetch: async () =>
|
|
(await fetchJsLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: true,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (Dart)',
|
|
relPath: 'llms/dart.txt',
|
|
fetch: async () =>
|
|
(await fetchDartLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: sdkDartEnabled,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (Swift)',
|
|
relPath: 'llms/swift.txt',
|
|
fetch: async () =>
|
|
(await fetchSwiftLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: sdkSwiftEnabled,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (Kotlin)',
|
|
relPath: 'llms/kotlin.txt',
|
|
fetch: async () =>
|
|
(await fetchKtLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: sdkKotlinEnabled,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (Python)',
|
|
relPath: 'llms/python.txt',
|
|
fetch: async () =>
|
|
(await fetchPythonLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: sdkPythonEnabled,
|
|
},
|
|
{
|
|
title: 'Supabase Reference (C#)',
|
|
relPath: 'llms/csharp.txt',
|
|
fetch: async () =>
|
|
(await fetchCSharpLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: sdkCsharpEnabled,
|
|
},
|
|
{
|
|
title: 'Supabase CLI Reference',
|
|
relPath: 'llms/cli.txt',
|
|
fetch: async () =>
|
|
(await fetchCliLibReferenceSource()).filter(
|
|
(item): item is SearchSource => item !== undefined
|
|
),
|
|
enabled: true,
|
|
},
|
|
]
|
|
|
|
// Product overview .txt files are hand-curated and live in apps/www/public/llms/.
|
|
// These links always point to production since llms.txt is only meaningful in prod.
|
|
const PRODUCT_OVERVIEW_LINKS = [
|
|
'- [Supabase Overview](https://supabase.com/llms/homepage.txt)',
|
|
'- [Supabase Database](https://supabase.com/llms/database.txt)',
|
|
'- [Supabase Auth](https://supabase.com/llms/auth.txt)',
|
|
'- [Supabase Storage](https://supabase.com/llms/storage.txt)',
|
|
'- [Supabase Edge Functions](https://supabase.com/llms/edge-functions.txt)',
|
|
'- [Supabase Realtime](https://supabase.com/llms/realtime.txt)',
|
|
'- [Supabase Vector](https://supabase.com/llms/vector.txt)',
|
|
'- [Supabase Pricing](https://supabase.com/llms/pricing.txt)',
|
|
].join('\n')
|
|
|
|
async function generateMainLlmsTxt() {
|
|
const sourceLinks = SOURCES.filter((source) => source.enabled !== false)
|
|
.map((source) => `- ${toLink(source)}`)
|
|
.join('\n')
|
|
|
|
const fullText = [
|
|
`# ${metadataTitle}`,
|
|
'',
|
|
'For the complete documentation in a single file, see [Full Documentation](https://supabase.com/llms-full.txt).',
|
|
'',
|
|
'## Documentation',
|
|
'',
|
|
sourceLinks,
|
|
'',
|
|
'## Product Overview',
|
|
'',
|
|
PRODUCT_OVERVIEW_LINKS,
|
|
].join('\n')
|
|
|
|
await fs.writeFile('public/llms.txt', fullText)
|
|
}
|
|
|
|
// Product overview .txt files live in apps/www/public/llms/, read at build time.
|
|
// Order matters: homepage first, pricing last, products alphabetical in between.
|
|
const PRODUCT_LLM_FILES = [
|
|
'homepage.txt',
|
|
'auth.txt',
|
|
'database.txt',
|
|
'edge-functions.txt',
|
|
'realtime.txt',
|
|
'storage.txt',
|
|
'vector.txt',
|
|
'pricing.txt',
|
|
]
|
|
|
|
const PRODUCT_LLMS_DIR = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
'../../../apps/www/public/llms'
|
|
)
|
|
|
|
async function readProductLlmContent(): Promise<string> {
|
|
const contents = await Promise.all(
|
|
PRODUCT_LLM_FILES.map((file) => {
|
|
const filePath = path.join(PRODUCT_LLMS_DIR, file)
|
|
return fs.readFile(filePath, 'utf-8')
|
|
})
|
|
)
|
|
return contents.join('\n\n---\n\n')
|
|
}
|
|
|
|
async function generateLlmsTxt() {
|
|
try {
|
|
await fs.mkdir('public/llms', { recursive: true })
|
|
|
|
const enabledSources = SOURCES.filter((source) => source.enabled !== false)
|
|
|
|
// Fetch all sources once, reuse for both per-SDK files and llms-full.txt
|
|
const [productContent, ...fetchedSources] = await Promise.all([
|
|
readProductLlmContent(),
|
|
...enabledSources.map(async (sourceDefn) => {
|
|
const source = await sourceDefn.fetch()
|
|
const sourceText = source
|
|
.map((section) => {
|
|
section.process()
|
|
return section.extractIndexedContent()
|
|
})
|
|
.join('\n\n')
|
|
return { defn: sourceDefn, text: sourceText }
|
|
}),
|
|
])
|
|
|
|
await Promise.all([
|
|
generateMainLlmsTxt(),
|
|
// Per-SDK files
|
|
...fetchedSources.map(({ defn, text }) =>
|
|
fs.writeFile(`public/${defn.relPath}`, `${defn.title}\n\n${text}`)
|
|
),
|
|
// Combined file: product overview + all docs
|
|
fs.writeFile(
|
|
'public/llms-full.txt',
|
|
[
|
|
'# Supabase',
|
|
'',
|
|
'## Product Overview',
|
|
'',
|
|
productContent,
|
|
'',
|
|
'---',
|
|
'',
|
|
'## Documentation',
|
|
'',
|
|
fetchedSources.map(({ defn, text }) => `# ${defn.title}\n\n${text}`).join('\n\n---\n\n'),
|
|
].join('\n')
|
|
),
|
|
])
|
|
} catch (err) {
|
|
console.error(err)
|
|
throw err
|
|
}
|
|
}
|
|
|
|
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
generateLlmsTxt()
|
|
}
|