/** * Seed file types for bootstrapping EmDash sites * * Seed files are DB-agnostic JSON documents that declare everything needed to set up a site: * collections, fields, menus, settings, taxonomies, redirects, widget areas, and optional sample content. */ import type { CollectionAdminConfig, FieldType } from "../schema/types.js"; import type { SiteSettings } from "../settings/types.js"; import type { Storage } from "../storage/types.js"; /** * Root seed file structure */ export interface SeedFile { /** JSON schema reference (optional) */ $schema?: string; /** Seed format version */ version: "1"; /** * Default locale for locale-bearing rows (menus, taxonomies, content) that * omit an explicit `locale`. Lets a non-`en` single-locale project survive an * `export-seed` → `seed` round-trip: `apply` runs outside the Astro runtime * (no i18n config), so without this it would backfill the omitted locale as * `en`. See #1421. */ defaultLocale?: string; /** Metadata about the seed */ meta?: { name?: string; description?: string; author?: string; }; /** Site settings */ settings?: Partial; /** Collection definitions */ collections?: SeedCollection[]; /** Taxonomy definitions */ taxonomies?: SeedTaxonomy[]; /** Navigation menus */ menus?: SeedMenu[]; /** Redirect rules */ redirects?: SeedRedirect[]; /** Widget areas */ widgetAreas?: SeedWidgetArea[]; /** Sections (reusable content blocks, like WP patterns/reusable blocks) */ sections?: SeedSection[]; /** Bylines used for presentation credits */ bylines?: SeedByline[]; /** Sample content (organized by collection) */ content?: Record; } /** * Collection definition in seed */ export interface SeedCollection { /** Where entries live: "db" (default) or "git" (files in the site repo). */ storage?: "db" | "git"; slug: string; label: string; labelSingular?: string; description?: string; icon?: string; admin?: CollectionAdminConfig; supports?: ("drafts" | "revisions" | "preview" | "scheduling" | "search" | "seo")[]; urlPattern?: string; /** Require a slug before an entry can be published. Defaults to true. */ routable?: boolean; /** * Omit this collection from the admin sidebar. It stays reachable through * the API, MCP, plugin hooks, and direct `/content/:collection` URLs. */ hidden?: boolean; /** * Explicit position in the admin sidebar (ascending). Collections without * a `sortOrder` keep the alphabetical order and follow the ordered ones. */ sortOrder?: number; /** Enable comments on this collection */ commentsEnabled?: boolean; /** Field slug powering the admin list Title column (defaults to title display) */ titleField?: string; /** Field slug (a datetime field) powering the admin list Date column (defaults to last-updated) */ dateField?: string; fields: SeedField[]; } /** * Field definition in seed */ export interface SeedField { slug: string; label: string; type: FieldType; required?: boolean; unique?: boolean; searchable?: boolean; indexed?: boolean; defaultValue?: unknown; validation?: Record; widget?: string; options?: Record; } /** * Taxonomy definition in seed. For multi-locale exports each locale variant * is its own entry, linked via `translationOf` (referencing another entry's `id`). */ export interface SeedTaxonomy { /** Optional seed-local id, e.g. "tax:category:en". Target of `translationOf`. */ id?: string; name: string; label: string; labelSingular?: string; hierarchical: boolean; collections: string[]; locale?: string; translationOf?: string; terms?: SeedTaxonomyTerm[]; } /** * Taxonomy term in seed */ export interface SeedTaxonomyTerm { /** Optional seed-local id, e.g. "term:category:news:en". */ id?: string; slug: string; label: string; description?: string; parent?: string; // Slug of parent term (for hierarchical taxonomies) locale?: string; translationOf?: string; } /** * Menu definition in seed */ export interface SeedMenu { /** Optional seed-local id, e.g. "menu:primary:en". */ id?: string; name: string; label: string; locale?: string; translationOf?: string; items: SeedMenuItem[]; } /** * Menu item in seed */ export interface SeedMenuItem { /** Optional seed-local id, e.g. "item:primary:home:en". */ id?: string; type: string; label?: string; url?: string; // For custom type ref?: string; // For page/post: content id in seed; for taxonomy: term slug collection?: string; // Collection name for page/post/taxonomy types target?: "_blank" | "_self"; titleAttr?: string; cssClasses?: string; locale?: string; translationOf?: string; children?: SeedMenuItem[]; } /** * Redirect definition in seed */ export interface SeedRedirect { source: string; destination: string; type?: 301 | 302 | 307 | 308; enabled?: boolean; groupName?: string | null; } /** * Widget area definition in seed */ export interface SeedWidgetArea { name: string; label: string; description?: string; widgets: SeedWidget[]; } /** * Widget in seed */ export interface SeedWidget { type: "content" | "menu" | "component"; title?: string; // For content widgets - using loose type since @portabletext/types is optional content?: Array<{ _type: string; _key?: string; [key: string]: unknown }>; // For menu widgets menuName?: string; // For component widgets componentId?: string; props?: Record; } /** * Section (reusable content block) in seed */ export interface SeedSection { slug: string; title: string; description?: string; /** Search keywords */ keywords?: string[]; /** Portable Text content */ content: Array<{ _type: string; _key?: string; [key: string]: unknown }>; /** Source: "theme" for seed-provided, "import" for WP imports */ source?: "theme" | "import"; } /** * Byline profile in seed */ export interface SeedByline { /** Seed-local ID for byline references in content entries */ id: string; slug: string; displayName: string; bio?: string; websiteUrl?: string; isGuest?: boolean; /** * Avatar for the byline, seeded as an already-stored media item. Unlike a * content `$media` reference, nothing is downloaded: the caller supplies the * `storageKey` of a file that already exists in the configured storage (the * common case when seeding alongside a media migration). A `media` row is * created and linked via `avatarMediaId`. */ avatar?: SeedBylineAvatar; } export interface SeedBylineAvatar { /** Storage key of an avatar file that already exists in the configured storage. */ storageKey: string; /** Alt text for the avatar image. */ alt?: string; /** Filename for the media record. Defaults to the storage key's basename. */ filename?: string; /** MIME type for the media record. Defaults to `image/jpeg`. */ mimeType?: string; width?: number; height?: number; } /** * Content entry in seed */ export interface SeedContentEntry { /** Seed-local ID for $ref resolution */ id: string; /** URL slug. May be omitted for entries in non-routable collections. */ slug?: string | null; /** Publication status */ status?: "published" | "draft"; /** Content data (field slug -> value) */ data: Record; /** Taxonomy term assignments (taxonomy name -> term slugs) */ taxonomies?: Record; /** Ordered byline credits for this entry */ bylines?: SeedBylineCredit[]; /** BCP 47 locale code. When omitted, defaults to defaultLocale. */ locale?: string; /** * Seed-local ID of the source content entry this translates. * Must reference another entry's `id` in the same collection. */ translationOf?: string; } export interface SeedBylineCredit { /** Seed byline ID from root `bylines[]` */ byline: string; roleLabel?: string; } /** * Options for applying a seed */ export interface SeedApplyOptions { /** * Include sample data: content entries, bylines, and taxonomy terms * (default: false). Schema and structure (collections, fields, taxonomy * definitions, menus, settings, redirects, widget areas, sections) are * always applied. */ includeContent?: boolean; /** How to handle conflicts (default: "skip") */ onConflict?: "skip" | "update" | "error"; /** Base path for local media files (for $media.file resolution) */ mediaBasePath?: string; /** * Storage adapter for media uploads. * Required if seed contains $media references with URLs. */ storage?: Storage; /** * Skip downloading and storing media for $media references. * * When true, $media references are resolved to a MediaValue * that uses the original external URL directly as the `src`, * with provider set to "external". No storage adapter is needed. * * Useful for playground/demo environments where media storage * is unavailable or undesirable. */ skipMediaDownload?: boolean; } /** * Result of applying a seed */ export interface SeedApplyResult { collections: { created: number; skipped: number; updated: number }; fields: { created: number; skipped: number; updated: number }; taxonomies: { created: number; terms: number }; bylines: { created: number; skipped: number; updated: number }; menus: { created: number; items: number }; redirects: { created: number; skipped: number; updated: number }; widgetAreas: { created: number; widgets: number }; sections: { created: number; skipped: number; updated: number }; settings: { applied: number }; content: { created: number; skipped: number; updated: number }; media: { created: number; skipped: number }; } /** * Validation result */ export interface ValidationResult { valid: boolean; errors: string[]; warnings: string[]; } /** * Media queue item for background download */ export interface MediaQueueItem { url: string; targetMediaId: string; alt?: string; filename?: string; } /** * $media reference in seed content * * Use this syntax in seed files to import media from URLs: * ```json * { * "featured_image": { * "$media": { * "url": "https://images.unsplash.com/photo-xxx", * "alt": "Description of the image", * "filename": "my-image.jpg" * } * } * } * ``` * * The seed engine will: * 1. Download the image from the URL * 2. Upload it to the configured storage * 3. Create a media record in the database * 4. Replace the $media object with the proper field value */ export interface SeedMediaReference { $media: { /** URL to download the media from */ url: string; /** Alt text for the image */ alt?: string; /** Custom filename (defaults to URL basename or generated) */ filename?: string; /** Caption for the media */ caption?: string; }; }