feat(docs): discover package metadata
This commit is contained in:
@@ -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
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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: {
|
||||
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user