@elytracms/cli

中文文档
0.0.113发布于 2026/11/07MITES Module近 30 天下载 4811

The Elytra CLI — run a consuming repo's studio, frontend, and Convex backend under one command.

原始使用文档
文档目录10 节
  1. Commands
  2. Project layout
  3. Mode-aware
  4. Dev ports
  5. Deploying (Vercel)
  6. Which env lives on which plane
  7. Route conflicts ( elytra doctor )
  8. Conflicting hierarchy mounts
  9. Declarations that serve nothing
  10. Precedence: a match is not a result

@elytracms/cli

The Elytra CLI — run an Elytra consuming repo's studio, delivery frontend, and Convex backend under one command.

Installed as a devDependency in projects scaffolded by create-elytra; its dev/build scripts call elytra.

Commands

elytra dev      Start the studio + frontend (and Convex in connected mode)
                together as one supervised process group. One Ctrl-C stops
                everything; if any process exits, the rest are torn down.

elytra build    Build the studio + frontend (and deploy Convex in connected
                mode), in order — failing as a whole if any step fails.
                Optional target (the per-app Vercel build commands):
                  elytra build studio     Convex deploy coupled with the studio
                                          build (functions + client in lockstep)
                  elytra build frontend   frontend build only

elytra init     First-run setup: provision Convex, generate the auth signing
                keys, verify readiness. Idempotent — safe to re-run.

elytra doctor   Diagnose the active deployment's readiness and print each fix.
                Includes the expected deployment env vars, reported set/missing
                by NAME only — values are never read into the report or printed.
                When the publish revalidate pair is set, also sends a signed
                no-op ping to the endpoint to prove the publish → live chain
                end to end. Also checks your cms/ route set for URL conflicts
                (see below), and reports the generated-previews artifacts
                (manifest present, image files present, no strays — warnings
                only; previews are optional). Read-only; exits non-zero if any
                check fails.

elytra previews Render every canvas-eligible component's fixtures against your
                theme and write committed insert-menu preview artifacts:
                640×400 WebP images in studio/public/elytra-previews/ plus an
                importable studio/elytra-previews.json manifest (per-entry
                input hashes — re-running with unchanged inputs rewrites
                nothing; removed components' images are pruned). Wire it with
                `generatedPreviewsFromManifest` from
                `@elytracms/studio/customization` (two lines — see the docs).
                Rendering needs Playwright once (optional, on demand), at
                the workspace root:
                  pnpm add -Dw playwright && pnpm exec playwright install chromium
                Options:
                  --check     verify the input hashes without rendering (no
                              Playwright needed); exits non-zero when previews
                              are missing/stale/orphaned. Wire into CI/builds.
                  --port <n>  harness dev-server port (default: first free 5440+)

elytra help     Show help
elytra version  Show version

Run from anywhere inside an Elytra project (a directory with elytra.config.ts).

Every subcommand accepts --help/-h and prints its own usage — inertly: no project inspection, no process spawned, no network call, no write. elytra dev --help never starts a server; elytra doctor --help never runs the network diagnostic; elytra build --help never validates a target.

Project layout

The studio app, the frontend app and the theme are found at studio/, frontend/ and components/theme.css — the scaffold layout — unless the repo-root package.json (beside elytra.config.ts) declares otherwise:

{ "elytra": { "studio": "./apps/editor", "frontend": "./apps/site", "theme": "./styles/site.css" } }

Every key is optional. An invalid section or a declared path that does not exist is reported (elytra doctor fails project-layout; the other commands refuse to start) — never silently replaced by the default. Full rules: the guide's 06 · CLI reference, which installs with @elytracms/core (node_modules/@elytracms/core/dist/guide/06-cli-reference.md).

Mode-aware

The CLI reads the studio app's env files — the ones Vite loads for it (.env, .env.local, .env.<mode>, .env.<mode>.local) — through the SAME backend-mode parser the studio runs, so the two never disagree:

  • Playground (VITE_ELYTRA_MODE=playground) — runs the studio + frontend only; no Convex.
  • Connected (VITE_CONVEX_URL set) — also runs convex dev (or deploys on build) once a deployment is provisioned (CONVEX_DEPLOYMENT present).
  • Any other VITE_ELYTRA_MODE value is the studio's boot error, and the CLI names it the same way.

It orchestrates and owns the process lifecycle; it does not reimplement Vite, Next, or Convex — each step shells out to the underlying tool and inherits its watch/HMR/build behaviour.

Dev ports

The studio defaults to :5180, the frontend to :3000. One lever moves both together — set ELYTRA_DEV_PORT_OFFSET in studio/.env (e.g. 100 → studio :5280, frontend :3100) to run a second Elytra project alongside this one without a clash. elytra dev reads it from studio/.env and forwards it to both child processes; the studio's own vite.config.ts also reads it directly (so vite dev run standalone honors it too). Unset (the default) leaves both ports exactly as they are today.

Deploying (Vercel)

Deploys are git-push-driven — there is no elytra deploy. A consuming repo deploys as two Vercel projects on the same git repo, plus the Convex deployment that the studio build pushes functions to:

Vercel projectRoot DirectoryBuild command
studiostudiopnpm exec elytra build studio
frontendfrontendpnpm exec elytra build frontend

elytra build studio wraps Convex's recommended Vercel integration: it runs convex deploy --cmd 'pnpm run build', which pushes your Convex functions and then runs the studio build with the deployment URL injected into its env (VITE_CONVEX_URL). The built studio always matches the functions it was deployed with, and there is no hand-set URL to drift out of sync with the deploy key's target. elytra build frontend runs the Next.js build only — the frontend reads its Convex URL at runtime, not at build time.

Which env lives on which plane

Env lives on three planes. Keep them apart — most misconfiguration is a value set on the wrong plane:

  1. Local .env files (studio/.env, frontend/.env) — local development only. Gitignored; they never reach Vercel or the Convex deployment.
  2. Vercel project env — build- and runtime-env per Vercel project:
    • Studio project: CONVEX_DEPLOY_KEY (a production deploy key generated in the Convex dashboard — it selects and authorizes the deployment). Do not hand-set VITE_CONVEX_URL; the build injects it (above). Optional: the AI provider key (ANTHROPIC_API_KEY, or the Vercel AI Gateway) for the studio's AI route — a host/server env, never VITE_-prefixed.
    • Frontend project: ELYTRA_CONVEX_URL, ELYTRA_PROJECT_ID, REVALIDATE_SECRET, DRAFT_PREVIEW_TOKEN — see the scaffold's CONNECT.md for what each pairs with.
  3. Convex deployment env — set with npx convex env set (from studio/); lives on the deployment itself, visible in the Convex dashboard but in no file: the auth signing keys (JWT_PRIVATE_KEY, JWKS — set via npx @convex-dev/auth), SITE_URL, the instant-publish pair CANVAS_REVALIDATE_ENDPOINT / CANVAS_REVALIDATE_SECRET, and DRAFT_PREVIEW_TOKEN (the secret and the token must match their frontend-plane counterparts). elytra doctor diagnoses this plane's readiness.

Because plane 3 lives in no file, it is the easiest to overlook — elytra doctor therefore enumerates the expected vars there (auth keys, SITE_URL, the instant-publish pair, the draft-preview token, plus the AI provider key on the studio-host plane when the AI route is present) and reports each one set or missing, by name only: the doctor parses names out of npx convex env list and discards every value on the spot — values are never stored, printed, or logged. A missing required var (the auth keys) fails the run with its exact fix line; a missing recommended var (SITE_URL, the AI key, the draft-preview token) is a named warning that doesn't change the exit code. The studio's boot-time "Deployment setup" screen lists the same expected vars, so the CLI and the studio tell one story.

The instant-publish pair — CANVAS_REVALIDATE_ENDPOINT and CANVAS_REVALIDATE_SECRET — gets special treatment, because the publish path silently no-ops while either is unset: the write-chokepoint trigger schedules the revalidate webhook, and the action delivers it, only while both are set. Publishes then never update the live site until a redeploy, with no error anywhere. Both missing is a warning carrying exactly that consequence (a project without a delivery frontend may legitimately leave them unset); one of the two set fails the run, because a half-configured pair has no legitimate reading (documented together in the scaffold's CONNECT.md, set in one shell block, so a partial state is far more likely an interrupted setup than a deliberate choice). When the pair IS set, the doctor goes one step further and proves the chain end to end: it POSTs a signed no-op ping (the probe event — the one event that derives no cache tags) to the endpoint. A 200 {"revalidated":[]} passes (endpoint reachable, the frontend's REVALIDATE_SECRET matches, and the frontend speaks this engine's event wire); a 401 fails as a secret mismatch between Convex and the frontend host; a 400 fails as a version skew (the frontend runs a different Elytra version); a network error only warns (unreachable from your machine is not the same as broken — Convex may still reach it). The pair's values are read in-process for the ping only and never printed. (There is no third, scope var any more: the webhook carries the event, and the frontend derives its cache tags with its own ELYTRA_PROJECT_ID.)

The draft-preview token — DRAFT_PREVIEW_TOKEN — gates the deployment's draftSnapshot query and must equal the frontend host's own DRAFT_PREVIEW_TOKEN (the same secret that gates the frontend's preview-enable route); the doctor can only see the Convex-deployment side; it has no pairWith escalation because its real partner lives on a plane this doctor cannot read. Missing it is a warning: draft preview is closed everywhere (401 on both sides) until it's set — a project that never uses draft preview may legitimately leave it unset.

Route conflicts (elytra doctor)

Routes are code, so a route conflict is a build-time bug, and elytra doctor reports it: two routes that would match the exact same URLs fail the run with the offending ids and patterns named. Nothing about this needs a deployment — the check runs in playground mode too.

When the studio's deps are installed, the doctor executes elytra.config.ts through the studio's own Vite and judges the routes it resolved — exactly what the studio and delivery serve. Without them it falls back to reading your project's cms/index.ts natively (the collections + routes the scaffold's elytra.config.ts passes through), and the route-conflicts row says which of the two it judged. That tree is plain, React-free TypeScript by convention, so the CLI imports it natively — no bundler, no Vite, no dev server. Two consequences of the fallback worth knowing:

  • It needs a Node that runs TypeScript natively (≥22.18). On an older Node the check reports "not checked here" and names the version — never a false pass.
  • If the route set cannot be read for any reason (no cms/index.ts, a module that throws, an import the CLI cannot resolve such as a tsconfig path alias, TypeScript syntax Node cannot strip, or a routes export of the wrong shape), the check reports why, as "not checked here". It never crashes the doctor and never claims your routes are fine.

Conflicting hierarchy mounts

A hierarchy mount (/:path*, /docs/:path*) is not a router route: it is pulled out of the router and resolved per request against a collection's composed page tree. So the route-conflict check above cannot see mounts at all, and a second row — mount-conflicts — judges them by what they declare instead:

  • Two collections mounted at the same path fail the run. They claim an identical URL space, and the only thing separating them is declaration order.
  • A mount nested inside another's URL space is a warning. It is a real shadow, but only where the outer collection's tree actually composes a path into the inner mount's prefix — that is content, which a CLI cannot see, so naming the precedence is honest and failing your build on it would not be.

The precedence being named is worth stating plainly, because nothing else does: mounts are tried in declaration order, not by specificity, and the first whose collection resolves a document for that URL wins. A root mount declared first therefore outranks a deeper one — the opposite of how ordinary routes behave, where more static segments always win. Declare the more specific mount first.

Declarations that serve nothing

A third row — unreachable-routes — asks a different question from the two above: not "do two declarations fight?" but "can this declaration ever serve anything at all?" Three shapes trip it, all of which used to 404 silently with no issue raised anywhere:

  • An all-dynamic route over a mount's URL space — a warning. /:slug matches every one-segment URL, so the /:path* mount is consulted there only where the route resolves no document. Nothing is unconditionally unreachable (the resolver falls through — see Precedence below), but precedence at that depth is then decided by your content: a page whose composed path collides with a post slug is silently shadowed, and neither side is told. /blog/:slug is fine and always was: a static segment claims only the namespace you carved out. It is the all-dynamic pattern that takes a whole depth.
  • A mount on a collection that cannot back it — no parent self-relation, so there are no nested paths to compose, or the collection is not declared at all. The resolver skips such a mount silently; every URL under it 404s. This one fails the run: no content and no ordering can make it work.
  • A catch-all with a dynamic prefix (/:lang/:path*) — also a failure. It is dead in both directions: every router drops it as a catch-all, and no mount is built from it because a mount prefix must be static. For per-locale mounts, declare one mount per locale instead: { pattern: '/de/:path*', locale: 'de' }.

Precedence: a match is not a result

A route always wins over a mount for the URLs it can actually serve. When a route matches a URL but resolves no document there — no post with that slug, or none published in this perspective — the resolver falls through to the hierarchy mounts instead of returning 404. So declaring /blog/:slug does not take the /blog/* namespace away from your page tree: a page at blog/kid is served, and it stops being served the moment a post with slug kid exists.

The consequence worth knowing: which collection serves such a URL depends on what exists, not on the routes alone, and it is evaluated per perspective — a draft-only post wins in preview while the published page still serves live.

Redirect loops are not checked here. Since EC-303 redirects are editorial data in the backend, not code — a CLI has no editorial data. The studio checks them (and route conflicts, and the route-side half of unreachable-routes) live, at the section-rail foot, because it holds both the config routes and the redirects rail.

版本与发布记录

@elytracms/cli 共发布 60 个版本,首次发布于 2026/06/26;过去 12 个月发布了 60 个版本。

版本发布日期距今
0.0.113latest2026/11/076 天前
0.0.1122026/11/076 天前
0.0.1112026/11/058 天前
0.0.1102026/11/058 天前
0.0.1092026/11/058 天前
0.0.1082026/11/049 天前

关于 @elytracms/cli 的常见问题

@elytracms/cli 是什么?

The Elytra CLI — run a consuming repo's studio, frontend, and Convex backend under one command.

如何安装 @elytracms/cli?

在项目目录运行 npm install @elytracms/cli,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 elytra,可以用 npx elytra 直接运行。

@elytracms/cli 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/elytracms__cli 是否存在。

@elytracms/cli 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@elytracms/cli 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.18.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@elytracms/cli 可以商用吗?

@elytracms/cli 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@elytracms/cli 的下载量有多少?

根据 npm 官方下载统计,@elytracms/cli 在 2026/10/14 – 2026/11/12 这 30 天内被下载 4,811 次,日均约 160 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@elytracms/cli 还在维护吗?

最新版本 0.0.113 发布于 2026/11/07(6 天前);至今共发布 60 个版本,其中过去 12 个月发布了 60 个;首次发布于 2026/06/26。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。