news 2026/9/24 16:32:43

EmDash Seed 文件完全指南:从 Schema 定义到数据导出的实战手册

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
EmDash Seed 文件完全指南:从 Schema 定义到数据导出的实战手册

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 文件按以下优先级解析:

  1. 命令行位置参数(显式传入的路径);
  2. 约定路径.emdash/seed.json
  3. package.json中的emdash.seed字段指定的路径;
  4. 项目中常见的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)一一对应:

作用可选性
$schemaJSON Schema 引用,用于编辑器校验可选
version种子格式版本,当前固定为"1"必填
meta站点名称、描述、作者可选
settings站点级设置(titletagline等)可选
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-seedseed往返保留非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);
  • editLockinghiddensortOrdergroup:后台编辑锁、隐藏、排序与分组。

Collection Supports

Support描述
drafts草稿/发布工作流
revisions修订历史
search全文搜索索引
seo后台中的 SEO 元字段

Slug 规则

  • 小写字母数字 + 下划线:/^[a-z][a-z0-9_]*$/
  • 最长 63 个字符
  • 不能与保留 slug 冲突

Field Types:字段类型与存储映射

字段类型决定了数据库列类型与运行时数据结构:

Type列类型运行时形态备注
stringTEXTstring单行文本
textTEXTstring多行文本(textarea)
numberREALnumber浮点数
integerINTEGERnumber整数
booleanINTEGERboolean存储为 0/1
datetimeTEXTDate数据库中为 ISO 8601 字符串
imageTEXT{ id, src?, alt?, width?, height? }对象而非字符串
referenceTEXTstring(ID)引用其他条目
portableTextJSONPortableTextBlock[]富文本结构化 JSON
jsonJSONany任意 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—— 是否纳入全文搜索索引
  • 另有可选:uniqueindexeddefaultValuevalidationwidget(后台组件)、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 还支持iddescription(术语描述)、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"titleAttrcssClasses、子菜单children(树形结构),多语言下支持localetranslationOf

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核心或自定义组件componentIdsettings

核心部件组件

  • 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 还可能携带biowebsiteUrl;内容条目通过bylines数组以{ "byline": "byline-editorial" }的形式引用(见下文 Content 示例)。SKILL.md 还提醒:查询内容时条目自带data.bylinedata.bylines,一般无需单独调用getByline

Settings:站点设置

"settings": { "title": "My Blog", "tagline": "Thoughts on building for the web" }

可用键:titletaglinelogofaviconsocialtimezonedateFormat

实现上,设置以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)与pagesurlPattern: "/{slug}")两个集合、categorytag两个分类法、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"] } ] }

块样式:normalh1h6blockquote

草稿内容

设置"status": "draft"即可创建未发布内容:

{ "id": "post-draft", "slug": "work-in-progress", "status": "draft", "data": { ... } }

应用 Seeds:校验与常见错误

seed 的加载路径依次为.emdash/seed.jsonpackage.json#emdash.seedseed/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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 16:32:15

热门题目分类+清单

题单来源&#xff1a;https://leetcode.cn/studyplan/top-100-liked/ 类别题目解题思路哈希表1.两数之和&#xff08;简单&#xff09;49.字母异位词分组&#xff08;中等&#xff09;128.最长连续序列&#xff08;中等&#xff09;①以每个item为开端且item-1不在set里面&…

作者头像 李华
网站建设 2026/9/24 16:23:42

Wand-Enhancer 上手记:一次本地补丁,免费解锁 WeMod 专业版

Wand-Enhancer 上手记&#xff1a;一次本地补丁&#xff0c;免费解锁 WeMod 专业版 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer Wand-Enhancer …

作者头像 李华