Seed 文件描述 EmDash 站点的初始 schema 与可选示例数据。当前模板将其放在 seed/seed.json,并用 package.json#emdash.seed 指向它。
EmDash 在构建时嵌入 seed。它用于首次配置与显式 seed 命令,而不是每次部署都运行的迁移。
文件发现
Astro 集成按以下顺序查找 seed:
.emdash/seed.json。package.json#emdash.seed中的路径。seed/seed.json。- 没有用户 seed 时的内置默认 seed。
以下包字段选择模板的约定路径:
{
"emdash": {
"seed": "seed/seed.json"
}
}
根形状
以下示例包含每个根属性:
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"defaultLocale": "en",
"meta": {
"name": "Publication",
"description": "A publication seed",
"author": "Example Studio"
},
"settings": {},
"blockTypes": [],
"collections": [],
"relations": [],
"taxonomies": [],
"bylines": [],
"content": {},
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": []
}
| Property | Required | Purpose |
|---|---|---|
$schema | No | 编辑器 schema URL |
version | Yes | Seed 格式;唯一接受的值是 "1" |
defaultLocale | No | 省略 locale 的带 locale 行所用的 locale;默认为运行时配置,然后是 en |
meta | No | 配置期间显示的描述性名称、说明与作者 |
settings | No | 部分站点设置 |
blockTypes | No | blocks 字段使用的版本化定义 |
collections | No | Collection 与字段定义 |
taxonomies | No | 分类法定义与可选术语 |
bylines | No | 可选的署名展示资料 |
content | No | 按 collection slug 分组的示例条目 |
menus | No | 菜单与嵌套项 |
redirects | No | 本地重定向规则 |
widgetAreas | No | Widget 区域与 widget |
sections | No | 可复用的 Portable Text section |
defaultLocale 必须是非空字符串,且首尾无空白。
Settings
settings 是部分站点设置对象。常见属性包括 title、tagline、logo、favicon、url、postsPerPage、dateFormat、timezone、social 和 seo。
配置向导允许管理员覆盖已 seed 的标题与标语。默认 onConflict: "skip" 在重新应用 seed 时保留这些值,并补全仍缺失的已提供设置。
{
"version": "1",
"settings": {
"title": "Field Notes",
"tagline": "Reports from the team",
"postsPerPage": 12,
"dateFormat": "MMMM d, yyyy",
"timezone": "Europe/London"
}
}
区块类型
blockTypes 定义 collection blocks 字段使用的版本化形状。EmDash 在 collection 之前应用这些定义,因此字段可以在 validation.allowedTypes 中命名它们。
以下 seed 为已存储内容与修订保留版本 1,同时让版本 2 成为新区块的活动版本:
{
"version": "1",
"blockTypes": [
{
"slug": "hero",
"label": "Hero",
"category": "Layout",
"currentVersion": 2,
"versions": [
{
"version": 1,
"fields": [
{ "slug": "heading", "label": "Heading", "type": "string", "required": true }
]
},
{
"version": 2,
"fields": [
{ "slug": "title", "label": "Title", "type": "string", "required": true },
{ "slug": "image", "label": "Image", "type": "image" }
]
}
]
}
],
"collections": [
{
"slug": "pages",
"label": "Pages",
"fields": [
{
"slug": "layout",
"label": "Layout",
"type": "blocks",
"validation": { "allowedTypes": ["hero"], "maxItems": 20 }
}
]
}
]
}
版本号是从 1 开始的连续正整数。currentVersion 必须命名已声明的版本。导出并重新应用 seed 会保留确切的版本号与活动指针;EmDash 不会重新编号。
使用 onConflict: "update" 时,仅当新定义兼容时,seed 才能更改已存储版本。对不兼容定义重用现有编号会以 BLOCK_TYPE_VERSION_CONFLICT 失败。对不兼容定义请添加新的版本号。
已存储的区块值包含 _type、_version 和 _key。当 seed 内容指向保留版本时请提供这些属性。运行时会在新写入的区块省略它们时分配活动版本与键。
Collections
Collection 需要 slug、label 和 fields:
{
"version": "1",
"collections": [
{
"slug": "posts",
"label": "Posts",
"labelSingular": "Post",
"description": "Published articles",
"supports": ["drafts", "revisions", "scheduling", "search", "seo"],
"urlPattern": "/posts/{slug}",
"routable": true,
"commentsEnabled": true,
"editLocking": true,
"titleField": "title",
"dateField": "event_date",
"admin": {
"listColumns": ["event_date"]
},
"fields": [
{ "slug": "title", "label": "Title", "type": "string", "required": true },
{ "slug": "event_date", "label": "Event date", "type": "datetime", "indexed": true },
{ "slug": "content", "label": "Content", "type": "portableText" }
]
}
]
}
Collection 属性
| Property | Type | Behavior |
|---|---|---|
slug | string | 必需的数据库与 API 名称;以小写字母开头,包含小写字母、数字和下划线 |
label | string | 必需的复数 UI 标签 |
labelSingular | string | 可选的单数 UI 标签 |
description | string | 可选的管理说明 |
icon | string | 可选的图标名称 |
admin.listColumns | string[] | 内容列表中显示的最多四个已声明字段 slug |
supports | string[] | drafts、revisions、preview、scheduling、search 和 seo 中的任意项 |
urlPattern | string | 如 /posts/{slug} 的公开模式 |
routable | boolean | 已发布条目是否需要 slug;默认 true |
hidden | boolean | 隐藏生成的侧边栏链接与仪表盘快捷操作;collection 仍可通过 URL 与 API 访问 |
sortOrder | number | 管理侧边栏中的显式位置;已排序的 collection 先出现,按升序 |
group | string | 管理侧边栏文件夹;相同 group 的 collection 共享一个可折叠条目 |
commentsEnabled | boolean | 为 collection 启用评论 |
editLocking | boolean | 启用编辑锁定;默认 true |
titleField | string | 用于内容列表标题的字段 |
dateField | string | 用于内容列表日期的 datetime 字段 |
fields | SeedField[] | 必需的字段定义 |
sortOrder 属于 collection,并控制侧边栏顺序。SeedField 没有 sortOrder 属性。字段按其数组顺序创建。
字段属性
| Property | Type | Purpose |
|---|---|---|
slug | string | 符合 collection slug 模式的必需字段名 |
label | string | 必需的 UI 标签 |
type | FieldType | 必需的存储字段类型 |
required | boolean | 拒绝空的必填值 |
unique | boolean | 添加唯一性约束 |
searchable | boolean | 将字段纳入 collection 搜索 |
indexed | boolean | 为支持的标量类型添加查询索引 |
translatable | boolean | 按 locale 存储值;默认 true。false 在各翻译间共享一个值 |
defaultValue | any | 省略字段时的初始值 |
validation | object | 生成的内容 schema 使用的校验规则 |
widget | string | 管理字段 widget 覆盖 |
options | object | Widget 特定选项 |
支持的字段类型为:
string、text、url和slug。number、integer和boolean。datetime。select和multiSelect。portableText、json和repeater。blocks。image、file和reference。
reference 字段不在 collection 表中存储任何内容。其链接存在于绑定到的关系中;
参见 Relations。
只有 string、url、number、integer、boolean、datetime、select、reference 和 slug 可以设置 indexed: true。在 reference 字段上,该标志仅在字段尚无关系时适用,因为已绑定字段没有可索引的列。
字段校验
生成的 collection schema 在字段类型支持的地方识别这些规则:
| Rule | Used by |
|---|---|
min、max | 数字字段 |
minLength、maxLength、pattern | 字符串形字段 |
options | select 和 multiSelect |
subFields、minItems、maxItems | repeater |
allowedTypes、minItems、maxItems | blocks |
allowedMimeTypes | 媒体字段 |
blocks 字段上的 retiredTypes 由服务端处理。从 allowedTypes 移除一个 slug 会将其退役,使现有已存储区块保持有效,而新区块不能使用它。
validateSeed() 不会深入检查 validation 或 options 中的每条规则。因此无效规则可能通过 seed 校验,并在之后创建 collection schema 或写入内容时失败。
Relations
关系连接两个 collection,并拥有其条目之间的链接。reference 字段绑定到
其中一个,并从一端查看其链接。以下 seed 声明 posts 与
authors 之间的关系,允许每篇文章一位作者:
{
"relations": [
{
"slug": "post_authors",
"parentCollection": "posts",
"childCollection": "authors",
"parentLabel": "Posts",
"parentLabelSingular": "Post",
"childLabel": "Authors",
"childLabelSingular": "Author",
"maxChildrenPerParent": 1
}
]
}
| Property | Type | Required | Description |
|---|---|---|---|
slug | string | Yes | reference 字段寻址它的唯一名称 |
parentCollection | string | Yes | 父端的 collection |
childCollection | string | Yes | 子端的 collection |
parentLabel | string | Yes | 从子端看父端角色的名称 |
parentLabelSingular | string | No | parentLabel 的单数形式 |
childLabel | string | Yes | 从父端看子端角色的名称 |
childLabelSingular | string | No | childLabel 的单数形式 |
maxChildrenPerParent | number | null | No | 一个父端可链接的子端数量(null:无限制) |
maxParentsPerChild | number | null | No | 一个子端可链接的父端数量(null:无限制) |
collections 中的 reference 字段命名它所绑定的关系:
{
"slug": "author",
"label": "Author",
"type": "reference",
"validation": { "relation": "post_authors" }
}
字段也可以改为命名 targetCollection 并让系统为其创建关系,这是
只有一个 collection 能看到的链接的最短路径。当两个
collection 都需要看到它,或要设置其标签与限制时,请在 relations 中声明关系。
reference 记录了两种形式以及各自
接受的校验键。
关系的两个 collection 一旦存在即固定:命名不同 collection 的 seed 会失败,而不是
让其持有的链接指向不再是一端的 collection。标签与
限制在以 onConflict: "update" 应用 seed 时更新。
分类法
分类法定义标识其目标 collection。术语是示例数据,仅在 includeContent 为 true 时应用。
{
"version": "1",
"taxonomies": [
{
"name": "category",
"label": "Categories",
"labelSingular": "Category",
"hierarchical": true,
"collections": ["posts"],
"terms": [
{ "slug": "engineering", "label": "Engineering" },
{ "slug": "platform", "label": "Platform", "parent": "engineering" }
]
}
]
}
分类法可以带有 seed 本地的 id、locale 和 translationOf。术语也可以带有这些属性。translationOf 指向另一个 seed 本地 ID。术语必须排在它所翻译的术语之后。同一 name 的分类法条目可以按任意顺序出现,因为声明分类法形状的条目会在其翻译之前应用。
hierarchical 和 collections 由分类法的每个 locale 共享,因此其 translationOf 指向同名条目的分类法条目可以省略它们。应用引擎会沿着同名条目跟随 translationOf,并从最后一个取它们。当翻译声明的值与取到的不同,或两个为某分类法声明它们的条目不一致时,校验会发出警告。现有分类法会保留其值,除非没有 translationOf 的条目替换它们。这在 onConflict: "update" 时发生;当条目的 locale 拥有 Conflict behavior 中描述的未修改内置 category 或 tag 定义,或该内置定义是该分类法唯一的定义时,在任何模式下都会发生。导出只把它们写到翻译所指向的条目上。
术语的 parent 是同一 locale 中父术语的 slug。非层级分类法上的 parent 会产生警告并被忽略。带有 translationOf 且没有 parent 的术语会取它所翻译术语的 parent。
Bylines
根级 bylines 定义署名展示。它们是示例数据,需要 includeContent: true。
{
"version": "1",
"bylines": [
{
"id": "byline-editor",
"slug": "alex-editor",
"displayName": "Alex Editor",
"isGuest": true
}
]
}
id 是 seed 本地的,由内容署名使用。可选属性有 bio、websiteUrl、isGuest 和 avatar。
Byline 头像指向已配置存储中已存在的文件:
{
"id": "byline-editor",
"slug": "alex-editor",
"displayName": "Alex Editor",
"avatar": {
"storageKey": "avatars/alex.jpg",
"filename": "alex.jpg",
"mimeType": "image/jpeg",
"alt": "Alex Editor",
"width": 400,
"height": 400
}
}
Byline 头像 seed 会为存储键创建或复用媒体行。它不会上传或下载文件。
Content
content 按 collection slug 分组条目。每个条目需要一个 seed 本地 id 和一个 data 对象。可路由 collection 还需要非空 slug。
{
"version": "1",
"content": {
"posts": [
{
"id": "post-welcome",
"slug": "welcome",
"status": "published",
"data": {
"title": "Welcome",
"content": []
},
"taxonomies": {
"category": ["engineering"]
},
"bylines": [
{ "byline": "byline-editor", "roleLabel": "Editor" }
]
}
]
}
}
| Property | Required | Behavior |
|---|---|---|
id | Yes | Seed 本地引用 ID |
slug | 可路由 collection | 公开 slug 与冲突键 |
status | No | published 或 draft;默认 published |
data | Yes | 按 collection 字段 slug 键控的值 |
taxonomies | No | 分类法名称到术语 slug 数组 |
bylines | No | 引用根 byline ID 的有序署名 |
locale | No | BCP 47 locale;通过 defaultLocale 默认 |
translationOf | No | 同一 collection 中的 seed 本地内容 ID |
对于可路由条目,seed 本地 id 不是其数据库身份。EmDash 会创建数据库 ID,并记录映射供后续引用。对于 routable: false 的 collection 中无 slug 的条目,EmDash 使用 seed 的 id 作为存储 ID,使重新应用保持幂等。
读取时,entry.id 是 Astro 路由标识符,通常是 slug。存储的数据库 ID 是 entry.data.id。
内容引用
在 data 中使用 $ref: 字符串,将 seed 本地内容 ID 替换为已创建的数据库 ID:
{
"id": "event-opening",
"slug": "opening-night",
"data": {
"title": "Opening night",
"venue": "$ref:venue-main-hall"
}
}
引用目标必须足够早出现,以便进入应用引擎的 ID 映射。未解析的 $ref: 值会保留为原始字面字符串;validateSeed() 不会拒绝它。
对于 reference 字段,从其关系的父端声明链接。两端看到同一 组链接,因此子 collection 上的字段会重复断言父端已有的链接。
媒体引用
在内容数据中使用 $media 以下载 URL、用提供的存储适配器上传、创建媒体行,并将对象替换为媒体字段值:
{
"featured_image": {
"$media": {
"url": "https://example.com/images/launch.jpg",
"filename": "launch.jpg",
"alt": "A product launch on stage",
"caption": "Launch event"
}
}
}
在 Portable Text image 块或 gallery 图片中,asset 中的 $media 会变成媒体引用(_type: "reference"、_ref、url、provider),并在缺失时用媒体的替代文本与尺寸填充图片的 alt、width 和 height。
在一次应用调用内,对同一 URL 的重复引用会复用已解析的媒体值。Seed 媒体引用不接受本地 file 属性。mediaBasePath 仍在公开类型 SeedApplyOptions 中,但当前应用引擎不读取它。
未提供存储适配器时,$media 引用会被跳过并解析为 null。使用 skipMediaDownload: true 时,它们成为外部媒体值,且不需要存储适配器。
菜单
菜单是结构数据,即使 includeContent 为 false 也会应用:
{
"version": "1",
"menus": [
{
"name": "primary",
"label": "Primary navigation",
"items": [
{
"type": "page",
"label": "About",
"ref": "page-about",
"collection": "pages"
},
{
"type": "custom",
"label": "Contact",
"url": "/contact",
"target": "_self"
}
]
}
]
}
允许的项类型为 custom、page、post、taxonomy 和 collection。custom 需要 url;page 和 post 需要 ref。项可以包含 id、translationOf、label、collection、titleAttr、cssClasses、locale、target 和嵌套的 children。
对于 page 和 post,ref 命名 seed 内容 ID。缺失目标会产生校验警告,并得到没有已解析内容引用的菜单项。每当应用该菜单时,现有菜单项都会被删除并重建,与 onConflict 无关。
重定向
重定向需要本地源路径与目标路径:
{
"version": "1",
"redirects": [
{
"source": "/old-path",
"destination": "/new-path",
"type": 308,
"enabled": true,
"groupName": "WordPress migration"
}
]
}
两条路径都必须以 / 开头。协议相对 URL、路径遍历段和换行会被拒绝。允许的状态码为 301、302、307 和 308。
Widget 区域
Widget 区域包含 content、menu 或 component widget:
{
"version": "1",
"widgetAreas": [
{
"name": "sidebar",
"label": "Sidebar",
"widgets": [
{
"type": "menu",
"title": "Explore",
"menuName": "primary"
},
{
"type": "component",
"title": "Recent posts",
"componentId": "core:recent-posts",
"props": { "count": 5 }
}
]
}
]
}
内容 widget 将 Portable Text 存储在 content 中。菜单 widget 需要 menuName。组件 widget 需要 componentId,并可传递 props。SeedWidget 上没有 settings 属性。
每当应用该区域时,区域中的现有 widget 都会被删除并重建,与 onConflict 无关。
Sections
Section 包含可复用的 Portable Text 内容:
{
"version": "1",
"sections": [
{
"slug": "newsletter-signup",
"title": "Newsletter signup",
"description": "Signup call to action",
"keywords": ["newsletter", "email"],
"source": "theme",
"content": []
}
]
}
Section slug 包含小写字母、数字和连字符。source 为 theme、user 或 import;seed 默认将其设为 theme。主题 section 不能在管理中删除。Section 是结构性的,即使 includeContent 为 false 也会应用。
本地化
defaultLocale 为分类法、术语、菜单、菜单项和内容填充缺失的 locale。存在活动运行时 i18n 配置时,以后者为准。
本地化的分类法、术语、菜单、菜单项和内容使用 seed 本地的 id 与 translationOf 字段。将源项放在翻译之前,以便应用引擎解析其翻译组。翻译后的内容条目必须设置 locale,且其 translationOf 必须命名同一 collection 中的另一条目。
以编程方式应用 seed
applySeed() 和 validateSeed() 从 emdash/seed 导出。以下辅助函数在应用前先校验:
import {
applySeed,
validateSeed,
type SeedApplyOptions,
type SeedFile,
} from "emdash/seed";
type SeedDatabase = Parameters<typeof applySeed>[0];
export async function applyProjectSeed(
db: SeedDatabase,
seed: SeedFile,
options: SeedApplyOptions,
) {
const validation = validateSeed(seed);
if (!validation.valid) {
throw new Error(validation.errors.join("\n"));
}
return applySeed(db, seed, options);
}
SeedApplyOptions
| Option | Default | Current behavior |
|---|---|---|
includeContent | false | 包含内容条目、byline 和分类法术语 |
onConflict | "skip" | 对支持的实体冲突为 "skip"、"update" 或 "error" |
storage | none | 下载 $media URL 所需的存储适配器 |
skipMediaDownload | false | 将 $media URL 保留为外部媒体值 |
mediaBasePath | none | 存在于公开类型中,但当前应用引擎不使用 |
编程应用默认将 includeContent 设为 false。配置向导传递管理员的示例内容选择。CLI emdash seed 默认包含内容,除非设置了 --no-content。
冲突行为
onConflict 不是整个 seed 的事务策略:
- Collection、字段、byline、内容、重定向和 section 支持 skip、update 和 error 行为。
- 分类法定义与术语遵循适用的冲突模式。例外是每个新数据库开始时的内置
category和tag定义:在站点修改它们之前,声明它们的 seed 在任何模式下都会替换它们。 - Settings 使用按键的冲突处理。
skip创建缺失设置并保留现有值。update覆盖每个已提供设置。error在第一个已存在设置处停止;按 seed 顺序更早创建的设置仍保持已应用。 - 现有菜单保留其菜单行,但替换所有项。
- 现有 widget 区域保留其区域行,但替换所有 widget。
- 内容冲突按 collection、slug 和 locale 匹配。不可路由 collection 中无 slug 的条目按其 seed ID 匹配。
使用 onConflict: "update" 时,内容数据会被替换,其 byline 与分类法分配会与 seed 对齐。在对现有站点使用之前,先在副本上测试 update 模式。
applySeed() 返回 collection、字段、分类法、byline、菜单、重定向、widget 区域、section、设置、内容和媒体的计数器。
校验行为
validateSeed() 返回 { valid, errors, warnings }。applySeed() 会调用它,并在存在错误时抛出 Invalid seed file。
校验器检查应用引擎所需的结构规则,包括:
- 版本与非空
defaultLocale。 - Collection、字段、分类法、术语、菜单、widget 区域、section、byline 和内容的容器形状。
- 必需的名称、标签、ID、slug,以及支持的字段或 widget 类型。
- 相关范围内的重复标识符。
- 已索引字段类型与
admin.listColumns引用。 - 分类法父级、内容翻译、内容 byline 引用,以及菜单项要求。
- 安全的本地重定向路径与状态码。
某些条件是警告而非错误。例如:没有 collection 的分类法、扁平分类法上的 parent,或 seed 中缺失的菜单内容引用。
校验器不证明所有 data 值都符合其 collection 字段。它也不深入校验站点设置、字段 validation、字段 options、任意 Portable Text 块、组件 widget props、内容数据中的 $ref: 目标,或 $media 的远程可用性。有效 seed 仍可能在 schema 创建、内容校验、网络下载或存储上传期间失败。
使用 $schema URL 获得编辑器辅助,并在应用前运行可执行校验器:
npx emdash seed seed/seed.json --validate
CLI 命令
以显式冲突行为将 seed 应用到本地 SQLite 数据库:
npx emdash seed seed/seed.json --database ./data.db --on-conflict skip
将当前本地 schema 与全部内容导出回模板路径:
npx emdash export-seed --database ./data.db --with-content=all > seed/seed.json
export-seed 直接处理本地 SQLite 文件。对于已部署的 D1 数据库,先将其导出到本地文件。在提交结果之前,请检查导出的设置、内容与媒体引用。要使导出的媒体可在另一站点导入,请传入 --media-base-url;参见 Media URLs。
下一步
- Create a theme:在可复用的 Astro 模板中使用 seed。
- Schema evolution:更新现有已部署站点的 schema。
- CLI reference:数据库与导出选项。