1
0
mirror of https://github.com/twbs/bootstrap.git synced 2025-10-03 08:41:59 +02:00
Files
bootstrap/site/src/libs/data.ts
Mark Otto c8e8d28d29 v6: Add sub-groups to Utilities docs (#41758)
* Split the flex.mdx file into separate pages

* Add subgroups to docs utils nav

* More new groups, split pages

* Update MDX linter

* fixes
2025-09-22 11:55:12 -07:00

154 lines
4.1 KiB
TypeScript

import fs from 'node:fs'
import yaml from 'js-yaml'
import { z } from 'zod'
import {
zHexColor,
zLanguageCode,
zNamedHexColors,
zPxSizeOrEmpty,
zVersionMajorMinor,
zVersionSemver
} from './validation'
import { capitalizeFirstLetter } from './utils'
// An object containing all the data types and their associated schema. The key should match the name of the data file
// in the `./site/data/` directory.
const dataDefinitions = {
breakpoints: z
.object({
breakpoint: z.string(),
abbr: z.string(),
name: z.string(),
'min-width': zPxSizeOrEmpty,
container: zPxSizeOrEmpty
})
.array(),
colors: zNamedHexColors(13),
'core-team': z
.object({
name: z.string(),
user: z.string()
})
.array(),
'docs-versions': z
.object({
group: z.string(),
baseurl: z.string().url(),
description: z.string(),
versions: z.union([zVersionSemver, zVersionMajorMinor]).array()
})
.array(),
examples: z
.object({
category: z.string(),
external: z.boolean().optional(),
description: z.string(),
examples: z
.object({
description: z.string(),
indexPath: z.string().optional(),
name: z.string(),
url: z.string().optional()
})
.array()
})
.array(),
grays: zNamedHexColors(9),
icons: z.object({
preferred: z
.object({
name: z.string(),
website: z.string().url()
})
.array(),
more: z
.object({
name: z.string(),
website: z.string().url()
})
.array()
}),
plugins: z
.object({
description: z.string(),
link: z.string().startsWith('components/'),
name: z.string()
})
.array(),
sidebar: z
.object({
title: z.string(),
icon: z.string().optional(),
icon_color: z.string().optional(),
pages: z
.object({
title: z.string().optional(),
group: z.string().optional(),
pages: z
.object({
title: z.string()
})
.array()
.optional()
})
.array()
.optional()
})
.array(),
'theme-colors': z
.object({
name: z.string(),
hex: zHexColor,
contrast_color: z.union([z.literal('dark'), z.literal('white')]).optional()
})
.array()
.transform((val) => {
// Add a `title` property to each theme color object being the capitalized version of the `name` property.
return val.map((themeColor) => ({ ...themeColor, title: capitalizeFirstLetter(themeColor.name) }))
}),
translations: z
.object({
name: z.string(),
code: zLanguageCode,
description: z.string(),
url: z.string().url()
})
.array()
} satisfies Record<string, DataSchema>
let data = new Map<DataType, z.infer<DataSchema>>()
// A helper to get data loaded fom a yml file in the `./site/data/` directory. If the data does not match its associated
// schema from `dataDefinitions`, an error is thrown to indicate that the data file is invalid and some action is
// required.
export function getData<TType extends DataType>(type: TType): z.infer<(typeof dataDefinitions)[TType]> {
if (data.has(type)) {
// Returns the data if it has already been loaded.
return data.get(type) as z.infer<(typeof dataDefinitions)[TType]>
}
const dataPath = `./site/data/${type}.yml`
try {
// Load the data from the yml file.
const rawData = yaml.load(fs.readFileSync(dataPath, 'utf8'))
// Parse the data using the data schema to validate its content and get back a fully typed data object.
const parsedData = dataDefinitions[type].parse(rawData) as z.infer<(typeof dataDefinitions)[TType]>
// Cache the data.
data.set(type, parsedData)
return parsedData
} catch (error) {
if (error instanceof z.ZodError) {
console.error(`The \`${dataPath}\` file content is invalid:`, error.issues)
}
throw new Error(`Failed to load data from \`${dataPath}\``, { cause: error })
}
}
type DataType = keyof typeof dataDefinitions
type DataSchema = z.ZodTypeAny