feat(docs): discover package metadata

This commit is contained in:
Maofeng
2026-09-29 17:20:25 +08:00
parent 844acad316
commit f56397fb97
16 changed files with 162 additions and 96 deletions
+1
View File
@@ -89,6 +89,7 @@
"vite": "^8.3.0",
"vite-plugin-satteri": "^0.3.5",
"wrangler": "^4.135.0",
"yaml": "^2.9.1",
},
},
"packages/appearance": {
+2 -1
View File
@@ -45,6 +45,7 @@
"typescript": "~6.0.2",
"vite": "^8.3.0",
"vite-plugin-satteri": "^0.3.5",
"wrangler": "^4.135.0"
"wrangler": "^4.135.0",
"yaml": "^2.9.1"
}
}
+74 -93
View File
@@ -21,101 +21,23 @@ type DocContentModule = {
type PackageDefinition = {
key: string
label: string
description: Translation<string>
description: Record<DocLocale, string>
order: number
}
type ParsedDocPage = DocPage & { packageKey: string }
const packageDefinitions: readonly PackageDefinition[] = [
{
key: "blocks",
label: "Blocks",
description: {
zh: "构建应用外壳,并接入导航、通知、聊天和外观控制。",
us: "Build an application shell and add navigation, notifications, chat, and appearance controls.",
},
},
{
key: "appearance",
label: "Appearance",
description: {
zh: "使用原子化控件、主题词典和可组合偏好副作用构建应用外观设置。",
us: "Build application appearance settings with atomic controls, theme tokens, and composable preference effects.",
},
},
{
key: "i18n",
label: "I18n",
description: {
zh: "接入应用自有词典、本地化运行时、开发工具和完整翻译工作流。",
us: "Add application-owned catalogs, localized runtime rendering, development tools, and translation workflows.",
},
},
{
key: "lexical",
label: "Lexical",
description: {
zh: "从根节点和内容区域开始,逐步构建支持自定义操作与媒体的富文本编辑器。",
us: "Build a rich-text editor progressively from its root and content surface to custom actions and media.",
},
},
{
key: "media",
label: "Media",
description: {
zh: "通过存储 Adapter 构建媒体浏览、上传、目录管理和资源选择流程。",
us: "Build media browsing, uploads, folder management, and asset selection with a storage adapter.",
},
},
{
key: "overlays",
label: "Overlays",
description: {
zh: "使用统一的异步 API 编排 alert、confirm、prompt、modal、sheet、drawer 和 toast。",
us: "Orchestrate alerts, confirms, prompts, modals, sheets, drawers, and toasts through one asynchronous API.",
},
},
{
key: "page",
label: "Page",
description: {
zh: "组合页面标题、描述、操作、面包屑、路由标签和内容区,并按需将标题挂载到外部布局。",
us: "Compose page headings, actions, breadcrumbs, route tabs, and sections, with optional title portals into an external layout.",
},
},
{
key: "preferences",
label: "Preferences",
description: {
zh: "使用 SSR 快照、持久化回调和可组合副作用管理类型安全且可扩展的应用偏好。",
us: "Manage typed, extensible application preferences with SSR snapshots, persistence callbacks, and composable effects.",
},
},
{
key: "record",
label: "Record",
description: {
zh: "通过可组合上下文、类型安全字段路径和记录迭代器构建数据驱动界面。",
us: "Build data-driven interfaces with composable contexts, typed field paths, and record iteration.",
},
},
{
key: "search",
label: "Search",
description: {
zh: "通过可替换的 Adapter、分组结果、分页和搜索历史构建应用级搜索体验。",
us: "Build application search with a replaceable adapter, grouped results, pagination, and search history.",
},
},
{
key: "ui",
label: "UI",
description: {
zh: "配置共享 UI 层,并学习组合组件、Hooks、图标和本地化词典。",
us: "Set up the shared UI layer and learn how to compose its components, hooks, icons, and locale catalogs.",
},
},
]
const packageMetaModules = import.meta.glob<unknown>(
"../../../packages/*/docs/meta.yaml",
{ eager: true, import: "default" }
)
const packageDefinitions = Object.entries(packageMetaModules)
.map(([path, metadata]) => parsePackageDefinition(path, metadata))
.toSorted(
(left, right) =>
left.order - right.order || left.key.localeCompare(right.key)
)
const docModuleLoaders = import.meta.glob<DocContentModule>(
"../../../packages/*/docs/*/**/*.mdx",
@@ -283,12 +205,47 @@ function createDocPackage(
return {
key: definition.key,
label: definition.label,
description: trans(locale, definition.description),
description: definition.description[locale],
locale,
pages,
}
}
function parsePackageDefinition(
path: string,
value: unknown
): PackageDefinition {
const match = path.match(/\/packages\/([^/]+)\/docs\/meta\.yaml$/)
if (!match) {
throw new Error(
`Invalid package documentation metadata path "${path}". Expected packages/<package>/docs/meta.yaml.`
)
}
if (!isRecord(value)) {
throw new Error(
`Package documentation metadata must be an object: "${path}".`
)
}
const description = value.description
if (!isRecord(description)) {
throw new Error(
`Package documentation metadata requires a localized "description" object: "${path}".`
)
}
return {
key: match[1]!,
label: readMetaString(value, "label", path),
description: {
"zh-Hans": readMetaString(description, "zh-Hans", path),
"en-US": readMetaString(description, "en-US", path),
},
order: readMetaNumber(value, "order", path),
}
}
function parseDocPage(
path: string,
frontmatter: Record<string, unknown>,
@@ -379,6 +336,30 @@ function readNumber(frontmatter: Record<string, unknown>, key: string) {
throw new Error(`Documentation frontmatter requires a number "${key}".`)
}
function readMetaString(
metadata: Record<string, unknown>,
key: string,
path: string
) {
const value = metadata[key]
if (typeof value === "string" && value.trim()) return value
throw new Error(
`Package documentation metadata requires a string "${key}": "${path}".`
)
}
function readMetaNumber(
metadata: Record<string, unknown>,
key: string,
path: string
) {
const value = metadata[key]
if (typeof value === "number" && Number.isFinite(value)) return value
throw new Error(
`Package documentation metadata requires a number "${key}": "${path}".`
)
}
function readToc(value: unknown): readonly DocTocItem[] {
if (value === undefined) return []
if (!Array.isArray(value)) {
@@ -399,5 +380,5 @@ function readToc(value: unknown): readonly DocTocItem[] {
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null
return typeof value === "object" && value !== null && !Array.isArray(value)
}
+25 -2
View File
@@ -7,12 +7,13 @@ import expressiveCode from "satteri-expressive-code"
import { fileURLToPath } from "node:url"
import { defineConfig, type Plugin } from "vite"
import { satteri } from "vite-plugin-satteri"
import { parse } from "yaml"
const packagesDirectory = fileURLToPath(new URL("../packages", import.meta.url))
/**
* Vite 默认监听 docs 根目录。现有的外部 MDX 会作为模块被监听,但新建 package
* 或新建 MDX 文件在首次进入模块图之前不会触发 import.meta.glob 重新计算。
* Vite 默认监听 docs 根目录。现有的外部文档与元数据会作为模块被监听,但新建
* package、meta.yaml 或 MDX 文件在首次进入模块图之前不会触发 glob 重新计算。
*/
function watchPackageDocs(): Plugin {
return {
@@ -23,6 +24,27 @@ function watchPackageDocs(): Plugin {
}
}
function loadYamlModules(): Plugin {
return {
name: "load-yaml-modules",
enforce: "pre",
transform(source, id) {
if (!/\/meta\.ya?ml(?:\?|$)/.test(id)) return
try {
return {
code: `export default ${JSON.stringify(parse(source))}`,
map: null,
}
} catch (error) {
this.error(
`Unable to parse ${id}: ${error instanceof Error ? error.message : String(error)}`
)
}
},
}
}
function loadDocSearchSources(): Plugin {
const suffix = "?docs-search-raw"
const virtualPrefix = "\0docs-search-raw:"
@@ -60,6 +82,7 @@ function loadDocSearchSources(): Plugin {
export default defineConfig({
plugins: [
watchPackageDocs(),
loadYamlModules(),
loadDocSearchSources(),
satteri({
features: {
+5
View File
@@ -0,0 +1,5 @@
order: 20
label: Appearance
description:
zh-Hans: 使用原子化控件、主题词典和可组合偏好副作用构建应用外观设置。
en-US: Build application appearance settings with atomic controls, theme tokens, and composable preference effects.
+5
View File
@@ -0,0 +1,5 @@
order: 10
label: Blocks
description:
zh-Hans: 构建应用外壳,并接入导航、通知、聊天和外观控制。
en-US: Build an application shell and add navigation, notifications, chat, and appearance controls.
+5
View File
@@ -0,0 +1,5 @@
order: 30
label: Cropper
description:
zh-Hans: 通过可组合交互、几何计算和 Canvas 导出构建图片裁剪流程。
en-US: Build image-cropping workflows with composable interactions, geometry utilities, and Canvas export.
+5
View File
@@ -0,0 +1,5 @@
order: 40
label: I18n
description:
zh-Hans: 接入应用自有词典、本地化运行时、开发工具和完整翻译工作流。
en-US: Add application-owned catalogs, localized runtime rendering, development tools, and translation workflows.
+5
View File
@@ -0,0 +1,5 @@
order: 50
label: Lexical
description:
zh-Hans: 从根节点和内容区域开始,逐步构建支持自定义操作与媒体的富文本编辑器。
en-US: Build a rich-text editor progressively from its root and content surface to custom actions and media.
+5
View File
@@ -0,0 +1,5 @@
order: 60
label: Media
description:
zh-Hans: 通过存储 Adapter 构建媒体浏览、上传、目录管理和资源选择流程。
en-US: Build media browsing, uploads, folder management, and asset selection with a storage adapter.
+5
View File
@@ -0,0 +1,5 @@
order: 70
label: Overlays
description:
zh-Hans: 使用统一的异步 API 编排 alert、confirm、prompt、modal、sheet、drawer 和 toast。
en-US: Orchestrate alerts, confirms, prompts, modals, sheets, drawers, and toasts through one asynchronous API.
+5
View File
@@ -0,0 +1,5 @@
order: 80
label: Page
description:
zh-Hans: 组合页面标题、描述、操作、面包屑、路由标签和内容区,并按需将标题挂载到外部布局。
en-US: Compose page headings, actions, breadcrumbs, route tabs, and sections, with optional title portals into an external layout.
+5
View File
@@ -0,0 +1,5 @@
order: 90
label: Preferences
description:
zh-Hans: 使用 SSR 快照、持久化回调和可组合副作用管理类型安全且可扩展的应用偏好。
en-US: Manage typed, extensible application preferences with SSR snapshots, persistence callbacks, and composable effects.
+5
View File
@@ -0,0 +1,5 @@
order: 100
label: Record
description:
zh-Hans: 通过可组合上下文、类型安全字段路径和记录迭代器构建数据驱动界面。
en-US: Build data-driven interfaces with composable contexts, typed field paths, and record iteration.
+5
View File
@@ -0,0 +1,5 @@
order: 110
label: Search
description:
zh-Hans: 通过可替换的 Adapter、分组结果、分页和搜索历史构建应用级搜索体验。
en-US: Build application search with a replaceable adapter, grouped results, pagination, and search history.
+5
View File
@@ -0,0 +1,5 @@
order: 120
label: UI
description:
zh-Hans: 配置共享 UI 层,并学习组合组件、Hooks、图标和本地化词典。
en-US: Set up the shared UI layer and learn how to compose its components, hooks, icons, and locale catalogs.