使用 EmDash CLI 从命令行管理 EmDash CMS:认证、内容 CRUD、Schema、媒体与发布流程全指南
2026/9/24 14:24:11 网站建设 项目流程
  • CMS
  • 后端
  • 前端
  • 插件系统

【免费下载链接】emdash

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress

项目地址:https://gitcode.com/gh_mirrors/emdas/emdash
点击查看免费下载

EmDash 是一套基于 Astro 构建的全栈 TypeScript CMS(可视为 WordPress 的精神继承者),其内置 CLI(命令名emdash,短别名em)允许开发者和 AI Agent 在不打开后台界面的情况下,直接对运行中的 CMS 实例完成内容、Schema、媒体、分类、菜单、搜索、认证、种子数据、迁移与类型生成等全部管理操作。本文将系统讲解 EmDash CLI 的命令体系、认证与反代场景下的鉴权配置、内容 CRUD 与草稿发布语义、Schema 管理以及 Portable Text/Markdown 转换与_rev乐观并发等核心机制,并给出可复制的实战命令示例。

EmDash CLI 概览:本地命令与远程命令

EmDash CLI 的所有子命令在仓库的 packages/core/src/cli/index.ts 中集中注册,主体基于citty命令行框架实现。命令分为两大类:

  • 本地命令:操作项目文件或已配置的数据库,包括initdoctorseedmigrateexport-seedsecrets
  • 远程命令:与运行中的 EmDash 实例通信,包括typesloginlogoutwhoamicontentschemamediasearchtaxonomymenuplugin

值得注意的是,仓库源码中还保留了auth作为login的向后兼容别名(见 index.ts 中的注释:Deprecated alias kept for backwards compat; will be removed in a future minor),在旧脚本中仍然可用,但新代码应直接使用login

运行以下命令可以查看当前安装版本的确切命令与参数:

npx emdash --help npx emdash <command> --help

CLI 被明确设计为面向 Agent 的工具(skill 文档原文:"The CLI is designed for agents")。一个重要的使用原则是:在执行破坏性操作或批量变更之前,先用只读命令(如content getschema get)确认目标实例与对象,不要在未确认范围的情况下修改实例。

认证机制:四层解析顺序

所有远程命令都会自动解析认证凭据,解析顺序在 packages/core/src/cli/client-factory.ts 的注释中有明确说明:

  1. --token命令行参数
  2. EMDASH_TOKEN环境变量
  3. emdash login存储的凭据(~/.config/emdash/auth.json
  4. Dev 绕过:仅当目标 URL 是 localhost(localhost127.0.0.1)时生效,无需 token

对应源码实现(client-factory.ts):

const baseUrl = args.url || process.env["EMDASH_URL"] || "http://localhost:4321"; let token = args.token || process.env["EMDASH_TOKEN"]; const isLocal = baseUrl.includes("localhost") || baseUrl.includes("127.0.0.1");

对于本地开发服务器,只要开启了开发绕过(dev bypass),客户端可以自动完成认证;对于远程实例,则需要先执行emdash login --url https://my-site.pages.dev,或提供一个作用域受限的 token。此外,存储的访问令牌过期时,客户端会通过 refresh token 自动续期,并回调onTokenRefresh把新令牌写回凭据文件(见 client-factory.ts)。

自定义请求头与反向代理场景

部署在 Cloudflare Access 或其他反向代理之后的站点,需要在每个请求上附加认证请求头。CLI 通过--header(短选项-H)和环境变量两种方式支持这一点。请求头的合并优先级(从低到高)为:存储凭据中的请求头 <EMDASH_HEADERS环境变量 < 命令行--header参数(见 client-factory.ts)。

自动化场景使用 Service Token

在 CI 中,应通过环境变量提供敏感请求头,避免把服务密钥写进凭据文件或 shell 历史:

export EMDASH_HEADERS="CF-Access-Client-Id: xxx CF-Access-Client-Secret: yyy" npx emdash whoami --url https://my-site.pages.dev

emdash login --header则会把自定义请求头持久化到~/.config/emdash/auth.json,供后续命令复用。skill 文档明确建议:CI 中优先使用环境变量提供请求头,以免服务密钥落入凭据文件或 shell 历史。

Cloudflare Access 浏览器流程

如果没有 service token 且本机安装了cloudflared,CLI 会自动完成以下降级流程:

  1. 检测到 Access 拦截请求
  2. 尝试通过cloudflared access token获取缓存的 JWT
  3. 回退到cloudflared access login进行浏览器交互式认证

源码实现位于 packages/core/src/client/cf-access.ts(注释描述了完整的 redirect → token → login 链路),其中getCachedAccessToken通过cloudflared access token -app <origin>获取缓存 JWT(cf-access.ts),失败后调用cloudflared access login <origin>启动浏览器认证(cf-access.ts)。

这种方式适合交互式使用,但不适合 CI——自动化环境请使用 service token。

通用反向代理认证

--header参数对任何认证方案都通用,比如基础认证(Basic Auth)或自定义 API Key:

# Basic auth npx emdash login --url https://example.com -H "Authorization: Basic dXNlcjpwYXNz" # Custom auth header npx emdash login --url https://example.com -H "X-API-Key: secret123"

快速参考:数据库初始化与种子数据

常规站点启动直接使用项目自带的 package script。首次请求会自动执行待处理的迁移,并在数据库为空且 setup 未完成时应用内置种子数据。Astro 集成会在服务器启动时生成emdash-env.d.ts

# 用项目的 package script 启动站点 pnpm dev # 把现有数据库导出为种子文件 # (运行时首次启动会自动发现 .emdash/seed.json; # 目录可能还不存在,所以先 mkdir -p) mkdir -p .emdash npx emdash export-seed > .emdash/seed.json npx emdash export-seed --with-content=all > .emdash/seed.json

种子文件的发现顺序有明确的源码实现依据。在 packages/core/src/astro/integration/virtual-modules.ts 的generateSeedModule中,搜索顺序为:

  1. .emdash/seed.json
  2. package.json中的emdash.seed引用
  3. seed/seed.json(模板约定路径)

若三者都不存在,则回退到内置默认种子(virtual-modules.ts),此时 setup 向导不会提供演示内容,并在 dev 模式下打印警告。例如 marketing 模板的 package.json 就通过"emdash": { "seed": "seed/seed.json" }显式指定了种子文件。

export-seed命令本身(packages/core/src/cli/commands/export-seed.ts)支持--database(默认./data.db)、--with-contentall或逗号分隔的集合名列表)、--pretty(美化输出)等参数,内部会读取 Schema、内容、媒体、分类、菜单、Byline 等多个仓储来生成完整的种子 JSON。

类型生成:把 CMS Schema 变成 TypeScript

# 从本地开发服务器生成类型 npx emdash types # 从远程生成 npx emdash types --url https://my-site.pages.dev # 自定义输出路径 npx emdash types --output src/types/cms.ts

命令会写入.emdash/types.ts(TypeScript 接口)和.emdash/schema.json(Schema 快照)。从 packages/core/src/cli/commands/types.ts 的实现可以看到:默认输出路径为.emdash/types.ts(可通过--output/-o覆盖),命令先调用client.schemaExport()获取集合列表,再调用client.schemaTypes()获取生成的 TS 类型,写入类型文件的同时也会把schema.json写到同一目录,并打印 Schema 版本号。

认证相关命令

# 登录(OAuth Device Flow) npx emdash login --url https://my-site.pages.dev # 查看当前用户 npx emdash whoami # 登出 npx emdash logout # 为部署生成加密密钥 npx emdash secrets generate

whoami是验证连接与认证配置是否生效的最快捷方式;secrets generate用于生成部署所需的加密密钥。

内容 CRUD:为 Agent 设计的读写语义

CLI 的内容命令覆盖完整的生命周期。默认情况下createupdate会自动发布,这样 Agent 无需管理草稿即可获得"写后读一致"(read-after-write consistency)的体验。

# 列出内容 npx emdash content list posts npx emdash content list posts --status published --limit 10 # 获取单条(Portable Text 字段自动转为 markdown) # 若存在待发布草稿,返回的是草稿数据 npx emdash content get posts 01ABC123 npx emdash content get posts 01ABC123 --raw # 跳过 PT -> markdown 转换 npx emdash content get posts 01ABC123 --published # 忽略待发布草稿 # 创建内容(默认自动发布) npx emdash content create posts --data '{"title": "Hello", "body": "# World"}' npx emdash content create posts --file post.json --slug hello-world npx emdash content create posts --draft --data '...' # 保留为草稿 cat post.json | npx emdash content create posts --stdin # 更新(需要来自之前 get 的 --rev,默认自动发布) npx emdash content update posts 01ABC123 --rev MToyMDI2... --data '{"title": "Updated"}' npx emdash content update posts 01ABC123 --rev MToyMDI2... --draft --data '...' # 保留为草稿 # 删除(软删除) npx emdash content delete posts 01ABC123 # 生命周期 npx emdash content publish posts 01ABC123 npx emdash content unpublish posts 01ABC123 npx emdash content schedule posts 01ABC123 --at 2026-03-01T09:00:00Z npx emdash content restore posts 01ABC123

草稿与发布语义

CLI 默认在createupdate时自动发布,具体表现为:

  • create:创建条目并立即发布,返回的条目处于published状态;
  • update:更新条目,若集合启用了修订(revisions)且本次更新生成了草稿修订,则自动将草稿提升到内容表,返回结果反映更新后的数据;
  • get:返回最新状态。如果存在待发布草稿(例如有人在后台 UI 编辑了但未发布),返回草稿数据而非已发布数据;用--published可只看已发布数据。

create/update时使用--draft可跳过自动发布。支持修订的集合会把编辑存为草稿修订,CLI 会透明处理这一差异——Agent 无需关心集合是否使用修订机制。之所以默认自动发布,是因为:若集合支持修订,update会把数据写入草稿修订而非内容表,如果不自动发布,Agent 更新后再get会看到旧的已发布数据,产生"自己的修改消失了"的困惑。

Schema 管理:编程式调整内容模型

# 列出集合 npx emdash schema list # 获取集合及其字段 npx emdash schema get posts # 创建集合 npx emdash schema create articles --label Articles --description "Blog articles" # 删除集合前先检查并确认目标 npx emdash schema get articles npx emdash schema delete articles # 添加字段 npx emdash schema add-field posts body --type portableText --label "Body Content" npx emdash schema add-field posts featured --type boolean --required # 移除字段 npx emdash schema remove-field posts featured

schema add-field支持的字段类型以npx emdash schema add-field --help打印的列表为准。需要注意的是:产品级 Schema 支持的字段类型比该命令可创建的更全(skill 文档原文:"The full product schema supports additional field types that are not necessarily creatable through this command"),部分高级字段类型需要通过后台或种子文件等方式配置。

媒体、搜索、分类与菜单

# 媒体 npx emdash media list npx emdash media list --mime image/png npx emdash media upload ./photo.jpg --alt "A sunset" --caption "Bristol, 2026" npx emdash media get 01MEDIA123 npx emdash media delete 01MEDIA123 # 搜索 npx emdash search "hello world" npx emdash search "hello" --collection posts --limit 5 # 分类法(Taxonomy) npx emdash taxonomy list npx emdash taxonomy terms categories npx emdash taxonomy add-term categories --name "Tech" --slug tech npx emdash taxonomy add-term categories --name "Frontend" --parent 01PARENT123 # 菜单 npx emdash menu list npx emdash menu get primary

其中taxonomy add-term--parent参数支持父子层级关系,media upload支持--alt--caption元数据,media list可通过--mime过滤文件类型。

JSON 输出:管道与脚本化

所有远程命令都支持--json输出机器可读的结果,并且当 stdout 被管道化(piped)时会自动启用。

# 管道到 jq npx emdash content list posts --json | jq '.items[].slug' # 在脚本中使用 ID=$(npx emdash content create posts --data '{"title":"Hello"}' --json | jq -r '.id')

这一特性让 CLI 可以无缝嵌入 shell 脚本、CI 流水线和 Agent 工作流。

深入编辑流程:Portable Text、_rev与并发控制

CLI 的内容编辑机制(Portable Text/Markdown 转换、_rev令牌、raw 模式)在配套文档 EDITING-FLOW.md 中有完整说明,本节提炼核心要点。

Portable Text 与 Markdown 双向转换

EmDash 使用 Portable Text(PT,一种结构化 JSON 格式)存储富文本。CLI 会自动在 PT 与 markdown 之间转换:

  • portableText字段中的 PT 数组转换为 markdown 字符串;
  • portableText字段中的 markdown 字符串转换回 PT 数组;
  • 非 PT 字段(string、text、number 等)原样透传。

CLI 通过获取集合的字段 Schema 判断哪些字段需要转换。支持的标准块级语法(无损往返):# Heading######(h1-h6)、普通段落、> Quote(blockquote)、- item/* item(无序列表,两级缩进嵌套)、1. item(有序列表)、代码块(含语言标注)、alt图片块。内联标记支持:**bold**strong_italic_em`code`code~~strike~~strikethroughtext→link 注解。

未知块与 Raw 模式

转换器无法识别的块(自定义块、嵌入等)会被序列化为 HTML 注释形式的"不透明栅栏":

<!--ec:block {"_type":"callout","level":"warning","text":"Be careful"} -->

这类注释能完整地往返保留——你可以看到并移动它们,但直接编辑其中的 JSON 有损坏风险;写入时它们会被反序列化回原始 PT 块。

需要精确控制 PT 结构、处理自定义块类型或在不做转换的情况下在条目间复制 PT 时,使用 raw 模式:

npx emdash content get posts 01ABC123 --raw

写入时的字段检查规则:portableText字段 +字符串值→ 先把 markdown 转成 PT 再发送;portableText字段 +数组值→ 作为原始 PT 直接透传;其他字段类型 → 原样发送。

# markdown 字符串——自动转换为 PT npx emdash content create posts --data '{"title": "Hello", "body": "# Welcome\n\nThis is **bold**."}' # 原始 PT 数组——原样透传 npx emdash content create posts --data '{"title": "Hello", "body": [{"_type": "block", "children": [{"_type": "span", "text": "Welcome"}]}]}'

_rev令牌与乐观并发

更新操作使用_rev令牌实现乐观并发(optimistic concurrency),其原则与文件编辑工具"先读后写"完全一致:你必须先看到自己要覆盖的内容。工作流如下:

  1. content get在输出中返回带_rev令牌的条目;
  2. 通过--rev_rev传回content update
  3. 服务端校验:如果条目自你读取后发生了变化,返回409 Conflict
  4. 更新成功后返回新的_rev,供后续编辑使用。

_rev是不透明的 base64 字符串,不要解析它,直接原样传回即可。CLI 在更新时强制要求--rev(源码见 packages/core/src/cli/commands/content.ts 中rev参数的描述:"Revision token from get (prevents overwriting unseen changes)"),不带--rev的更新会被拒绝,从而确保你总是知道自己覆盖了什么。

典型工作流:

# 1. 读取条目——注意输出中的 _rev npx emdash content get posts 01ABC123 # 输出包含:_rev: MToyMDI2LTAyLTE0... # 2. 用拿到的 _rev 更新——默认自动发布 npx emdash content update posts 01ABC123 \ --rev MToyMDI2LTAyLTE0... \ --data '{"title": "New Title"}' # 输出显示更新后的条目与新 _rev

冲突处理与条目锁

如果在读与写之间条目被他人修改,会看到:

EmDashApiError: Content has been modified since last read (version conflict) status: 409 code: CONFLICT

解决办法:重新get,检查新状态,再用新的_rev执行update

但 409 并不总是意味着内容已变更。如果有人在后台打开了该条目,写入会被拒绝并返回不同的错误码:

EmDashApiError: Ada is holding this entry status: 409 code: ENTRY_LOCKED

重新读取无法解除这个锁——条目没有变化,所以新的_rev依然会被拒绝。务必先检查code再决定重试策略:要么等待,要么传--override-lock强行写入。编辑者关闭条目时锁即释放;崩溃标签页遗留的锁会在其最后一次心跳的 7 分钟后过期。注意:覆盖锁并不会夺取锁,编辑者仍持有它,其下一次保存会被当作版本冲突拒绝——除非确定对方已离开,否则请等待。

--override-lock可用于content updatecontent deletecontent publishcontent unpublishcontent schedule(相关参数定义与透传可见 content.ts)。关闭了编辑锁的集合永远不会返回ENTRY_LOCKED

哪些操作需要_rev

只有update需要_rev,其余操作要么幂等要么非破坏性:

命令需要--rev原因
content create尚无已存在的对象
content update覆盖已有数据
content delete软删除,可恢复
content publish幂等的状态变更
content unpublish幂等的状态变更
content schedule仅修改元数据
content restore从回收站恢复

小结

EmDash CLI 将 CMS 的日常管理完整地暴露给了命令行:通过四层认证解析与--header自定义请求头适配本地与各类反向代理场景,通过默认自动发布和--draft/--published开关为 Agent 提供可预期的读写语义,通过_rev乐观并发与条目锁机制保证多人协作下的数据安全,再以--json输出无缝融入脚本与 CI。开发者既可以在终端中快速完成内容维护,也可以让 AI Agent 直接驱动这套接口实现内容的自动化创建、更新与发布。进一步的编辑流程细节(PT/Markdown 转换、_rev、raw 模式)可继续阅读 EDITING-FLOW.md,命令的完整注册表可查看 packages/core/src/cli/index.ts。

  • CMS
  • 后端
  • 前端
  • 插件系统

【免费下载链接】emdash

EmDash is a full-stack TypeScript CMS based on Astro; the spiritual successor to WordPress

项目地址:https://gitcode.com/gh_mirrors/emdas/emdash
点击查看免费下载
上一篇:SuperSync 服务器 migrate deploy 停在 P3009 错误时怎么处理?
下一篇:torsniff实战指南:高效构建BitTorrent种子数据库的7个关键步骤

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询