Add CMS configuration schema, docs, and validation (#551)

This commit is contained in:
shenlan 2025-10-17 16:41:22 +08:00 committed by GitHub
parent d91f2b928f
commit 302794d2a6
8 changed files with 418 additions and 9 deletions

29
.github/workflows/cms-config.yml vendored Normal file
View File

@ -0,0 +1,29 @@
name: Validate CMS configuration
on:
pull_request:
paths:
- 'config/cms.json'
- 'config/cms.schema.json'
- 'scripts/validate_cms_config.py'
- '.github/workflows/cms-config.yml'
push:
branches: [main]
paths:
- 'config/cms.json'
- 'config/cms.schema.json'
- 'scripts/validate_cms_config.py'
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: python -m pip install --upgrade pip jsonschema
- name: Validate cms.json
run: python scripts/validate_cms_config.py

View File

@ -80,7 +80,7 @@ endif
# Database initialization
# -----------------------------------------------------------------------------
init-db:
@psql $(PG_DSN) -f rag-server/sql/schema.sql
@psql $(PG_DSN) -f rag-server/sql/schema.sql
# -----------------------------------------------------------------------------
# Build targets
@ -89,16 +89,16 @@ init-db:
build: update-dashboard-manifests build-cli build-server build-dashboard
build-cli:
$(MAKE) -C rag-server/cmd/rag-server-cli build
$(MAKE) -C rag-server/cmd/rag-server-cli build
build-server:
$(MAKE) -C rag-server build
$(MAKE) -C rag-server build
build-dashboard:
$(MAKE) -C dashboard build SKIP_SYNC=1
$(MAKE) -C dashboard build SKIP_SYNC=1
update-dashboard-manifests:
$(MAKE) -C dashboard sync-dl-index
$(MAKE) -C dashboard sync-dl-index
# -----------------------------------------------------------------------------
# Run targets
@ -107,19 +107,19 @@ $(MAKE) -C dashboard sync-dl-index
start: start-openresty start-server start-dashboard start-dl start-docs
start-server:
$(MAKE) -C rag-server start
$(MAKE) -C rag-server start
start-dashboard:
$(MAKE) -C dashboard start
$(MAKE) -C dashboard start
stop: stop-server stop-dashboard stop-openresty
stop-server:
$(MAKE) -C rag-server stop
$(MAKE) -C rag-server stop
stop-dashboard:
$(MAKE) -C dashboard stop
$(MAKE) -C dashboard stop
start-openresty:
ifeq ($(OS),Darwin)
@ -152,3 +152,10 @@ else
endif
restart: stop start
# -----------------------------------------------------------------------------
# CMS configuration validation
# -----------------------------------------------------------------------------
.PHONY: lint-cms
lint-cms:
python3 scripts/validate_cms_config.py

View File

@ -37,6 +37,13 @@ XControl 通过 LangChainGo 统一接入多种大模型,并为 AskAI、CLI 与
- **Memory 与历史追踪**:支持 Conversation Buffer 等对话记忆机制,增强交互体验。
## CMS configuration
A unified CMS setup is defined in [`config/cms.json`](config/cms.json). The schema at [`config/cms.schema.json`](config/cms.schema.json) ensures templates, themes, extensions and content sources stay in sync across deployments.
- Refer to [`docs/cms/README.md`](docs/cms/README.md) for usage instructions, extension development notes and theme customization guidelines.
- Follow the migration playbook in [`docs/cms/migration-guide.md`](docs/cms/migration-guide.md) when switching existing sites to the CMS architecture.
## Supported Platforms
Tested on **Ubuntu 22.04 x64** and **macOS 26 arm64**.

69
config/cms.json Normal file
View File

@ -0,0 +1,69 @@
{
"$schema": "./cms.schema.json",
"templates": [
{
"name": "marketing-landing",
"entry": "templates/marketing/index.tsx",
"description": "Default landing page for campaign microsites.",
"previewPath": "previews/marketing-landing.png"
},
{
"name": "docs-home",
"entry": "templates/docs/home.tsx",
"description": "Documentation homepage wiring search, changelog and highlights."
}
],
"theme": {
"name": "xcontrol-galaxy",
"version": "1.0.0",
"author": "XControl Design Systems",
"variables": {
"primaryColor": "#4055ff",
"accentColor": "#39c2f0",
"fontFamily": "Inter, system-ui, sans-serif"
}
},
"extensions": [
{
"name": "search",
"package": "@xcontrol/cms-extension-search",
"enabled": true,
"config": {
"provider": "algolia",
"indexName": "xcontrol_docs"
}
},
{
"name": "ab-testing",
"package": "@xcontrol/cms-extension-experiments",
"enabled": false,
"config": {
"allocation": "5%"
}
}
],
"contentSources": [
{
"type": "git",
"name": "marketing-site",
"readOnly": false,
"options": {
"remote": "git@github.com:xcontrol/marketing-site.git",
"branch": "main",
"contentPath": "content/"
}
},
{
"type": "filesystem",
"name": "product-docs",
"readOnly": true,
"options": {
"path": "../docs"
}
}
],
"deployment": {
"preview": true,
"defaultLocale": "en-US"
}
}

158
config/cms.schema.json Normal file
View File

@ -0,0 +1,158 @@
{
"$id": "https://xcontrol.dev/schemas/cms.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "XControl CMS Configuration",
"type": "object",
"required": [
"templates",
"theme",
"extensions",
"contentSources"
],
"additionalProperties": false,
"properties": {
"templates": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"name",
"entry"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"entry": {
"description": "The relative path to the template entry point.",
"type": "string",
"minLength": 1
},
"description": {
"type": "string"
},
"previewPath": {
"description": "Optional static preview asset path.",
"type": "string"
}
}
}
},
"theme": {
"type": "object",
"required": [
"name",
"version"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"version": {
"type": "string",
"minLength": 1
},
"author": {
"type": "string"
},
"variables": {
"description": "Theme tokens exposed to templates.",
"type": "object",
"additionalProperties": {
"type": [
"string",
"number",
"boolean"
]
}
}
}
},
"extensions": {
"type": "array",
"items": {
"type": "object",
"required": [
"name",
"package"
],
"additionalProperties": false,
"properties": {
"name": {
"type": "string",
"minLength": 1
},
"package": {
"description": "Resolvable package name or path.",
"type": "string",
"minLength": 1
},
"enabled": {
"type": "boolean",
"default": true
},
"config": {
"type": "object"
}
}
}
},
"contentSources": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"required": [
"type",
"name",
"options"
],
"additionalProperties": false,
"properties": {
"type": {
"type": "string",
"enum": [
"git",
"filesystem",
"api",
"database"
]
},
"name": {
"type": "string",
"minLength": 1
},
"options": {
"description": "Source specific configuration payload.",
"type": "object"
},
"readOnly": {
"type": "boolean",
"default": false
}
}
}
},
"deployment": {
"type": "object",
"additionalProperties": false,
"properties": {
"preview": {
"type": "boolean"
},
"defaultLocale": {
"type": "string"
}
}
},
"$schema": {
"type": "string",
"description": "Optional JSON Schema declaration for tooling support."
}
}
}

50
docs/cms/README.md Normal file
View File

@ -0,0 +1,50 @@
# XControl CMS 平台指南
本文档介绍如何使用全新的 CMS 配置、扩展生态以及主题定制流程,帮助团队快速落地内容管理方案。
## 使用说明
1. **配置文件位置**:所有实例统一使用 [`config/cms.json`](../../config/cms.json) 描述模板、主题、扩展及内容源。提交前可运行 `make lint-cms` 或 GitHub Actions 自动校验以确保配置符合 JSON Schema。
2. **模板管理**
- `templates` 数组声明可用模板的 `name` 与入口文件 `entry`
- 可添加 `description``previewPath` 提供运营预览信息;
- 模板内可读取主题变量 `theme.variables`,应通过公共的 `@xcontrol/cms-theme` SDK 获取。
3. **主题切换**
- `theme` 字段记录版本、作者与可覆写的变量;
- 在 CI/CD 中读取 `theme.version` 控制静态资源缓存;
- 自定义变量建议遵循 `kebabCase``camelCase`,并在设计系统中记录来源。
4. **扩展启用**
- `extensions` 数组列出扩展包;`enabled=false` 的扩展会被打包但默认不挂载;
- `config` 字段由扩展自行解析,应在扩展仓库提供 JSON Schema 或 TypeScript 类型定义;
- 推荐将第三方密钥通过环境变量传入扩展,避免写入配置文件。
5. **内容源配置**
- `contentSources` 支持 `git`、`filesystem`、`api` 和 `database` 四种类型;
- 每个内容源都需要一个唯一的 `name` 与自定义 `options`
- 若设定 `readOnly: true`,发布面板会禁用写入操作。
## 扩展开发手册
1. **开发准备**
- 使用 `pnpm create @xcontrol/cms-extension <name>` 脚手架初始化项目;
- 在 `package.json` 中声明入口 `main``module`,同时导出扩展元数据 `getExtensionMeta()`
2. **生命周期**
- 扩展需实现 `register(app, context)` 方法,在其中挂载路由、配置表单或任务;
- 扩展可通过 `context.cmsConfig` 读取解析后的 `cms.json`
- 对长耗时任务使用 `context.queue.enqueue`,避免阻塞渲染线程。
3. **测试与发布**
- 使用 `pnpm test` 运行扩展单测;
- 通过 `pnpm build` 生成产物,并在 `dist/manifest.json` 中输出能力声明;
- 发布到内网 NPM 仓库后,可在主项目 `config/cms.json` 中引用。
## 主题定制指南
1. **继承基础主题**:复制官方主题仓库 `@xcontrol/cms-theme-galaxy`,在 `tokens/` 中调整颜色、排版、阴影等设计变量。
2. **变量发布**
- 执行 `pnpm build` 生成 `theme.json`
- 在仓库中创建 `releases/<version>` 标签,与 `cms.json``theme.version` 对齐;
- 将 `theme.json` 发布到静态资源 CDN配置 `THEME_REGISTRY_URL` 指向该地址。
3. **模板调试**
- 本地运行 `pnpm dev --template <template-name>`,自动读取主题变量热更新;
- 使用 `previewPath` 提供的静态图检视最终效果;
- 主题变量新增时记得更新 `theme.variables` 以同步到配置文件。

View File

@ -0,0 +1,41 @@
# 迁移至 XControl CMS 的实施指南
本文梳理了从旧版静态页面体系迁移到全新 CMS 架构的步骤,并提供回滚方案以保障发布安全。
## 迁移准备
1. **梳理现有页面**:整理旧系统的路由、模板和数据源,并标注关键依赖(例如第三方 API、Webhook
2. **建立对照表**:在项目 wiki 中维护“旧页面 → 新模板”的映射,明确所需内容源与扩展。
3. **同步配置**:将上述映射写入 [`config/cms.json`](../../config/cms.json),并针对每个模板准备默认内容样例。
## 实施步骤
1. **搭建预览环境**
- 创建 `feature/cms-migration` 分支;
- 部署独立的 CMS 预览环境,指向 `cms.json` 中定义的内容源;
- 确保团队成员可登录并在预览中校对页面。
2. **内容迁移**
- 使用 `scripts/content-migrator`TODO或手动脚本将旧 Markdown/HTML 转换为 CMS 支持的内容块;
- 对照对照表验证每个页面的链接、组件与扩展生效情况;
- 在预览环境执行冒烟测试,覆盖核心业务流(注册、下单、反馈表单等)。
3. **灰度发布**
- 在 CDN/网关上新增 `cms-beta` 域名,对应新的渲染服务;
- 内部用户和部分真实流量切换到新域,收集性能及错误日志;
- 若未出现阻塞问题,逐步放大流量并更新主域名解析。
## 回滚方案
1. **保留旧环境**:迁移期间保留旧渲染服务与静态资源,避免同名覆盖;
2. **可控切换**:所有流量切换通过网关开关或流量调度平台完成,开关命名为 `enable_new_cms`
3. **回滚流程**
- 若监控中断或错误率超出阈值,立即关闭 `enable_new_cms` 将流量打回旧系统;
- 执行 `make revert-cms-release`(脚本需在部署仓库中实现)撤销最新配置发布;
- 将问题归档在 incident 工单,收集 `cms.json` 与扩展日志,确认修复后重新灰度。
## 里程碑检查
- ✅ 完成 `cms.json` 配置与 Schema 校验;
- ✅ 预览环境可完整渲染所有页面;
- ✅ 灰度发布通过性能、监控、无障碍检查;
- ✅ 回滚演练至少一次,确保运维手册可执行。

48
scripts/validate_cms_config.py Executable file
View File

@ -0,0 +1,48 @@
#!/usr/bin/env python3
"""Validate the CMS configuration using the JSON schema."""
from __future__ import annotations
import json
import pathlib
import sys
try:
import jsonschema
except ImportError as exc: # pragma: no cover - handled in CI setup
raise SystemExit(
"jsonschema package is required to validate cms.json; install with 'pip install jsonschema'."
) from exc
ROOT = pathlib.Path(__file__).resolve().parents[1]
CONFIG_PATH = ROOT / "config" / "cms.json"
SCHEMA_PATH = ROOT / "config" / "cms.schema.json"
def load_json(path: pathlib.Path) -> dict:
try:
with path.open("r", encoding="utf-8") as handle:
return json.load(handle)
except FileNotFoundError as exc:
raise SystemExit(f"Missing required file: {path}") from exc
except json.JSONDecodeError as exc:
raise SystemExit(f"Failed to parse {path}: {exc}") from exc
def main() -> int:
schema = load_json(SCHEMA_PATH)
config = load_json(CONFIG_PATH)
try:
jsonschema.validate(instance=config, schema=schema)
except jsonschema.ValidationError as exc:
location = " > ".join(str(item) for item in exc.absolute_path)
message = f"cms.json validation error at {location or '<root>'}: {exc.message}"
raise SystemExit(message) from exc
print("cms.json matches cms.schema.json")
return 0
if __name__ == "__main__":
sys.exit(main())