このページで分かること

  • シードファイルの役割(初回のセットアップ用で、デプロイのたびに実行されるマイグレーションではない)と、ファイルを探す順番、ルートの構造
  • 設定・コレクション・フィールド・タクソノミー・バイライン・コンテンツ・参照・メディア・メニュー・リダイレクト・ウィジェットエリア・セクション・ローカライズの書き方
  • applySeed() による適用と競合したときの動作、validateSeed() が確かめること・確かめないこと、CLIのコマンド
難易度
上級
読む時間
10分
このページの目次

シードファイルは、EmDashのサイトの最初のモデルと、任意のサンプルデータを記述するファイルです。現在のテンプレートは、シードファイルを seed/seed.json に置き、package.json#emdash.seed でその場所を指しています。

EmDashは、ビルド時にシードを埋め込みます。シードは、初回のセットアップと明示的なシードのコマンドのためのもので、デプロイのたびに実行されるマイグレーションではありません。

ファイルを探す順番

Astroのインテグレーションは、次の順番でシードを探します。

  1. .emdash/seed.json
  2. package.json#emdash.seed のパス。
  3. seed/seed.json
  4. ユーザーのシードがない場合は、組み込みの標準のシード。

次のパッケージのフィールドは、テンプレートの慣例的なパスを指定します。

package.json
{
  "emdash": {
    "seed": "seed/seed.json"
  }
}

ルートの構造

次の例には、ルートのすべてのプロパティが含まれています。

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": {},
  "collections": [],
  "taxonomies": [],
  "bylines": [],
  "content": {},
  "menus": [],
  "redirects": [],
  "widgetAreas": [],
  "sections": []
}
プロパティ 必須 用途
$schema いいえ エディター用のスキーマのURL
version はい シードの形式。受け付ける値は "1" だけです
defaultLocale いいえ ロケールを持つ行のうち locale を省略したもののロケール。省略すると実行時の設定、次に en になります
meta いいえ セットアップ中に表示する名前、説明、作成者
settings いいえ サイトの設定の一部
collections いいえ コレクションとフィールドの定義
taxonomies いいえ タクソノミーの定義と、任意のターム
bylines いいえ 表示用のクレジットの任意のプロフィール
content いいえ コレクションのスラッグごとにまとめたサンプルのエントリー
menus いいえ メニューと、入れ子になった項目
redirects いいえ サイト内のリダイレクトの規則
widgetAreas いいえ ウィジェットエリアとウィジェット
sections いいえ 再利用できるPortable Textのセクション

defaultLocale は、先頭と末尾に空白のない、空でない文字列でなければなりません。

設定

settings は、サイトの設定の一部を表すオブジェクトです。よく使うプロパティは、titletaglinelogofaviconurlpostsPerPagedateFormattimezonesocialseo です。

セットアップウィザードでは、管理者がシードのタイトルとキャッチフレーズを置き換えられます。プログラムからシードを適用する場合は、onConflict にかかわらず、指定されたすべての設定が書き込まれます。

seed/seed.json
{
  "version": "1",
  "settings": {
    "title": "Field Notes",
    "tagline": "Reports from the team",
    "postsPerPage": 12,
    "dateFormat": "MMMM d, yyyy",
    "timezone": "Europe/London"
  }
}

コレクション

コレクションには、sluglabelfields が必要です。

seed/seed.json
{
  "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" }
      ]
    }
  ]
}

コレクションのプロパティ

プロパティ 動作
slug string 必須のデータベースとAPIでの名前。英小文字で始まり、英小文字・数字・アンダースコアを含みます
label string 必須のUI用のラベル(複数形)
labelSingular string 任意のUI用のラベル(単数形)
description string 任意の管理画面用の説明
icon string 任意のアイコン名
admin.listColumns string[] コンテンツの一覧に表示する、宣言済みのフィールドのスラッグ(4つまで)
supports string[] draftsrevisionspreviewschedulingsearchseo のうち任意のもの
urlPattern string /posts/{slug} のような公開側のパターン
routable boolean 公開するエントリーにスラッグが必要かどうか。初期値は true
hidden boolean 自動で作られるサイドバーのリンクと、ダッシュボードのクイックアクションを非表示にします。コレクションには、URLとAPIから引き続きアクセスできます
sortOrder number 管理画面のサイドバーでの明示的な位置。順番を指定したコレクションが先に、昇順で並びます
group string 管理画面のサイドバーのフォルダー。同じグループのコレクションは、折りたためる1つの項目にまとまります
commentsEnabled boolean コレクションのコメントを有効にします
editLocking boolean 編集のロックを有効にします。初期値は true
titleField string コンテンツの一覧のタイトルに使うフィールド
dateField string コンテンツの一覧の日付に使う datetime フィールド
fields SeedField[] 必須のフィールドの定義

sortOrder はコレクションのプロパティで、サイドバーの並び順を制御します。SeedField には sortOrder プロパティがありません。フィールドは、配列の順番に作成されます。

フィールドのプロパティ

プロパティ 用途
slug string 必須のフィールド名。コレクションのスラッグと同じパターンを使います
label string 必須のUI用のラベル
type FieldType 必須の、保存するフィールドの型
required boolean 必須の値が空の場合に拒否します
unique boolean 一意性の制約を追加します
searchable boolean コレクションの検索の対象にします
indexed boolean 対応するスカラー型に、クエリ用のインデックスを追加します
defaultValue 任意 フィールドが省略された場合の初期値
validation オブジェクト 生成されるコンテンツのスキーマが使う検証の規則
widget string 管理画面のフィールドのウィジェットの上書き
options オブジェクト ウィジェットごとのオプション

対応しているフィールドの型は次のとおりです。

  • stringtexturlslug
  • numberintegerboolean
  • datetime
  • selectmultiSelect
  • portableTextjsonrepeater
  • imagefilereference

indexed: true を設定できるのは、stringurlnumberintegerbooleandatetimeselectreferenceslug だけです。

フィールドの検証

生成されるコレクションのスキーマは、フィールドの型が対応している場合に、次の規則を認識します。

規則 使う型
minmax 数値のフィールド
minLengthmaxLengthpattern 文字列の形のフィールド
options selectmultiSelect
subFieldsminItemsmaxItems repeater
allowedMimeTypes メディアのフィールド

validateSeed() は、validationoptions のすべての規則を、深い部分まで型チェックするわけではありません。そのため、不正な規則がシードの検証に通り、あとでコレクションのスキーマを組み立てるときや、コンテンツを書き込むときに失敗する場合があります。

本サイトの補足 やさしい解説

WordPressでいえば、カスタム投稿タイプとその入力項目を、コードではなく1つのJSONファイルにまとめて書いておくイメージです。collections の1つ1つがコレクション(管理画面では「コンテンツタイプ」)で、その中の fields がフィールドです。slug はデータベースとAPIで使う名前なので、英小文字で始め、英小文字・数字・アンダースコアだけを使います。validateSeed() はすべての値を細かく確かめるわけではないため、検証に通っても、適用するときに失敗する場合があります。

タクソノミー

タクソノミーの定義では、対象にするコレクションを指定します。タームはサンプルデータで、includeContent がtrueの場合にだけ適用されます。

seed/seed.json
{
  "version": "1",
  "taxonomies": [
    {
      "name": "category",
      "label": "Categories",
      "labelSingular": "Category",
      "hierarchical": true,
      "collections": ["posts"],
      "terms": [
        { "slug": "engineering", "label": "Engineering" },
        { "slug": "platform", "label": "Platform", "parent": "engineering" }
      ]
    }
  ]
}

タクソノミーは、シードの中だけの idlocaletranslationOf を持てます。タームも同じプロパティを持てます。translationOf は、シードの中だけの別のIDを参照します。シードを適用するときは、参照先より後ろに並べる必要があります。

タームの parent は、同じロケールにある親のタームのスラッグです。階層を持たないタクソノミーで親を指定すると、警告が出て、その指定は無視されます。

バイライン

ルートの bylines は、表示用のクレジットを定義します。バイラインはサンプルデータで、includeContent: true が必要です。

seed/seed.json
{
  "version": "1",
  "bylines": [
    {
      "id": "byline-editor",
      "slug": "alex-editor",
      "displayName": "Alex Editor",
      "isGuest": true
    }
  ]
}

id はシードの中だけの値で、コンテンツのクレジットで使います。任意のプロパティは、biowebsiteUrlisGuestavatar です。

バイラインのアバターは、設定済みのストレージにすでにあるファイルを指します。

seed/seed.json
{
  "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
  }
}

バイラインのアバターのシードは、ストレージのキーに対応するメディアの行を作成するか、既存の行を再利用します。ファイルのアップロードやダウンロードはしません。

コンテンツ

content は、エントリーをコレクションのスラッグごとにまとめます。各エントリーには、シードの中だけの id と、data オブジェクトが必要です。ルートを持つコレクションでは、空でない slug も必要です。

seed/seed.json
{
  "version": "1",
  "content": {
    "posts": [
      {
        "id": "post-welcome",
        "slug": "welcome",
        "status": "published",
        "data": {
          "title": "Welcome",
          "content": []
        },
        "taxonomies": {
          "category": ["engineering"]
        },
        "bylines": [
          { "byline": "byline-editor", "roleLabel": "Editor" }
        ]
      }
    ]
  }
}
プロパティ 必須 動作
id はい シードの中だけで使う参照用のID
slug ルートを持つコレクションでは必須 公開側のスラッグで、競合を判定するキー
status いいえ published または draft。初期値は published
data はい コレクションのフィールドのスラッグをキーにした値
taxonomies いいえ タクソノミー名と、タームのスラッグの配列の組
bylines いいえ ルートのバイラインのIDを参照する、順番付きのクレジット
locale いいえ BCP 47のロケール。省略すると defaultLocale を通じて決まります
translationOf いいえ 同じコレクションにある、シードの中だけのコンテンツのID

ルートを持つエントリーでは、シードの中だけの id はデータベース上の識別子ではありません。EmDashはデータベースのIDを作成し、あとの参照のために対応関係を記録します。routable: false のコレクションにあるスラッグのないエントリーでは、再適用しても同じ結果になるように、EmDashはシードの id を保存するIDとして使います。

読み込むときの entry.id はAstroのルートの識別子で、通常はスラッグです。保存されたデータベースのIDは entry.data.id です。

コンテンツの参照

data の中で $ref: の文字列を使うと、シードの中だけのコンテンツのIDを、作成されたデータベースのIDに置き換えられます。

seed/seed.json
{
  "id": "event-opening",
  "slug": "opening-night",
  "data": {
    "title": "Opening night",
    "venue": "$ref:venue-main-hall"
  }
}

参照先は、適用の処理のIDの対応表に入っているように、十分に前に並べる必要があります。解決できない $ref: の値は、元の文字列のまま残ります。validateSeed() はこれを拒否しません。

メディアの参照

コンテンツのデータで $media を使うと、URLのファイルをダウンロードし、指定したストレージのアダプターでアップロードし、メディアの行を作成して、そのオブジェクトをメディアのフィールドの値に置き換えます。

seed/seed.json
{
  "featured_image": {
    "$media": {
      "url": "https://example.com/images/launch.jpg",
      "filename": "launch.jpg",
      "alt": "A product launch on stage",
      "caption": "Launch event"
    }
  }
}

1回の適用の中では、同じURLへの参照が繰り返されると、解決済みのメディアの値を再利用します。シードのメディアの参照は、ローカルの file プロパティを受け付けません。mediaBasePath は公開されている SeedApplyOptions の型に残っていますが、現在の適用の処理はこれを読み込みません。

ストレージのアダプターが指定されていない場合、$media の参照はスキップされ、null になります。skipMediaDownload: true の場合は外部のメディアの値になり、ストレージのアダプターは必要ありません。

メニューは構造のデータで、includeContent がfalseの場合でも適用されます。

seed/seed.json
{
  "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"
        }
      ]
    }
  ]
}

使える項目の種類は、custompageposttaxonomycollection です。custom には url が、pagepost には ref が必要です。項目には、idtranslationOflabelcollectiontitleAttrcssClasseslocaletarget、入れ子の children を含められます。

pagepost では、ref にシードのコンテンツのIDを指定します。参照先がない場合は、検証の警告が出て、コンテンツへの参照が解決されていないメニュー項目が作られます。メニューを適用するたびに、onConflict とは関係なく、そのメニューの既存の項目は削除され、作り直されます。

リダイレクト

リダイレクトには、サイト内の転送元と転送先のパスが必要です。

seed/seed.json
{
  "version": "1",
  "redirects": [
    {
      "source": "/old-path",
      "destination": "/new-path",
      "type": 308,
      "enabled": true,
      "groupName": "WordPress migration"
    }
  ]
}

どちらのパスも、1つの / で始まる必要があります。プロトコル相対URL、パスをさかのぼるセグメント、改行は拒否されます。使えるステータスコードは、301302307308 です。

ウィジェットエリア

ウィジェットエリアには、contentmenucomponent のウィジェットを入れます。

seed/seed.json
{
  "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 }
        }
      ]
    }
  ]
}

コンテンツのウィジェットは、content にPortable Textを保存します。メニューのウィジェットには menuName が必要です。コンポーネントのウィジェットには componentId が必要で、props を渡せます。SeedWidget には settings プロパティはありません。

ウィジェットエリアを適用するたびに、onConflict とは関係なく、そのエリアの既存のウィジェットは削除され、作り直されます。

セクション

セクションには、再利用できるPortable Textのコンテンツを入れます。

seed/seed.json
{
  "version": "1",
  "sections": [
    {
      "slug": "newsletter-signup",
      "title": "Newsletter signup",
      "description": "Signup call to action",
      "keywords": ["newsletter", "email"],
      "source": "theme",
      "content": []
    }
  ]
}

セクションのスラッグには、英小文字、数字、ハイフンを使います。sourcetheme または import で、シードでは初期値が theme になります。セクションは構造のデータで、includeContent がfalseの場合でも適用されます。

ローカライズ

defaultLocale は、タクソノミー、ターム、メニュー、メニュー項目、コンテンツで省略されたロケールを補います。実行時のi18nの設定が有効な場合は、そちらが優先されます。

ローカライズしたタクソノミー、ターム、メニュー、メニュー項目、コンテンツは、シードの中だけの idtranslationOf のフィールドを使います。適用の処理が翻訳のグループを解決できるように、元の項目を翻訳より前に置きます。翻訳したコンテンツのエントリーには locale を設定する必要があり、その translationOf には同じコレクションの別のエントリーを指定する必要があります。

プログラムからのシードの適用

applySeed()validateSeed() は、emdash/seed からエクスポートされています。次のヘルパーは、適用する前に検証します。

src/apply-project-seed.ts
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

オプション 初期値 現在の動作
includeContent false コンテンツのエントリー、バイライン、タクソノミーのタームを含めます
onConflict "skip" 対応しているものの競合時の動作。"skip""update""error" のいずれか
storage なし $media のURLをダウンロードするために必要なストレージのアダプター
skipMediaDownload false $media のURLを外部のメディアの値のままにします
mediaBasePath なし 公開されている型にはありますが、現在の適用の処理では使われません

プログラムから適用する場合、includeContent の初期値は false です。セットアップウィザードは、管理者がサンプルコンテンツについて選んだ内容を渡します。emdash seed のCLIは、--no-content を指定しない限り、初期状態でコンテンツを含めます。

競合したときの動作

onConflict は、シード全体に対するトランザクションのポリシーではありません。

  • コレクション、フィールド、バイライン、コンテンツ、リダイレクト、セクションは、スキップ、更新、エラーの動作に対応しています。
  • タクソノミーの定義とタームは、該当する競合時のモードに従います。
  • 設定は常に適用されます。
  • 既存のメニューは、メニューの行を残したまま、すべての項目を置き換えます。
  • 既存のウィジェットエリアは、エリアの行を残したまま、すべてのウィジェットを置き換えます。
  • コンテンツの競合は、コレクション、スラッグ、ロケールで照合します。ルートを持たないコレクションにあるスラッグのないエントリーは、シードのIDで照合します。

onConflict: "update" の場合、コンテンツのデータは置き換えられ、そのバイラインとタクソノミーの割り当ては、シードの内容に合わせて調整されます。既存のサイトに対して使う前に、コピーで更新のモードをテストします。

applySeed() は、コレクション、フィールド、タクソノミー、バイライン、メニュー、リダイレクト、ウィジェットエリア、セクション、設定、コンテンツ、メディアの件数を返します。

本サイトの補足 やさしい解説

WordPressでは、サイトの設定やメニューを管理画面で1つずつ変更します。シードの適用は、ファイルに書いた内容をまとめてデータベースに書き込む処理で、すでにあるデータとぶつかる場合の動作が種類ごとに決まっています。onConflict で、すでにあるものをスキップするか、更新するか、エラーにするかを選べますが、設定はいつも書き込まれ、メニューとウィジェットエリアは中身がいつも作り直されます。"update" ではコンテンツのデータも置き換えられるため、公式ドキュメントは、既存のサイトに使う前にコピーで試すよう求めています。

検証の動作

validateSeed(){ valid, errors, warnings } を返します。applySeed() はこれを呼び出し、エラーがある場合は Invalid seed file を投げます。

検証では、適用の処理が必要とする構造の規則を確かめます。たとえば次のものです。

  • バージョンと、空でない defaultLocale
  • コレクション、フィールド、タクソノミー、ターム、メニュー、ウィジェットエリア、セクション、バイライン、コンテンツの入れ物の形。
  • 必須の名前、ラベル、ID、スラッグと、対応しているフィールドやウィジェットの型。
  • それぞれの範囲の中での識別子の重複。
  • インデックスを付けるフィールドの型と、admin.listColumns の参照。
  • タクソノミーの親、コンテンツの翻訳、コンテンツのバイラインの参照、メニュー項目の要件。
  • サイト内のリダイレクトのパスが安全であることと、ステータスコード。

エラーではなく警告になる条件もあります。たとえば、コレクションのないタクソノミー、階層を持たないタクソノミーでの親の指定、シードにないコンテンツを参照するメニューなどです。

検証では、すべての data の値がコレクションのフィールドに合っていることまでは確かめません。また、サイトの設定、フィールドの validation、フィールドの options、任意のPortable Textのブロック、コンポーネントのウィジェットのprops、コンテンツのデータの中の $ref: の参照先、リモートの $media を取得できるかどうかも、深い部分までは検証しません。有効なシードでも、スキーマの作成、コンテンツの検証、ネットワークからのダウンロード、ストレージへのアップロードの途中で失敗する場合があります。

エディターでの補助には $schema のURLを使い、適用する前に実行できる検証を実行します。

npx emdash seed seed/seed.json --validate

CLIのコマンド

競合したときの動作を明示して、ローカルのSQLiteデータベースにシードを適用します。

npx emdash seed seed/seed.json --database ./data.db --on-conflict skip

現在のローカルのモデルとすべてのコンテンツを、テンプレートのパスにエクスポートします。

npx emdash export-seed --database ./data.db --with-content=all > seed/seed.json

export-seed は、ローカルのSQLiteファイルに対して直接動きます。デプロイ済みのD1データベースの場合は、まずローカルのファイルにエクスポートします。結果をコミットする前に、エクスポートされた設定、コンテンツ、メディアの参照を確認します。

次のステップ