Files
upstream-tanstack-query/scripts/generate-docs.ts
upstream-mirror 3a013ccdfe
Some checks failed
autofix.ci / autofix (push) Failing after 4s
Release / Release (push) Has been skipped
GitHub Actions Security Analysis / Run zizmor (push) Failing after 2s
snapshot of https://github.com/TanStack/query.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 01:13:08 +05:00

351 lines
12 KiB
TypeScript

import { mkdir, readFile, readdir, rm, writeFile } from 'node:fs/promises'
import { createRequire } from 'node:module'
import { dirname, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = fileURLToPath(new URL('.', import.meta.url))
const require = createRequire(import.meta.url)
const typedocConfigPackageJson =
require.resolve('@tanstack/typedoc-config/package.json')
const typedocConfigDir = dirname(typedocConfigPackageJson)
const typedocConfigRequire = createRequire(typedocConfigPackageJson)
const TypeDoc = await import(typedocConfigRequire.resolve('typedoc'))
type PackageReferenceDocsConfig = {
entryPoints: Array<string>
tsconfig: string
outputDir: string
exclude?: Array<string>
excludeExternals?: boolean
simplifyLitQueriesControllerTypes?: boolean
trimGeneratedMarkdown?: boolean
// Maps a generated page's path (relative to outputDir, no extension) to the flat
// `docs/framework/<framework>/reference/<name>.md` URLs it replaced, so old links/bookmarks redirect
// instead of falling through to the framework docs index. See https://github.com/TanStack/query/issues/11371
redirectFrom?: Record<string, Array<string>>
}
type TypeDocReflectionWithSignatures = {
name: string
children?: Array<TypeDocReflectionWithSignatures>
signatures?: Array<{
typeParameters?: Array<{
name: string
default?: unknown
}>
}>
}
function simplifyLitQueriesControllerTypes(
project: TypeDocReflectionWithSignatures,
) {
const stack: Array<TypeDocReflectionWithSignatures> = [project]
for (const reflection of stack) {
stack.push(...(reflection.children ?? []))
if (reflection.name !== 'createQueriesController') {
continue
}
for (const signature of reflection.signatures ?? []) {
const combinedResult = signature.typeParameters?.find(
(typeParameter) => typeParameter.name === 'TCombinedResult',
)
if (!combinedResult?.default) {
continue
}
const queryOptionsType = TypeDoc.ReferenceType.createBrokenReference(
'TQueryOptions',
project,
undefined,
)
queryOptionsType.refersToTypeParameter = true
// CreateQueriesResults is internal; render it as plain text, not a link.
const queriesResultsType = TypeDoc.ReferenceType.createBrokenReference(
'CreateQueriesResults',
project,
undefined,
)
queriesResultsType.typeArguments = [queryOptionsType]
combinedResult.default = queriesResultsType
}
}
}
async function trimTrailingWhitespaceInMarkdown(outputDir: string) {
const entries = await readdir(outputDir, { withFileTypes: true })
await Promise.all(
entries.map(async (entry) => {
const path = resolve(outputDir, entry.name)
if (entry.isDirectory()) {
await trimTrailingWhitespaceInMarkdown(path)
return
}
if (!entry.isFile() || !path.endsWith('.md')) {
return
}
const markdown = await readFile(path, 'utf8')
const trimmed = markdown.replace(/[ \t]+$/gm, '')
if (trimmed !== markdown) {
await writeFile(path, trimmed)
}
}),
)
}
async function addRedirectFromToFileFrontmatter(
filePath: string,
fromPaths: Array<string>,
) {
const markdown = await readFile(filePath, 'utf8')
const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n/)
if (!frontmatterMatch) {
throw new Error(`Expected frontmatter in ${filePath}`)
}
const redirectLines = fromPaths
.map((fromPath) => ` - ${fromPath}`)
.join('\n')
const updatedFrontmatter = `---\n${frontmatterMatch[1]}\nredirect_from:\n${redirectLines}\n---\n`
await writeFile(
filePath,
updatedFrontmatter + markdown.slice(frontmatterMatch[0].length),
)
}
async function addRedirectFromToFrontmatter(
outputDir: string,
redirectFrom: Record<string, Array<string>>,
) {
for (const [pagePath, fromPaths] of Object.entries(redirectFrom)) {
await addRedirectFromToFileFrontmatter(
resolve(outputDir, `${pagePath}.md`),
fromPaths,
)
}
}
async function generatePackageReferenceDocs(pkg: PackageReferenceDocsConfig) {
const outputDir = pkg.outputDir
await rm(outputDir, { recursive: true, force: true })
await mkdir(outputDir, { recursive: true })
const app = await TypeDoc.Application.bootstrapWithPlugins({
plugin: [
'typedoc-plugin-markdown',
'typedoc-plugin-frontmatter',
resolve(typedocConfigDir, './src/typedoc-custom-settings.js'),
],
hideGenerator: true,
readme: 'none',
entryFileName: 'index',
hideBreadcrumbs: true,
hidePageHeader: true,
hidePageTitle: true,
useCodeBlocks: true,
excludePrivate: true,
excludeInternal: true,
excludeExternals: pkg.excludeExternals,
sourceLinkTemplate:
'https://github.com/TanStack/query/blob/{gitRevision}/{path}#L{line}',
gitRevision: 'main',
entryPoints: pkg.entryPoints,
tsconfig: pkg.tsconfig,
...(pkg.exclude && { exclude: pkg.exclude }),
out: outputDir,
})
const project = await app.convert()
if (project) {
if (pkg.simplifyLitQueriesControllerTypes) {
simplifyLitQueriesControllerTypes(project)
}
await app.generateOutputs(project)
if (pkg.trimGeneratedMarkdown) {
await trimTrailingWhitespaceInMarkdown(outputDir)
}
if (pkg.redirectFrom) {
await addRedirectFromToFrontmatter(outputDir, pkg.redirectFrom)
}
}
}
const packages: Array<PackageReferenceDocsConfig> = [
{
entryPoints: [
resolve(__dirname, '../packages/angular-query-experimental/src/index.ts'),
],
tsconfig: resolve(
__dirname,
'../packages/angular-query-experimental/tsconfig.json',
),
outputDir: resolve(__dirname, '../docs/framework/angular/reference'),
},
{
entryPoints: [resolve(__dirname, '../packages/svelte-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/svelte-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/svelte/reference'),
},
{
entryPoints: [resolve(__dirname, '../packages/solid-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/solid-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/solid/reference'),
redirectFrom: {
'functions/infiniteQueryOptions': [
'framework/solid/reference/infiniteQueryOptions',
],
'functions/mutationOptions': [
'framework/solid/reference/mutationOptions',
],
'functions/queryOptions': ['framework/solid/reference/queryOptions'],
'functions/useInfiniteQuery': [
'framework/solid/reference/useInfiniteQuery',
],
'functions/useIsFetching': ['framework/solid/reference/useIsFetching'],
'functions/useIsMutating': ['framework/solid/reference/useIsMutating'],
'functions/useMutation': ['framework/solid/reference/useMutation'],
'functions/useMutationState': [
'framework/solid/reference/useMutationState',
],
'functions/useQueries': ['framework/solid/reference/useQueries'],
'functions/useQuery': ['framework/solid/reference/useQuery'],
},
},
{
entryPoints: [resolve(__dirname, '../packages/vue-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/vue-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/vue/reference'),
},
{
entryPoints: [resolve(__dirname, '../packages/react-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/react-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/react/reference'),
redirectFrom: {
'functions/infiniteQueryOptions': [
'framework/react/reference/infiniteQueryOptions',
],
'functions/mutationOptions': [
'framework/react/reference/mutationOptions',
],
'functions/QueryClientProvider': [
'framework/react/reference/QueryClientProvider',
],
'functions/QueryErrorResetBoundary': [
'framework/react/reference/QueryErrorResetBoundary',
],
'functions/queryOptions': ['framework/react/reference/queryOptions'],
'functions/useInfiniteQuery': [
'framework/react/reference/useInfiniteQuery',
],
'functions/useIsFetching': ['framework/react/reference/useIsFetching'],
'functions/useIsMutating': ['framework/react/reference/useIsMutating'],
'functions/useMutation': ['framework/react/reference/useMutation'],
'functions/useMutationState': [
'framework/react/reference/useMutationState',
],
'functions/usePrefetchInfiniteQuery': [
'framework/react/reference/usePrefetchInfiniteQuery',
],
'functions/usePrefetchQuery': [
'framework/react/reference/usePrefetchQuery',
],
'functions/useQueries': ['framework/react/reference/useQueries'],
'functions/useQuery': ['framework/react/reference/useQuery'],
'functions/useQueryClient': ['framework/react/reference/useQueryClient'],
'functions/useQueryErrorResetBoundary': [
'framework/react/reference/useQueryErrorResetBoundary',
],
'functions/useSuspenseInfiniteQuery': [
'framework/react/reference/useSuspenseInfiniteQuery',
],
'functions/useSuspenseQueries': [
'framework/react/reference/useSuspenseQueries',
],
'functions/useSuspenseQuery': [
'framework/react/reference/useSuspenseQuery',
],
// Redirects from the legacy hand-written docs/reference/*.md pages, removed in favor of
// this generated reference.
'classes/QueryClient': [
'reference/QueryClient',
'framework/react/reference/QueryClient',
],
'classes/QueryCache': [
'reference/QueryCache',
'framework/react/reference/QueryCache',
],
'classes/MutationCache': [
'reference/MutationCache',
'framework/react/reference/MutationCache',
],
'classes/QueryObserver': [
'reference/QueryObserver',
'framework/react/reference/QueryObserver',
],
'classes/InfiniteQueryObserver': [
'reference/InfiniteQueryObserver',
'framework/react/reference/InfiniteQueryObserver',
],
'classes/QueriesObserver': [
'reference/QueriesObserver',
'framework/react/reference/QueriesObserver',
],
// focusManager/onlineManager/timeoutManager are class instances, not object literals, so
// TypeDoc can't inline their methods onto the `variables/*` instance page — the method docs
// that the legacy pages covered now live on the `interfaces/*` page for the class itself.
'interfaces/FocusManager': [
'reference/focusManager',
'framework/react/reference/focusManager',
],
'interfaces/OnlineManager': [
'reference/onlineManager',
'framework/react/reference/onlineManager',
],
'interfaces/TimeoutManager': ['reference/timeoutManager'],
'variables/notifyManager': [
'reference/notifyManager',
'framework/react/reference/notifyManager',
],
'variables/environmentManager': ['reference/environmentManager'],
'functions/experimental_streamedQuery': ['reference/streamedQuery'],
},
},
{
entryPoints: [resolve(__dirname, '../packages/preact-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/preact-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/preact/reference'),
},
{
entryPoints: [resolve(__dirname, '../packages/lit-query/src/index.ts')],
tsconfig: resolve(__dirname, '../packages/lit-query/tsconfig.json'),
outputDir: resolve(__dirname, '../docs/framework/lit/reference'),
excludeExternals: true,
simplifyLitQueriesControllerTypes: true,
trimGeneratedMarkdown: true,
},
]
for (const pkg of packages) {
await generatePackageReferenceDocs(pkg)
}
console.log('\n✅ All markdown files have been processed!')
process.exit(0)