種子檔案是用於初始化 EmDash 網站的 JSON 文件。它們定義集合、欄位、分類法、選單、重新導向、小工具區域、網站設定和選用的範例內容。
根結構
{
"$schema": "https://emdashcms.com/seed.schema.json",
"version": "1",
"meta": {},
"settings": {},
"collections": [],
"taxonomies": [],
"bylines": [],
"menus": [],
"redirects": [],
"widgetAreas": [],
"sections": [],
"content": {}
}
| 欄位 | 類型 | 必填 | 說明 |
|---|---|---|---|
$schema | string | 否 | 用於編輯器驗證的 JSON schema URL |
version | "1" | 是 | 種子檔案格式版本 |
meta | object | 否 | 種子檔案的中繼資料 |
settings | object | 否 | 網站設定 |
collections | array | 否 | 集合定義 |
taxonomies | array | 否 | 分類法定義 |
bylines | array | 否 | 署名檔案定義 |
menus | array | 否 | 導覽選單 |
redirects | array | 否 | 重新導向規則 |
widgetAreas | array | 否 | 小工具區域定義 |
sections | array | 否 | 可重複使用的內容區塊 |
content | object | 否 | 範例內容條目 |
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 表。設定精靈允許使用者覆寫 title 和 tagline。
Collections
集合定義會在資料庫中建立內容類型:
{
"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"
}
]
}
]
}
集合屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
slug | string | 是 | URL 安全的識別碼(小寫、底線) |
label | string | 是 | 複數顯示名稱 |
labelSingular | string | 否 | 單數顯示名稱 |
description | string | 否 | 管理 UI 說明 |
icon | string | 否 | Lucide 圖示名稱 |
supports | array | 否 | 功能:"drafts"、"revisions" |
fields | array | 是 | 欄位定義 |
欄位屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
slug | string | 是 | 欄名(小寫、底線) |
label | string | 是 | 顯示名稱 |
type | string | 是 | 欄位類型 |
required | boolean | 否 | 驗證:欄位必須有值 |
unique | boolean | 否 | 驗證:值必須唯一 |
defaultValue | any | 否 | 新條目的預設值 |
validation | object | 否 | 額外驗證規則 |
widget | string | 否 | 管理 UI 小工具覆寫 |
options | object | 否 | 小工具特定設定 |
欄位類型
| 類型 | 說明 | 儲存為 |
|---|---|---|
string | 短文字 | TEXT |
text | 長文字(文字區域) | TEXT |
number | 數值 | REAL |
integer | 整數 | INTEGER |
boolean | 真/假 | INTEGER |
date | 日期值 | TEXT(ISO 8601) |
datetime | 日期與時間 | TEXT(ISO 8601) |
email | 電子郵件地址 | TEXT |
url | URL | TEXT |
slug | URL 安全字串 | TEXT |
portableText | 富文字內容 | JSON |
image | 圖片參考 | JSON |
file | 檔案參考 | JSON |
json | 任意 JSON | JSON |
reference | 參考另一條目 | TEXT |
Taxonomies
內容的分類系統:
{
"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"]
}
]
}
分類法屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
name | string | 是 | 唯一識別碼 |
label | string | 是 | 複數顯示名稱 |
labelSingular | string | 否 | 單數顯示名稱 |
hierarchical | boolean | 是 | 允許巢狀詞彙(分類)或扁平(標籤) |
collections | array | 是 | 此分類法適用的集合 |
terms | array | 否 | 預定義詞彙 |
詞彙屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
slug | string | 是 | URL 安全的識別碼 |
label | string | 是 | 顯示名稱 |
description | string | 否 | 詞彙說明 |
parent | string | 否 | 父詞彙 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 | 自訂 URL | url |
page | 連結到頁面條目 | ref |
post | 連結到文章條目 | ref |
taxonomy | 連結到分類法存檔 | ref、collection |
collection | 連結到集合存檔 | collection |
選單項目屬性
| 屬性 | 類型 | 說明 |
|---|---|---|
type | string | 項目類型(見上方) |
label | string | 顯示文字(page/post 參考會自動產生) |
url | string | 自訂 URL(用於 custom 類型) |
ref | string | 種子中的內容 ID(用於 page/post 類型) |
collection | string | 集合 slug |
target | string | "_blank" 在新視窗開啟 |
titleAttr | string | HTML title 屬性 |
cssClasses | string | 自訂 CSS 類別 |
children | array | 巢狀選單項目 |
Bylines
署名檔案與所有權(author_id)是分開的。先定義一次可重複使用的署名身分,再從內容條目中參考。
{
"bylines": [
{
"id": "editorial",
"slug": "emdash-editorial",
"displayName": "EmDash Editorial"
},
{
"id": "guest",
"slug": "guest-contributor",
"displayName": "Guest Contributor",
"isGuest": true
}
]
}
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
id | string | 是 | 種子本地 ID,由 content[].bylines 使用 |
slug | string | 是 | URL 安全的署名 slug |
displayName | string | 是 | 在範本和 API 中顯示的名稱 |
bio | string | 否 | 選用的個人簡介 |
websiteUrl | string | 否 | 選用的網站 URL |
isGuest | boolean | 否 | 標記署名為來賓檔案 |
Redirects
遷移後保留舊 URL 的重新導向規則:
{
"redirects": [
{ "source": "/old-about", "destination": "/about" },
{ "source": "/legacy-feed", "destination": "/rss.xml", "type": 308 },
{
"source": "/category/news",
"destination": "/categories/news",
"groupName": "migration"
}
]
}
重新導向屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
source | string | 是 | 來源路徑(必須以 / 開頭) |
destination | string | 是 | 目標路徑(必須以 / 開頭) |
type | number | 否 | HTTP 狀態碼:301、302、307 或 308 |
enabled | boolean | 否 | 重新導向是否啟用(預設:true) |
groupName | string | 否 | 選用的群組標籤,供管理後台篩選/搜尋 |
Widget Areas
可設定的內容區域:
{
"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
可重複使用的內容區塊,編輯者可透過 /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." }
]
}
]
}
]
}
區塊屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
slug | string | 是 | URL 安全的識別碼 |
title | string | 是 | 在區塊選擇器中顯示的名稱 |
description | string | 否 | 說明何時使用此區塊 |
keywords | array | 否 | 用於尋找區塊的搜尋詞 |
content | array | 是 | Portable Text 區塊 |
source | string | 否 | "theme"(種子的預設值)或 "import" |
來自種子檔案的區塊標記為 source: "theme",無法從管理 UI 刪除。編輯者可建立自己的區塊(source: "user"),並在編輯內容時插入任何區塊類型。
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." }]
}
]
}
}
]
}
}
內容條目屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
id | string | 是 | 用於參考的種子本地 ID |
slug | string | 是 | URL slug |
status | string | 否 | "published" 或 "draft"(預設:"published") |
data | object | 是 | 欄位值 |
bylines | array | 否 | 有序署名(byline,選用 roleLabel) |
taxonomies | object | 否 | 依分類法名稱的詞彙指派 |
內容參考
使用 $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"
}
}
}
}
媒體屬性
| 屬性 | 類型 | 必填 | 說明 |
|---|---|---|---|
url | string | 是* | 要下載的遠端 URL |
file | string | 是* | .emdash/media/ 中的本地檔名 |
alt | string | 否 | 無障礙的替代文字 |
filename | string | 否 | 覆寫檔名 |
caption | string | 否 | 媒體說明文字 |
*url 或 file 二擇一,不能同時使用。
以程式方式套用種子
使用種子 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 }
// }
套用選項
| 選項 | 類型 | 預設值 | 說明 |
|---|---|---|---|
includeContent | boolean | false | 建立範例內容條目 |
onConflict | string | "skip" | "skip"、"update" 或 "error" |
mediaBasePath | string | — | 本地媒體檔案的基礎路徑 |
storage | Storage | — | 媒體上傳的儲存配接器 |
baseUrl | string | — | 媒體 URL 的基礎 URL |
冪等性
種子可以安全地多次執行。各實體類型的衝突行為:
| 實體 | 行為 |
|---|---|
| 集合 | slug 存在時跳過 |
| 欄位 | 集合 + slug 存在時跳過 |
| 分類法定義 | 名稱存在時跳過 |
| 分類法詞彙 | 名稱 + slug 存在時跳過 |
| 署名檔案 | slug 存在時跳過 |
| 選單 | 名稱存在時跳過 |
| 選單項目 | 全部取代(重新建立選單) |
| 重新導向 | 來源存在時跳過 |
| 小工具區域 | 名稱存在時跳過 |
| 小工具 | 全部取代(重新建立區域) |
| 區塊 | 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 是否遵循命名慣例(小寫、底線)
- 欄位類型是否有效
- 參考是否指向現有內容
- 階層式詞彙的父項是否存在
- 重新導向路徑是否為安全的本地 URL
- 重新導向來源是否唯一
- 集合內無重複 slug
CLI 命令
# 套用種子檔案
npx emdash seed .emdash/seed.json
# 不含範例內容套用
npx emdash seed .emdash/seed.json --no-content
# 僅驗證
npx emdash seed .emdash/seed.json --validate
# 將目前的結構描述匯出為種子
npx emdash export-seed > seed.json
# 包含內容匯出
npx emdash export-seed --with-content > seed.json