chore: Define new structure to support other definitions

This commit is contained in:
Jeremias Menichelli committed 2026-05-05 16:50:10 +02:00
1 parent fe40cae142
commit e5dc0db5e6
5 files changed
+135 -68355

No files matched your search

+4 -4
View File
@@ -36,7 +36,7 @@ export type LibKey = keyof typeof libs
*/
async function getReferenceContent(library: string, version: string | undefined) {
const libKey = getLibKey(library, version)
const filePath = join(REFERENCE_DIRECTORY, `${libKey}.mdx`)
const filePath = join(REFERENCE_DIRECTORY, libKey, 'index.mdx')
const fileContent = await fs.readFile(filePath, 'utf-8')
const { data: meta, content } = matter(fileContent)
return { meta, content } as { content: string; meta: GuideFrontmatter }
@@ -44,7 +44,7 @@ async function getReferenceContent(library: string, version: string | undefined)
async function getReferenceSections(library: string, version: string | undefined) {
const libKey = getLibKey(library, version)
const filePath = join(REFERENCE_DIRECTORY, `${libKey}.sections.json`)
const filePath = join(REFERENCE_DIRECTORY, libKey, 'sections.json')
const fileContent = await fs.readFile(filePath, 'utf-8')
return JSON.parse(fileContent) as AbbrevApiReferenceSection[]
}
@@ -79,7 +79,7 @@ async function ReferencePage() {
const { library, version } = params
const libKey = `${library}${version ? `-${version}` : ''}` as LibKey
const { name, icon, currentVersion, isLatestVersion } = libs[libKey]
// const sections = await getReferenceSections(library, version)
const sections = await getReferenceSections(library, version)
const { meta, content } = await getReferenceContent(library, version)
return (
@@ -89,7 +89,7 @@ async function ReferencePage() {
library={library}
version={currentVersion}
isLatestVersion={isLatestVersion}
sections={[]}
sections={sections}
meta={meta}
content={content}
/>
+102 -21
View File
@@ -1,17 +1,37 @@
import { mkdirSync, readFileSync, writeFileSync } from 'fs'
import { join, dirname, resolve } from 'path'
import { mkdirSync, readdirSync, writeFileSync } from 'fs'
import { join, dirname, relative } from 'path'
import { fileURLToPath } from 'url'
import { processSpec } from './process-tsdoc.js'
import { processSpec, type SpecCategory, type SpecConfig } from './process-tsdoc.js'
const __dirname = dirname(fileURLToPath(import.meta.url))
const CONFIG_PATH = join(__dirname, '../spec/enrichments/tsdoc_v2/config.json')
const config: {
title?: string
subtitle?: string
referenceLink?: string
referenceLinkLabel?: string
} = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'))
// ---------------------------------------------------------------------------
// Folder discovery
// ---------------------------------------------------------------------------
/**
* Recursively finds all folders under `baseDir` that directly contain at least
* one .json file other than config.json. These are treated as spec source folders.
*/
function findSpecFolders(baseDir: string): string[] {
const result: string[] = []
function walk(dir: string) {
const entries = readdirSync(dir, { withFileTypes: true })
const hasSource = entries.some(
(e) => e.isFile() && e.name.endsWith('.json') && e.name !== 'config.json'
)
if (hasSource) result.push(dir)
for (const e of entries) {
if (e.isDirectory()) walk(join(dir, e.name))
}
}
walk(baseDir)
return result
}
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
function escapeMdxProse(text: string): string {
// Escape { and } outside code blocks/spans so MDX doesn't treat them as JSX.
@@ -25,6 +45,35 @@ function prop(value: unknown): string {
return `{${JSON.stringify(value)}}`
}
function toSlug(name: string): string {
return name
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
// ---------------------------------------------------------------------------
// Sections builder
// ---------------------------------------------------------------------------
function buildSections(categories: SpecCategory[]): object[] {
return categories.map(({ category, definitions }) => ({
id: toSlug(category),
type: 'category',
title: category,
items: definitions.map((def) => ({
id: def.name,
type: 'function',
title: def.name,
slug: def.name,
})),
}))
}
// ---------------------------------------------------------------------------
// MDX builder
// ---------------------------------------------------------------------------
let exampleCounter = 0
function generateExamplesBlock(examples: any[]): string[] {
@@ -86,14 +135,16 @@ function generateExamplesBlock(examples: any[]): string[] {
return lines
}
function generateMdx(categories: ReturnType<typeof processSpec>): string {
function generateMdx(categories: SpecCategory[], config: SpecConfig): string {
const fmVal = (v: string) => `"${v.replace(/"/g, '\\"')}"`
const frontmatter = [
'---',
`title: ${fmVal(config.title ?? 'Reference')}`,
...(config.subtitle ? [`subtitle: ${fmVal(config.subtitle)}`] : []),
...(config.referenceLink ? [`referenceLink: ${fmVal(config.referenceLink)}`] : []),
...(config.referenceLinkLabel ? [`referenceLinkLabel: ${fmVal(config.referenceLinkLabel)}`] : []),
...(config.referenceLinkLabel
? [`referenceLinkLabel: ${fmVal(config.referenceLinkLabel)}`]
: []),
'---',
'',
'',
@@ -145,13 +196,43 @@ function generateMdx(categories: ReturnType<typeof processSpec>): string {
return lines.join('\n')
}
const processed = processSpec()
const mdx = generateMdx(processed)
// ---------------------------------------------------------------------------
// Main: discover and process all spec folders
// ---------------------------------------------------------------------------
const outPath = join(__dirname, '../content/reference/javascript.mdx')
mkdirSync(dirname(outPath), { recursive: true })
writeFileSync(outPath, mdx)
console.log(`MDX written to ${outPath}`)
console.log(
`${processed.length} categories, ${processed.flatMap((c) => c.definitions).length} definitions`
)
const specRefDir = join(__dirname, '../spec/reference')
const contentRefDir = join(__dirname, '../content/reference')
const specFolders = findSpecFolders(specRefDir)
if (specFolders.length === 0) {
console.warn(`No spec folders found under ${specRefDir}`)
process.exit(0)
}
for (const specDir of specFolders) {
exampleCounter = 0 // reset per spec so IDs don't bleed across files
const { categories, config } = processSpec(specDir)
// Mirror the folder path: spec/reference/<rel> → content/reference/<rel>
const rel = relative(specRefDir, specDir)
const outDir = join(contentRefDir, rel)
mkdirSync(outDir, { recursive: true })
// Write MDX
const mdx = generateMdx(categories, config)
const mdxPath = join(outDir, 'index.mdx')
writeFileSync(mdxPath, mdx)
// Write sections
const sections = buildSections(categories)
const sectionsPath = join(outDir, 'sections.json')
writeFileSync(sectionsPath, JSON.stringify(sections, null, 2))
console.log(`[${rel}] MDX → ${mdxPath}`)
console.log(`[${rel}] sections → ${sectionsPath}`)
console.log(
`[${rel}] ${categories.length} categories, ${categories.flatMap((c) => c.definitions).length} definitions`
)
}
+29 -36
View File
@@ -1,8 +1,5 @@
import { readFileSync, writeFileSync } from 'fs'
import { join, dirname, resolve } from 'path'
import { fileURLToPath } from 'url'
const __dirname = dirname(fileURLToPath(import.meta.url))
import { readFileSync, readdirSync } from 'fs'
import { join } from 'path'
type ContentItem = { kind: string; text: string }
type BlockTag = { tag: string; name?: string; content?: ContentItem[] }
@@ -167,6 +164,7 @@ function parseExamples(blockTags: BlockTag[]): any[] {
const responseTag = findByName(responses, title) ?? responses[i]
const descTag = findByName(descs, title) ?? descs[i]
// Use findLast so inline code spans (e.g. `select()`) before the fenced block are skipped
const sqlBlock = sqlTag?.content?.findLast((c) => c.kind === 'code')
const responseBlock = responseTag?.content?.findLast((c) => c.kind === 'code')
const notes = descTag?.content
@@ -205,29 +203,39 @@ function collectDeclarations(node: any): any[] {
return out
}
const SOURCE_FILES = [
'functions.json',
'gotrue.json',
'postgrest.json',
'realtime.json',
'storage.json',
'supabase.json',
]
export interface SpecConfig {
title?: string
subtitle?: string
referenceLink?: string
referenceLinkLabel?: string
ignoreDefinitions?: string[]
categoryOrder?: string[]
}
export function processSpec() {
const specDir = join(__dirname, '../spec/enrichments/tsdoc_v2')
export interface SpecCategory {
category: string
definitions: any[]
}
const config: { ignoreDefinitions?: string[]; categoryOrder?: string[] } = (() => {
export function processSpec(specDir: string): { categories: SpecCategory[]; config: SpecConfig } {
// Load config (optional — missing config is fine)
const config: SpecConfig = (() => {
try {
return JSON.parse(readFileSync(join(specDir, 'config.json'), 'utf-8'))
} catch {
return {}
}
})()
const ignoredNames = new Set(config.ignoreDefinitions ?? [])
const categoryOrder = config.categoryOrder ?? []
const roots = SOURCE_FILES.map((f) => JSON.parse(readFileSync(join(specDir, f), 'utf-8')))
// Discover all JSON source files in the spec folder, excluding config.json
const sourceFiles = readdirSync(specDir)
.filter((f) => f.endsWith('.json') && f !== 'config.json')
.map((f) => join(specDir, f))
const roots = sourceFiles.map((f) => JSON.parse(readFileSync(f, 'utf-8')))
// Build a per-file targetMap so IDs from different packages don't collide
const fileMaps = roots.map((root) => buildTargetMap(root))
@@ -244,14 +252,14 @@ export function processSpec() {
if (!categoryTag) continue // skip internal declarations with no category
const category = (categoryTag.content?.[0]?.text ?? '').split('\n')[0].trim()
if (ignoredNames.has(decl.name)) continue
if (!categoryMap.has(category)) categoryMap.set(category, [])
const sig = decl.signatures[0]
const remarkTags = blockTags.filter((t) => t.tag === '@remarks')
const examples = parseExamples(blockTags)
if (ignoredNames.has(decl.name)) continue
const definition: any = {
name: decl.name,
description: contentToMd(decl.comment?.summary ?? []),
@@ -266,7 +274,7 @@ export function processSpec() {
categoryMap.get(category)!.push(definition)
}
const result = Array.from(categoryMap.entries())
const categories = Array.from(categoryMap.entries())
.map(([category, definitions]) => ({ category, definitions }))
.sort((a, b) => {
const ai = categoryOrder.indexOf(a.category)
@@ -281,20 +289,5 @@ export function processSpec() {
return 0
})
console.log(result)
return result
}
// Only write to disk when run directly
const isMain = fileURLToPath(import.meta.url) === resolve(process.argv[1])
if (isMain) {
const output = processSpec()
writeFileSync(
join(__dirname, '../spec/enrichments/tsdoc_v2/processed.json'),
JSON.stringify(output, null, 2)
)
console.log(
`Done: ${output.length} categories, ${output.flatMap((c) => c.definitions).length} declarations`
)
return { categories, config }
}
@@ -1,16 +0,0 @@
{
"title":"JavaScript Client library",
"subtitle": "Use the `supabase-js` package to interact with your Postgres database, listen to database changes, invoke Deno Edge Functions, build login and user management functionality, and manage large files.",
"referenceLink": "https://github.com/supabase/supabase-js",
"referenceLinkLabel": "@supabase/supabase-js",
"versions": [
"v1",
"v2"
],
"isLatestVersion": true,
"version": "v2",
"ignoreDefinitions": [
"constructor"
],
"categoryOrder": [ "Initializing", "Database", "Auth", "Functions", "Realtime", "Storage" ]
}
File diff suppressed because it is too large. Load diff