EmDash Seed 文件完全指南:从 Schema 定义到数据导出的实战手册
【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址: https://gitcode.com/gh_mirrors/emdas/emdash
seed 文件(seed/seed.json)是 EmDash 站点的"蓝图":它以一份 JSON 同时定义整个内容模型(collections、fields、taxonomies)与可选的演示数据,并在首次请求、数据库为空且尚未完成设置向导时被内联进构建产物并自动应用。本文将以 schema-and-seed.md 为骨架,结合 EmDash 源码(种子引擎、CLI 命令)与仓库中的真实种子文件(如 templates/starter/seed/seed.json),系统讲解 seed 文件的结构、字段类型、内容写法、校验规则与导入导出全流程,帮助你从零搭建一个可运行、可复现的 EmDash 站点。
Seed 文件的作用与应用时机
EmDash 采用"schema 存于数据库而非代码"的设计:集合、字段、分类法等结构不是写在 TypeScript 里,而是通过 seed 文件在初始化时写入数据库。一份 seed 文件被内联进构建产物,并在以下条件同时满足时自动应用:
- 数据库为空(尚无任何数据);
- 设置向导尚未完成。
关键保证:已有数据永远不会被覆盖。种子引擎在应用时会跳过已经存在的实体(默认onConflict: "skip"),因此 seed 文件具备幂等性——applySeed的实现注释明确写着"safe to run multiple times"(见 packages/core/src/seed/apply.ts)。
Seed 文件的位置约定
从 packages/core/src/cli/commands/seed.ts 的resolveSeedPath可以确认,seed 文件按以下优先级解析:
- 命令行位置参数(显式传入的路径);
- 约定路径
.emdash/seed.json; package.json中的emdash.seed字段指定的路径;- 项目中常见的
seed/seed.json(仓库模板即采用此布局)。
Seed 文件结构总览
{ "$schema": "https://emdashcms.com/seed.schema.json", "version": "1", "meta": { "name": "My Site", "description": "A description of this site", "author": "Author Name" }, "settings": { ... }, "collections": [ ... ], "taxonomies": [ ... ], "menus": [ ... ], "widgetAreas": [ ... ], "sections": [ ... ], "bylines": [ ... ], "content": { ... } }各顶层键的含义,与源码中SeedFile接口(packages/core/src/seed/types.ts)一一对应:
| 键 | 作用 | 可选性 |
|---|---|---|
$schema | JSON Schema 引用,用于编辑器校验 | 可选 |
version | 种子格式版本,当前固定为"1" | 必填 |
meta | 站点名称、描述、作者 | 可选 |
settings | 站点级设置(title、tagline等) | 可选 |
collections | 内容类型定义(每个集合对应一张ec_{slug}表) | 可选 |
taxonomies | 分类/标签体系 | 可选 |
menus | 导航菜单 | 可选 |
widgetAreas | 部件区域(侧边栏等) | 可选 |
sections | 可复用内容块(类似 WordPress 的 pattern/reusable block) | 可选 |
bylines | 署名作者档案(独立于用户账号) | 可选 |
content | 按集合组织的示例内容 | 可选 |
源码中的SeedFile还包含redirects(重定向规则)与defaultLocale(单语言项目默认 locale,见 packages/core/src/seed/types.ts)两个扩展键——defaultLocale是为export-seed→seed往返保留非en默认语言而设计的。
Collections:定义内容类型
Collections 定义内容类型,每个 collection 会成为一张数据库表ec_{slug}:
{ "slug": "posts", "label": "Posts", "labelSingular": "Post", "supports": ["drafts", "revisions", "search", "seo"], "commentsEnabled": true, "fields": [ ... ] }结合 SeedCollection 接口 与导出命令 export-seed.ts,collection 还支持以下可选项:
description/icon:后台展示用描述与图标;urlPattern:URL 模式,例如"/{slug}"(templates/starter/seed/seed.json 中的pages集合即使用此模式);routable: false:该集合不生成公开路由;titleField/dateField:指定标题字段与日期字段(在字段创建后单独写入校验,见 apply.ts);editLocking、hidden、sortOrder、group:后台编辑锁、隐藏、排序与分组。
Collection Supports
| Support | 描述 |
|---|---|
drafts | 草稿/发布工作流 |
revisions | 修订历史 |
search | 全文搜索索引 |
seo | 后台中的 SEO 元字段 |
Slug 规则
- 小写字母数字 + 下划线:
/^[a-z][a-z0-9_]*$/ - 最长 63 个字符
- 不能与保留 slug 冲突
Field Types:字段类型与存储映射
字段类型决定了数据库列类型与运行时数据结构:
| Type | 列类型 | 运行时形态 | 备注 |
|---|---|---|---|
string | TEXT | string | 单行文本 |
text | TEXT | string | 多行文本(textarea) |
number | REAL | number | 浮点数 |
integer | INTEGER | number | 整数 |
boolean | INTEGER | boolean | 存储为 0/1 |
datetime | TEXT | Date | 数据库中为 ISO 8601 字符串 |
image | TEXT | { id, src?, alt?, width?, height? } | 对象而非字符串 |
reference | TEXT | string(ID) | 引用其他条目 |
portableText | JSON | PortableTextBlock[] | 富文本结构化 JSON |
json | JSON | any | 任意 JSON 数据 |
易错点:
image字段的运行时值是对象。若在模板中写<img src={post.data.featured_image} />会渲染成[object Object],必须使用emdash/ui的<Image image={...} />组件(参见 SKILL.md 的 Common Gotchas)。
Field Definition
{ "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true }字段可具备的属性,与 SeedField 接口 一致:
slug(必填)—— 字段标识符label(必填)—— 后台显示标签type(必填)—— 上述类型之一required—— 校验是否必填searchable—— 是否纳入全文搜索索引- 另有可选:
unique、indexed、defaultValue、validation、widget(后台组件)、options(如 reference 字段的目标集合)
常见字段模式
博客文章:
"fields": [ { "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true }, { "slug": "featured_image", "label": "Featured Image", "type": "image" }, { "slug": "content", "label": "Content", "type": "portableText", "searchable": true }, { "slug": "excerpt", "label": "Excerpt", "type": "text" } ]作品集项目:
"fields": [ { "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true }, { "slug": "featured_image", "label": "Featured Image", "type": "image", "required": true }, { "slug": "client", "label": "Client", "type": "string" }, { "slug": "year", "label": "Year", "type": "string" }, { "slug": "summary", "label": "Summary", "type": "text", "searchable": true }, { "slug": "content", "label": "Content", "type": "portableText", "searchable": true }, { "slug": "gallery", "label": "Gallery", "type": "json" }, { "slug": "url", "label": "Project URL", "type": "string" } ]页面(极简):
"fields": [ { "slug": "title", "label": "Title", "type": "string", "required": true, "searchable": true }, { "slug": "content", "label": "Content", "type": "portableText", "searchable": true } ]Taxonomies:分类法
分类法(taxonomy)是挂接到集合上的标签/分类体系,类似 WordPress 的分类与标签:
{ "name": "category", "label": "Categories", "labelSingular": "Category", "hierarchical": true, "collections": ["posts"], "terms": [ { "slug": "development", "label": "Development" }, { "slug": "design", "label": "Design" } ] }hierarchical: true—— 树形结构(类似 WordPress 分类目录 categories)hierarchical: false—— 扁平列表(类似 WordPress 标签 tags)collections—— 该分类法应用于哪些集合terms—— 预定义的术语(term)列表
从导出实现(export-seed.ts)可以看到,taxonomy 还支持id、description(术语描述)、parent(父术语 slug)以及多语言下的locale/translationOf字段,导入时会先写锚点(anchor)再写翻译项以保证translationOf可解析。
注意:查询时 taxonomy 名称必须与 seed 中完全一致——定义了"name": "category",就必须用getTerm("category", slug)查询,写错名称只会得到空结果而不会报错(SKILL.md Gotcha #3)。
Menus:导航菜单
菜单由后台管理,seed 中可预置初始项:
{ "name": "primary", "label": "Primary Navigation", "items": [ { "type": "custom", "label": "Home", "url": "/" }, { "type": "custom", "label": "About", "url": "/pages/about" }, { "type": "custom", "label": "Posts", "url": "/posts" } ] }菜单项类型:
custom—— 任意 URL- 内容引用(
type非 custom 时,导出为collection+ref,见 buildMenuItemTree)在渲染时解析
菜单项还可选target: "_blank"、titleAttr、cssClasses、子菜单children(树形结构),多语言下支持locale与translationOf。
Widget Areas:部件区域
部件区域是命名区域,编辑者可以在其中添加可配置部件。完整的侧边栏示例:
{ "name": "sidebar", "label": "Sidebar", "description": "Widget area displayed on single post pages", "widgets": [ { "type": "component", "componentId": "core:search", "title": "Search" }, { "type": "component", "componentId": "core:categories", "title": "Categories" }, { "type": "component", "componentId": "core:tags", "title": "Tags" }, { "type": "component", "componentId": "core:recent-posts", "title": "Recent Posts", "settings": { "count": 5, "showDate": true } }, { "type": "component", "componentId": "core:archives", "title": "Archives", "settings": { "type": "monthly", "limit": 6 } }, { "type": "content", "title": "About", "content": [ { "_type": "block", "style": "normal", "children": [{ "_type": "span", "text": "Some rich text content." }] } ] } ] }说明:原文档中
settings键在导出的 seed 中对应源码里的props(组件属性),见 exportWidgetAreas。
部件类型
| 类型 | 描述 | 关键字段 |
|---|---|---|
content | 富文本(Portable Text) | content |
menu | 导航菜单 | menuName |
component | 核心或自定义组件 | componentId、settings |
核心部件组件
core:search—— 搜索表单core:categories—— 带计数的分类列表core:tags—— 标签云core:recent-posts—— 最新文章列表core:archives—— 月度归档链接
Sections:可复用内容块
可复用内容块,编辑者可通过编辑器中的/section斜杠命令插入:
{ "slug": "newsletter-signup", "title": "Newsletter Signup", "description": "A call-to-action block for newsletter subscriptions", "keywords": ["newsletter", "subscribe", "email", "cta"], "source": "theme", "content": [ { "_type": "block", "style": "h3", "children": [{ "_type": "span", "text": "Stay in the loop" }] }, { "_type": "block", "style": "normal", "children": [{ "_type": "span", "text": "Get notified when new posts are published." }] } ] }source: "theme"表示该 section 由主题提供;keywords用于编辑器的搜索联想。
Bylines:署名作者档案
署名档案独立于用户账号,用于内容的呈现性署名:
{ "id": "byline-editorial", "slug": "emdash-editorial", "displayName": "EmDash Editorial" }客座作者:
{ "id": "byline-guest", "slug": "guest-contributor", "displayName": "Guest Contributor", "isGuest": true }从 exportBylines 可见,导出时 byline 还可能携带bio、websiteUrl;内容条目通过bylines数组以{ "byline": "byline-editorial" }的形式引用(见下文 Content 示例)。SKILL.md 还提醒:查询内容时条目自带data.byline与data.bylines,一般无需单独调用getByline。
Settings:站点设置
"settings": { "title": "My Blog", "tagline": "Thoughts on building for the web" }可用键:title、tagline、logo、favicon、social、timezone、dateFormat。
实现上,设置以site:前缀存入 options 表(见 exportSettings 与 applySiteSettings),应用时会使用 compare-and-set 避免覆盖已有设置。
Content:示例内容
示例内容按集合 slug 组织:
"content": { "posts": [ { "id": "post-1", "slug": "hello-world", "status": "published", "data": { "title": "Hello World", "excerpt": "My first post.", "featured_image": { "$media": { "url": "https://images.unsplash.com/photo-xxx?w=1200&h=800&fit=crop", "alt": "Description of image", "filename": "hello-world.jpg" } }, "content": [ { "_type": "block", "style": "normal", "children": [{ "_type": "span", "text": "This is the body text." }] } ] }, "bylines": [ { "byline": "byline-editorial" } ], "taxonomies": { "category": ["development"], "tag": ["webdev", "opinion"] } } ], "pages": [ { "id": "about", "slug": "about", "status": "published", "data": { "title": "About", "content": [ { "_type": "block", "style": "normal", "children": [{ "_type": "span", "text": "About this site." }] } ] } } ] }仓库中的 templates/starter/seed/seed.json 提供了一个完整可运行的例子:包含posts(支持 drafts/revisions/search/seo)与pages(urlPattern: "/{slug}")两个集合、category与tag两个分类法、primary菜单、sidebar部件区域,以及一个欢迎文章(taxonomies引用general/starter/example)和 about 页面。
Content 中的媒体引用($media)
图片字段使用$media时,EmDash 会下载并存储该图片:
"featured_image": { "$media": { "url": "https://images.unsplash.com/photo-xxx?w=1200&h=800&fit=crop", "alt": "Description", "filename": "my-image.jpg" } }若不想下载、直接使用外部图片:
"featured_image": "https://images.unsplash.com/photo-xxx?w=1200"从源码看,applySeed支持skipMediaDownload选项(packages/core/src/seed/apply.ts),媒体下载还会经过ssrfSafeFetch/validateExternalUrl做 SSRF 防护(同文件 import 语句)。导出时(processDataForExport),图片字段会被反向转换为$media语法(export-seed.ts)。
Content 中的引用字段($ref)
使用$ref:id格式引用其他条目:
"author": "$ref:byline-editorial"导出时引用字段会被转换为$ref:${seedId}(export-seed.ts)。导入引擎按引用目标先后顺序处理集合,保证被引用的条目先写入(orderByReferenceTargets,export-seed.ts)。
Content 中的 Portable Text
portableText字段是 block 数组:
[ { "_type": "block", "style": "normal", "children": [{ "_type": "span", "text": "A paragraph." }] }, { "_type": "block", "style": "h2", "children": [{ "_type": "span", "text": "A heading" }] }, { "_type": "block", "style": "blockquote", "children": [{ "_type": "span", "text": "A quote." }] } ]内联标记(粗体、斜体、链接):
{ "_type": "block", "style": "normal", "children": [ { "_type": "span", "text": "This is " }, { "_type": "span", "text": "bold", "marks": ["strong"] }, { "_type": "span", "text": " and " }, { "_type": "span", "text": "italic", "marks": ["em"] } ] }块样式:normal、h1–h6、blockquote。
草稿内容
设置"status": "draft"即可创建未发布内容:
{ "id": "post-draft", "slug": "work-in-progress", "status": "draft", "data": { ... } }应用 Seeds:校验与常见错误
seed 的加载路径依次为.emdash/seed.json、package.json#emdash.seed或seed/seed.json(以及 CLI 位置参数),被内联进构建,并在数据库为空且设置向导未完成时于首次请求应用。已有数据不会被覆盖。
校验在应用时执行(validateSeed,见 packages/core/src/seed/apply.ts),常见错误:
- 图片字段使用原始 URL(应使用
$media) - 引用字段使用原始 ID(应使用
$ref:id) - PortableText 不是数组或缺少
_type - 类型不匹配(string vs number 等)
若 seed 无效,首次请求会失败并记录错误。修复后需重启开发服务器。
CLI 也提供了独立的校验/应用入口:emdash seed支持--validate(仅校验不应用)、--no-content(跳过示例内容)、--on-conflict(skip/update/error,默认 skip)与--uploads-dir等参数(packages/core/src/cli/commands/seed.ts)。另外要注意:从旧版本数据库导出前,需先执行emdash migrate,否则导出会因 pending migrations 而失败(export-seed.ts)。
导出 Seeds:将已有数据库变为可复现的 seed
npx emdash export-seed # 仅 Schema npx emdash export-seed --with-content # Schema + 全部内容 npx emdash export-seed --with-content=posts,pages # 指定集合export-seed命令(packages/core/src/cli/commands/export-seed.ts)从当前数据库导出完整 schema(settings、collections/fields、taxonomies、menus、widget areas、bylines),--with-content时附带内容。该命令是幂等往返的基石:导出的 seed 可再次通过emdash seed或首次启动自动应用到新数据库,实现站点结构的版本化与可复现部署。
导出时还应注意:
- 输出写入 stdout,诊断信息写入 stderr,因此
emdash export-seed > seed.json重定向是安全的; - 命令自动检测数据库是否为多语言(多 locale)项目,并在导出中自描述
defaultLocale,保证非en单语言项目往返不丢失(export-seed.ts); - 集合按引用依赖排序输出,被引用的集合排在前面,确保重新导入时
$ref可解析(orderByReferenceTargets)。
小结:一条从空白站点到可复现内容的完整链路
- 设计:在
seed/seed.json中声明 collections、fields、taxonomies、menus、widgetAreas、sections、bylines 与示例 content; - 启动:
pnpm dev后首次请求自动迁移数据库并应用 seed(emdash migrate手动迁移亦可); - 校验:
emdash seed --validate或依赖应用时校验,遵循$media、$ref:id、Portable Text 结构规范; - 版本化:
emdash export-seed --with-content把线上/开发数据库还原成 seed 文件,实现 schema 与演示数据的迁移与备份。
进一步阅读:完整的站点搭建流程(astro.config、live.config、查询渲染、站点特性)可参考 building-emdash-site SKILL,以及同目录下的 configuration.md、querying-and-rendering.md 与 site-features.md。
【免费下载链接】emdashEmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress项目地址: https://gitcode.com/gh_mirrors/emdas/emdash
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考