Seed 文件

本页内容

Seed 文件描述 EmDash 站点的初始 schema 与可选示例数据。当前模板将其放在 seed/seed.json,并用 package.json#emdash.seed 指向它。

EmDash 在构建时嵌入 seed。它用于首次配置与显式 seed 命令,而不是每次部署都运行的迁移。

文件发现

Astro 集成按以下顺序查找 seed:

  1. .emdash/seed.json。
  2. package.json#emdash.seed 中的路径。
  3. seed/seed.json。
  4. 没有用户 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": []
}
PropertyRequiredPurpose
$schemaNo编辑器 schema URL
versionYesSeed 格式;唯一接受的值是 "1"
defaultLocaleNo省略 locale 的带 locale 行所用的 locale;默认为运行时配置,然后是 en
metaNo配置期间显示的描述性名称、说明与作者
settingsNo部分站点设置
blockTypesNoblocks 字段使用的版本化定义
collectionsNoCollection 与字段定义
taxonomiesNo分类法定义与可选术语
bylinesNo可选的署名展示资料
contentNo按 collection slug 分组的示例条目
menusNo菜单与嵌套项
redirectsNo本地重定向规则
widgetAreasNoWidget 区域与 widget
sectionsNo可复用的 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 属性

PropertyTypeBehavior
slugstring必需的数据库与 API 名称;以小写字母开头,包含小写字母、数字和下划线
labelstring必需的复数 UI 标签
labelSingularstring可选的单数 UI 标签
descriptionstring可选的管理说明
iconstring可选的图标名称
admin.listColumnsstring[]内容列表中显示的最多四个已声明字段 slug
supportsstring[]drafts、revisions、preview、scheduling、search 和 seo 中的任意项
urlPatternstring如 /posts/{slug} 的公开模式
routableboolean已发布条目是否需要 slug;默认 true
hiddenboolean隐藏生成的侧边栏链接与仪表盘快捷操作;collection 仍可通过 URL 与 API 访问
sortOrdernumber管理侧边栏中的显式位置;已排序的 collection 先出现,按升序
groupstring管理侧边栏文件夹;相同 group 的 collection 共享一个可折叠条目
commentsEnabledboolean为 collection 启用评论
editLockingboolean启用编辑锁定;默认 true
titleFieldstring用于内容列表标题的字段
dateFieldstring用于内容列表日期的 datetime 字段
fieldsSeedField[]必需的字段定义

sortOrder 属于 collection,并控制侧边栏顺序。SeedField 没有 sortOrder 属性。字段按其数组顺序创建。

字段属性

PropertyTypePurpose
slugstring符合 collection slug 模式的必需字段名
labelstring必需的 UI 标签
typeFieldType必需的存储字段类型
requiredboolean拒绝空的必填值
uniqueboolean添加唯一性约束
searchableboolean将字段纳入 collection 搜索
indexedboolean为支持的标量类型添加查询索引
translatableboolean按 locale 存储值;默认 true。false 在各翻译间共享一个值
defaultValueany省略字段时的初始值
validationobject生成的内容 schema 使用的校验规则
widgetstring管理字段 widget 覆盖
optionsobjectWidget 特定选项

支持的字段类型为:

  • 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 在字段类型支持的地方识别这些规则:

RuleUsed by
min、max数字字段
minLength、maxLength、pattern字符串形字段
optionsselect 和 multiSelect
subFields、minItems、maxItemsrepeater
allowedTypes、minItems、maxItemsblocks
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
		}
	]
}
PropertyTypeRequiredDescription
slugstringYesreference 字段寻址它的唯一名称
parentCollectionstringYes父端的 collection
childCollectionstringYes子端的 collection
parentLabelstringYes从子端看父端角色的名称
parentLabelSingularstringNoparentLabel 的单数形式
childLabelstringYes从父端看子端角色的名称
childLabelSingularstringNochildLabel 的单数形式
maxChildrenPerParentnumber | nullNo一个父端可链接的子端数量(null:无限制)
maxParentsPerChildnumber | nullNo一个子端可链接的父端数量(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" }
        ]
      }
    ]
  }
}
PropertyRequiredBehavior
idYesSeed 本地引用 ID
slug可路由 collection公开 slug 与冲突键
statusNopublished 或 draft;默认 published
dataYes按 collection 字段 slug 键控的值
taxonomiesNo分类法名称到术语 slug 数组
bylinesNo引用根 byline ID 的有序署名
localeNoBCP 47 locale;通过 defaultLocale 默认
translationOfNo同一 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

OptionDefaultCurrent behavior
includeContentfalse包含内容条目、byline 和分类法术语
onConflict"skip"对支持的实体冲突为 "skip"、"update" 或 "error"
storagenone下载 $media URL 所需的存储适配器
skipMediaDownloadfalse将 $media URL 保留为外部媒体值
mediaBasePathnone存在于公开类型中,但当前应用引擎不使用

编程应用默认将 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。

下一步