Fetch versioned references from repos (#12987)

* save point

* Versioned references straight from github

* Remove old hardcoded references

* Fix copy as markdown

* Error handling

* Point to the real repos
This commit is contained in:
Vincent (Wen Yu) Ge
2025-10-17 15:15:17 -04:00
committed by GitHub
parent 3a7b1eb695
commit 0367239ece
10 changed files with 387 additions and 13257 deletions

View File

@@ -162,14 +162,6 @@ module.exports = {
ignore: [`**/*.{png,jpg,jpeg,gif,svg,webp,mp4,avi,mov}`],
},
},
{
resolve: `gatsby-source-filesystem`,
options: {
name: `sdkReferences`,
path: `${__dirname}/src/data/sdkReferences`,
ignore: [`**/*.{png,jpg,jpeg,gif,svg,webp,mp4,avi,mov}`],
},
},
{
resolve: `gatsby-source-strapi-pages`,
options: {

View File

@@ -336,73 +336,78 @@ export const createPages: GatsbyNode['createPages'] = async ({ actions: { create
type
}
}
allTypes: allSdkReferencesJson {
edges {
node {
info {
version
id
title
allSdkReferences {
nodes {
info {
description
id
specUrl
slugPrefix
title
version
}
referenceId
hogRef
id
categories
classes {
description
functions {
category
description
slugPrefix
}
types {
id
name
properties {
description
type
details
examples {
code
name
id
}
id
params {
description
isOptional
name
type
}
path
example
releaseTag
showDocs
returnType {
id
name
}
title
}
id
title
}
version
}
}
allSdkReferencesJson {
edges {
node {
allSdkTypes: allSdkReferences {
nodes {
id
version
referenceId
info {
description
id
hogRef
info {
version
slugPrefix
specUrl
title
version
}
hogRef
categories
types {
example
id
name
path
properties {
description
id
slugPrefix
specUrl
title
name
type
}
classes {
description
id
title
functions {
category
description
details
id
showDocs
title
releaseTag
examples {
code
id
name
}
params {
description
isOptional
type
name
}
returnType {
id
name
}
}
}
categories
}
}
}
@@ -829,36 +834,76 @@ export const createPages: GatsbyNode['createPages'] = async ({ actions: { create
})
})
const types = result.data.allTypes.edges.map(({ node }) => node.types.map(({ name }) => name)).flat()
result.data.allSdkReferencesJson.edges.forEach(({ node }) => {
createPage({
path: `/docs/references/${node.info.slugPrefix}`,
component: SdkReferenceTemplate,
context: {
name: node.info.title,
description: node.info.description,
fullReference: node,
regex: `/docs/references/${node.info.slugPrefix}`,
types,
},
})
// Grab types available for each SDK and version
const sdkTypesByReference = result.data.allSdkTypes.nodes.reduce((acc, node) => {
const { referenceId, version, ...types } = node
if (!acc[referenceId]) {
acc[referenceId] = {}
}
acc[referenceId][version] = types.types.map(({ name }) => name)
return acc
}, {} as Record<string, Record<string, any>>)
result.data.allSdkReferences.nodes.forEach((node) => {
if (node.version.includes('latest')) {
createPage({
path: `/docs/references/${node.referenceId}`,
component: SdkReferenceTemplate,
context: {
name: node.info.title,
description: node.info.description,
fullReference: node,
regex: `/docs/references/${node.referenceId}`,
types: sdkTypesByReference?.[node.referenceId]?.[node.version] ?? [],
},
})
} else {
createPage({
path: `/docs/references/${node.id}`,
component: SdkReferenceTemplate,
context: {
name: node.info.title,
description: node.info.description,
fullReference: node,
regex: `/docs/references/${node.id}`,
// Null checks, only affects type crosslinking, won't break build
types: sdkTypesByReference?.[node.referenceId]?.[node.version] ?? [],
},
})
}
})
result.data.allTypes.edges.forEach(({ node }) => {
const version = node.info.version
result.data.allSdkTypes.nodes.forEach((node) => {
node.types?.forEach((type) => {
if (type.id && (type.properties || type.example)) {
createPage({
path: `/docs/references/${node.info.slugPrefix}/types/${type.id}`,
component: SdkTypeTemplate,
context: {
typeData: type,
version,
id: node.info.id,
types,
slugPrefix: node.info.slugPrefix,
},
})
if (node.version.includes('latest')) {
createPage({
path: `/docs/references/${node.referenceId}/types/${type.id}`,
component: SdkTypeTemplate,
context: {
typeData: type,
version: node.version,
id: node.id,
types: sdkTypesByReference?.[node.referenceId]?.[node.version] ?? [],
slugPrefix: node.referenceId,
},
})
} else {
createPage({
path: `/docs/references/${node.id}/types/${type.id}`,
component: SdkTypeTemplate,
context: {
typeData: type,
version: node.version,
id: node.id,
types: sdkTypesByReference?.[node.referenceId]?.[node.version] ?? [],
slugPrefix: node.id,
},
})
}
}
})
})

View File

@@ -337,56 +337,57 @@ export const onPostBuild: GatsbyNode['onPostBuild'] = async ({ graphql }) => {
const sdkReferencesQuery = (await graphql(`
query {
allSdkReferencesJson {
edges {
node {
allSdkReferences {
nodes {
info {
description
id
hogRef
info {
version
description
id
slugPrefix
specUrl
title
}
classes {
description
id
title
functions {
category
description
details
id
showDocs
title
releaseTag
examples {
code
id
name
}
params {
description
isOptional
type
name
}
returnType {
id
name
}
}
}
categories
specUrl
slugPrefix
title
version
}
referenceId
hogRef
id
categories
classes {
description
functions {
category
description
details
examples {
code
name
id
}
id
params {
description
isOptional
name
type
}
path
releaseTag
showDocs
returnType {
id
name
}
title
}
id
title
}
version
}
}
}
`)) as { data: { allSdkReferencesJson: { edges: { node: SdkReferenceData }[] } } }
`)) as { data: { allSdkReferences: { nodes: SdkReferenceData[] } } }
sdkReferencesQuery.data.allSdkReferencesJson.edges.forEach(({ node }) => {
sdkReferencesQuery.data.allSdkReferences.nodes.forEach((node) => {
generateSdkReferencesMarkdown(node)
})

View File

@@ -184,7 +184,16 @@ export const generateSdkReferencesMarkdown = (sdkReferences: SdkReferenceData) =
}
const sdkLanguage = getLanguageFromSdkId(sdkReferences.info.id)
const filePath = path.join(sdkSpecDir, `${sdkReferences.info.slugPrefix}.md`)
// Follow the same path logic as createPages.ts
let fileName: string
if (sdkReferences.version.includes('latest')) {
fileName = `${sdkReferences.referenceId}.md`
} else {
fileName = `${sdkReferences.id}.md`
}
const filePath = path.join(sdkSpecDir, fileName)
const renderTypeAsText = (type: string): string => {
return type

View File

@@ -75,6 +75,121 @@ export const sourceNodes: GatsbyNode['sourceNodes'] = async ({ actions, createCo
components: JSON.stringify(spec.components),
})
// PostHog SDK references
// Paths to the SDK references folders
const SDK_REFERENCES_FOLDER_PATHS = [
{
id: 'posthog-python',
repo: 'posthog/posthog-python',
repo_branch: 'master',
folder_path: 'references',
},
{
id: 'posthog-js',
repo: 'posthog/posthog-js',
repo_branch: 'main',
folder_path: 'packages/browser/references',
},
{
id: 'posthog-node',
repo: 'posthog/posthog-js',
repo_branch: 'main',
folder_path: 'packages/node/references',
},
{
id: 'posthog-react-native',
repo: 'posthog/posthog-js',
repo_branch: 'main',
folder_path: 'packages/react-native/references',
},
]
const referencesData = {}
function parseReferencesVersion(version: string): string {
return version.split('-').pop()?.replace('.json', '') || ''
}
// Fetch all folder contents in a batch
const folderPromises = SDK_REFERENCES_FOLDER_PATHS.map(async (folderPath) => {
const url = `https://api.github.com/repos/${folderPath.repo}/contents/${folderPath.folder_path}?ref=${folderPath.repo_branch}`
const response = await fetch(url)
const data = await response.json()
return { folderPath, data }
})
const folderResults = await Promise.allSettled(folderPromises).then((results) =>
results
.map((result) => {
if (result.status === 'fulfilled' && result.value?.data && Array.isArray(result.value.data)) {
return { folderPath: result.value.folderPath, data: result.value.data }
}
console.error('Failed to fetch SDK reference files')
return null
})
.filter((result) => result !== null)
)
for (const { folderPath, data } of folderResults) {
referencesData[folderPath.id] = {}
const filePromises = data
.filter((item) => item.type === 'file')
.map(async (item) => {
const fileUrl = `https://api.github.com/repos/${folderPath.repo}/contents/${folderPath.folder_path}/${item.name}?ref=${folderPath.repo_branch}`
const fileResponse = await fetch(fileUrl)
const fileData = await fileResponse.json()
const version = parseReferencesVersion(item.name)
return { version, content: fileData.content }
})
const fileResults = await Promise.allSettled(filePromises).then((results) =>
results.map((result) => {
if (result.status === 'fulfilled' && result.value?.version && result.value?.content) {
return { version: result.value.version, content: result.value.content }
}
console.error(`Failed to fetch SDK reference file in ${folderPath.repo}/${folderPath.folder_path}`)
return null
})
)
for (const { version, content } of fileResults.filter((result) => result !== null)) {
if (version) {
referencesData[folderPath.id][version] = content
}
}
}
for (const id in referencesData) {
// Create version-specific nodes
for (const version in referencesData[id]) {
// Decode the base64 data first
const versionDataBase64 = referencesData[id][version]
let versionData
try {
const decodedString = Buffer.from(versionDataBase64, 'base64').toString('utf-8')
versionData = JSON.parse(decodedString)
} catch (error) {
console.error(`Failed to decode version ${version} for ${id}:`, error)
continue
}
versionData.id = `${id}-${version}`
const versionNode = {
parent: null,
children: [],
internal: {
type: `SdkReferences`,
contentDigest: createContentDigest(versionData),
},
referenceId: id,
version: version,
...versionData, // Spread the decoded JSON data
}
createNode(versionNode)
}
}
const postHogIssues = await fetch(
'https://api.github.com/repos/posthog/posthog/issues?sort=comments&per_page=5'
).then((res) => res.json())

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +1,19 @@
import React, { useState, useEffect } from 'react'
import Layout from '../../components/Layout'
import { graphql } from 'gatsby'
import SEO from '../../components/seo'
import PostLayout from '../../components/PostLayout'
import CommunityQuestions from '../../components/CommunityQuestions'
import { docsMenu } from '../../navs'
import ReactMarkdown from 'react-markdown'
import Accordion from '../../components/SdkReferences/Accordion'
import Parameters from '../../components/SdkReferences/Parameters'
import FunctionReturn from '../../components/SdkReferences/Return'
import FunctionExamples from '../../components/SdkReferences/Examples'
import CopyMarkdownActionsDropdown from '../../components/MarkdownActionsDropdown'
import { useLocation } from '@reach/router'
import { navigate } from 'gatsby'
import Link from '../../components/Link'
import { getLanguageFromSdkId } from '../../components/SdkReferences/utils'
import { Heading } from '../../components/Heading'
import Chip from '../../components/Chip'
import ReaderView from 'components/ReaderView'
import { Popover } from 'components/RadixUI/Popover'
import { IconChevronDown } from '@posthog/icons'
export interface Parameter {
name: string
@@ -58,6 +55,8 @@ export interface Class {
export interface SdkReferenceData {
id: string
hogRef: string
referenceId: string
version: string
info: {
description: string
id: string
@@ -75,6 +74,16 @@ export interface PageContext {
types: string[]
}
export interface VersionsData {
allSdkReferences: {
nodes: Array<{
id: string
version: string
referenceId: string
}>
}
}
const padDescription = (description: string): string => {
return description?.replace(/\n/g, '\n\n') || ''
}
@@ -121,7 +130,7 @@ function groupFunctionsByCategory(functions: SdkFunction[]): { label: string | n
return ordered
}
export default function SdkReference({ pageContext }: { pageContext: PageContext }) {
export default function SdkReference({ pageContext, data }: { pageContext: PageContext; data: VersionsData }) {
const { fullReference } = pageContext
const location = useLocation()
@@ -129,8 +138,27 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
const sdkLanguage = getLanguageFromSdkId(fullReference.info.id)
const validTypes = pageContext.types
const sdkVersions = data.allSdkReferences.nodes
// Get versions for current referenceId
const currentReferenceId = fullReference.info.id
// Sort versions by version string (descending)
const availableVersions = sdkVersions
.filter((version) => version.referenceId === currentReferenceId)
.sort((a, b) => {
if (a.version === 'latest') return -1
if (b.version === 'latest') return 1
return b.version.localeCompare(a.version, undefined, { numeric: true })
})
// State for filtering
const [currentFilter, setCurrentFilter] = useState('all')
const [versionPopoverOpen, setVersionPopoverOpen] = useState(false)
// Derive current version from fullReference.id
const getCurrentVersion = () => {
return fullReference.id.replace(`${currentReferenceId}-`, '')
}
// Pre-transform classes with sorted functions
const sortedClasses = fullReference.classes.map((classData) => ({
@@ -161,6 +189,17 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
navigate(`${location.pathname}?filter=${queryParam}`)
}
// Go to selected version page
const handleVersionChange = (version: string) => {
setVersionPopoverOpen(false)
const versionId = `${currentReferenceId}-${version}`
if (version === 'latest') {
navigate(`/docs/references/${currentReferenceId}`)
} else {
navigate(`/docs/references/${versionId}`)
}
}
// Initialize filter from query params
useEffect(() => {
const params = new URLSearchParams(location?.search)
@@ -213,22 +252,6 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
const filteredClasses = getFilteredClasses()
// Generate ToC from filtered classes and functions
const tableOfContents = filteredClasses.flatMap((classData) => [
{
url: classData.id,
value: `${classData.title}`,
depth: 0,
},
...classData.sortedFunctions.flatMap(({ functions }) =>
functions.map((func) => ({
url: `${classData.id}-${func.id}`,
value: `${func.title}()`,
depth: 1,
}))
),
])
return (
<ReaderView markdownContent={JSON.stringify(fullReference, null, 2)}>
<SEO title={`${fullReference.info.title} - PostHog`} />
@@ -240,8 +263,37 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
<h1 className="dark:text-white text-3xl sm:text-4xl m-0">{fullReference.info.title}</h1>
<div className="flex space-x-2 items-center mb-4 md:mt-1 md:mb-0 text-black dark:text-white">
<p className="m-0 font-semibold text-primary/30 dark:text-primary-dark/30">
SDK Version: {fullReference.info.version}
SDK Version:
</p>
<Popover
trigger={
<button className="text-primary hover:text-primary dark:text-primary-dark dark:hover:text-primary-dark text-left items-center justify-center text-sm font-semibold flex select-none gap-2">
<span>
{getCurrentVersion()}
{getCurrentVersion() === 'latest' && (
<span className="text-xs text-primary/60 dark:text-primary-dark/60">
{' '}
({fullReference.info.version})
</span>
)}
</span>
<IconChevronDown className="size-6" />
</button>
}
dataScheme="secondary"
open={versionPopoverOpen}
onOpenChange={setVersionPopoverOpen}
>
{availableVersions.map((version) => (
<button
key={version.version}
onClick={() => handleVersionChange(version.version)}
className="flex items-center gap-2 px-2 py-1 text-sm rounded hover:bg-accent transition-colors w-full"
>
<span>{version.version}</span>
</button>
))}
</Popover>
</div>
</div>
</div>
@@ -305,7 +357,7 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
</Accordion>
)}
<Parameters
slugPrefix={fullReference.info.slugPrefix}
slugPrefix={`${currentReferenceId}-${fullReference.info.version}`}
params={func.params}
validTypes={validTypes}
/>
@@ -314,7 +366,7 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
<div className="lg:sticky top-[108px] space-y-6">
<FunctionExamples examples={func.examples} language={sdkLanguage} />
<FunctionReturn
slugPrefix={fullReference.info.slugPrefix}
slugPrefix={`${currentReferenceId}-${fullReference.info.version}`}
returnType={func.returnType}
validTypes={validTypes}
/>
@@ -331,3 +383,15 @@ export default function SdkReference({ pageContext }: { pageContext: PageContext
</ReaderView>
)
}
export const query = graphql`
query SdkReferencesQuery {
allSdkReferences {
nodes {
id
version
referenceId
}
}
}
`