种子文件格式

本页内容

种子文件是引导 EmDash 站点的 JSON 文档。它们定义集合、字段、分类法、菜单、重定向、小部件区域、站点设置和可选的示例内容。

根结构

种子文件具有以下顶层结构:

{
	"$schema": "https://emdashcms.com/seed.schema.json",
	"version": "1",
	"meta": {},
	"settings": {},
	"collections": [],
	"taxonomies": [],
	"bylines": [],
	"menus": [],
	"redirects": [],
	"widgetAreas": [],
	"sections": [],
	"content": {}
}
字段类型必需描述
$schemastring用于编辑器验证的 JSON Schema URL
version"1"种子格式版本
metaobject关于种子的元数据
settingsobject站点设置
collectionsarray集合定义
taxonomiesarray分类法定义
bylinesarray署名配置文件定义
menusarray导航菜单
redirectsarray重定向规则
widgetAreasarray小部件区域定义
sectionsarray可复用的内容块
contentobject示例内容条目

元数据

meta 对象包含关于种子的可选描述性元数据:

{
	"meta": {
		"name": "Blog Starter",
		"description": "A simple blog with posts, pages, and categories",
		"author": "EmDash"
	}
}

设置

settings 对象包含站点范围的配置值:

{
	"settings": {
		"title": "My Site",
		"tagline": "A modern CMS",
		"postsPerPage": 10,
		"dateFormat": "MMMM d, yyyy"
	}
}

设置以 site: 前缀应用到 options 表。如果提供了种子文件中的 titletagline,设置向导将预填充它们,允许用户在初始设置期间覆盖。

集合

每个集合定义在数据库中创建一个内容类型:

{
	"collections": [
		{
			"slug": "posts",
			"label": "Posts",
			"labelSingular": "Post",
			"description": "Blog posts",
			"icon": "file-text",
			"supports": ["drafts", "revisions"],
			"fields": [
				{
					"slug": "title",
					"label": "Title",
					"type": "string",
					"required": true
				},
				{
					"slug": "content",
					"label": "Content",
					"type": "portableText"
				},
				{
					"slug": "featured_image",
					"label": "Featured Image",
					"type": "image"
				}
			]
		}
	]
}

集合属性

属性类型必需描述
slugstringURL 安全标识符(小写字母、下划线)
labelstring复数显示名称
labelSingularstring单数显示名称
descriptionstring管理 UI 描述
iconstringLucide 图标名称
supportsarray特性:"drafts""revisions"
hiddenboolean从管理侧边栏中隐藏集合条目
sortOrdernumber在管理侧边栏中的位置(升序)
fieldsarray字段定义

字段属性

属性类型必需描述
slugstring列名(小写字母、下划线)
labelstring显示名称
typestring字段类型
requiredboolean验证:字段必须有值
uniqueboolean验证:值必须唯一
indexedboolean启用按此字段的索引排序
defaultValueany新条目的默认值
validationobject额外验证规则
widgetstring管理 UI 小部件覆盖
optionsobject小部件特定配置

字段类型

类型描述存储为
string短文本TEXT
text长文本(文本区域)TEXT
number数值REAL
integer整数INTEGER
boolean真/假INTEGER
date日期值TEXT(ISO 8601)
datetime日期和时间TEXT(ISO 8601)
email邮箱地址TEXT
urlURLTEXT
slugURL 安全字符串TEXT
portableText富文本内容JSON
image图片引用JSON
file文件引用JSON
json任意 JSONJSON
reference对另一条目的引用TEXT

分类法

分类法是内容的分类系统:

{
	"taxonomies": [
		{
			"name": "category",
			"label": "Categories",
			"labelSingular": "Category",
			"hierarchical": true,
			"collections": ["posts"],
			"terms": [
				{ "slug": "news", "label": "News" },
				{ "slug": "tutorials", "label": "Tutorials" },
				{
					"slug": "advanced",
					"label": "Advanced Tutorials",
					"parent": "tutorials"
				}
			]
		},
		{
			"name": "tag",
			"label": "Tags",
			"labelSingular": "Tag",
			"hierarchical": false,
			"collections": ["posts"]
		}
	]
}

分类法属性

属性类型必需描述
namestring唯一标识符
labelstring复数显示名称
labelSingularstring单数显示名称
hierarchicalboolean允许嵌套术语(分类)或扁平(标签)
collectionsarray此分类法适用的集合
termsarray预定义的术语

术语属性

属性类型必需描述
slugstringURL 安全标识符
labelstring显示名称
descriptionstring术语描述
parentstring父术语 slug(仅限层级分类法)

菜单

menus 数组定义可从管理面板编辑的导航菜单:

{
	"menus": [
		{
			"name": "primary",
			"label": "Primary Navigation",
			"items": [
				{ "type": "custom", "label": "Home", "url": "/" },
				{ "type": "page", "ref": "about" },
				{ "type": "custom", "label": "Blog", "url": "/posts" },
				{
					"type": "custom",
					"label": "External",
					"url": "https://example.com",
					"target": "_blank"
				}
			]
		}
	]
}

菜单项类型

类型描述必需字段
custom自定义 URLurl
page链接到页面条目ref
post链接到文章条目ref
taxonomy链接到分类法归档refcollection
collection链接到集合归档collection

菜单项属性

属性类型描述
typestring项目类型(见上文)
labelstring显示文本(页面/文章引用自动生成)
urlstring自定义 URL(用于 custom 类型)
refstring种子中的内容 ID(用于 page/post 类型)
collectionstring集合 slug
targetstring"_blank" 在新窗口打开
titleAttrstringHTML title 属性
cssClassesstring自定义 CSS 类
childrenarray嵌套菜单项

署名

署名配置文件与所有权(author_id)是分开的。定义可复用的署名身份一次,然后从内容条目中引用它们。

{
	"bylines": [
		{
			"id": "editorial",
			"slug": "emdash-editorial",
			"displayName": "EmDash Editorial"
		},
		{
			"id": "guest",
			"slug": "guest-contributor",
			"displayName": "Guest Contributor",
			"isGuest": true
		}
	]
}
属性类型必需描述
idstring种子本地 ID,由 content[].bylines 使用
slugstringURL 安全的署名 slug
displayNamestring在模板和 API 中显示的名称
biostring可选的个人简介
websiteUrlstring可选的网站 URL
isGuestboolean标记署名为访客配置文件

重定向

redirects 数组定义在迁移后保留旧 URL 的重定向规则:

{
	"redirects": [
		{ "source": "/old-about", "destination": "/about" },
		{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
		{
			"source": "/category/news",
			"destination": "/categories/news",
			"groupName": "migration"
		}
	]
}

重定向属性

属性类型必需描述
sourcestring源路径(必须以 / 开头)
destinationstring目标路径(必须以 / 开头)
typenumberHTTP 状态码:301302307308
enabledboolean重定向是否激活(默认:true
groupNamestring用于管理面板过滤/搜索的可选分组标签

小部件区域

widgetAreas 数组定义可配置的内容区域:

{
	"widgetAreas": [
		{
			"name": "sidebar",
			"label": "Main Sidebar",
			"description": "Appears on blog posts and pages",
			"widgets": [
				{
					"type": "component",
					"title": "Recent Posts",
					"componentId": "core:recent-posts",
					"props": { "count": 5 }
				},
				{
					"type": "menu",
					"title": "Quick Links",
					"menuName": "footer"
				},
				{
					"type": "content",
					"title": "About",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "Welcome to our site!" }]
						}
					]
				}
			]
		}
	]
}

小部件类型

类型描述必需字段
content富文本内容content(Portable Text)
menu渲染菜单menuName
component已注册的组件componentId

内置组件

组件 ID描述
core:recent-posts最近文章列表
core:categories分类列表
core:tags标签云
core:search搜索表单
core:archives月度归档

Sections

Sections 是可复用的内容块,编辑者通过 /section 斜杠命令将其插入到 Portable Text 字段中:

{
	"sections": [
		{
			"slug": "hero-centered",
			"title": "Centered Hero",
			"description": "Full-width hero with centered heading and CTA button",
			"keywords": ["hero", "banner", "header", "landing"],
			"content": [
				{
					"_type": "block",
					"style": "h1",
					"children": [{ "_type": "span", "text": "Welcome to Our Site" }]
				},
				{
					"_type": "block",
					"children": [
						{ "_type": "span", "text": "Your compelling tagline goes here." }
					]
				}
			]
		}
	]
}

Section 属性

属性类型必需描述
slugstringURL 安全标识符
titlestring在 section 选择器中显示的名称
descriptionstring说明何时使用此 section
keywordsarray用于查找 section 的搜索词
contentarrayPortable Text 块
sourcestring"theme"(种子的默认值)或 "import"

来自种子文件的 Section 被标记为 source: "theme",不能从管理 UI 中删除。编辑者可以创建自己的 section(source: "user"),并在编辑内容时插入任何 section 类型。

内容

content 对象包含按集合组织的示例内容条目:

{
	"content": {
		"posts": [
			{
				"id": "hello-world",
				"slug": "hello-world",
				"status": "published",
				"bylines": [
					{ "byline": "editorial" },
					{ "byline": "guest", "roleLabel": "Guest essay" }
				],
				"data": {
					"title": "Hello World",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "Welcome!" }]
						}
					],
					"excerpt": "Your first post."
				},
				"taxonomies": {
					"category": ["news"],
					"tag": ["welcome", "first-post"]
				}
			}
		],
		"pages": [
			{
				"id": "about",
				"slug": "about",
				"status": "published",
				"data": {
					"title": "About Us",
					"content": [
						{
							"_type": "block",
							"style": "normal",
							"children": [{ "_type": "span", "text": "About page content." }]
						}
					]
				}
			}
		]
	}
}

内容条目属性

属性类型必需描述
idstring用于引用的种子本地 ID
slugstringURL slug
statusstring"published""draft"(默认:"published"
dataobject字段值
bylinesarray有序署名信用(byline,可选 roleLabel
taxonomiesobject按分类法名称的术语分配

内容引用

使用 $ref: 前缀引用其他内容条目:

{
	"data": {
		"related_posts": ["$ref:another-post", "$ref:third-post"]
	}
}

$ref: 前缀在填充种子时将种子 ID 解析为数据库 ID。

媒体引用

通过 URL 包含图片:

{
	"data": {
		"featured_image": {
			"$media": {
				"url": "https://images.unsplash.com/photo-xxx",
				"alt": "Description of the image",
				"filename": "hero.jpg",
				"caption": "Photo by Someone"
			}
		}
	}
}

通过 .emdash/media/ 包含本地图片:

{
	"data": {
		"featured_image": {
			"$media": {
				"file": "hero.jpg",
				"alt": "Description of the image"
			}
		}
	}
}

媒体属性

属性类型必需描述
urlstring是*要下载的远程 URL
filestring是*.emdash/media/ 中的本地文件名
altstring无障碍替代文本
filenamestring覆盖文件名
captionstring媒体标题

*urlfile 二选一,不能同时使用。

以编程方式应用种子

使用种子 API 用于 CLI 工具或脚本:

import { applySeed, validateSeed } from "emdash/seed";
import seedData from "./.emdash/seed.json";

// 先验证
const validation = validateSeed(seedData);
if (!validation.valid) {
	console.error(validation.errors);
	process.exit(1);
}

// 应用种子
const result = await applySeed(db, seedData, {
	includeContent: true,
	onConflict: "skip",
	storage: myStorage,
	baseUrl: "http://localhost:4321",
});

console.log(result);
// {
//   collections: { created: 2, skipped: 0 },
//   fields: { created: 8, skipped: 0 },
//   taxonomies: { created: 2, terms: 5 },
//   bylines: { created: 2, skipped: 0 },
//   menus: { created: 1, items: 4 },
//   redirects: { created: 3, skipped: 0 },
//   widgetAreas: { created: 1, widgets: 3 },
//   settings: { applied: 3 },
//   content: { created: 3, skipped: 0 },
//   media: { created: 2, skipped: 0 }
// }

应用选项

选项类型默认值描述
includeContentbooleanfalse创建示例内容条目
onConflictstring"skip""skip""update""error"
mediaBasePathstring本地媒体文件的基路径
storageStorage用于媒体上传的存储适配器
baseUrlstring媒体 URL 的基 URL

幂等性

填充种子可以安全地多次运行。按实体类型的冲突行为:

实体行为
集合如果 slug 存在则跳过
字段如果集合 + slug 存在则跳过
分类法定义如果 name 存在则跳过
分类法术语如果 name + slug 存在则跳过
署名配置文件如果 slug 存在则跳过
菜单如果 name 存在则跳过
菜单项全部替换(菜单被重新创建)
重定向如果 source 存在则跳过
小部件区域如果 name 存在则跳过
小部件全部替换(区域被重新创建)
Section如果 slug 存在则跳过
设置更新(设置本就是可变的)
内容如果集合中存在 slug 则跳过

验证

种子文件在应用前会被验证:

import { validateSeed } from "emdash/seed";

const { valid, errors, warnings } = validateSeed(seedData);

if (!valid) {
	errors.forEach((e) => console.error(e));
}

warnings.forEach((w) => console.warn(w));

验证检查:

  • 必需字段已存在
  • Slug 对其类型有效(集合和字段 slug 允许小写字母、数字和下划线;其他 slug 还允许连字符)
  • 字段类型有效
  • 引用指向现有内容
  • 层级术语的父级存在
  • 重定向路径是安全的本地 URL
  • 重定向源是唯一的
  • 集合内没有重复的 slug

CLI 命令

位于 .emdash/seed.jsonpackage.json#emdash.seedseed/seed.json 的种子文件会内联到构建中,并在数据库为空时在首次请求时应用。要将现有站点的 schema(以及可选的内容)导出为种子文件:

# `mkdir -p` 因为 .emdash/ 在新项目中可能还不存在
mkdir -p .emdash

# 将当前 schema 导出为种子文件
npx emdash export-seed > .emdash/seed.json

# 连同内容一起导出
npx emdash export-seed --with-content > .emdash/seed.json

后续步骤