@celljs/cli
Cell 4 的命令行工具 cell:读取 cell.config.ts,按 preset 构建应用,运行和部署构建产物。设计见仓库的 docs/design/architecture.md,从 3.x 迁移见 docs/migration-4.md。
需要 Node.js ^20.19.0 || >=22.12.0。在项目里安装:
npm install -D @celljs/cli
npx cell build命令
| 命令 | 作用 |
|---|---|
cell init [template] [dir] | 从模板创建项目(backend、fullstack),自带 AGENTS.md、cell 技能和 .mcp.json;不带参数时列出模板 |
cell dev | 开发:前后端同一个端口,前端 HMR,后端改文件自动重载,配置变化自动重启 |
cell build | 构建到 .cell/output:server/index.mjs(依赖内联的单个文件)、public/(前端)、manifest.json |
cell info | 描述项目:preset 及其来源、mode、目标、组件、DI 模块、hooks、产物状态。只读,不构建、不执行 hooks |
cell check | 一次完成配置、TypeScript 类型、装饰器设置、后端 DI 绑定和模块注册的检查,有错误时退出码 1 |
cell preview | 按 manifest.json 运行构建产物,不重新构建 |
cell deploy | 用 preset 构建,再交给 preset 的部署器发布;--dry-run 只给出计划 |
cell config get <key> | 某个配置项合并后的值,--why 列出设置它的文件和环境变量 |
cell docs <query> | 检索已安装组件和模块随包的文档(与项目所用版本一致) |
cell mcp | stdio MCP 服务器,把 info、check、config_get、docs_search、build、deploy_plan 和模块注册的工具提供给 coding agent |
cell agent sync | 按已安装的版本重写 AGENTS.md 中由 Cell 管理的部分和 .agents/skills/cell/SKILL.md |
Preset
| preset | 产物 | 运行 / 预览 | 部署器 |
|---|---|---|---|
node(默认) | Node.js HTTP 服务:node .cell/output/server/index.mjs | cell preview 用 Node.js 运行 | 无,产物自行部署 |
cloudflare | Cloudflare Workers:server/worker.mjs + wrangler.json,前端作为静态资源 | cell preview 用 wrangler dev 在本机的 workerd 中运行 | cloudflare(Wrangler) |
cli | Node.js 命令行程序:node .cell/output/server/index.mjs 启动应用但不起服务,由组件(如 ApplicationLifecycle)完成工作;不需要 @celljs/express-adapter,也不报缺少入口 | cell preview 用 Node.js 运行,继承当前终端 | 无 |
fc | 阿里云函数计算 3.0 自定义运行时:产物加 bootstrap 启动脚本,前端由服务端从 public/ 返回 | Node.js 运行 | fc(函数计算 API) |
scf | 腾讯云 SCF Web 函数:产物加 scf_bootstrap | Node.js 运行 | scf(云函数 API) |
lambda | AWS Lambda + 函数 URL:产物加 server/lambda.mjs 处理器 | Node.js 运行 | lambda(AWS API) |
vercel | Vercel Build Output API:.vercel/output,前端在 CDN,服务端是一个函数 | Node.js 运行函数的 bundle,前端取自 static/ | vercel(Vercel CLI) |
每个 preset 的选项写在 cell.config.ts 里与 preset 同名的字段(如 fc: { region: 'cn-shanghai' }),按 preset 的 schema 校验,未知选项报 CELL_CONFIG_INVALID;切换 preset 时其他 preset 的字段保留不报错。
cloudflare
cell build --preset cloudflare # 构建到 .cell/output
cell preview # wrangler dev,在 workerd(Cloudflare 的运行时)中运行,默认 8787 端口
cell deploy --preset cloudflare --dry-run # 构建并执行 wrangler deploy --dry-run,输出计划,不上传
cell deploy --preset cloudflare # 构建并部署,输出 workers.dev 地址- 运行方式:Cell 后端(Express)运行在 Workers 的
node:http服务端上(需要nodejs_compat),由@celljs/express-adapter的http入口提供,与nodepreset 相同。前端(public/)由 Cloudflare 作为静态资源直接返回,只有不匹配静态文件的请求进入 Worker。产物只支持 ESM,--format cjs报CELL_CONFIG_CONFLICT。 - 启动时机:Workers 只允许在模块加载阶段生成代码(
new Function,Express 的依赖会用到),只允许在处理请求时做 I/O。所以应用的模块在 Worker 启动时加载,Application.start()和onStart在第一个请求时执行(ApplicationHost.beforeStart)。模块加载失败会让 Worker 启动失败,wrangler dev/wrangler deploy直接报出。 - Cloudflare 的配置写在项目根目录的
wrangler.jsonc或wrangler.json(可选,按 Cloudflare 文档的写法):name、compatibility_date、vars、KV / D1 / R2 等绑定、routes都在这里。构建时 Cell 以它为基础生成.cell/output/wrangler.json,并设置:main(项目里写了会被忽略并警告)、有前端时的assets.directory、compatibility_flags里的nodejs_compat。没有配置文件时,name取自 package.json 的包名(如@acme/shop→acme-shop),compatibility_date默认2026-03-17,这也是允许的最早日期(node:http服务端与node:tty从这天起默认可用)。wrangler.toml不支持,请改写成wrangler.jsonc。 - 单页应用:有前端时默认
assets.not_found_handling: "single-page-application":浏览器导航到不存在的路径时由 Cloudflare 返回index.html(与 Node.js 上@celljs/serve-static的spa一致),API 等其他请求仍进入 Worker;在wrangler.jsonc里写了not_found_handling就以项目为准。Worker 里没有前端文件,@celljs/serve-static发现没有前端目录时直接放行,未知路径得到正常的 404。 - 自定义域名:域名的 DNS 托管在 Cloudflare 时,在
wrangler.jsonc里写"routes": [{ "pattern": "app.example.com", "custom_domain": true }],部署时 Cloudflare 自动创建 DNS 记录和证书,deployment.url报告这个域名。国内网络无法访问*.workers.dev,面向国内访问时要绑定自定义域名。 - 应用配置:
vars和 secrets(wrangler secret put)进入process.env,所以cell.yml里的${env.X}和CELL_BACKEND__*变量照常生效。 - Wrangler:不是 Cell 的依赖。项目装了
wrangler就用项目的,否则由npx --yes wrangler@4在第一次使用时安装(npm 缓存)。凭据由 Wrangler 管理:设置CLOUDFLARE_API_TOKEN(必要时加CLOUDFLARE_ACCOUNT_ID),或执行一次npx wrangler login。也可以不用cell deploy,直接执行npx wrangler deploy --config .cell/output/wrangler.json。 - Workers Builds(Cloudflare 的 CI,设置
WORKERS_CI=1)里自动选用cloudflarepreset:构建命令npx cell build,部署命令npx cell deploy --skip-build。 wrangler dev会在项目根目录创建.wrangler/,请加入.gitignore。
fc、scf、lambda
三个函数平台的做法相同:Cell 服务端原样运行,产物目录整体打成 zip 作为函数代码;部署器直接调用云 API(请求签名自己实现:阿里云 ACS3、腾讯云 TC3、AWS SigV4),不需要云 SDK 或云厂商 CLI。函数名默认取包名(@acme/shop → shop),可用 name 指定;env 写入函数的环境变量(另带 NODE_ENV=production),cell.yml 里的 ${env.X} 和 CELL_BACKEND__* 照常生效。
| preset | 运行方式 | 选项 | 凭据(环境变量) | 部署结果 |
|---|---|---|---|---|
fc | custom.debian10 运行时,bootstrap 用镜像自带的 Node.js 20(node: '18' 可选)在 9000 端口(FC_SERVER_PORT)启动服务 | name、region(默认 ALIBABA_CLOUD_REGION_ID 或 cn-hangzhou)、node、memory(MB,默认 1024)、cpu(默认按内存 2 GB/核)、timeout(默认 60)、concurrency(单实例并发,默认 20)、env | ALIBABA_CLOUD_ACCESS_KEY_ID、ALIBABA_CLOUD_ACCESS_KEY_SECRET(可选 ALIBABA_CLOUD_SECURITY_TOKEN) | 创建或更新函数,没有时创建匿名 HTTP 触发器 http(已有的触发器不改),返回 fcapp.run 地址 |
scf | Web 函数(Type: HTTP),scf_bootstrap 用运行时的 Node.js 18(runtime: 'Nodejs16.13' 可选)在 9000 端口启动服务;只有 /tmp 可写 | name、region(默认 TENCENTCLOUD_REGION 或 ap-guangzhou)、namespace(默认 default)、runtime、memory(默认 512)、timeout(默认 60)、env | TENCENTCLOUD_SECRET_ID、TENCENTCLOUD_SECRET_KEY(可选 TENCENTCLOUD_SESSION_TOKEN) | 创建或更新函数(先代码后配置,等待状态变为 Active),没有时创建公网、免鉴权的函数 URL |
lambda | nodejs22.x,初始化阶段在回环端口启动服务,server/lambda.handler 把函数 URL / HTTP API(payload 2.0)事件转成请求,响应以 base64 返回,多个 Cookie 放在 cookies | name、region(默认 AWS_REGION 或 us-east-1)、runtime、architecture(x86_64 / arm64)、memory(默认 1024)、timeout(默认 30)、role、env | AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY(可选 AWS_SESSION_TOKEN),或 ~/.aws/credentials 的 AWS_PROFILE / default | 没有 role 时创建或复用执行角色 cell-lambda-execution(只有日志权限);创建或更新函数;没有时创建 AuthType: NONE 的函数 URL 及其两项公开调用权限 |
- 代码包直接随请求上传,上限:FC 100 MB、SCF 20 MB、Lambda 50 MB(zip 后),超出报
CELL_DEPLOY_PACKAGE_TOO_LARGE。体积大多来自build.external留在server/node_modules的包。 - 没有凭据时
--dry-run照样给出计划(动作为deploy,并警告不知道函数是否已存在);有凭据时查询云端,动作是create或update。实际部署缺凭据报CELL_DEPLOY_CREDENTIALS_MISSING,云 API 的错误报CELL_DEPLOY_API_ERROR/CELL_DEPLOY_UNAUTHORIZED,消息里带云厂商的错误码和请求 ID。 - 函数计算的默认域名
fcapp.run会让浏览器下载 HTML 页面,网页应用请绑定自定义域名;API 不受影响。 - 镜像里的 Node.js 较旧(FC 20.10、SCF 18.15):服务端 bundle 自带
import.meta.dirname/filename的补丁。
vercel
cell build --preset vercel # .vercel/output(Build Output API v3)
cell preview # Node.js 运行函数的 bundle,前端取自 .vercel/output/static
cell deploy --preset vercel # vercel deploy --prebuilt,不在 Vercel 上再构建- 前端放在
static/由 Vercel CDN 返回(/assets/*带一年的 immutable 缓存头);其他请求进入函数functions/index.func(nodejs22.x),函数里的 Cell 服务端处理 API,并用其中的public/index.html做单页回退。vercel build留在.vercel/output的文件在构建时清除。 - 选项:
project(默认取包名)、scope(团队)、environment(production默认 /preview)、runtime、memory、maxDuration、regions(逗号分隔)。输出目录固定.vercel/output(--out-dir到别处时部署报CELL_DEPLOY_OUTPUT_MISMATCH);不接受--single/--compile。 - Vercel CLI 不是依赖:项目装了就用项目的,否则
npx --yes vercel@65。凭据:VERCEL_TOKEN,或执行一次npx vercel login;项目不存在时自动创建(--create-if-missing)。 - Vercel 的构建环境(
VERCEL=1)里自动选用vercelpreset。.vercel/请加入.gitignore。
cell dev
| 选项 | 说明 |
|---|---|
--preset <name> | 用哪个 preset 的入口运行后端,默认 node |
-m, --mode <mode> | 额外的 mode,可重复;development 总是自动加上 |
-p, --port <port> | 端口,默认取 cell.server.port(考虑 CELL_BACKEND__SERVER__PORT),否则 3000;0 表示随机端口 |
--host <host> | 监听地址,默认取 cell.server.host,否则 127.0.0.1 |
- 一个端口:Vite 提供前端页面和模块(HMR),
/favicon.ico与组件的assets和构建产物里的位置一致,其余请求交给后端。 - 后端不打包,在 Vite 的 server 环境里由 module runner 加载:Cell 包和项目源码经过 Cell 插件(生成的 DI 容器与配置写到
.cell/generated/),其他依赖由 Node 直接加载。入口通过ApplicationHost(@celljs/core)把请求处理函数交给cell dev,不自己监听端口。 - 后端用到的文件变化时:先停止旧应用(触发
onStop),再从新的模块重新启动,并触发dev:reload。启动失败时请求返回 500 和CellError(CELL_DEV_BACKEND_FAILED),保存后自动重试。 - 项目或组件的
cell*.yml、package.json、cell.config.*变化时:重新解析工具链并在同一端口重启,再次触发dev:ready。配置写错时报错并等待下一次修改。 - 源码里的
require()和项目内的 CommonJS 文件(如生成的 prisma client)照常可用。 POST /_cell/mcp:运行中应用的 MCP 端点(Streamable HTTP,JSON 响应),工具有get_errors(后端启动错误、失败的重载、5xx 请求、错误日志)、list_routes、list_bindings、get_config(${...}已求值)、recent_requests、logs。只接受本机来源(无Origin或回环地址),其他来源返回 403。--json:就绪时 stdout 输出一行结果(url、port、mode、targets、mcp,后端启动失败时带backendError),之后应用和 Vite 的输出都走 stderr。收到 SIGINT / SIGTERM 时停止应用并以 0 退出。- React Fast Refresh 等框架插件由组件的 toolchain 模块提供(模块宿主完成后);在那之前前端修改会整页刷新,CSS 热替换。
cell build
| 选项 | 说明 |
|---|---|
--preset <name> | 产物形态:node(默认)、cloudflare、cli、fc、scf、lambda、vercel |
-m, --mode <mode> | 额外的 mode,读取 cell.<mode>.yml,可重复;production 总是自动加上 |
--format <esm|cjs> | 服务端产物格式,默认取 preset(node 为 esm) |
--single | 前端内嵌进 server/index.mjs:一个文件运行整个应用(见下) |
--compile [bun|node|deno] | 编译成一个可执行文件 .cell/output/app,默认 bun(见下) |
--compile-target <target> | 交叉编译,交给 bun(如 bun-linux-x64)或 deno(如 x86_64-unknown-linux-gnu) |
--sourcemap | 生成 source map |
--out-dir <dir> | 输出目录,默认 .cell/output |
单文件(--single):先构建前端,再把 public/ 的全部文件(页面、脚本、favicon、组件的 assets)以 base64 写进服务端 bundle,启动时注册到 Symbol.for('@celljs/serve-static/assets'),@celljs/serve-static 从内存返回它们(Content-Type、ETag / 304、Cache-Control 与从磁盘时一致,单页应用回退到 index.html)。产物只有 server/index.mjs 和 manifest.json(single: true,没有 public),把这一个文件拷到任意目录用 node index.mjs 即可运行。项目只有后端时与不加 --single 相同(会提示);build.external 等留在 server/node_modules 的包仍要放在它旁边。也可在 cell.config.ts 写 build: { single: true }。
可执行文件(--compile):在单文件 bundle 的基础上编译成 .cell/output/app(Windows 目标为 app.exe),拷到没有 Node.js 和 node_modules 的机器也能运行;manifest.json 记录 executable,cell preview 直接运行它。
| 工具 | 做法 | 需要 |
|---|---|---|
bun(默认) | bun build --compile | 本机装有 Bun |
deno | deno compile --allow-all | 本机装有 Deno |
node | Node.js 单可执行应用(SEA):CommonJS bundle 生成 blob,用 npx postject 注入 node 的副本,macOS 上重新签名 | 产物格式自动为 cjs;第一次使用时 npx 下载 postject;不能交叉编译 |
留在 bundle 外的包(build.external、组件的 optionalDependencies,通常是原生模块)无法放进可执行文件,报 CELL_COMPILE_EXTERNALS 并列出它们。cloudflare、vercel preset 的前端由平台托管,不接受 --single / --compile。
cell info
接受与 cell build 相同的 --preset、-m、--format、--out-dir 等选项,描述同样参数下 cell build 会用到的内容;另有:
| 选项 | 说明 |
|---|---|
--app-config | 同时输出每个目标合并后的应用配置(cell.yml);后端的 ${...} 保持原样,运行时才求值 |
只读取 package.json、cell*.yml、cell.config.ts 和 manifest.json,不运行项目代码,不执行 hooks。--json 结果的主要字段:
| 字段 | 内容 |
|---|---|
name、version、cellVersion、configFile | 项目、CLI 版本、cell.config 文件(相对根目录) |
preset | name、source(flag / config / env / ci / default)、entry、format、runtime(workerd 表示 Cloudflare Workers)、deployer、available |
mode、targets、targetSources | 生效的 mode;构建目标及其来由(cell.config.ts、default 或声明它的组件) |
build | 生效的 format、outDir、external、sourcemap、single、compile |
components | 按加载顺序;dir、configFiles 相对根目录,targets / entries / toolchain / optionalDependencies 只在组件声明时出现 |
frontend、backend | entry、提供入口的 entryComponent、staticModules / modules(DI 模块,按加载顺序)、config(仅 --app-config) |
hooks、modules、moduleOptions | cell.config.ts 里的 hooks、modules 条目、toolchain 模块选项的键 |
output | dir、built,构建过时还有 manifest 里的 preset、format、targets、server、public |
frontend.envOverrides、backend.envOverrides | 当前环境里的 CELL_FRONTEND__* / CELL_BACKEND__* 变量:applied(变量 → 配置路径)和 problems(找不到键或类型不符)。没有这类变量时不出现,不输出值 |
warnings 会提前指出不起作用或会让 cell build 失败的设置:没有组件提供 preset 的入口、-m 指定的 mode 没有任何 cell.<mode>.yml(附拼写建议)、使用了尚未提供的 single / compile、不生效的 CELL_FRONTEND__* / CELL_BACKEND__* 变量。
cell check
| 选项 | 说明 |
|---|---|
--preset <name>、-m, --mode <mode> | 同 cell build |
--skip <check> | 跳过某项检查,可重复;未知名字报 CELL_ARGUMENT_INVALID 并列出可用的检查 |
| 检查 | 内容 |
|---|---|
config | 与 cell info 的 warnings 相同:缺少入口(CELL_ENTRY_MISSING)、无效 mode(CELL_MODE_UNKNOWN)、不生效或被遮住的环境变量(CELL_ENV_OVERRIDE_IGNORED / CELL_ENV_OVERRIDE_SHADOWS)、不可用的 single / compile |
types | 用项目的 TypeScript 执行 tsc --noEmit -p tsconfig.json,每条诊断带文件、行列和 TSxxxx 码;没有 tsconfig.json 或 typescript 时跳过 |
decorators | tsconfig 要开启 experimentalDecorators 和 emitDecoratorMetadata(CELL_TSCONFIG_DECORATORS),与构建时的装饰器语义一致 |
di | 用 cell dev 的方式加载后端 DI 容器(不启动应用),逐个解析绑定:CELL_DI_NO_BINDING、CELL_DI_AMBIGUOUS、CELL_DI_CIRCULAR、CELL_DI_MISSING_INJECT 为错误,带修复建议;只能在注入点或请求中创建的绑定记为 info |
| 模块注册的检查 | toolchain 模块用 addCheck 注册,名字如 typeorm:entities |
--json 结果:passed、summary(errors / warnings / info 计数)、issues(每条带 check、level、code、message,可能有 file、line、column、hint)、checks(每项的耗时、问题数、跳过原因)。不加 --json 时按编译器格式输出 src/a.ts:3:5 error TS2322 …,info 不显示。
cell init
npx @celljs/cli init backend my-app # 或 fullstack:React 前端 + 后端,经 RPC 调用
cd my-app && npm install && npx cell agent sync && npx cell dev| 选项 | 说明 |
|---|---|
--name <name> | 包名,默认取目录名(转成小写、去掉空格) |
--force | 允许写入非空目录(同名文件被覆盖) |
从不提问;目录非空时报 CELL_INIT_NOT_EMPTY,模板不存在时报 CELL_TEMPLATE_NOT_FOUND(带拼写建议)。项目依赖 @celljs/* 的 ^<cli 版本>,测试用 bun test + createTestApp()。为 coding agent 生成:AGENTS.md(Cell 指南,与版本匹配)、.agents/skills/cell/SKILL.md、CLAUDE.md(@AGENTS.md)、.mcp.json(npx cell mcp)。
cell agent sync
把 @celljs/cli 自带的 Cell 指南和已安装组件、toolchain 模块(addDocs 目录)的 AGENTS.md 片段写进 AGENTS.md 的 <!-- cell:begin --> … <!-- cell:end --> 之间(其余内容保持不变;没有标记时追加到末尾),并重写 .agents/skills/cell/SKILL.md。升级 Cell 或增减组件后执行一次。
cell config get
cell config get cell.server.port --why
# cell.server.port (backend) = 8080
# node_modules/@celljs/web/cell.yml: 3000
# cell.yml [backend]: 8080| 选项 | 说明 |
|---|---|
-m, --mode <mode> | 额外的 mode,可重复 |
--target <target> | backend 或 frontend,默认有后端时取 backend |
--why | 列出设置该值的文件(按合并顺序:所有文件的公共部分在前,目标段 backend: / frontend: 在后,后者覆盖前者)和环境变量 |
键可写 a.b.c、items[0].name。--json 结果有 value、layers(file、component、section、value)、env(设置了该键或其上下级的 CELL_<TARGET>__* 变量)、expressions(后端值含 ${...},服务端启动时求值)。键不存在时报 CELL_CONFIG_KEY_NOT_FOUND,带最接近的键。
cell docs
cell docs <query> [--limit n]:在各组件的 README.md、docs/ 和模块 addDocs() 的目录里按标题分段检索,返回最匹配的段落和 file:line。
cell mcp
在 stdio 上运行 MCP 服务器(每行一条 JSON-RPC 消息),直到 stdin 关闭。工具都对应同名命令的 --json 结果:
| 工具 | 对应命令 | 副作用 |
|---|---|---|
info | cell info | 只读 |
check | cell check | 只读 |
config_get | cell config get | 只读 |
docs_search | cell docs | 只读 |
build | cell build | 写输出目录 |
deploy_plan | cell deploy --dry-run | 写输出目录,部署器可能联网;不部署 |
不提供部署工具:实际部署由人执行 cell deploy。
之后是 toolchain 模块用 addMcpTool 注册的工具(与内置工具重名的会被忽略并警告);cell.config.*、package.json 或模块文件变化后自动重新加载。每个工具标注 readOnlyHint / openWorldHint;参数按 inputSchema 校验,失败时返回 isError 和 CellError。stdout 只有协议消息,模块和 hooks 的输出都走 stderr。
在 agent 中配置(项目根目录下运行):
// Claude Code / Cursor / CodeBuddy:.mcp.json 或 .cursor/mcp.json
{ "mcpServers": { "cell": { "command": "npx", "args": ["cell", "mcp"] } } }# Codex:~/.codex/config.toml
[mcp_servers.cell]
command = "npx"
args = ["cell", "mcp"]cell dev 运行时,另外把 http://127.0.0.1:<port>/_cell/mcp 配成 HTTP 类型的 MCP 服务器即可查看运行中的应用。
cell preview
| 选项 | 说明 |
|---|---|
-p, --port <port> | 监听端口,默认取 cell.server.port(3000);等同于设置 CELL_BACKEND__SERVER__PORT。cloudflare 产物传给 wrangler dev --port,默认 8787 |
--out-dir <dir> | 产物目录,默认 .cell/output |
产物由哪个 preset 构建,就按它的方式运行:node、cli、fc、scf、lambda 用 Node.js 运行 server/index.mjs(标准输入输出继承当前终端),cloudflare 用 wrangler dev,vercel 用 Node.js 运行函数里的 bundle 并从 static/ 返回前端。
cell deploy
| 选项 | 说明 |
|---|---|
--preset <name> | 部署到哪个平台:cloudflare、fc、scf、lambda、vercel;node、cli 没有部署器,报 CELL_DEPLOY_UNSUPPORTED(构建之前就检查) |
--dry-run | 构建并计划,不上传(cloudflare:wrangler deploy --dry-run;fc / scf / lambda:打包并查询函数是否存在;vercel:列出函数和静态文件) |
--skip-build | 部署 --out-dir 里已有的产物;产物的 preset 与 --preset 不一致时报 CELL_DEPLOY_OUTPUT_MISMATCH |
-m, --mode <mode>、--sourcemap、--out-dir <dir> | 同 cell build |
先 plan 后 apply:计划列出要部署的资源(resources,每项有 type、name、action、details),--dry-run 到此为止。依次触发 deploy:before(闸门,hooks 里的命令失败会取消部署)、deploy:plan、deploy:done 事件。部署工具(Wrangler、Vercel CLI)的输出在 --json 时走 stderr。--json 结果:preset、deployer、dryRun、plan,实际部署时还有 deployment(url,以及 details.versionId)。
通用选项
| 选项 | 说明 |
|---|---|
--root <dir> | 项目根目录,默认当前目录 |
--json | stdout 只输出一行 JSON 结果,其余输出(进度、hook 命令的输出)走 stderr |
-h, --help / -v, --version | 帮助 / 版本 |
命令从不交互提问。退出码:成功 0,执行失败 1,用法错误(未知命令、选项、取值)2。
--json 输出
{"schemaVersion":1,"command":"build","ok":true,"root":"/app","preset":"node","format":"esm","mode":["production"],"targets":["backend"],"outDir":".cell/output","entry":"@celljs/express-adapter/lib/node/http-application-entry","durationMs":480,"warnings":[]}失败时:
{"schemaVersion":1,"command":"build","ok":false,"error":{"code":"CELL_PRESET_NOT_FOUND","message":"Unknown preset 'nod'. Available: node.","hint":"Did you mean 'node'?","docs":"https://cell.cellbang.com/errors/cell-preset-not-found"},"warnings":[]}schemaVersion 不变时,字段只增不改。
cell.config.ts
可选。只放工具链配置;应用配置(@Value 读取的内容)写在 cell.yml。
import { defineConfig } from '@celljs/cli';
export default defineConfig({
preset: 'node',
targets: ['frontend', 'backend'],
modules: ['!@celljs/serve-static', { source: '@x/ui', runtime: false }],
build: { format: 'esm', external: ['sharp'], sourcemap: false, outDir: '.cell/output' },
vite: {},
hooks: { 'build:before': 'prisma generate' },
});| 字段 | 说明 |
|---|---|
preset | 产物形态。优先级:--preset → 本字段 → CELL_PRESET → CI 环境识别 → node |
targets | 构建目标。默认 backend 加上组件声明的目标(装了 @celljs/react 会经 @celljs/widget 加上 frontend);写了就完全以它为准 |
modules | '!包名' 去掉一个组件(连同只经由它引入的依赖);{ source, runtime: false } 去掉它的 DI 模块、保留配置;{ source, toolchain: false } 去掉它的 toolchain 模块;本地路径或包名表示额外加载的 toolchain 模块(见下文) |
build.format | esm 或 cjs |
build.external | 不内联的包,按依赖关系拷到 server/node_modules |
build.sourcemap、build.outDir | 同命令行选项 |
build.single、build.compile | 单文件全栈 / 可执行文件,尚未提供(CELL_UNSUPPORTED) |
vite | 合并进每个环境的 Vite 配置 |
hooks | 在工具链事件上执行的 shell 命令,见下文 |
其他顶层字段是 toolchain 模块的选项,值必须是对象。文件支持 .ts/.mts/.cts/.js/.mjs/.cjs,同时存在多个报 CELL_CONFIG_AMBIGUOUS。命令行参数优先于配置文件。
用环境变量覆盖配置
cell.yml 里已有的任意键都可以用环境变量覆盖,不用改文件:
CELL_BACKEND__SERVER__PORT=8080 cell preview # 服务端启动时应用:cell.server.port
CELL_FRONTEND__API_BASE=/api cell build # 构建前端时应用:cell.apiBase__ 分隔层级,第一段先在顶层找、再到 cell 下找(CELL__… 总是指 cell),一个键只能由一个变量设置,不区分大小写并忽略 _、-;值按原值类型转换(JSON 写法的有限数、true / false、对象和数组用 JSON,原值为 null 时是 JSON 就按 JSON),整体是一个 ${...} 的值保留到求值时再定类型(如 CELL_BACKEND__SERVER__PORT='${env.PORT}'),在 ${...} 求值之前生效。只能覆盖已存在的键,找不到键或类型不符的变量不生效并给出警告:服务端在启动时打印,cell build 检查 CELL_FRONTEND__*,cell info 两种都检查。完整规则见迁移指南 5.3。
hooks
hooks: {
'build:before': 'prisma generate',
'output:done': 'cp extra/* "$CELL_OUT_DIR/server/"',
}- 命令经 shell 在项目根目录执行,不读标准输入。PATH 最前面是项目及上级目录的
node_modules/.bin。 - 环境变量:
CELL_EVENT、CELL_PRESET、CELL_MODE(逗号分隔)、CELL_OUT_DIR(输出目录的绝对路径)。 cell dev触发modules:done→config:resolved→dev:ready,后端重载时dev:reload,重启时close后重新来一遍。cell build依次触发:modules:done→config:resolved→build:before→ 构建 →output:done→close。build:before是闸门事件:命令非零退出即取消构建(CELL_BUILD_CANCELLED)。其他事件里非零退出报CELL_HOOK_FAILED。- 只接受命令字符串。需要写代码或修改事件对象(
build:done、deploy:plan)的扩展写成 toolchain 模块。
toolchain 模块
需要写代码的扩展写成 toolchain 模块:默认导出一个工厂函数,拿到 cell 后注册能力(设计见 docs/design/modules.md)。
// modules/audit.ts,在 cell.config.ts 里写 modules: ['./modules/audit'], audit: { url: '...' }
import type { CellAPI } from '@celljs/kit';
export default function (cell: CellAPI) {
const { url } = cell.options('audit', { url: 'http://localhost:9000' });
cell.addVitePlugin({ name: 'audit', transform: code => code }, { env: 'server' });
cell.on('build:done', event => {
event.files['audit.json'] = JSON.stringify({ url, targets: event.project.targets });
});
}- 加载顺序固定:依赖里的组件(package.json 的
cell.toolchain,按依赖顺序)→cell.config.ts的modules(本地路径或包名,按书写顺序)。{ source, toolchain: false }关掉某个组件的模块,'!包名'连组件一起去掉。 - 工厂只做注册,不启动进程、端口、监听或定时器:
cell info也会执行工厂。 cell.config.ts里除preset、targets、modules、build、vite、hooks之外的顶层字段都是模块选项,必须由某个模块用options()或addConfigSchema()认领,否则报CELL_CONFIG_UNKNOWN_OPTION(附拼写建议);addConfigSchema接受 Standard Schema(zod、valibot 等),不合法报CELL_CONFIG_INVALID。- 工厂抛错报
CELL_MODULE_ERROR(带模块名);默认导出不是函数报CELL_MODULE_INVALID。cell dev中通知类事件的处理函数出错只报告不中断,cell build中任何处理函数出错都失败。 - 每个模块注册了什么都列在
cell info --json的toolchainModules里。
| API | 作用 |
|---|---|
command、mode、dev、ci、root、preset、components、logger | 只读信息;preset 是命令行 / 配置 / 环境变量里写的名字,解析后的 preset 在 config:resolved 事件里 |
options(key, defaults)、addConfigSchema(key, schema) | 读并认领 cell.config.ts 的 key 字段(只能在工厂里同步调用) |
updateConfig({ build: { external }, vite }) | 追加 external、在项目的 vite 之下合并 Vite 配置(项目优先);应用配置仍写在组件自己的 cell.yml |
addVitePlugin(plugin, { env }) | 加 Vite 插件,env 为 client / server 时只作用于前端 / 后端 |
addRuntimeModule(path, { target, static })、addVirtualModule(id, code)、setEntry(target, path) | 追加 DI 模块、提供虚拟模块、指定入口 |
addDevHandler(path, handler)、addWatchFile(path) | cell dev 中在后端之前处理某个路径;文件变化时重启 |
addPreset(preset)、addOutputFile(path, content) | 注册 preset(参与 --preset 和 CI 识别);构建后写入输出目录的文件(build:done 的 event.files 可增删改) |
addCheck(def)、addMcpTool(def)、addDocs(dir) | 供 cell check、cell mcp、cell docs 使用 |
on(event, handler)、events | 订阅工具链事件(组合规则见 modules.md §3.2);模块之间的事件总线 |
exec(cmd, args, opts)、resolve(spec) | 不经 shell 执行命令;从项目根目录解析模块 |
错误码
所有失败都以 CellError { code, message, file?, line?, hint, docs } 输出,code 在 4.x 内保持稳定。
| 错误码 | 含义 |
|---|---|
CELL_ARGUMENT_INVALID | 未知命令、选项或取值(退出码 2) |
CELL_PROJECT_NOT_FOUND、CELL_PACKAGE_JSON_INVALID | 根目录没有 package.json、某个 package.json 无法读取 |
CELL_CONFIG_LOAD、CELL_CONFIG_AMBIGUOUS | cell.config.ts 加载或执行失败、存在多个配置文件 |
CELL_CONFIG_INVALID、CELL_CONFIG_CONFLICT | 配置字段错误(带字段路径和拼写建议)、选项互相冲突 |
CELL_CONFIG_PARSE、CELL_CONFIG_NOT_OBJECT、CELL_CONFIG_EXPRESSION | cell.yml 不是合法 YAML、顶层不是映射、${...} 表达式错误 |
CELL_COMPONENT_NOT_FOUND、CELL_LOCAL_MODULE_NOT_FOUND | modules 里写的组件或本地模块不存在 |
CELL_MODULE_NOT_FOUND、CELL_CORE_NOT_FOUND | 组件声明的 DI 模块无法解析、项目没有依赖 @celljs/core |
CELL_CORE_EL_MISSING | 项目的 @celljs/core 早于 4.1.0,没有构建期求值配置所需的 @celljs/core/el 入口 |
CELL_PRESET_NOT_FOUND、CELL_PRESET_DUPLICATE、CELL_PRESET_CYCLE | preset 不存在、重名、extends 成环 |
CELL_BUILD_FAILED | 打包失败,带出错文件和位置 |
CELL_BUILD_CANCELLED、CELL_HOOK_FAILED | hook 或模块取消了构建、hook 命令失败 |
CELL_MODULE_ERROR、CELL_MODULE_INVALID | toolchain 模块的工厂或处理函数出错、默认导出不是工厂函数 |
CELL_CHECK_FAILED、CELL_DI_MODULE_FAILED | 某项检查自身出错、cell check 加载后端 DI 模块失败 |
CELL_CONFIG_UNKNOWN_OPTION | cell.config.ts 有没有模块认领的选项字段 |
CELL_DEV_FAILED、CELL_DEV_BACKEND_FAILED、CELL_DEV_START_TIMEOUT | cell dev 无法启动(端口等)、后端启动失败、后端入口超时未就绪 |
CELL_DEV_BACKEND_NOT_RUNNING | dev MCP 工具需要运行中的后端,但后端启动失败或尚未启动 |
CELL_CONFIG_KEY_NOT_FOUND | cell config get / get_config 的键不存在 |
CELL_COMPILE_TOOL_MISSING、CELL_COMPILE_FAILED | --compile 需要的 bun / deno / node 不存在、编译器报错(带输出的最后几行) |
CELL_COMPILE_EXTERNALS、CELL_COMPILE_NO_SERVER | 有留在 bundle 外的包、项目没有后端可编译 |
CELL_TEMPLATE_NOT_FOUND、CELL_INIT_NOT_EMPTY | cell init 的模板不存在、目标目录非空 |
CELL_UNSUPPORTED | 使用了尚未提供的功能 |
CELL_OUTPUT_NOT_FOUND、CELL_PREVIEW_NO_SERVER | cell preview / cell deploy --skip-build 前没有构建、产物里没有服务端 |
CELL_DEPLOY_UNSUPPORTED、CELL_DEPLOY_OUTPUT_MISMATCH、CELL_DEPLOYER_NOT_FOUND | preset 没有部署器、已有产物不是该 preset 构建的、部署器不存在 |
CELL_DEPLOY_PLAN_FAILED、CELL_DEPLOY_FAILED、CELL_DEPLOY_CANCELLED | 计划失败(如打包失败)、部署失败(凭据问题时 hint 说明如何设置)、hook 或模块取消了部署 |
CELL_DEPLOY_CREDENTIALS_MISSING、CELL_DEPLOY_UNAUTHORIZED | 没有云平台凭据、凭据无效或权限不足 |
CELL_DEPLOY_API_ERROR、CELL_DEPLOY_NETWORK、CELL_DEPLOY_TIMEOUT、CELL_DEPLOY_PACKAGE_TOO_LARGE | 云 API 报错(带云厂商错误码和请求 ID)、连不上云 API、等待函数就绪超时、代码包超过直传上限 |
CELL_CLOUDFLARE_CONFIG_INVALID、CELL_CLOUDFLARE_CONFIG_UNSUPPORTED、CELL_CLOUDFLARE_COMPATIBILITY_DATE | wrangler.json(c) 不是合法 JSON 或字段类型错误、使用了 wrangler.toml、compatibility_date 早于 2026-03-17 |
CELL_INTERNAL_ERROR | Cell 或模块的缺陷;设置 CELL_DEBUG=1 可看到调用栈 |
测试:createTestApp
import { createTestApp } from '@celljs/cli';
const app = await createTestApp({ root, mode: ['extra'] }); // mode 前面总有 test
const res = await app.fetch('/users', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' });
const { UserService } = await app.import('/src/user-service.ts'); // 后端加载的那份模块
app.get(UserService); // DI 容器里的对象
await app.close(); // 停止应用(onStop)app.get(X) 中测试从项目源码导入的类或 Symbol('X') 与应用里的不是同一个模块实例(应用在 Vite 的 module runner 里),按类名或 symbol 描述匹配;没有或重名时报 CELL_DI_NO_BINDING / CELL_DI_AMBIGUOUS。要拿到应用用的那一份(重名时、或要用模块里的其他导出),用 app.import():项目内的文件写 /src/...,包写包名。
在测试进程里像 cell dev 一样从源码加载后端,不构建、不监听端口;请求经内存中的 req/res 对象走完整的 HTTP 处理链,fetch 返回标准 Response。启动失败抛 CellError(如 CELL_DEV_BACKEND_FAILED,带出错文件);后端没有 HTTP 入口时 fetch 报 CELL_TEST_APP_NO_HTTP。
编程接口
import { CellCli } from '@celljs/cli';
const code = await new CellCli({ stdout, stderr, env }).run(['build', '--json']);也导出 defineConfig、ConfigResolver、PresetRegistry、DeployerRegistry、EventBus、HookRunner 等,用于测试和编写工具。