chore(docs): turn on strictNullChecks (#36180)

strictNullChecks was off for docs, which lets errors slip through and
leads to incorrect required/optional typing on Zod-inferred types. This
PR enables strictNullChecks and fixes all the existing violations.
This commit is contained in:
Charis authored and GitHub committed 2025-06-04 17:05:37 -04:00
1 parent 5862505164
commit cf3ecc93eb
70 files changed
+487 -531

No files matched your search

+2 -2
View File
@@ -29,8 +29,8 @@ export async function GET(request: Request) {
slug = maybeVersion
}
let section: AbbrevApiReferenceSection
let sectionsWithUrl: Array<AbbrevApiReferenceSection & { url: URL }>
let section: AbbrevApiReferenceSection | undefined
let sectionsWithUrl: Array<AbbrevApiReferenceSection & { url: URL }> = []
try {
const flattenedSections = (await getFlattenedSections(lib, version)) ?? []
sectionsWithUrl = flattenedSections.map((section) => {
@@ -22,9 +22,9 @@ export function ContributingToc({ className }: { className?: string }) {
...document.querySelectorAll('article.prose > h2,h3:not(#feedback-title)'),
] as Array<HTMLHeadingElement>
const tocItems = headings
.filter((heading) => !!heading.id)
.filter((heading) => !!heading.id && heading.textContent)
.map((heading) => ({
label: heading.textContent.substring(0, heading.textContent.length - 1), // Remove ending `#`
label: heading.textContent!.substring(0, heading.textContent!.length - 1), // Remove ending `#`
anchor: heading.id,
}))
setTocItems(tocItems)
@@ -83,6 +83,12 @@ export async function generateAiPromptMetadata(props: { params: Promise<{ slug:
const { slug } = await props.params
const prompt = await getAiPrompt(slug)
if (!prompt) {
return {
title: 'AI Prompt | Supabase Docs',
}
}
return {
title: `AI Prompt: ${prompt.heading} | Supabase Docs`,
}
@@ -1,3 +1,4 @@
import { notFound } from 'next/navigation'
import { GuideTemplate, newEditLink } from '~/features/docs/GuidesMdx.template'
import {
generateAiPromptMetadata,
@@ -12,7 +13,12 @@ export default async function AiPromptsPage(props: { params: Promise<{ slug: str
const { slug } = params
let { heading, content } = await getAiPrompt(slug)
const prompt = await getAiPrompt(slug)
if (!prompt) {
notFound()
}
let { heading, content } = prompt
content = `
## How to use
+16 -10
View File
@@ -15,8 +15,12 @@ const generateMetadata = async (_, parent: ResolvingMetadata): Promise<Metadata>
return {
alternates: {
...parentAlternates,
canonical: `${BASE_PATH}`,
...(parentAlternates && {
languages: parentAlternates.languages || undefined,
media: parentAlternates.media || undefined,
types: parentAlternates.types || undefined,
}),
},
}
}
@@ -251,15 +255,17 @@ const HomePage = () => (
</div>
<ul className="grid col-span-8 grid-cols-12 gap-6 not-prose">
{MIGRATION_PAGES.sort((a, b) => a.name.localeCompare(b.name)).map((guide) => {
return (
<li key={guide.name} className="col-span-6 md:col-span-4">
<Link href={guide.url} passHref>
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
</Link>
</li>
)
})}
{MIGRATION_PAGES.sort((a, b) => (a.name || '').localeCompare(b.name || '')).map(
(guide) => {
return (
<li key={guide.name} className="col-span-6 md:col-span-4">
<Link href={guide.url || '#'} passHref>
<IconPanel {...guide} title={guide.name} background={true} showLink={false} />
</Link>
</li>
)
}
)}
</ul>
</div>
+2 -2
View File
@@ -6,11 +6,11 @@ import { Tabs, TabPanel } from '~/features/ui/Tabs'
type IParamProps = any
const ApiSchema = ({ schema, id }: IParamProps) => {
let example: string
let example: string | undefined
try {
example = sample(schema, { skipReadOnly: true, quiet: true })
} catch {
// ignore
example = undefined
}
return (
<Tabs
@@ -133,7 +133,7 @@ const AppleSecretGenerator = () => {
<input
type="file"
onChange={(e) => {
setFile({ file: e.target.files[0] })
setFile({ file: e.target.files?.[0] || null })
}}
/>
</div>
@@ -156,7 +156,7 @@ const AppleSecretGenerator = () => {
keyID,
teamID,
serviceID,
file.file
file.file!
)
setKeyID(kid)
setSecretKey(jwt)
+1 -1
View File
@@ -181,7 +181,7 @@ function useBreadcrumbs() {
return findMenuItemByUrl(menu, pathname, [])
}
function findMenuItemByUrl(menu, targetUrl, parents = []) {
function findMenuItemByUrl(menu: any, targetUrl: string, parents: any[] = []) {
// If the menu has items, recursively search through them
if (menu.items) {
for (let item of menu.items) {
@@ -24,7 +24,7 @@ function getLinkTarget(link: string): LinkTarget {
}
function getUniqueTags(json: Extension[]): string[] {
const tags = []
const tags: string[] = []
for (const item of json) {
if (item.tags) {
tags.push(...item.tags)
@@ -96,6 +96,8 @@ function Feedback({ className }: { className?: string }) {
const showNo = unanswered || isNo
async function sendFeedbackVote(response: Response) {
if (!supabase) return
const { error } = await supabase.from('feedback').insert({
vote: response,
page: pathname,
@@ -59,7 +59,7 @@ export default function JwtGenerator() {
const [jwtSecret, setJwtSecret] = useState(secret)
const [token, setToken] = useState(anonToken)
const [signedToken, setSignedToken] = useState('')
const [err, setErr] = useState<string>(null)
const [err, setErr] = useState<string>('')
const handleKeySelection = (e: ChangeEvent<HTMLSelectElement>) => {
const val = e.target.value
@@ -71,7 +71,7 @@ export default function JwtGenerator() {
try {
const newTok = JSON.parse(e.target.value)
setToken(newTok)
setErr(null)
setErr('')
} catch (err) {
const errMessage =
!!err && typeof err === 'object' && 'message' in err && typeof err.message === 'string'
-196
View File
@@ -1,196 +0,0 @@
'use client'
import { ChevronsUpDown } from 'lucide-react'
import {
createContext,
forwardRef,
useCallback,
useContext,
useId,
useState,
PropsWithChildren,
useRef,
useEffect,
} from 'react'
import {
cn,
Badge,
Checkbox_Shadcn_ as Checkbox,
Popover_Shadcn_ as Popover,
PopoverContent_Shadcn_ as PopoverContent,
PopoverTrigger_Shadcn_ as PopoverTrigger,
} from 'ui'
interface Item {
value: string
label: string
}
const MultiSelectContext = createContext<{
selected: Item[]
handleSelect: (item: Item) => void
setOpen: (open: boolean) => void
}>({
selected: [],
handleSelect: () => {},
setOpen: () => {},
})
function MultiSelectProvider({
selected,
onSelectedChange,
setOpen,
children,
}: PropsWithChildren<{
selected: Item[]
onSelectedChange: (selected: Item[] | ((selected: Item[]) => Item[])) => void
setOpen: (open: boolean) => void
}>) {
const handleSelect = useCallback(
(item: Item) => {
onSelectedChange((current: Item[]) => {
const isSelected = current.some((currItem) => currItem.value === item.value)
if (isSelected) {
return current.filter((currItem) => currItem.value !== item.value)
} else {
return [...current, item]
}
})
},
[onSelectedChange]
)
return (
<MultiSelectContext.Provider value={{ selected, handleSelect, setOpen }}>
{children}
</MultiSelectContext.Provider>
)
}
function useMultiSelect() {
const context = useContext(MultiSelectContext)
if (!context) {
throw new Error('useMultiSelect must be used within a MultiSelectProvider')
}
return context
}
export function MultiSelect({
open: _controlledOpen,
setOpen: _setControlledOpen,
selected,
onSelectedChange,
children,
...props
}: PropsWithChildren<
{
open?: boolean
setOpen?: React.Dispatch<React.SetStateAction<boolean>>
selected?: Item[]
onSelectedChange: (selected: Item[] | ((selected: Item[]) => Item[])) => void
} & React.ComponentProps<typeof Popover>
>) {
const [_internalOpen, _setInternalOpen] = useState(false)
const open = _controlledOpen ?? _internalOpen
const setOpen = _setControlledOpen ?? _setInternalOpen
return (
<MultiSelectProvider selected={selected} onSelectedChange={onSelectedChange} setOpen={setOpen}>
<Popover open={open} onOpenChange={setOpen} {...props}>
{children}
</Popover>
</MultiSelectProvider>
)
}
const Trigger = forwardRef<
HTMLButtonElement,
{ label?: string; className?: string } & React.ComponentProps<typeof PopoverTrigger>
>(({ label, className, ...props }, ref) => {
const { selected } = useMultiSelect()
const [measuredWidth, setMeasuredWidth] = useState(0)
const measuredRef = (node: HTMLElement) => {
if (node) {
setMeasuredWidth(node.getBoundingClientRect().width)
}
}
const badgeWidth = 100 // Approximate width of a badge in pixels
const maxBadges = Math.max(1, Math.floor((measuredWidth || 200) / badgeWidth))
const visibleBadges = selected.slice(0, maxBadges)
const extraBadgesCount = Math.max(0, selected.length - maxBadges)
const badgeClasses = 'bg-200'
return (
<PopoverTrigger asChild ref={ref}>
<button
role="combobox"
className={cn(
'flex w-full min-w-[200px] items-center justify-between rounded-md border',
'border-alternative bg-foreground/[.026] px-3 py-2 text-sm',
'ring-offset-background placeholder:text-muted-foreground',
'focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
'disabled:cursor-not-allowed disabled:opacity-50',
'hover:border-primary transition-colors duration-200',
className
)}
{...props}
>
{selected.length === 0 ? (
// Leading prevents shift when switching to badges
<span className="text-muted-foreground leading-[1.375rem]">{label}</span>
) : (
<div ref={measuredRef} className="flex gap-1 overflow-hidden">
{visibleBadges.map((item) => (
<Badge key={item.value} className={cn('shrink-0', badgeClasses)}>
{item.label}
</Badge>
))}
{extraBadgesCount > 0 && <Badge className={badgeClasses}>+{extraBadgesCount}</Badge>}
</div>
)}
<ChevronsUpDown className="h-4 w-4 shrink-0 opacity-50" />
</button>
</PopoverTrigger>
)
})
Trigger.displayName = 'Trigger'
MultiSelect.Trigger = Trigger
const Content = forwardRef<
HTMLDivElement,
PropsWithChildren<{ className?: string } & React.ComponentProps<typeof PopoverContent>>
>(({ children, className, ...props }, ref) => {
return (
<PopoverContent ref={ref} className={cn('w-full p-4 space-y-4', className)} {...props}>
{children}
</PopoverContent>
)
})
Content.displayName = 'Content'
MultiSelect.Content = Content
export function Item({ item, className }: { item: Item; className?: string }) {
const id = useId()
const { selected, handleSelect } = useMultiSelect()
return (
<div className={cn('flex items-center space-x-2', className)}>
<Checkbox
id={`${id}-checkbox-${item.value}`}
checked={selected.some((sel) => sel.value === item.value)}
onCheckedChange={() => handleSelect(item)}
/>
<label
htmlFor={`${id}-checkbox-${item.value}`}
className="text-sm leading-none cursor-pointer peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
{item.label}
</label>
</div>
)
}
MultiSelect.Item = Item
@@ -71,7 +71,7 @@ const AccordionMenuItem = ({ section }: { section: DropdownMenuItem[] }) => {
</Accordion.Item>
) : (
<Link
href={section[0].href}
href={section[0].href || '#'}
className={cn(activeLabel === section[0].label && '!text-foreground', itemClassName)}
>
{section[0].label}
@@ -81,7 +81,7 @@ const GlobalNavigationMenu: FC = () => {
)}
>
{section[0].label === 'Home' ? (
<MenuIconPicker icon={section[0].icon} />
<MenuIconPicker icon={section[0].icon || ''} />
) : (
section[0].label
)}
@@ -125,7 +125,7 @@ const GlobalNavigationMenu: FC = () => {
>
<NavigationMenuLink asChild>
<Link
href={section[0].href}
href={section[0].href || '#'}
className={cn(
navigationMenuTriggerStyle(),
triggerClassName,
@@ -133,7 +133,7 @@ const GlobalNavigationMenu: FC = () => {
)}
>
{section[0].label === 'Home' ? (
<MenuIconPicker icon={section[0].icon} />
<MenuIconPicker icon={section[0].icon || ''} />
) : (
section[0].label
)}
@@ -3,7 +3,7 @@
import { createContext, useContext } from 'react'
interface ContextProps {
activeRefItem: string
activeRefItem: string | undefined
setActiveRefItem: (x: string) => void
}
@@ -15,7 +15,7 @@ interface Provider extends ContextProps {
// createContext matches the shape that the consumers expect!
const NavMenuContext = createContext<ContextProps>({
activeRefItem: undefined,
setActiveRefItem: undefined,
setActiveRefItem: () => {},
})
export const NavigationMenuContextProvider = (props: Provider) => {
@@ -1,9 +1,9 @@
import { memo } from 'react'
import type { NavMenuSection } from '../Navigation.types'
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
import NavigationMenuGuideList from './NavigationMenuGuideList'
import NavigationMenuRefList from './NavigationMenuRefList'
import { useCloseMenuOnRouteChange } from './NavigationMenu.utils'
enum MenuId {
GettingStarted = 'gettingstarted',
@@ -251,6 +251,8 @@ function getMenuById(id: MenuId) {
}
function getMenuElement(menu: Menu | undefined, props?: any) {
if (!menu) return null
const menuType = menu?.type
switch (menuType) {
case 'guide':
@@ -260,7 +262,7 @@ function getMenuElement(menu: Menu | undefined, props?: any) {
<NavigationMenuRefList
id={menu.id}
basePath={menu.path}
commonSectionsFile={menu.commonSectionsFile}
commonSectionsFile={menu.commonSectionsFile || ''}
/>
)
default:
@@ -283,5 +285,5 @@ const NavigationMenu = ({
return getMenuElement(menu, { additionalNavItems })
}
export { MenuId, getMenuById }
export { getMenuById, MenuId }
export default memo(NavigationMenu)
@@ -1,7 +1,7 @@
'use client'
import { useEffect, useState } from 'react'
import { usePathname } from 'next/navigation'
import { useEffect, useState } from 'react'
import { MenuId } from '~/components/Navigation/NavigationMenu/NavigationMenu'
import type { ICommonItem } from '~/components/reference/Reference.types'
import type { Json } from '~/features/helpers.types'
@@ -52,7 +52,10 @@ export function deepFilterSections<T extends ICommonItem>(
*
* See https://webpack.js.org/api/module-methods/#dynamic-expressions-in-import
*/
export function useCommonSections(commonSectionsFile: string) {
export function useCommonSections(
commonSectionsFile: string,
{ enabled = true }: { enabled: boolean }
) {
const [commonSections, setCommonSections] = useState<ICommonItem[]>()
useEffect(() => {
@@ -67,6 +70,10 @@ export function useCommonSections(commonSectionsFile: string) {
fetchCommonSections()
}, [commonSectionsFile])
if (!enabled) {
return null
}
return commonSections
}
@@ -16,7 +16,7 @@ const NavigationMenuRefList = ({
commonSectionsFile,
specFile,
}: NavigationMenuRefListProps) => {
const commonSections = useCommonSections(commonSectionsFile)
const commonSections = useCommonSections(commonSectionsFile, { enabled: !!commonSectionsFile })
const spec = useSpec(specFile)
if (!commonSections) {
+3 -3
View File
@@ -28,7 +28,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
if (foundItem) return group
})
const currentSubSection: NavMenuSection =
const currentSubSection: NavMenuSection | undefined =
currentSection !== undefined
? currentSection.items.find((section) => {
if (section.items.length === 0) {
@@ -113,7 +113,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
{group.items.map((section: NavMenuSection) => {
if (section.items.length === 0) {
return (
<Link href={section.url} key={section.name}>
<Link href={section.url || '#'} key={section.name}>
<div
className={[
'py-1.5 px-5 rounded text-sm transition',
@@ -150,7 +150,7 @@ const SideBar = ({ menuItems = [] }: { menuItems: any }) => {
</Accordion.Trigger>
<Accordion.Content className="my-2 data-open:animate-slide-down data-closed:animate-slide-up">
{section.items.map((item: NavMenuSection) => (
<Link key={item.name} href={item.url}>
<Link key={item.name} href={item.url || '#'}>
<div
key={item.name}
className={[
@@ -8,7 +8,7 @@ import type {
} from '~/components/ProjectConfigVariables/ProjectConfigVariables.utils'
import type { ProjectApiData } from '~/lib/fetch/projectApi'
import { Copy, Check } from 'lucide-react'
import { Check, Copy } from 'lucide-react'
import Link from 'next/link'
import { useEffect, useMemo } from 'react'
import CopyToClipboard from 'react-copy-to-clipboard'
@@ -16,7 +16,7 @@ import { withErrorBoundary } from 'react-error-boundary'
import { proxy, useSnapshot } from 'valtio'
import { LOCAL_STORAGE_KEYS, useIsLoggedIn, useIsUserLoading } from 'common'
import { Button_Shadcn_ as Button, Input_Shadcn_ as Input, cn } from 'ui'
import { Button_Shadcn_ as Button, cn, Input_Shadcn_ as Input } from 'ui'
import {
ComboBox,
@@ -35,7 +35,7 @@ import { useBranchesQuery } from '~/lib/fetch/branches'
import { useOrganizationsQuery } from '~/lib/fetch/organizations'
import { type SupavisorConfigData, useSupavisorConfigQuery } from '~/lib/fetch/pooler'
import { useProjectApiQuery } from '~/lib/fetch/projectApi'
import { isProjectPaused, type ProjectsData, useProjectsQuery } from '~/lib/fetch/projects'
import { isProjectPaused, useProjectsQuery } from '~/lib/fetch/projects'
import { retrieve, storeOrRemoveNull } from '~/lib/storage'
import { useOnLogout } from '~/lib/userAuth'
@@ -122,14 +122,16 @@ function OrgProjectSelector() {
() =>
stateSummary !== 'loggedIn.dataSuccess.hasData'
? []
: projects.map((project) => {
const organization = organizations.find((org) => org.id === project.organization_id)
return {
id: project.ref,
value: toOrgProjectValue(organization, project),
displayName: toDisplayNameOrgProject(organization, project),
}
}),
: (projects!
.map((project) => {
const organization = organizations!.find((org) => org.id === project.organization_id)!
return {
id: project.ref,
value: toOrgProjectValue(organization, project),
displayName: toDisplayNameOrgProject(organization, project),
}
})
.filter(Boolean) as ComboBoxOption[]),
[organizations, projects, stateSummary]
)
@@ -138,18 +140,18 @@ function OrgProjectSelector() {
const storedMaybeOrgId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_ORG)
const storedMaybeProjectRef = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_PROJECT)
let storedOrg: Org
let storedProject: Project
let storedOrg: Org | undefined
let storedProject: Project | undefined
if (storedMaybeOrgId && storedMaybeProjectRef) {
storedOrg = organizations.find((org) => org.id === Number(storedMaybeOrgId))
storedProject = projects.find((project) => project.ref === storedMaybeProjectRef)
storedOrg = organizations!.find((org) => org.id === Number(storedMaybeOrgId))
storedProject = projects!.find((project) => project.ref === storedMaybeProjectRef)
}
if (storedOrg && storedProject && storedProject.organization_id === storedOrg.id) {
setSelectedOrgProject(storedOrg, storedProject)
} else {
const firstProject = projects[0]
const matchingOrg = organizations.find((org) => org.id === firstProject.organization_id)
} else if (projects!.length > 0) {
const firstProject = projects![0]
const matchingOrg = organizations!.find((org) => org.id === firstProject.organization_id)
if (matchingOrg) setSelectedOrgProject(matchingOrg, firstProject)
}
}
@@ -172,8 +174,8 @@ function OrgProjectSelector() {
const [orgId, projectRef] = fromOrgProjectValue(optionValue)
if (!orgId || !projectRef) return
const org = organizations.find((org) => org.id === orgId)
const project = projects.find((project) => project.ref === projectRef)
const org = organizations?.find((org) => org.id === orgId)
const project = projects?.find((project) => project.ref === projectRef)
if (org && project && project.organization_id === org.id) {
setSelectedOrgProject(org, project)
@@ -214,7 +216,7 @@ function BranchSelector() {
const formattedData: ComboBoxOption[] =
stateSummary !== 'loggedIn.branches.dataSuccess.hasData'
? []
: data.map((branch) => ({
: data!.map((branch) => ({
id: branch.id,
displayName: branch.name,
value: toBranchValue(branch),
@@ -224,18 +226,18 @@ function BranchSelector() {
if (stateSummary === 'loggedIn.branches.dataSuccess.hasData' && !selectedBranch) {
const storedMaybeBranchId = retrieve('local', LOCAL_STORAGE_KEYS.SAVED_BRANCH)
let storedBranch: Branch
let storedBranch: Branch | undefined
if (storedMaybeBranchId) {
storedBranch = data.find((branch) => branch.id === storedMaybeBranchId)
storedBranch = data!.find((branch) => branch.id === storedMaybeBranchId)
}
if (storedBranch) {
setSelectedBranch(storedBranch)
} else {
const productionBranch = data.find(
const productionBranch = data!.find(
(branch) => branch.project_ref === branch.parent_project_ref
)
setSelectedBranch(productionBranch ?? data[0])
setSelectedBranch(productionBranch ?? data![0])
}
}
}, [data, selectedBranch, setSelectedBranch, stateSummary])
@@ -255,7 +257,7 @@ function BranchSelector() {
onSelectOption={(option) => {
const [branchId] = fromBranchValue(option)
if (branchId) {
const branch = data.find((branch) => branch.id === branchId)
const branch = data?.find((branch) => branch.id === branchId)
if (branch) setSelectedBranch(branch)
}
}}
@@ -322,23 +324,23 @@ function VariableView({ variable, className }: { variable: Variable; className?:
? 'loggedIn.selectedProject.dataPending'
: (
needsApiQuery
? isApiError || isInvalidApiData(apiData)
: isSupavisorError || isInvalidSupavisorData(supavisorConfig)
? isApiError || isInvalidApiData(apiData!)
: isSupavisorError || isInvalidSupavisorData(supavisorConfig!)
)
? 'loggedIn.selectedProject.dataError'
: 'loggedIn.selectedProject.dataSuccess'
let variableValue: string = null
let variableValue: string = ''
if (stateSummary === 'loggedIn.selectedProject.dataSuccess') {
switch (variable) {
case 'url':
variableValue = `https://${apiData.app_config!.endpoint}`
variableValue = `https://${apiData?.app_config?.endpoint}`
break
case 'anonKey':
variableValue = apiData.service_api_keys!.find((key) => key.tags === 'anon')!.api_key
variableValue = apiData?.service_api_keys?.find((key) => key.tags === 'anon')?.api_key || ''
break
case 'sessionPooler':
variableValue = supavisorConfig[0].connection_string
variableValue = supavisorConfig?.[0]?.connection_string || ''
}
}
@@ -137,7 +137,11 @@ export default function RealtimeLimitsEstimater({}) {
<Button
type="text"
icon={
<ChevronDown size={18} strokeWidth={2} className={expandPreview && 'rotate-180'} />
<ChevronDown
size={18}
strokeWidth={2}
className={expandPreview ? 'rotate-180' : undefined}
/>
}
className="px-1"
onClick={() => setExpandPreview(!expandPreview)}
@@ -2,7 +2,10 @@ import { createContext } from 'react'
// Make sure the shape of the default value passed to
// createContext matches the shape that the consumers expect!
export const StepHikeContext = createContext({
export const StepHikeContext = createContext<{
activeStep: { titleId: string; step: number } | undefined
steps: any[] | undefined
}>({
activeStep: undefined,
steps: undefined,
})
+11 -7
View File
@@ -2,21 +2,25 @@ import { useEffect, useState } from 'react'
import { useInView } from 'react-intersection-observer'
import { StepHikeContext } from './StepHikeContext'
const StepHike = ({ children, title }) => {
const [activeStep, setActiveStep] = useState(undefined)
const StepHike = ({ children }) => {
const [activeStep, setActiveStep] = useState<{ titleId: string; step: number } | undefined>(
undefined
)
// check if there are any children
if (!children) throw 'StepHike component requires <StepHike.Step> children'
const steps = children.filter((x) => {
const steps = children.filter((x: any) => {
return x.type.name === 'Step'
})
useEffect(() => {
setActiveStep({
titleId: steps[0].props.title.replaceAll(' ', '-').toLowerCase(),
step: 0,
})
if (steps.length > 0) {
setActiveStep({
titleId: steps[0].props.title.replaceAll(' ', '-').toLowerCase(),
step: 0,
})
}
}, [])
// check if there is at least 1 StepHike subcomponent
+6 -3
View File
@@ -4,7 +4,7 @@ import { cn } from 'ui'
type TableProps = TableHTMLAttributes<HTMLTableElement>
const Table = ({ children, ...props }: TableProps) => {
const containerRef = useRef(null)
const containerRef = useRef<HTMLDivElement>(null)
const [showShadow, setShowShadow] = useState(true)
const handleScroll = () => {
@@ -18,8 +18,11 @@ const Table = ({ children, ...props }: TableProps) => {
}
useEffect(() => {
containerRef?.current?.addEventListener('scroll', handleScroll)
return () => containerRef?.current?.removeEventListener('scroll', handleScroll)
const container = containerRef.current
if (container) {
container.addEventListener('scroll', handleScroll)
return () => container.removeEventListener('scroll', handleScroll)
}
}, [])
return (
+18 -6
View File
@@ -236,7 +236,7 @@ async function fetchSourceCodeContent(tree: Root, deps: Dependencies) {
function rewriteNodes(contentMap: Map<MdxJsxFlowElement, [CodeSampleMeta, string]>) {
for (const [node, [meta, content]] of contentMap) {
const lang = matchLang(meta.path.split('.').pop())
const lang = matchLang(meta.path.split('.').pop() || '')
const source = isExternalSource(meta)
? `https://github.com/${meta.org}/${meta.repo}/blob/${meta.commit}${meta.path}`
@@ -286,6 +286,7 @@ function rewriteNodes(contentMap: Map<MdxJsxFlowElement, [CodeSampleMeta, string
existingWrapper.attributes[0].value = newSource
} else if (
typeof existingSource !== 'string' &&
existingSource &&
existingSource.type === 'mdxJsxAttributeValueExpression'
) {
const existingSourceArray =
@@ -405,7 +406,10 @@ export function _createElidedLine(
function isContainedInJsx(tree: acorn.Node, line: number) {
const acornNodeContainsLine = (node: acorn.Node, line) =>
node.loc?.start.line <= line && node.loc?.end.line >= line
node.loc?.start?.line != null &&
node.loc?.end?.line != null &&
node.loc.start.line <= line &&
node.loc.end.line >= line
if (!acornNodeContainsLine(tree, line)) {
return false
}
@@ -430,10 +434,18 @@ function isContainedInJsx(tree: acorn.Node, line: number) {
continue
} else {
if (
child.loc?.start?.line > candidateNarrowestContainingNode.loc?.start?.line ||
child.loc.end.line < candidateNarrowestContainingNode.loc?.end?.line ||
child.loc.start.column > candidateNarrowestContainingNode.loc?.start.column ||
child.loc.end.column < candidateNarrowestContainingNode.loc?.end.column
(child.loc?.start?.line != null &&
candidateNarrowestContainingNode.loc?.start?.line != null &&
child.loc.start.line > candidateNarrowestContainingNode.loc.start.line) ||
(child.loc?.end?.line != null &&
candidateNarrowestContainingNode.loc?.end?.line != null &&
child.loc.end.line < candidateNarrowestContainingNode.loc.end.line) ||
(child.loc?.start?.column != null &&
candidateNarrowestContainingNode.loc?.start?.column != null &&
child.loc.start.column > candidateNarrowestContainingNode.loc.start.column) ||
(child.loc?.end?.column != null &&
candidateNarrowestContainingNode.loc?.end?.column != null &&
child.loc.end.column < candidateNarrowestContainingNode.loc.end.column)
) {
candidateNarrowestContainingNode = child
getNarrowestContainingNode(child, line)
+1 -1
View File
@@ -35,7 +35,7 @@ const TocAnchorsProvider = ({ children }: PropsWithChildren) => {
const newHeadings = headings
.filter((heading) => heading.id)
.map((heading) => {
const text = heading.textContent.replace('#', '')
const text = heading.textContent?.replace('#', '') || ''
const link = heading.querySelector('a')?.getAttribute('href')
if (!link) return null
+2 -2
View File
@@ -92,14 +92,14 @@ const getGuidesMarkdown = cache_fullProcess_withDevCacheBust(
const genGuidesStaticParams = (directory?: string) => async () => {
const promises = directory
? (await readdir(join(GUIDES_DIRECTORY, directory), { recursive: true }))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1)?.startsWith('_'))
.map((file) => ({ slug: file.replace(/\.mdx$/, '').split(sep) }))
.concat(
(await existsFile(join(GUIDES_DIRECTORY, `${directory}.mdx`))) ? [{ slug: [] }] : []
)
: PUBLISHED_SECTIONS.map(async (section) =>
(await readdir(join(GUIDES_DIRECTORY, section), { recursive: true }))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1).startsWith('_'))
.filter((file) => extname(file) === '.mdx' && !file.split(sep).at(-1)?.startsWith('_'))
.map((file) => ({
slug: [section, ...file.replace(/\.mdx$/, '').split(sep)],
}))
@@ -78,12 +78,12 @@ function mapEndpointsById(
const endpoints = spec.paths
const endpointsById = new Map<string, IApiEndPoint>()
Object.entries(endpoints).forEach(([path, methods]) => {
Object.entries(methods).forEach(([method, details]) => {
Object.entries(endpoints as Record<string, any>).forEach(([path, methods]) => {
Object.entries(methods as Record<string, any>).forEach(([method, details]) => {
endpointsById.set(getId(details), {
id: getId(details),
path,
method,
method: method as 'get' | 'post' | 'put' | 'delete' | 'patch',
...details,
})
})
@@ -92,13 +92,16 @@ function mapEndpointsById(
return endpointsById
}
function genClientSdkSectionTree(fns: Array<{ id: unknown }>, excludeName: string) {
function genClientSdkSectionTree(
fns: Array<{ id: unknown }>,
excludeName: string
): AbbrevApiReferenceSection[] {
const validSections = deepFilterRec(
commonClientLibSections as Array<AbbrevApiReferenceSection>,
commonClientLibSections as AbbrevApiReferenceSection[],
'items',
(section) =>
section.type === 'markdown' || section.type === 'category'
? !('excludes' in section && section.excludes.includes(excludeName))
? !('excludes' in section && section.excludes?.includes(excludeName))
: section.type === 'function'
? fns.some(({ id }) => section.id === id)
: true
@@ -106,11 +109,11 @@ function genClientSdkSectionTree(fns: Array<{ id: unknown }>, excludeName: strin
return validSections
}
async function genCliSectionTree() {
async function genCliSectionTree(): Promise<AbbrevApiReferenceSection[]> {
const cliSpec = await getSpec('cli_v1_commands', { ext: 'yaml' })
const validSections = deepFilterRec(
cliCommonSections as Array<AbbrevApiReferenceSection>,
cliCommonSections as AbbrevApiReferenceSection[],
'items',
(section) =>
section.type === 'cli-command' ? cliSpec.commands.some(({ id }) => id === section.id) : true
@@ -118,9 +121,9 @@ async function genCliSectionTree() {
return validSections
}
function genApiSectionTree(endpointsById: Map<string, IApiEndPoint>) {
function genApiSectionTree(endpointsById: Map<string, IApiEndPoint>): AbbrevApiReferenceSection[] {
const validSections = deepFilterRec(
apiCommonSections as Array<AbbrevApiReferenceSection>,
apiCommonSections as AbbrevApiReferenceSection[],
'items',
(section) => (section.type === 'operation' ? endpointsById.has(section.id) : true)
)
@@ -131,7 +134,7 @@ function genSelfHostedSectionTree(
spec: Array<AbbrevApiReferenceSection>,
endpointsById: Map<string, IApiEndPoint>
) {
const validSections = deepFilterRec(spec, 'items', (section) =>
const validSections = deepFilterRec(spec as any, 'items', (section: any) =>
section.type === 'self-hosted-operation' ? endpointsById.has(section.id) : true
)
return validSections
@@ -143,7 +146,7 @@ export function flattenCommonClientLibSections(tree: Array<AbbrevApiReferenceSec
const prunedElem = { ...elem }
delete prunedElem.items
acc.push(prunedElem)
acc.push(...flattenCommonClientLibSections(elem.items))
acc.push(...flattenCommonClientLibSections(elem.items || []))
} else {
acc.push(elem)
}
@@ -289,7 +292,7 @@ async function writeSelfHostingReferenceSections() {
selfHostingSpecs.flatMap((service) => {
let tasks: Promise<any>[] = []
let endpointsById: Map<string, IApiEndPoint>
let endpointsById: Map<string, IApiEndPoint> = new Map()
if (service.spec) {
endpointsById = mapEndpointsById(service.spec, (details) =>
slugify(details.summary || `dummy-id-${String(id++)}`, {
@@ -313,7 +316,9 @@ async function writeSelfHostingReferenceSections() {
)
)
const flattenedSelfHostedSections = flattenCommonClientLibSections(selfHostedSectionTree)
const flattenedSelfHostedSections = flattenCommonClientLibSections(
selfHostedSectionTree as AbbrevApiReferenceSection[]
)
tasks.push(
writeFile(
join(GENERATED_DIRECTORY, `${service.id}.latest.flat.json`),
+1 -1
View File
@@ -22,7 +22,7 @@ function ClientLibHeader({ menuData, className }: ClientLibHeaderProps) {
<div className={cn('flex items-start gap-6', className)}>
{'icon' in menuData && (
<MenuIconPicker
icon={menuData.icon}
icon={menuData.icon || ''}
width={35}
height={35}
className="text-foreground-light"
@@ -61,7 +61,7 @@ export function ReferenceNavigationScrollHandler({
...rest
}: PropsWithChildren & HTMLAttributes<HTMLDivElement>) {
const parentRef = useRef<HTMLElement>()
const ref = useRef<HTMLDivElement>()
const ref = useRef<HTMLDivElement>(null)
const initialScrollHappened = useContext(ReferenceContentInitiallyScrolledContext)
useEffect(() => {
@@ -70,7 +70,9 @@ export function ReferenceNavigationScrollHandler({
let scrollingParent: HTMLElement = ref.current
while (scrollingParent && !(scrollingParent.scrollHeight > scrollingParent.clientHeight)) {
scrollingParent = scrollingParent.parentElement
const parent = scrollingParent.parentElement
if (!parent) break
scrollingParent = parent
}
parentRef.current = scrollingParent
@@ -180,7 +182,7 @@ export function RefLink({
skipChildren?: boolean
className?: string
}) {
const ref = useRef<HTMLAnchorElement>()
const ref = useRef<HTMLAnchorElement>(null)
const pathname = usePathname()
const href = deriveHref(basePath, section)
@@ -189,7 +191,7 @@ export function RefLink({
useEffect(() => {
if (ref.current) {
ref.current.ariaCurrent = isActive ? 'page' : undefined
ref.current.ariaCurrent = isActive ? 'page' : null
ref.current.className = getLinkStyles(isActive, className)
}
}, [isActive, className])
@@ -201,7 +203,8 @@ export function RefLink({
if (!('title' in section)) return null
const isCompoundSection = !skipChildren && 'items' in section && section.items.length > 0
const isCompoundSection =
!skipChildren && 'items' in section && section.items && section.items.length > 0
return (
<>
@@ -232,7 +235,7 @@ function useCompoundRefLinkActive(basePath: string, section: AbbrevApiReferenceS
const isParentActive = pathname === parentHref
const childHrefs = useMemo(
() => new Set(section.items.map((item) => deriveHref(basePath, item))),
() => new Set((section.items || []).map((item) => deriveHref(basePath, item))),
[basePath, section]
)
const isChildActive = childHrefs.has(pathname)
@@ -288,7 +291,7 @@ function CompoundRefLink({
>
<ul className="space-y-2">
<RefLink basePath={basePath} section={section} skipChildren />
{section.items.map((item, idx) => {
{(section.items || []).map((item, idx) => {
return (
<li key={`${section.id}-${idx}`}>
<RefLink basePath={basePath} section={item} />
@@ -35,12 +35,12 @@ export async function ReferenceNavigation({
return (
<ReferenceNavigationScrollHandler className="w-full flex flex-col pt-3 pb-5 gap-3">
<div className="flex items-center gap-3">
{'icon' in menuData && <MenuIconPicker icon={menuData.icon} width={21} height={21} />}
{'icon' in menuData && <MenuIconPicker icon={menuData.icon || ''} width={21} height={21} />}
<span className="text-base text-brand-600">{name}</span>
<RefVersionDropdown library={libPath} currentVersion={version} />
</div>
<ul className="flex flex-col gap-2">
{navSections.map((section) => (
{navSections?.map((section) => (
<Fragment key={section.id}>
{section.type === 'category' ? (
<li>
@@ -67,14 +67,14 @@ function RefCategory({
basePath: string
section: AbbrevApiReferenceSection
}) {
if (!('items' in section && section.items.length > 0)) return null
if (!('items' in section && section.items && section.items.length > 0)) return null
return (
<>
<Divider />
{'title' in section && <SideMenuTitle className="py-2">{section.title}</SideMenuTitle>}
<ul className="space-y-2">
{section.items.map((item) => (
{section.items?.map((item) => (
<li key={item.id} className={topLvlRefNavItemStyles}>
<RefLink basePath={basePath} section={item} />
</li>
@@ -38,11 +38,13 @@ type RefSectionsProps = {
async function RefSections({ libraryId, version }: RefSectionsProps) {
let flattenedSections = await getFlattenedSections(libraryId, version)
flattenedSections = trimIntro(flattenedSections)
if (flattenedSections) {
flattenedSections = trimIntro(flattenedSections)
}
return (
<div className="flex flex-col my-16 gap-16">
{flattenedSections
{(flattenedSections || [])
.filter((section) => section.type !== 'category')
.map((section, idx) => (
<Fragment key={`${section.id}-${idx}`}>
@@ -405,7 +407,7 @@ async function FunctionSection({
}: FunctionSectionProps) {
const fns = await getFunctionsList(sdkId, version)
const fn = fns.find((fn) => fn.id === section.id)
const fn = fns?.find((fn) => fn.id === section.id)
if (!fn) return null
let types: MethodTypes | undefined
@@ -194,7 +194,7 @@ interface CommentBlockTag {
content: CommentKind[]
}
function normalizeComment(original: TypedocComment | Comment): Comment {
function normalizeComment(original: TypedocComment | Comment | undefined): Comment | undefined {
if (!original) return
if ('shortText' in original || 'text' in original) {
@@ -299,7 +299,7 @@ function parseModInternal(
case 'property':
case 'reference':
default:
return undefined
return
}
}
@@ -379,7 +379,7 @@ function parseSignature(
map: Map<number, any>
): {
params: Array<FunctionParameterType>
ret: ReturnType
ret: ReturnType | undefined
comment?: Comment
} {
const params: Array<FunctionParameterType> = (signature.parameters ?? []).map((param: any) => {
@@ -401,7 +401,7 @@ function parseSignature(
return res
})
let ret: ReturnType
let ret: ReturnType | undefined
if ('type' in signature) {
const retType = parseType(signature.type, map)
if (retType) {
@@ -643,10 +643,10 @@ function parsePickType(type: any, map: Map<number, any>) {
return undefined
}
function parseReflectionType(type: any, map: Map<number, any>) {
function parseReflectionType(type: any, map: Map<number, any>): TypeDetails | undefined {
if (!type.declaration) return undefined
let res: TypeDetails
let res: TypeDetails | undefined
switch ((type.declaration.kindString ?? type.declaration.variant).toLowerCase()) {
case 'type literal':
res = parseTypeLiteral(type, map)
@@ -662,7 +662,7 @@ function parseReflectionType(type: any, map: Map<number, any>) {
return res
}
function parseTypeLiteral(type: any, map: Map<number, any>): TypeDetails {
function parseTypeLiteral(type: any, map: Map<number, any>): TypeDetails | undefined {
const name = nameOrAnonymous(type)
if ('children' in type.declaration) {
@@ -67,7 +67,7 @@ export function ApiOperationBodySchemeSelector({
requestBody: IApiEndPoint['requestBody']
className?: string
}) {
const availableSchemes = Object.keys(requestBody.content) as Array<
const availableSchemes = Object.keys(requestBody?.content || {}) as Array<
'application/json' | 'application/x-www-form-urlencoded'
>
const [selectedScheme, setSelectedScheme] = useState(availableSchemes[0])
@@ -75,11 +75,10 @@ export function ApiOperationBodySchemeSelector({
const containerRef = useRef<HTMLDivElement>(null)
const allSchemeDetails = useRef<HTMLUListElement[]>([])
useEffect(() => {
allSchemeDetails.current = Array.from(
containerRef.current?.querySelectorAll(
`[${API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY}]`
)
const elements = containerRef.current?.querySelectorAll(
`[${API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY}]`
)
allSchemeDetails.current = elements ? (Array.from(elements) as HTMLUListElement[]) : []
}, [])
useEffect(() => {
+47 -32
View File
@@ -31,7 +31,7 @@ function Section({ slug, link, columns = 'single', children }: SectionProps) {
return (
<ReferenceSectionWrapper
id={slug}
id={slug || ''}
link={link}
className={cn(
'grid grid-cols-[1fr] gap-x-16 gap-y-8',
@@ -61,7 +61,7 @@ function Examples({ children }: PropsWithChildren) {
function EducationSection({ children, slug, ...props }: SectionProps) {
return (
<ReferenceSectionWrapper id={slug} className={'prose max-w-none'} {...props}>
<ReferenceSectionWrapper id={slug || ''} className={'prose max-w-none'} {...props}>
{children}
</ReferenceSectionWrapper>
)
@@ -231,7 +231,7 @@ function ParamOrTypeDetails({ paramOrType }: { paramOrType: object }) {
</div>
)}
{subContent && subContent.length > 0 && (
<TypeSubDetails details={subContent} defaultOpen={defaultOpen} />
<TypeSubDetails details={subContent} defaultOpen={defaultOpen || false} />
)}
</>
)
@@ -240,27 +240,29 @@ function ParamOrTypeDetails({ paramOrType }: { paramOrType: object }) {
export function ReturnTypeDetails({ returnType }: { returnType: MethodTypes['ret'] }) {
// These custom names that aren't defined aren't particularly meaningful, so
// just don't display them.
const isNameOnlyType = returnType.type.type === 'nameOnly'
const isNameOnlyType = returnType?.type?.type === 'nameOnly'
if (isNameOnlyType) return
const subContent = getSubDetails(returnType)
const isDefaultOpen = isDefaultExpanded(returnType)
const isDefaultOpen = returnType ? isDefaultExpanded(returnType) : false
return (
<div>
<h3 className="mb-3 text-base text-foreground">Return Type</h3>
<div className="border-t border-b py-5 flex flex-col gap-3">
<div className="text-xs text-foreground-muted">{getTypeName(returnType)}</div>
{returnType.comment?.shortText && (
<div className="text-xs text-foreground-muted">
{returnType ? getTypeName(returnType) : ''}
</div>
{returnType?.comment?.shortText && (
<div className="prose text-sm">
<MDXRemoteBase
source={returnType.comment?.shortText}
source={returnType?.comment?.shortText}
customPreprocess={normalizeMarkdown}
/>
</div>
)}
{subContent && subContent.length > 0 && (
<TypeSubDetails defaultOpen={isDefaultOpen} details={subContent} />
<TypeSubDetails defaultOpen={isDefaultOpen || false} details={subContent} />
)}
</div>
</div>
@@ -371,7 +373,7 @@ export function ApiOperationRequestBodyDetails({
}: {
requestBody: IApiEndPoint['requestBody']
}) {
const availableSchemes = Object.keys(requestBody.content) as Array<
const availableSchemes = Object.keys(requestBody?.content || {}) as Array<
'application/json' | 'application/x-www-form-urlencoded'
>
@@ -380,7 +382,7 @@ export function ApiOperationRequestBodyDetails({
{availableSchemes.map((scheme, index) => (
<ApiOperationRequestBodyDetailsInternal
key={index}
schema={requestBody.content[scheme].schema}
schema={requestBody?.content?.[scheme]?.schema || ({} as ISchema)}
hidden={index > 0}
{...{
[API_REFERENCE_REQUEST_BODY_SCHEMA_DATA_ATTRIBUTES.KEY]: scheme,
@@ -452,12 +454,12 @@ function ApiOperationRequestBodyDetailsInternal({
} else if (schema.type === 'object') {
return (
<ul {...props}>
{Object.keys(schema.properties)
{Object.keys(schema.properties || {})
.map((property) => ({
name: property,
required: schema.required?.includes(property),
required: schema.required?.includes(property) || false,
in: 'body' as const,
schema: schema.properties[property],
schema: schema.properties?.[property] || ({} as ISchema),
}))
.map((property, index) => (
<ApiSchemaParamDetails key={index} param={property} />
@@ -599,7 +601,11 @@ function getTypeName(parameter: object): string {
return parameter.type
}
if (typeof parameter.type !== 'object' || !('type' in parameter.type)) {
if (
typeof parameter.type !== 'object' ||
parameter.type === null ||
!('type' in parameter.type)
) {
return ''
}
@@ -645,28 +651,28 @@ function nameOrDefault(node: object, fallback: string) {
}
function getSubDetails(parentType: MethodTypes['params'][number] | MethodTypes['ret']) {
let subDetails: Array<any>
let subDetails: Array<any> = []
switch (parentType.type?.type) {
switch (parentType?.type?.type) {
case 'object':
subDetails = parentType.type.properties
subDetails = parentType?.type?.properties
break
case 'function':
subDetails = [
...(parentType.type.params.length === 0
...(parentType?.type?.params?.length === 0
? []
: [
{
name: 'Parameters',
type: 'callback parameters',
isOptional: 'NA',
params: parentType.type.params.map((param) => ({
params: parentType?.type?.params?.map((param) => ({
...param,
isOptional: 'NA',
})),
},
]),
{ name: 'Return', type: parentType.type.ret.type, isOptional: 'NA' },
{ name: 'Return', type: parentType?.type?.ret?.type, isOptional: 'NA' },
]
break
// @ts-ignore -- Adding these fake types to take advantage of existing recursion
@@ -675,27 +681,34 @@ function getSubDetails(parentType: MethodTypes['params'][number] | MethodTypes['
subDetails = parentType.params
break
case 'union':
subDetails = parentType.type.subTypes.map((subType, index) => ({
subDetails = parentType?.type?.subTypes?.map((subType, index) => ({
name: `Option ${index + 1}`,
type: { ...subType },
isOptional: 'NA',
}))
break
case 'promise':
if (parentType.type.awaited.type === 'union') {
subDetails = parentType.type.awaited.subTypes.map((subType, index) => ({
if (parentType?.type?.awaited?.type === 'union') {
subDetails = parentType?.type?.awaited?.subTypes?.map((subType, index) => ({
name: `Option ${index + 1}`,
type: { ...subType },
isOptional: 'NA',
}))
} else if (parentType.type.awaited.type === 'object') {
subDetails = parentType.type.awaited.properties.map((property) => ({
} else if (
parentType?.type?.awaited?.type === 'object' &&
'properties' in parentType.type.awaited
) {
subDetails = (parentType.type.awaited as any).properties?.map((property) => ({
...property,
isOptional: 'NA',
}))
} else if (parentType.type.awaited.type === 'array') {
} else if (parentType?.type?.awaited?.type === 'array') {
subDetails = [
{ name: 'array element', type: parentType.type.awaited.elemType, isOptional: 'NA' },
{
name: 'array element',
type: (parentType?.type?.awaited as any)?.elemType,
isOptional: 'NA',
},
]
}
break
@@ -836,7 +849,7 @@ function applyParameterMergeStrategy(
if (clonedParametersByName.has(key)) {
clonedParametersByName.set(
key,
applyParameterMergeStrategy(clonedParametersByName.get(key), alternateValue)
applyParameterMergeStrategy(clonedParametersByName.get(key)!, alternateValue)
)
} else {
clonedParametersByName.set(key, alternateValue)
@@ -856,7 +869,7 @@ function applyParameterMergeStrategy(
*********/
function mergeIntoUnion() {
if (alternateParameter.type.type === 'union') {
if (alternateParameter.type?.type === 'union') {
const originalType = clonedParameter.type
if (
@@ -866,7 +879,7 @@ function applyParameterMergeStrategy(
} else {
clonedParameter.type = {
type: 'union',
subTypes: [originalType, ...alternateParameter.type.subTypes],
subTypes: [originalType as TypeDetails, ...(alternateParameter.type?.subTypes || [])],
}
}
} else {
@@ -874,7 +887,7 @@ function applyParameterMergeStrategy(
if (!isEqual(originalType, alternateParameter.type)) {
clonedParameter.type = {
type: 'union',
subTypes: [originalType, alternateParameter.type],
subTypes: [originalType as TypeDetails, alternateParameter.type!],
}
}
}
@@ -885,11 +898,13 @@ function isDefaultExpanded(meta: object) {
return (
'type' in meta &&
typeof meta.type === 'object' &&
meta.type &&
'type' in meta.type &&
(meta.type.type == 'union' ||
(meta.type.type === 'promise' &&
'awaited' in meta.type &&
typeof meta.type.awaited === 'object' &&
meta.type.awaited &&
'type' in meta.type.awaited &&
meta.type.awaited.type === 'union'))
)
+13 -8
View File
@@ -30,15 +30,21 @@ export function parseReferencePath(slug: Array<string>) {
const isSelfHostingReference = slug[0].startsWith('self-hosting-')
if (isClientSdkReference) {
let [sdkId, maybeVersion, maybeCrawlers, ...path] = slug
let sdkId: string
let maybeVersion: string | null
let maybeCrawlers: string | null
let path: string[]
;[sdkId, maybeVersion, maybeCrawlers, ...path] = slug
if (!/v\d+/.test(maybeVersion)) {
maybeVersion = null
maybeCrawlers = maybeVersion
path = [maybeCrawlers, ...path]
maybeCrawlers = maybeVersion
}
if (maybeCrawlers !== 'crawlers') {
if (typeof maybeCrawlers === 'string') {
path = [maybeCrawlers, ...path]
}
maybeCrawlers = null
path = [maybeCrawlers, ...path]
}
return {
@@ -75,7 +81,7 @@ export function parseReferencePath(slug: Array<string>) {
async function generateStaticParamsForSdkVersion(sdkId: string, version: string) {
const flattenedSections = await getFlattenedSections(sdkId, version)
return flattenedSections
return (flattenedSections || [])
.filter((section) => section.type !== 'category' && !!section.slug)
.map((section) => ({
slug: [
@@ -139,7 +145,7 @@ export async function generateReferenceMetadata(
const displayName = REFERENCES[sdkId].name
const sectionTitle =
slug.length > 0
? flattenedSections.find((section) => section.slug === slug[0])?.title
? flattenedSections?.find((section) => section.slug === slug[0])?.title
: undefined
const url = [BASE_PATH, 'reference', sdkId, path[0]].filter(Boolean).join('/')
@@ -154,7 +160,6 @@ export async function generateReferenceMetadata(
...(slug.length > 0
? {
alternates: {
...parentAlternates,
canonical: url,
},
}
@@ -219,8 +224,8 @@ export function normalizeMarkdown(markdownUnescaped: string): string {
visit(markdownTree, ['code', 'inlineCode'], (node) => {
codeBlocks.push({
type: node.type,
start: node.position.start.offset,
end: node.position.end.offset,
start: node.position?.start?.offset || 0,
end: node.position?.end?.offset || 0,
})
})
// Sort code blocks by start offset in descending order
@@ -58,7 +58,7 @@ export default async function TroubleshootingPage({ entry }: { entry: ITroublesh
<hr className="my-6" aria-hidden />
</>
)}
{entry.data.errors?.length > 0 && (
{entry.data.errors?.length && entry.data.errors.length > 0 && (
<>
<h3 className="text-sm text-foreground-lighter mb-3">Related error codes</h3>
<div className="flex flex-wrap gap-0.5">
@@ -76,9 +76,9 @@ export default async function TroubleshootingPage({ entry }: { entry: ITroublesh
<hr className="my-6" aria-hidden />
</>
)}
{entry.data.keywords?.length > 0 && (
{entry.data.keywords?.length && entry.data.keywords.length > 0 && (
<>
<h3 className="text-sm text-foreground-lighter mb-3">Tags</h3>
<h3 className="text-sm text-foreground-lighter mb-3">Keywords</h3>
<div className="flex flex-wrap gap-0.5">
{entry.data.keywords.map((keyword) => (
<Link
@@ -49,15 +49,15 @@ export async function TroubleshootingPreview({ entry }: { entry: ITroubleshootin
{entry.data.title}
</h3>
</Link>
{entry.data.errors?.length > 0 && (
{entry.data.errors?.length && entry.data.errors.length > 0 && (
<span className="text-xs text-foreground-lighter ml-1 @4xl/troubleshooting:ml-0">
{entry.data.errors
{(entry.data.errors || [])
.map(formatError)
.filter(Boolean)
.map((error, index) => (
<>
<code key={index}>{error}</code>
{index < entry.data.errors.length - 1 ? ', ' : ''}
{index < (entry.data.errors?.length || 0) - 1 ? ', ' : ''}
</>
))}
</span>
@@ -11,7 +11,7 @@ export const TROUBLESHOOTING_DATA_ATTRIBUTES = {
ERRORS_LIST_ATTRIBUTE: 'data-errors',
}
export function formatError(error: ITroubleshootingEntry['data']['errors'][number]) {
export function formatError(error: NonNullable<ITroubleshootingEntry['data']['errors']>[number]) {
return `${error.http_status_code ?? ''}${!!error.http_status_code && !!error.code ? ' ' : ''}${error.code ?? ''}`
}
@@ -67,7 +67,7 @@ export async function getAllTroubleshootingErrors() {
const entries = await getAllTroubleshootingEntries()
const allErrors = new Set(
entries
.flatMap((entry) => entry.data.errors)
.flatMap((entry) => entry.data.errors ?? [])
.filter((error) => error?.http_status_code || error?.code)
)
@@ -81,8 +81,8 @@ export async function getAllTroubleshootingErrors() {
}
function sortErrors(
a: ITroubleshootingMetadata['errors'][number],
b: ITroubleshootingMetadata['errors'][number]
a: NonNullable<ITroubleshootingMetadata['errors']>[number],
b: NonNullable<ITroubleshootingMetadata['errors']>[number]
) {
return formatError(a).localeCompare(formatError(b))
}
+25 -30
View File
@@ -1,32 +1,27 @@
type RecObj<T extends object, K extends keyof T> = T[K] extends Array<T> ? T : never
const EMPTY_ARRAY = new Array(0)
export function getEmptyArray() {
return EMPTY_ARRAY
}
export function deepFilterRec<T extends object, K extends keyof T>(
arr: Array<RecObj<T, K>>,
recKey: K,
export function deepFilterRec<T extends object>(
arr: Array<T>,
recKey: string,
filterFn: (item: T) => boolean
) {
return arr.reduce(
(acc, elem) => {
if (!filterFn(elem)) return acc
): Array<T> {
return arr.reduce((acc, elem) => {
if (!filterFn(elem)) return acc
if (recKey in elem) {
const newSubitems = deepFilterRec(elem[recKey] as Array<RecObj<T, K>>, recKey, filterFn)
const newElem = { ...elem, [recKey]: newSubitems }
if (recKey in elem && elem[recKey as keyof T]) {
const newSubitems = deepFilterRec(elem[recKey as keyof T] as Array<T>, recKey, filterFn)
const newElem = { ...elem, [recKey]: newSubitems }
if (newSubitems.length > 0 || filterFn(elem)) acc.push(newElem)
} else {
acc.push(elem)
}
if (newSubitems.length > 0 || filterFn(elem)) acc.push(newElem)
} else {
acc.push(elem)
}
return acc
},
[] as Array<RecObj<T, K>>
)
return acc
}, [] as Array<T>)
}
export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, key: K) {
@@ -37,12 +32,12 @@ export async function pluckPromise<T, K extends keyof T>(promise: Promise<T>, ke
export class Result<Ok, Error> {
constructor(private internal: { data: Ok; error: null } | { data: null; error: Error }) {}
static ok<Ok, Error>(data: Ok): Result<Ok, Error> {
return new Result({ data, error: null })
static ok<Error = unknown, Ok = unknown>(data: Ok): Result<Ok, Error> {
return new Result<Ok, Error>({ data, error: null })
}
static error<Ok, Error>(error: Error): Result<Ok, Error> {
return new Result({ data: null, error })
static error<Ok = unknown, Error = unknown>(error: Error): Result<Ok, Error> {
return new Result<Ok, Error>({ data: null, error })
}
static async tryCatchFlat<
@@ -67,24 +62,24 @@ export class Result<Ok, Error> {
}
map<Mapped>(fn: (data: Ok) => Mapped): Result<Mapped, Error> {
if (this.isOk()) return Result.ok(fn(this.internal.data))
if (this.isOk()) return Result.ok(fn(this.internal.data!))
return this as unknown as Result<Mapped, Error>
}
mapError<MappedError>(fn: (error: Error) => MappedError): Result<Ok, MappedError> {
if (this.isOk()) return this as unknown as Result<Ok, MappedError>
return Result.error(fn(this.internal.error))
return Result.error(fn(this.internal.error!))
}
flatMap<Mapped>(fn: (data: Ok) => Result<Mapped, Error>): Result<Mapped, Error> {
if (this.isOk()) return fn(this.internal.data)
if (this.isOk()) return fn(this.internal.data!)
return this as unknown as Result<Mapped, Error>
}
flatMapAsync<Mapped>(
fn: (data: Ok) => Promise<Result<Mapped, Error>>
): Promise<Result<Mapped, Error>> {
if (this.isOk()) return fn(this.internal.data)
if (this.isOk()) return fn(this.internal.data!)
return Promise.resolve(this as unknown as Result<Mapped, Error>)
}
@@ -92,7 +87,7 @@ export class Result<Ok, Error> {
onOk: (data: Ok) => Mapped,
onError: (error: Error) => MappedError
): Mapped | MappedError {
if (this.isOk()) return onOk(this.internal.data)
return onError(this.internal.error)
if (this.isOk()) return onOk(this.internal.data!)
return onError(this.internal.error!)
}
}
@@ -34,10 +34,10 @@ export async function CodeBlock({
skipTypeGeneration?: boolean
}>) {
let code = (contents || extractCode(children)).trim()
const lang = tryToBundledLanguage(langSetting) || extractLang(children)
const lang = tryToBundledLanguage(langSetting || '') || extractLang(children)
let twoslashed = null as null | Map<number, Map<number, Array<NodeHover>>>
if (!skipTypeGeneration && TWOSLASHABLE_LANGS.includes(lang)) {
if (!skipTypeGeneration && lang && TWOSLASHABLE_LANGS.includes(lang)) {
try {
const { code: editedCode, nodes } = twoslasher(code)
const hoverNodes: Array<NodeHover> = nodes.filter((node) => node.type === 'hover')
@@ -53,7 +53,7 @@ export async function CodeBlock({
}
const { tokens } = highlighter.codeToTokens(code, {
lang,
lang: lang || undefined,
theme: 'Supabase Theme',
})
@@ -121,7 +121,10 @@ function CodeLine({
annotations={twoslash.get(token.offset)!}
/>
) : (
<span key={token.offset} style={{ color: token.color, ...getFontStyle(token.fontStyle) }}>
<span
key={token.offset}
style={{ color: token.color, ...getFontStyle(token.fontStyle || 0) }}
>
{token.content}
</span>
)
@@ -12,7 +12,7 @@ enum FontStyle {
}
export function getFontStyle(styleFlags: number): CSSProperties {
let style: CSSProperties = null
let style: CSSProperties = {}
if (styleFlags & FontStyle.Italic) {
;(style ??= {}).fontStyle = 'italic'
+7 -1
View File
@@ -62,7 +62,13 @@ const InfoTooltip = ({
[_setTooltipOpen, id, isMobile]
)
useEffect(() => () => clearTimeout(timeout.current), [])
useEffect(() => {
return () => {
if (timeout.current) {
clearTimeout(timeout.current)
}
}
}, [])
return (
<>
+3 -3
View File
@@ -13,10 +13,10 @@ const cliSpec = yaml.load(fs.readFileSync(`spec/cli_v1_commands.yaml`, 'utf8'))
* @returns {Array<{link: string}>} - An array of CLI page links.
*/
export function generateCLIPages() {
let cliPages = []
let cliPages: Array<{ link: string }> = []
cliSpec.commands.map((section) => {
const slug = flatCLISections.find((item) => item.id === section.id)?.slug
cliSpec.commands.map((section: any) => {
const slug = (flatCLISections as any[]).find((item: any) => item.id === section.id)?.slug
if (slug) cliPages.push({ link: `reference/cli/${slug}` })
})
return cliPages
+2 -2
View File
@@ -1,5 +1,5 @@
export function flattenSections(sections) {
let a = []
export function flattenSections(sections: any[]): any[] {
let a: any[] = []
for (let i = 0; i < sections.length; i++) {
if (sections[i].id) {
// only push a section that has an id
+1 -1
View File
@@ -89,7 +89,7 @@ const StickyHeader: FC<StickyHeader> = ({ icon, ...props }) => {
// we're serving search bots a different file (/crawlers/[...slug])
// and need to modify content to suit that
const isCrawlerPage = router.route.includes('/crawlers/[...slug]')
const isCrawlerPage = router?.route.includes('/crawlers/[...slug]') || false
const { ref } = useInView({
threshold: 1,
+1 -1
View File
@@ -7,7 +7,7 @@ import remarkGfm from 'remark-gfm'
import type { ICommonMarkdown } from '~/components/reference/Reference.types'
async function generateRefMarkdown(sections: ICommonMarkdown[], slug: string) {
let markdownContent = []
let markdownContent: any[] = []
/**
* Read all the markdown files that might have
* - custom text
@@ -6,7 +6,7 @@ import { visit } from 'unist-util-visit'
export type UrlTransformFunction = (url: string, node: Element) => string
function modify(node: Element, prop: string, fn?: UrlTransformFunction) {
if (hasProperty(node, prop)) {
if (node.properties && hasProperty(node, prop)) {
const property = node.properties[prop]
if (typeof property !== 'string') {
return
@@ -25,7 +25,9 @@ const remarkMkDocsAdmonition = function () {
// Extract the admonition type, title, and remaining text
const [, , typeTitle, value] = match
const [, type, title] = typeTitle.match(/^(.+?) ?(?:"(.*)")?$/)
const typeTitleMatch = typeTitle.match(/^(.+?) ?(?:"(.*)")?$/)
if (!typeTitleMatch) return
const [, type, title] = typeTitleMatch
// Rewrite the node's value to remove the admonition syntax
firstChild.value = value
@@ -76,7 +78,7 @@ const remarkMkDocsAdmonition = function () {
* Splices the discovered siblings out of the original parent and returns them.
*/
function extractLinkedSiblings(parent: Root, node: Node, index: number, indentAmount = 4) {
const { column } = node.position.start
const { column } = node.position?.start || { column: 0 }
let nextSibling: Content
let i = index
+1
View File
@@ -125,6 +125,7 @@ const remarkPyMdownTabs = function () {
* Splices the discovered siblings out of the original parent and returns them.
*/
function extractLinkedSiblings(parent: Root, node: Node, index: number, indentAmount = 4) {
if (!node.position) return []
const { column } = node.position.start
let nextSibling: Content
+15 -10
View File
@@ -10,13 +10,16 @@ let octokitInstance: Octokit
export function octokit() {
if (!octokitInstance) {
const privateKey = process.env.DOCS_GITHUB_APP_PRIVATE_KEY
if (!privateKey) {
throw new Error('DOCS_GITHUB_APP_PRIVATE_KEY environment variable is required')
}
// https://github.com/gr2m/universal-github-app-jwt?tab=readme-ov-file#converting-pkcs1-to-pkcs8
const privateKeyPkcs8 = crypto
.createPrivateKey(process.env.DOCS_GITHUB_APP_PRIVATE_KEY)
.export({
type: 'pkcs8',
format: 'pem',
})
const privateKeyPkcs8 = crypto.createPrivateKey(privateKey).export({
type: 'pkcs8',
format: 'pem',
})
octokitInstance = new Octokit({
authStrategy: createAppAuth,
@@ -78,6 +81,7 @@ async function getGitHubFileContents({
} catch (err) {
console.error('Error fetching GitHub file: %o', err)
onError?.(err)
return ''
}
}
@@ -93,10 +97,10 @@ export async function getGitHubFileContentsImmutableOnly({
branch: string
path: string
options: {
onError: (err?: unknown) => void
fetch?: (info: RequestInfo, init?: RequestInit) => Promise<Response>
onError: (error: unknown) => void
fetch: (url: string) => Promise<Response>
}
}) {
}): Promise<string> {
const isImmutableCommit = await checkForImmutableCommit({
org,
repo,
@@ -106,13 +110,14 @@ export async function getGitHubFileContentsImmutableOnly({
throw Error('The commit is not an immutable commit SHA. Tags and branch names are not allowed.')
}
return getGitHubFileContents({
const result = await getGitHubFileContents({
org,
repo,
branch,
path,
options: { onError, fetch },
})
return result || ''
}
async function checkForImmutableCommit({
+5 -5
View File
@@ -24,17 +24,17 @@ export function extractTsDocNode(nodeToFind: string, definition: any) {
}
export function generateParameters(tsDefinition: any) {
let functionDeclaration = null
let functionDeclaration: any = null
if (tsDefinition.kindString == 'Method') {
functionDeclaration = tsDefinition
} else if (tsDefinition.kindString == 'Constructor') {
functionDeclaration = tsDefinition
} else functionDeclaration = tsDefinition?.type?.declaration
if (!functionDeclaration) return ''
if (!functionDeclaration || !functionDeclaration.signatures) return ''
// Functions can have multiple signatures - select the last one since that
// tends to be closer to primitive types (citation needed).
const paramDefinitions: TsDoc.TypeDefinition[] = functionDeclaration.signatures.at(-1).parameters
const paramDefinitions: TsDoc.TypeDefinition[] = functionDeclaration.signatures.at(-1)?.parameters
if (!paramDefinitions) return ''
// const paramsComments: TsDoc.CommentTag = tsDefinition.comment?.tags?.filter(x => x.tag == 'param')
@@ -46,7 +46,7 @@ function recurseThroughParams(paramDefinition: any) {
const param = { ...paramDefinition }
const labelParams = generateLabelParam(param)
let children: any[]
let children: any[] | undefined
if (param.type?.type === 'literal') {
// skip: literal types have no children
} else if (param.type?.type === 'intrinsic') {
@@ -241,7 +241,7 @@ export function gen_v3(
toArrayWithKey(val!, 'operation').forEach((o) => {
const operation = o as v3OperationWithPath
const operationId =
type === 'mgmt-api' && isValidSlug(operation.operationId)
type === 'mgmt-api' && operation.operationId && isValidSlug(operation.operationId)
? operation.operationId
: slugify(operation.summary!)
const enriched = {
+8 -4
View File
@@ -8,10 +8,14 @@ let supabaseAdminClient: SupabaseClient<Database> | null = null
export function supabaseAdmin() {
if (!supabaseAdminClient) {
supabaseAdminClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY
)
const url = process.env.NEXT_PUBLIC_SUPABASE_URL
const key = process.env.SUPABASE_SECRET_KEY
if (!url || !key) {
throw new Error('Missing required environment variables for Supabase admin client')
}
supabaseAdminClient = createClient(url, key)
}
return supabaseAdminClient
@@ -26,10 +26,14 @@ export abstract class SearchResultModel {
await supabase().rpc('search_content', {
embedding,
include_full_content: includeFullContent,
max_result: args.limit,
max_result: args.limit ?? undefined,
})
)
.map((matches) => matches.map(createModelFromMatch).filter(Boolean))
.map((matches) =>
matches
.map(createModelFromMatch)
.filter((item): item is SearchResultInterface => item !== null)
)
.mapError(convertPostgrestToApiError)
return matchResult
@@ -55,7 +59,7 @@ function createModelFromMatch({
})
case 'reference':
const { language } = metadata
if (SDKLanguageValues.includes(language)) {
if (language && SDKLanguageValues.includes(language)) {
return new ReferenceSDKFunctionModel({
title: page_title,
href,
@@ -71,7 +75,7 @@ function createModelFromMatch({
subsections,
})
} else {
break
return null
}
case 'github-discussions':
return new TroubleshootingModel({
+6 -6
View File
@@ -269,7 +269,7 @@ export class GraphQLCollectionBuilder {
const result = await fetch(args)
const { items: fetchedItems, totalCount, hasNextPage = false, hasPreviousPage = false } = result
const edges = fetchedItems.map((item) => {
return { node: item, cursor: getCursor(item) }
return { node: item, cursor: getCursor?.(item) ?? '' }
})
return {
@@ -291,7 +291,7 @@ export class GraphQLCollectionBuilder {
return { node: item, cursor: getCursor(item, idx) }
})
const { edges, hasPreviousPage, hasNextPage } = getRequestedSlice(allEdges, args)
const { edges, hasPreviousPage, hasNextPage } = getRequestedSlice(allEdges, args ?? {})
return {
edges,
@@ -323,12 +323,12 @@ function getRequestedSlice<ItemType>(
let beforeIndex = allEdges.length
let afterIndex = -1
const requestedBefore = toNumber(pageArgs.before)
const requestedBefore = pageArgs.before ? toNumber(pageArgs.before) : undefined
if (requestedBefore && requestedBefore >= 0 && requestedBefore < beforeIndex) {
beforeIndex = requestedBefore
hasNextPage = true
}
const requestedAfter = toNumber(pageArgs.after)
const requestedAfter = pageArgs.after ? toNumber(pageArgs.after) : undefined
if (requestedAfter && requestedAfter >= 0) {
afterIndex = requestedAfter
hasPreviousPage = true
@@ -336,10 +336,10 @@ function getRequestedSlice<ItemType>(
let edges = allEdges.slice(afterIndex + 1, beforeIndex)
if (pageArgs.first >= 0 && edges.length > pageArgs.first) {
if (pageArgs.first != null && pageArgs.first >= 0 && edges.length > pageArgs.first) {
edges = edges.slice(0, pageArgs.first)
hasNextPage = true
} else if (pageArgs.last >= 0 && edges.length > pageArgs.last) {
} else if (pageArgs.last != null && pageArgs.last >= 0 && edges.length > pageArgs.last) {
edges = edges.slice(edges.length - pageArgs.last)
hasPreviousPage = true
}
+25 -20
View File
@@ -19,7 +19,7 @@ import {
type ValueNode,
} from 'graphql'
let options = { processArguments: false, excludedFields: [] }
let options = { processArguments: false, excludedFields: [] as string[] }
function getSelections(ast: FieldNode | FragmentDefinitionNode | InlineFragmentNode) {
if (
@@ -47,7 +47,7 @@ function getAST(ast: FragmentSpreadNode | InlineFragmentNode, info: GraphQLResol
}
function getArguments(ast: FieldNode, info: GraphQLResolveInfo) {
return ast.arguments.map((argument) => {
return ast.arguments?.map((argument) => {
const argumentValue = getArgumentValue(argument.value, info)
return {
@@ -81,7 +81,8 @@ function getArgumentValue(arg: ValueNode, info: GraphQLResolveInfo) {
}
function getDirectiveValue(directive: DirectiveNode, info: GraphQLResolveInfo) {
const arg = directive.arguments[0]
const arg = directive.arguments?.[0]
if (!arg) return undefined
if (arg.value.kind !== 'Variable') {
return arg.value.kind === 'BooleanValue' ? arg.value.value : undefined
}
@@ -93,22 +94,26 @@ function getDirectiveResults(ast: SelectionNode, info: GraphQLResolveInfo) {
shouldInclude: true,
shouldSkip: false,
}
return ast.directives.reduce((result, directive) => {
switch (directive.name.value) {
case 'include':
const directiveValue = getDirectiveValue(directive, info)
if (directiveValue != undefined) {
return { ...result, shouldInclude: directiveValue }
}
case 'skip':
const directiveValule = getDirectiveValue(directive, info)
if (directiveValue != undefined) {
return { ...result, shouldSkip: directiveValue }
}
default:
return result
}
}, directiveResult)
return (
ast.directives?.reduce((result, directive) => {
switch (directive.name.value) {
case 'include':
const directiveValue = getDirectiveValue(directive, info)
if (directiveValue != undefined) {
return { ...result, shouldInclude: directiveValue }
}
return result
case 'skip':
const directiveSkipValue = getDirectiveValue(directive, info)
if (directiveSkipValue != undefined) {
return { ...result, shouldSkip: directiveSkipValue }
}
return result
default:
return result
}
}, directiveResult) ?? directiveResult
)
}
function flattenAST(
@@ -128,7 +133,7 @@ function flattenAST(
flattened = flattenAST(getAST(a, info), info, flattened)
} else {
const name = a.name.value
if (options.excludedFields.indexOf(name) !== -1) {
if (options.excludedFields && options.excludedFields.indexOf(name) !== -1) {
return flattened
}
if (flattened[name] && flattened[name] !== '__arguments') {
+3 -2
View File
@@ -45,7 +45,7 @@ function processMdx(content: string, options?: { yaml?: boolean }): ProcessedMdx
mdastExtensions: [mdxFromMarkdown()],
})
let meta: Record<string, unknown>
let meta: Record<string, unknown> | undefined
if (options?.yaml) {
meta = frontmatter
} else {
@@ -83,7 +83,8 @@ function processMdx(content: string, options?: { yaml?: boolean }): ProcessedMdx
const [firstNode] = tree.children
const content = toMarkdown(tree)
const rawHeading: string = firstNode.type === 'heading' ? toString(firstNode) : undefined
const rawHeading: string | undefined =
firstNode.type === 'heading' ? toString(firstNode) : undefined
if (!rawHeading) {
return { content }
+7 -5
View File
@@ -11,7 +11,7 @@
* the last Git commit date.
*/
import _configureDotEnv from './utils/dotenv'
import _configureDotEnv from './utils/dotenv.js'
import { createClient, type SupabaseClient } from '@supabase/supabase-js'
import matter from 'gray-matter'
@@ -111,8 +111,8 @@ function parseOptions(): Options {
function createSupabaseClient() {
return createClient(
process.env[REQUIRED_ENV_VARS.SUPABASE_URL],
process.env[REQUIRED_ENV_VARS.SERVICE_ROLE_KEY]
process.env[REQUIRED_ENV_VARS.SUPABASE_URL]!,
process.env[REQUIRED_ENV_VARS.SERVICE_ROLE_KEY]!
)
}
@@ -125,7 +125,9 @@ async function updateContentDates({ reset, ctx }: { reset: boolean; ctx: Ctx })
const updateTasks: Array<Promise<void>> = []
for (const file of mdxFiles) {
const tasks = await updateTimestamps(file, { reset, timestamp, ctx })
updateTasks.push(...tasks)
if (tasks) {
updateTasks.push(...tasks)
}
}
await Promise.all(updateTasks)
}
@@ -183,7 +185,7 @@ function processMdx(rawContent: string): Array<SectionWithChecksum> {
let heading = rawHeading
if (seenHeadings.has(rawHeading)) {
const idx = seenHeadings.get(rawHeading) + 1
const idx = (seenHeadings.get(rawHeading) ?? 0) + 1
seenHeadings.set(rawHeading, idx)
heading = `${rawHeading} (__UNIQUE_MARKER__${idx})`
} else {
+32 -13
View File
@@ -1,4 +1,4 @@
import _configureDotEnv from './utils/dotenv'
import './utils/dotenv.js'
import 'dotenv/config'
import fs from 'node:fs/promises'
@@ -15,8 +15,6 @@ import {
type SearchSource,
} from './search/sources/index.js'
const _ = _configureDotEnv
interface Source {
title: string
/**
@@ -39,37 +37,58 @@ const SOURCES: Source[] = [
{
title: 'Supabase Reference (JavaScript)',
relPath: 'llms/js.txt',
fetch: fetchJsLibReferenceSource,
fetch: async () =>
(await fetchJsLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (Dart)',
relPath: 'llms/dart.txt',
fetch: fetchDartLibReferenceSource,
fetch: async () =>
(await fetchDartLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (Swift)',
relPath: 'llms/swift.txt',
fetch: fetchSwiftLibReferenceSource,
fetch: async () =>
(await fetchSwiftLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (Kotlin)',
relPath: 'llms/kt.txt',
fetch: fetchKtLibReferenceSource,
relPath: 'llms/kotlin.txt',
fetch: async () =>
(await fetchKtLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (Python)',
relPath: 'llms/py.txt',
fetch: fetchPythonLibReferenceSource,
relPath: 'llms/python.txt',
fetch: async () =>
(await fetchPythonLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (C#)',
relPath: 'llms/csharp.txt',
fetch: fetchCSharpLibReferenceSource,
fetch: async () =>
(await fetchCSharpLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
{
title: 'Supabase Reference (CLI)',
title: 'Supabase CLI Reference',
relPath: 'llms/cli.txt',
fetch: fetchCliLibReferenceSource,
fetch: async () =>
(await fetchCliLibReferenceSource()).filter(
(item): item is SearchSource => item !== undefined
),
},
]
+13 -11
View File
@@ -2,24 +2,25 @@ import { readdir } from 'node:fs/promises'
import { extname, join } from 'node:path'
import {
GLOBAL_MENU_ITEMS,
gettingstarted,
cli,
auth,
database,
api,
graphql,
functions,
realtime,
storage,
ai,
api,
auth,
cli,
database,
functions,
gettingstarted,
graphql,
platform,
realtime,
resources,
self_hosting,
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants'
storage,
} from '../../components/Navigation/NavigationMenu/NavigationMenu.constants.js'
const DOCS_ROOT_DIR = join(__dirname, '..', '..')
const DEFAULT_DOCS_CONTENT_DIR = join(DOCS_ROOT_DIR, 'content')
// eslint-disable-next-line turbo/no-undeclared-env-vars
const DOCS_CONTENT_DIR = process.env.DOCS_CONTENT_DIR || DEFAULT_DOCS_CONTENT_DIR
const IGNORE_LIST = [
@@ -47,7 +48,8 @@ type RefItem = {
const recGetUrl = (items: readonly RefItem[], acc: string[] = []) =>
items.reduce((acc, item) => {
if (item.href || item.url) acc.push(item.href || item.url)
const url = item.href || item.url
if (url) acc.push(url)
if (item.items) acc.push(...recGetUrl(item.items, acc))
return acc
}, acc)
@@ -1,4 +1,4 @@
import _configureDotenv from '../utils/dotenv'
import '../utils/dotenv.js'
import { createClient } from '@supabase/supabase-js'
import { parseArgs } from 'node:util'
@@ -7,8 +7,6 @@ import { v4 as uuidv4 } from 'uuid'
import type { Json, Section } from '../helpers.mdx.js'
import { fetchAllSources } from './sources/index.js'
const _ = _configureDotenv
const args = parseArgs({
options: {
refresh: {
@@ -41,8 +39,8 @@ async function generateEmbeddings() {
}
const supabaseClient = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.SUPABASE_SECRET_KEY,
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.SUPABASE_SECRET_KEY!,
{
auth: {
persistSession: false,
@@ -38,7 +38,7 @@ export async function fetchDiscussions(owner: string, repo: string, categoryId:
auth: {
appId,
installationId,
privateKey: crypto.createPrivateKey(privateKey).export({ type: 'pkcs8', format: 'pem' }),
privateKey: crypto.createPrivateKey(privateKey!).export({ type: 'pkcs8', format: 'pem' }),
},
})
+5 -1
View File
@@ -139,7 +139,11 @@ export async function fetchAllSources() {
const partnerIntegrationSources = fetchPartners()
.then((partners) =>
Promise.all(partners.map((partner) => new IntegrationLoader(partner.slug, partner).load()))
partners
? Promise.all(
partners.map((partner) => new IntegrationLoader(partner.slug, partner).load())
)
: []
)
.then((data) => data.flat())
+1 -1
View File
@@ -46,6 +46,6 @@ export class MarkdownSource extends BaseSource {
const sections = this.sections ?? []
const sectionText = sections.map(({ content }) => content).join('\n\n')
return `# ${this.meta.title ?? ''}\n\n${this.meta.subtitle ?? ''}\n\n${sectionText}`
return `# ${this.meta?.title ?? ''}\n\n${this.meta?.subtitle ?? ''}\n\n${sectionText}`
}
}
@@ -8,15 +8,15 @@ type PartnerData = {
overview: string // The Markdown content for indexing
}
const supabaseUrl = process.env.NEXT_PUBLIC_MISC_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_MISC_ANON_KEY
const supabaseUrl = process.env.NEXT_PUBLIC_MISC_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_MISC_ANON_KEY!
let supabaseClient: SupabaseClient
function getSupabaseClient() {
if (!supabaseClient) {
supabaseClient = createClient(supabaseUrl, supabaseAnonKey)
return supabaseClient
}
return supabaseClient
}
export async function fetchPartners() {
@@ -27,7 +27,7 @@ export async function fetchPartners() {
.eq('approved', true)
// We want to show technology integrations, not agencies, in search
.neq('type', 'expert')
return partners
return partners ?? []
}
export class IntegrationLoader extends BaseLoader {
@@ -30,7 +30,7 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
super(source, path)
}
async load() {
async load(): Promise<BaseSource[]> {
const specContents = await readFile(this.specFilePath, 'utf8')
const refSectionsContents = await readFile(this.sectionsFilePath, 'utf8')
@@ -55,13 +55,13 @@ export abstract class ReferenceLoader<SpecSection> extends BaseLoader {
this.enhanceMeta(specSection)
)
})
.filter(Boolean)
.filter((item): item is ReferenceSource<SpecSection> => item !== undefined)
return sections
return sections as BaseSource[]
}
abstract getSpecSections(specContents: string): SpecSection[]
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection
abstract matchSpecSection(specSections: SpecSection[], id: string): SpecSection | undefined
enhanceMeta(section: SpecSection): Json {
return this.meta
}
@@ -136,7 +136,7 @@ export class OpenApiReferenceLoader extends ReferenceLoader<enrichedOperation> {
return generatedSpec.operations
}
matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation {
matchSpecSection(operations: enrichedOperation[], id: string): enrichedOperation | undefined {
return operations.find((operation) => operation.operationId === id)
}
}
@@ -166,9 +166,9 @@ export class OpenApiReferenceSource extends ReferenceSource<enrichedOperation> {
extractIndexedContent(): string {
const { summary, description, operation, tags } = this.specSection
return `# ${this.meta.title ?? ''}\n\n${summary ?? ''}\n\n${description ?? ''}\n\n${operation ?? ''}\n\n${tags.join(
', '
)}`
return `# ${this.meta.title ?? ''}\n\n${summary ?? ''}\n\n${description ?? ''}\n\n${operation ?? ''}\n\n${
tags?.join(', ') ?? ''
}`
}
}
@@ -190,7 +190,10 @@ export class ClientLibReferenceLoader extends ReferenceLoader<IFunctionDefinitio
return spec.functions
}
matchSpecSection(functionDefinitions: IFunctionDefinition[], id: string): IFunctionDefinition {
matchSpecSection(
functionDefinitions: IFunctionDefinition[],
id: string
): IFunctionDefinition | undefined {
return functionDefinitions.find((functionDefinition) => functionDefinition.id === id)
}
@@ -246,7 +249,7 @@ export class CliReferenceLoader extends ReferenceLoader<CliCommand> {
return spec.commands
}
matchSpecSection(cliCommands: CliCommand[], id: string): CliCommand {
matchSpecSection(cliCommands: CliCommand[], id: string): CliCommand | undefined {
return cliCommands.find((cliCommand) => cliCommand.id === id)
}
}
@@ -23,8 +23,19 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
try {
const openApiJson = JSON.parse(data)
const categories = []
const sections = []
const categories: string[] = []
const sections: Array<{
type: string
title: string
id?: string
slug?: string
items: Array<{
type: string
title: string
id: string
slug: string
}>
}> = []
if (openApiJson.paths) {
for (const route in openApiJson.paths) {
@@ -47,7 +58,7 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
}
const sectionCate = sections.find((i) => i.title === tag)
sectionCate.items.push({
sectionCate?.items.push({
id: operationId,
title: slugToTitle(operationId),
slug: operationId,
@@ -65,6 +76,7 @@ function extractSectionsFromOpenApi(filePath, outputPath) {
id: 'introduction',
slug: 'introduction',
type: 'markdown',
items: [],
})
fs.writeFile(outputPath, JSON.stringify(sections, null, 2), 'utf8', (err) => {
+1 -1
View File
@@ -25,7 +25,7 @@
"name": "next"
}
],
"strictNullChecks": false
"strictNullChecks": true
},
"include": [
"next-env.d.ts",
@@ -67,7 +67,7 @@ const Input = forwardRef<
setHidden(false)
}
let inputClasses = []
let inputClasses: string[] = []
if (icon) inputClasses.push(__styles.with_icon)
return (