# Cell 4.8.0 > Every page of https://cell.cellbang.com in one file. Error codes: https://cell.cellbang.com/errors/ # Introduction Cell is a **serverless-first**, **component-based**, **platform-independent** application framework for TypeScript. You write plain classes with dependency injection; Cell turns them into a full-stack application, bundles the backend into a single file, and ships the same code to a Node.js server, a container, a single executable, Cloudflare Workers, AWS Lambda, Alibaba Cloud Function Compute, Tencent Cloud SCF or Vercel. **Cell 4** rebuilds the toolchain on Vite 8 and treats coding agents as first-class users: every command speaks JSON, every error carries a code and a fix, and every project ships with an `AGENTS.md` and an MCP server. ## Why Cell - **One codebase, every runtime**: Pick a preset at build time: `node`, `cli`, `cloudflare`, `fc`, `scf`, `lambda`, `vercel`. No platform SDK in your code, no rewrite when you move. - **Single-file output**: The backend is one dependency-inlined `index.mjs`. Add `--single` to embed the frontend as well, or `--compile` for a standalone executable (Bun, Deno or Node.js SEA). - **Agent-first toolchain**: `--json` on every command, structured `CellError` codes with hints, `cell check` for types and DI wiring, version-matched docs, and MCP tools for both the project and the running dev server. - **Enterprise-grade architecture**: Dependency injection and AOP (`@Component`, `@Autowired`, `@Value`, `@Aspect`), MVC, RPC, declarative transactions, security, JWT and OAuth2. - **Full stack in one process**: Backend MVC plus React, served on one port in development with frontend HMR and backend hot reload. - **AI native**: Provider-neutral chat models, prompts and streaming; agents and [pi](https://www.npmjs.com/package/@earendil-works/pi-coding-agent) coding agent sessions whose tools are your own services. - **Zero configuration**: Sensible defaults everywhere. Override only what you need in `cell.yml`, `cell.config.ts` or `CELL_*` environment variables. - **Minimal footprint**: The toolchain depends on little more than Vite; cloud deployers sign their own requests instead of pulling in vendor SDKs. ## Quick Start Developing and building need Node.js `^20.19.0 || >=22.12.0` (Vite 8). The built application runs on Node.js 18 or later, Bun 1.2 or later and Deno 2: CI starts the builds of the examples with each of them. ```bash npx @celljs/cli init backend my-app # or: fullstack (React + backend over RPC) cd my-app npm install npx cell dev # http://127.0.0.1:3000, hot reload on save ``` A complete backend is a configuration file and a class: ```yaml # cell.yml app: greeting: Hello from my-app ``` ```typescript // src/node/home-controller.ts import { Value } from '@celljs/core'; import { Controller, Get, Text } from '@celljs/mvc/lib/node'; @Controller() export class HomeController { @Value('app.greeting') protected readonly greeting: string; @Get() @Text() home(): string { return this.greeting; } } ``` Test it in-process, without opening a port: ```typescript import { createTestApp } from '@celljs/cli'; const app = await createTestApp({ root: process.cwd() }); const res = await app.fetch('/'); // 200 "Hello from my-app" await app.close(); ``` The everyday commands: | Command | What it does | | --- | --- | | `cell dev` | One port for frontend and backend; HMR for the UI, hot reload for the server, automatic restart on config changes | | `cell build` | Production build into `.cell/output` (`--single`, `--compile`, `--preset `) | | `cell preview` | Run the build output exactly as the target platform would | | `cell deploy` | Build and publish with the preset's deployer; `--dry-run` prints the plan | | `cell check` | Configuration, TypeScript types, decorator settings and DI bindings in one pass | | `cell info` | Everything Cell resolved: preset, modes, components, DI modules, hooks, output state | | `cell config get --why` | The merged value of a setting and every file or variable that set it | ## Deploy Anywhere Your code stays the same; the preset decides the shape of the output and how it is deployed. | Preset | Output | Deploy | | --- | --- | --- | | `node` (default) | Node.js HTTP server, one `index.mjs` | Anywhere Node.js runs: VM, container, PaaS | | `cli` | Command-line program on Cell DI | Distribute as a file or an executable | | `cloudflare` | Worker + static assets + `wrangler.json` | `cell deploy --preset cloudflare` | | `fc` | Alibaba Cloud Function Compute (custom runtime) | `cell deploy --preset fc` | | `scf` | Tencent Cloud SCF web function | `cell deploy --preset scf` | | `lambda` | AWS Lambda (Function URL / API Gateway) | `cell deploy --preset lambda` | | `vercel` | Vercel Build Output API (`.vercel/output`) | `cell deploy --preset vercel` | ```bash cell build --single # the whole full-stack app in one file: node index.mjs cell build --compile # a standalone executable, no Node.js required on the host cell deploy --preset cloudflare --dry-run ``` ## Built for Coding Agents Cell assumes the next developer on your project may be an AI agent, and makes the framework something an agent can use correctly on the first try ([design principles](https://github.com/cellbang/cell/blob/main/docs/design/agent-first.md)). - **Machine-readable everything**: `--json` on every command returns exactly one JSON result; errors are `CellError`s with a stable `code`, a location and a `hint`. - **Context, not guesswork**: `cell init` writes `AGENTS.md`, a Cell skill and `.mcp.json`; `cell agent sync` keeps them matched to the installed versions; `cell docs ` searches the docs shipped with your exact dependencies. - **MCP built in**: `cell mcp` exposes `info`, `check`, `config_get`, `docs_search`, `build` and `deploy_plan`; `cell dev` serves `/_cell/mcp` with routes, DI bindings, resolved config, recent requests, logs and errors of the running app. - **Fast feedback**: `cell check` catches type errors, missing decorator settings and broken DI bindings before anything runs; `createTestApp()` gives in-process integration tests. ```jsonc // .mcp.json (Claude Code, Cursor, CodeBuddy, ...) { "mcpServers": { "cell": { "command": "npx", "args": ["cell", "mcp"] } } } ``` ## AI Native ```typescript import { Component, Autowired } from '@celljs/core'; import { ChatModel, ProxyChatModel, PromptTemplate } from '@celljs/ai-core'; @Component() export class Assistant { @Autowired(ProxyChatModel) protected chatModel: ChatModel; @Autowired(PromptTemplate) protected promptTemplate: PromptTemplate; async ask(question: string): Promise { const prompt = await this.promptTemplate.create('Answer briefly: {question}', { variables: { question }, // pi:/, routed by ProxyChatModel to @celljs/ai-pi chatOptions: { model: 'pi:anthropic/claude-sonnet-4-5' } }); const response = await this.chatModel.call(prompt); return response.result.output.content; } } ``` - **Models**: `@celljs/ai-pi` connects 20+ providers (Anthropic, OpenAI, Google, DeepSeek, Ollama and other OpenAI-compatible endpoints) behind the `@celljs/ai-core` `ChatModel` API (`pi:/`). - **Agents**: `@celljs/ai-agent` builds tool-using agents whose tools are ordinary Cell components; events stream over SSE. - **Coding agents**: `@celljs/ai-coding-agent` hosts pi coding agent sessions inside your application, managed by DI. - **Terminal UIs**: `@celljs/tui` turns an agent into an interactive command-line chat (see [`examples/tui-app`](https://github.com/cellbang/cell/tree/main/examples/tui-app)). ## More Examples
Dependency injection ```typescript import { Component, Autowired, Value } from '@celljs/core'; @Component() export class A {} @Component() export class B { @Autowired() protected a: A; // class injection } @Component() export class C { @Value('foo') // configuration injection: 'obj.xxx', 'arr[1]', ... protected foo: string; } ```
Database access with declarative transactions ```typescript import { Controller, Get, Param, Delete, Put, Post, Body } from '@celljs/mvc/lib/node'; import { Transactional, OrmContext } from '@celljs/typeorm/lib/node'; import { User } from './entity'; @Controller('users') export class UserController { @Get() @Transactional({ readOnly: true }) list(): Promise { return OrmContext.getRepository(User).find(); } @Get(':id') @Transactional({ readOnly: true }) get(@Param('id') id: number): Promise { return OrmContext.getRepository(User).findOneBy({ id }); } @Post() @Transactional() create(@Body() user: User): Promise { return OrmContext.getRepository(User).save(user); } @Put() @Transactional() async modify(@Body() user: User): Promise { await OrmContext.getRepository(User).update(user.id, user); } @Delete(':id') @Transactional() async remove(@Param('id') id: number): Promise { await OrmContext.getRepository(User).delete(id); } } ```
RPC between frontend, backend and services ```typescript // Shared contract export const UserService = 'UserService'; export interface UserService { hello(name: string): Promise; } // Provider import { Rpc } from '@celljs/rpc'; @Rpc(UserService) export class UserServiceImpl implements UserService { async hello(name: string): Promise { return `Hello, ${name}`; } } // Consumer: a typed remote proxy is injected import { Component } from '@celljs/core'; import { Autorpc } from '@celljs/rpc'; @Component() export class Greeter { @Autorpc(UserService) protected userService: UserService; } ```
Runnable applications live in [`examples/`](https://github.com/cellbang/cell/tree/main/examples): `backend-app`, `sample-app` (React), `security-app`, `jwt-app`, `grpc-app`, `schedule`, `prisma-app`, `docker-app`, `standalone-app`, `mycli`, `tui-app`, `coding-agent-app` and `openapi-app`. ## Ecosystem All packages are published under the [`@celljs`](https://www.npmjs.com/org/celljs) scope as dual CommonJS / ES module builds. | Area | Packages | | --- | --- | | Core ([`packages/`](https://github.com/cellbang/cell/tree/main/packages)) | `core`, `logger`, `web`, `mvc`, `http`, `express-adapter`, `serve-static`, `rpc`, `typeorm`, `cache`, `retry`, `schedule`, `cqrs`, `grpc`, `security`, `jwt`, `oauth2-core`, `oauth2-client`, `oauth2-jose`, `validation`, `openapi`, `wechat-pay`, `alipay`, `stripe` | | Cloud ([`cloud-packages/`](https://github.com/cellbang/cell/tree/main/cloud-packages)) | `cloud`, `faas-adapter`, `lambda-adapter`, `fc-adapter`, `scf-adapter`, `vercel-adapter`, `oss`, `cos` | | AI ([`ai-packages/`](https://github.com/cellbang/cell/tree/main/ai-packages)) | `ai-core`, `ai-pi`, `ai-agent`, `ai-coding-agent` | | UI ([`ui-packages/`](https://github.com/cellbang/cell/tree/main/ui-packages)) | `react`, `widget`, `tui` | | Toolchain ([`dev-packages/`](https://github.com/cellbang/cell/tree/main/dev-packages)) | `cli`, `vite`, `kit` | | Ta ([`apps/ta`](https://github.com/cellbang/cell/tree/main/apps/ta), [`ta-packages/`](https://github.com/cellbang/cell/tree/main/ta-packages)) | [Ta](https://cell.cellbang.com/ta), a coding agent built on Cell and pi (`npm i -g @celljs/ta`): the `ta` command, `ta serve` with its web client, desktop and mobile apps | ## Documentation The docs site is **https://cell.cellbang.com** (English and Chinese), with a page for every error code. - Guides: [getting started](https://cell.cellbang.com/guide/getting-started), [project structure and configuration](https://cell.cellbang.com/guide/project), [components and dependency injection](https://cell.cellbang.com/guide/components), [HTTP APIs](https://cell.cellbang.com/guide/web), [frontend and RPC](https://cell.cellbang.com/guide/frontend), [testing](https://cell.cellbang.com/guide/testing), [building and deploying](https://cell.cellbang.com/guide/deployment), [working with coding agents](https://cell.cellbang.com/guide/coding-agents), [package catalog](https://cell.cellbang.com/guide/packages) - For agents: [`llms.txt`](https://cell.cellbang.com/llms.txt) and [`llms-full.txt`](https://cell.cellbang.com/llms-full.txt) (every page in one file); inside a project, `npx cell docs ` searches the docs of the installed versions - [`cell` CLI reference](https://cell.cellbang.com/cli): commands, presets, deployers, `cell.config.ts` - [Migrating from Cell 3.x](https://cell.cellbang.com/migration-4) (Cell 3.x remains available at tag `v3.9.0`) - Design: [architecture](https://github.com/cellbang/cell/blob/main/docs/design/architecture.md), [modules](https://github.com/cellbang/cell/blob/main/docs/design/modules.md), [agent-first principles](https://github.com/cellbang/cell/blob/main/docs/design/agent-first.md) - [API reference](https://cell.cellbang.com/api/), generated with TypeDoc - [Ta](https://cell.cellbang.com/ta): the coding agent built on Cell, with a page for each of its packages - [Changelog](https://github.com/cellbang/cell/blob/main/CHANGELOG.md) ## Contributing Issues and pull requests are welcome. The repository is a Bun workspace monorepo built with Turborepo and TypeScript 7. Prerequisites: [Bun](https://bun.sh) (version pinned in `package.json` → `packageManager`) and Node.js `^20.19.0 || >=22.12.0`. ```bash bun install # install dependencies and sync tsconfig references bun run build # build all packages bun run test # run all tests bunx turbo run test --filter=@celljs/core # test one package bun run lint # Biome (bun run format to fix) bun changeset # describe your change for the next release ``` ## Activity ![Repobeats analytics](https://repobeats.axiom.co/api/embed/59b39c98717cf1ae18b57f24d2efe91617e3a6f1.svg "Repobeats analytics image") ## License [MIT](https://github.com/cellbang/cell/tree/main/LICENSE) # Getting started This page takes you from an empty directory to a tested, built and deployable Cell backend. It assumes Node.js `^20.19.0 || >=22.12.0` for development; the built application runs on Node.js 18 or later, Bun 1.2 or later and Deno 2. ## Create a project ```bash npx @celljs/cli init backend my-app # or: fullstack (React frontend + backend over RPC) cd my-app npm install npx cell dev # http://127.0.0.1:3000, reloads on save ``` `cell init` never asks questions. Without a template name it lists the templates; a non-empty directory needs `--force`. The project it writes: ```text my-app/ ├── cell.yml # application configuration, read with @Value ├── package.json # scripts: dev, build, preview, check, test ├── tsconfig.json # experimentalDecorators + emitDecoratorMetadata ├── src/node/ │ ├── module.ts # the backend DI module: imports every file with components │ └── home-controller.ts ├── test/app.spec.ts # bun test + createTestApp() ├── AGENTS.md, CLAUDE.md # guide for coding agents, matched to the installed version ├── .agents/skills/cell/ # the same guide as an agent skill └── .mcp.json # `npx cell mcp` for agents that speak MCP ``` ## Add an endpoint Create a controller, then import its file from `src/node/module.ts`. **A file that no module imports is not loaded**, so its components do not exist. ```ts // src/node/todo-controller.ts import { Body, Controller, Delete, Get, Param, Post } from '@celljs/mvc/lib/node'; import { NotFoundError } from '@celljs/web/lib/node'; interface Todo { id: number; title: string } @Controller('todos') export class TodoController { protected readonly todos = new Map(); protected nextId = 1; @Get() list(): Todo[] { return [...this.todos.values()]; } @Get(':id') get(@Param('id') id: number): Todo { const todo = this.todos.get(id); if (!todo) { throw new NotFoundError(`No todo ${id}`); // 404 } return todo; // objects are sent as JSON } @Post() create(@Body('title') title: string): Todo { const todo = { id: this.nextId++, title }; this.todos.set(todo.id, todo); return todo; } @Delete(':id') remove(@Param('id') id: number): void { this.todos.delete(id); } } ``` ```ts // src/node/module.ts import './home-controller'; import './todo-controller'; import { autoBind } from '@celljs/core'; export default autoBind(); ``` `@celljs/web` is a dependency of `@celljs/mvc`; add it to `package.json` too when you import from it (`npm install @celljs/web`). ```bash curl -X POST localhost:3000/todos -H 'content-type: application/json' -d '{"title":"write docs"}' curl localhost:3000/todos/1 ``` Move the state into a service when it grows: see [Components and dependency injection](https://cell.cellbang.com/guide/components). The controller decorators are listed in [HTTP APIs](https://cell.cellbang.com/guide/web). ## Configure it `cell.yml` holds the application configuration: keys of the components (`server.port`, ...) at the top level, your own under `app`. Configuration is server-only; only `app.public` and keys components mark public reach the browser: ```yaml app: greeting: Hello from my-app todos: max: ${(env.TODOS_MAX ?? 100) * 1} # evaluated when the server starts; keys under app have no schema type, * 1 makes the env text a number ``` ```ts @Value('app.todos.max') protected readonly max: number; ``` `npx cell config get app.todos.max --why` shows the merged value and every file or variable that set it. More in [Project structure and configuration](https://cell.cellbang.com/guide/project). ## Check and test ```bash npx cell check # TypeScript, configuration, decorator settings, DI bindings bun test # the tests under test/ ``` ```ts // test/todo.spec.ts import { afterAll, beforeAll, describe, expect, it } from 'bun:test'; import { join } from 'node:path'; import { createTestApp, type TestApp } from '@celljs/cli'; describe('todos', () => { let app: TestApp; beforeAll(async () => { app = await createTestApp({ root: join(__dirname, '..') }); }, 60000); afterAll(() => app?.close()); it('creates a todo', async () => { const res = await app.fetch('/todos', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{"title":"a"}' }); expect(await res.json()).toEqual({ id: 1, title: 'a' }); }); }); ``` `createTestApp()` starts the whole application inside the test process without opening a port. See [Testing](https://cell.cellbang.com/guide/testing). ## Build and run ```bash npx cell build # .cell/output/server/index.mjs: one file with every dependency inlined npx cell preview # runs the output as production would node .cell/output/server/index.mjs ``` `--single` puts the frontend into the same file, `--compile` makes an executable that needs no Node.js on the host. ## Deploy ```bash npx cell deploy --preset cloudflare --dry-run # the plan, nothing is uploaded npx cell deploy --preset cloudflare ``` The presets are `node` (default), `cli`, `cloudflare`, `fc`, `scf`, `lambda` and `vercel`. See [Building and deploying](https://cell.cellbang.com/guide/deployment). ## Next - [Package catalog](https://cell.cellbang.com/guide/packages): which `@celljs/*` package to add for databases, caching, scheduled jobs, security, AI and more. - [Working with coding agents](https://cell.cellbang.com/guide/coding-agents): the AGENTS.md, skill and MCP tools that `cell init` set up. # Project structure and configuration ## Layout A Cell project is an npm package. Its source follows a few conventions; nothing has to be registered elsewhere. | Path | What | | --- | --- | | `src/node/` | Backend code: controllers, services, middleware | | `src/browser/` | Frontend code (when the project has a frontend) | | `src/common/` | Code both sides import, such as RPC interfaces | | `src/node/module.ts` | The backend DI module | | `src/browser/module.ts` | The frontend DI module | | `cell.yml`, `cell..yml` | Application configuration | | `cell.config.ts` (optional) | Toolchain configuration | | `.cell/` | Generated: `generated/` (DI wiring, merged configuration) and `output/` (the build); add it to `.gitignore` | ### DI modules A DI module is a file that imports the files declaring components and exports `autoBind()`: ```ts // src/node/module.ts import './todo-controller'; import './todo-service'; import { autoBind } from '@celljs/core'; export default autoBind(); ``` `autoBind()` registers every class decorated so far (`@Component`, `@Controller`, `@Rpc`, ...). **A file that no module imports is never loaded**: if `cell check` reports `CELL_DI_NO_BINDING` for a class you wrote, import its file here. The toolchain looks for these module files of the project, in this order, and loads the ones that exist: | Target | Files (under `src/`, extension `.ts`, `.tsx`, `.js` or `.mjs`) | | --- | --- | | backend | `common/module`, `node/backend-module`, `node/module`, `module`, `backend-module` | | frontend | `common/module`, `browser/frontend-module`, `browser/module`, `module`, `frontend-module` | `src/common/module` and `src/module` are loaded on both sides. A package can declare other paths in the `cell.modules` field of its `package.json`. ### Components Every dependency whose `package.json` has a `cell` field is a **component**: installing it adds its DI modules and its `cell.yml` to the application. Installing `@celljs/security` turns on login, installing `@celljs/serve-static` serves the frontend. To leave out a component that a dependency brings in, list it in `cell.config.ts`: ```ts export default defineConfig({ modules: ['!@celljs/serve-static'] }); ``` `npx cell info` lists the components, their DI modules, the entry and the preset Cell resolved; `--json` gives the same as data. ## Application configuration: `cell.yml` ```yaml # yaml-language-server: $schema=./.cell/schema/cell.yml.json server: port: ${env.PORT ?? 8080} # keys of the components are at the top level typeorm: ormConfig: - type: mysql password: ${env.DB_PASSWORD} app: # your own configuration goes under app greeting: Hello public: # only app.public reaches the browser apiBase: /api ``` - Top-level keys are configuration keys; there is no `cell:`, `frontend:` or `backend:` level. Read them with `@Value('server.port')`, `@Value('app.greeting')` on a property or constructor parameter. - **Every top-level key has an owner**: a component declares the keys it owns in its `cell.schema.json` (`server` belongs to `@celljs/web`, `security` to `@celljs/security`...). Writing under them overrides the component's defaults. - **Your own configuration goes under `app`**, which no component takes, so an upgrade never collides with it. - Mistakes are errors: a top-level key nobody owns, an option a component does not have, a wrong type — `cell build`, `cell dev` and `cell check` report `CELL_CONFIG_INVALID` with the file, the line and a "did you mean". - `cell dev`, `cell build` and `cell check` write `.cell/schema/cell.yml.json`; with the `yaml-language-server` comment above as the first line of `cell.yml`, editors (the YAML extension of VS Code, ...) complete and check it. ### What the frontend sees **Configuration is server-only by default.** The browser only receives the keys components mark public (such as `server.path`, `rpc`, `react`, the page redirects of `security`) and `app.public`. A secret never reaches the frontend bundle unless you put it under `app.public`. - Frontend values are evaluated at build time; their expressions may refer to server keys, only the results ship. - `npx cell config get --target frontend` shows what the frontend gets; a server-only key is reported as such. - A project may have its own `cell.schema.json` describing the keys under `app` (types, descriptions, `"x-cell-public": true`). ### Merge order The files merge in this order: the `cell.yml` of every component (in dependency order), the project's `cell.yml`, then `cell..yml` for each active mode. Later files win; objects merge deeply, arrays are replaced. ### Modes | Command | Mode | | --- | --- | | `cell dev` | `development` | | `cell build` | `production` | | `createTestApp()` | `test` | Add more with `-m ` (repeatable): `cell build -m staging` also reads `cell.staging.yml`. A mode without any file is reported by `cell check` (`CELL_MODE_UNKNOWN`). ### Expressions `${...}` is evaluated against the configuration itself plus `env` (the process environment): ```yaml server: endpoint: http://localhost:${server.port} # another key cloud: region: ${env.REGION ?? 'eu-west-1'} # environment variable with a default typeorm: ormConfig: - port: ${env.DB_PORT ?? 5432} # the schema says integer: DB_PORT=6543 gives the number 6543 synchronize: ${env.DB_SYNC ?? false} # the schema says boolean: true or false only ``` - Defaults use `??`, as in JavaScript; **an empty environment variable counts as unset**. Values need no quotes. - **Schema types convert**: when a key is a number or a boolean, the text an expression gives is converted, and a value that does not convert is an error naming the key. Keys without a schema (those under `app`) keep the type of the result. - **Backend values are evaluated when the server starts**, so one build runs in every environment and secrets are not baked into it. Frontend values are evaluated at build time. - An expression inside other text gives a string; the result of an expression is never evaluated again, even when an environment variable contains `${...}`. ### Environment variables Override any existing key without editing files, with a variable starting with `CELL_`: ```bash CELL_SERVER__PORT=8080 node .cell/output/server/index.mjs # server.port, applied at startup CELL_SERVER__HOST=0.0.0.0 # listen on all interfaces (containers) CELL_APP__PUBLIC__API_BASE=https://api.example.com npx cell build # app.public.apiBase of the frontend, at build time ``` - One underscore after the prefix, `__` between levels (`CELL_SERVER__PORT`). `CELL_PRESET`, `CELL_MODE`, `CELL_EVENT`, `CELL_OUT_DIR` and `CELL_DEBUG` belong to the toolchain and are never configuration; a one-segment variable that is no top-level key (another tool's `CELL_REGION`) is left alone; a name with `__` that matches no key is reported, with the right spelling for the earlier `CELL__…` / `CELL_BACKEND__…`. - `__` separates levels, matched ignoring case, `_` and `-` (`ORM_CONFIG` matches `ormConfig`); array items by index. - The same variables apply to both sides: the server applies them at startup; `cell build` applies them, then picks the public keys for the frontend. - Only existing keys can be overridden; give a new key a default in `cell.yml` first. - Values convert to the type of the value (or the schema): numbers, booleans, JSON for objects and arrays. A value that is one `${...}` is kept and evaluated: `CELL_SERVER__PORT='${env.PORT}'`. - A variable that matches no key or does not fit its type is ignored with a warning; `cell check` and `cell info` report it. ### Where a value comes from ```bash npx cell config get server.port --why # server.port (backend) = 8080 # server-only, owned by @celljs/web # Port the server listens on. Override with CELL_SERVER__PORT. # node_modules/@celljs/web/cell.yml: 3000 # cell.yml: 8080 ``` Common keys of the framework: | Key | Default | Package | | --- | --- | --- | | `server.port` / `server.host` | `3000` / `127.0.0.1` | `@celljs/web` | | `server.path` | `/` | `@celljs/web` (prefix of every route; public) | | `web.body.limit` | `100kb` | `@celljs/web` | | `web.cors` | any origin (`*`) | `@celljs/web` | | `web.session` / `web.cookies` | see the README of `@celljs/web` | `@celljs/web` | | `html.title` / `html.rootId` | `Cell Framework` / `cell-root` | `@celljs/core` (the generated HTML page) | | `logger.level` | `info` | `@celljs/core` | | `rpc.path` | `api` | `@celljs/rpc` | | `serveStatic.spa` | `true` | `@celljs/serve-static` | Each package's README lists its keys; every key with its type and description is in `.cell/schema/cell.yml.json`. ## Toolchain configuration: `cell.config.ts` Optional, and only for the toolchain. Application configuration never goes here. ```ts import { defineConfig } from '@celljs/cli'; export default defineConfig({ preset: 'cloudflare', // default: node modules: ['!@celljs/serve-static'], // remove a component build: { external: ['sharp'], sourcemap: true }, vite: { resolve: { alias: { '~': '/src' } } }, // merged into the Vite configuration hooks: { 'build:before': 'prisma generate' }, // shell commands on toolchain events fc: { region: 'cn-shanghai', memory: 512 }, // options of a preset, under its name }); ``` Unknown fields are reported with the closest valid name (`CELL_CONFIG_INVALID`, `CELL_CONFIG_UNKNOWN_OPTION`). The full reference, including toolchain modules that add Vite plugins, checks, MCP tools or output files, is the [CLI reference](https://cell.cellbang.com/cli). ## Generated files `cell dev` and `cell build` write the code they generate to `.cell/generated/`: the DI modules each target loads, the merged configuration and the project model. Read them when you need to know exactly what the container contains; never edit them. # Components and dependency injection Every part of a Cell application is a **component**: a class the DI container creates, wires and keeps. Controllers, services, middleware and RPC services are all components. Cell's container is its own (`@celljs/core`), with no third-party IoC library. ## Declaring and injecting ```ts import { Autowired, Component, Value } from '@celljs/core'; @Component() export class TodoRepository { protected readonly todos: string[] = []; add(title: string) { this.todos.push(title); } all() { return [...this.todos]; } } @Component() export class TodoService { @Autowired() // by the declared type protected readonly repository: TodoRepository; @Value('app.todos.max') // configuration, see cell.yml protected readonly max: number; add(title: string) { if (this.repository.all().length >= this.max) { throw new Error('Too many todos'); } this.repository.add(title); } } ``` - Components are **singletons** by default: one instance per application. `@Component({ scope: Scope.Transient })` makes a new one for every injection. - Inject into properties (above) or constructor parameters (`constructor(@Autowired(TodoRepository) protected readonly repository: TodoRepository)`). Constructor parameters without a decorator are injected by their declared class. - The file must be imported by a [DI module](https://cell.cellbang.com/guide/project#di-modules), or the class is never registered. - `@Value('key')` reads the merged `cell.yml`; `@Value()` without a key uses the property name. ## Interfaces: a Symbol with the same name TypeScript interfaces do not exist at runtime, so the identifier of an interface is a `Symbol` exported under the same name. This is the convention throughout Cell: ```ts // src/common/mailer.ts export const Mailer = Symbol('Mailer'); export interface Mailer { send(to: string, text: string): Promise; } ``` ```ts // src/node/mailer-impl.ts import { Component } from '@celljs/core'; import { Mailer } from '../common/mailer'; @Component(Mailer) export class MailerImpl implements Mailer { async send(to: string, text: string): Promise { /* ... */ } } // elsewhere @Autowired(Mailer) protected readonly mailer: Mailer; ``` `@Component(Mailer)` registers the class under the symbol **and** under the class itself; both give the same instance. `@Component({ id: [A, B] })` registers more identifiers. ## Several implementations | Need | Write | | --- | --- | | All implementations | `@Autowired(Handler) protected readonly handlers: Handler[]` (an array type) | | None is fine | add `@Optional()`: `undefined`, or `[]` for an array | | Pick one by name | `@Component({ id: Store, name: 'redis' })` and `@Autowired(Store) @Named('redis')` | | Pick one by tag | `@Component({ id: Store, tag: { tag: 'region', value: 'eu' } })` and `@Autowired(Store) @Tagged('region', 'eu')` | | A default among several | `@Component({ id: Store, default: true })`: plain `@Autowired(Store)` gets it | | Replace a framework component | `@Component({ id: ErrorHandler, rebind: true })`: removes the earlier bindings of that id | | Many, sorted, created lazily | `@AutowiredProvider(Step) protected readonly steps: Provider`, then `this.steps.sortSync()` (highest `priority` first) | Extension points of the framework (`Middleware`, `ErrorHandler`, `ApplicationLifecycle`, `HealthIndicator`, ...) are such multi-implementation symbols: you add an implementation by declaring `@Component(Middleware)` on your class, and `rebind: true` replaces the built-in one. ## Lifecycle ```ts import { Application, ApplicationLifecycle, Component, PostConstruct } from '@celljs/core'; @Component(ApplicationLifecycle) export class Warmup implements ApplicationLifecycle { initialize(): void { /* synchronous, before any onStart */ } async onStart(app: Application): Promise { /* awaited one after another */ } onStop(app: Application): void { /* on shutdown and before every reload in cell dev */ } } @Component() export class Cache { @PostConstruct() protected init(): void { /* after the properties are injected, once per instance */ } } ``` An error thrown in `initialize`, `onStart` or `onStop` is logged and does not stop the other lifecycles. For work that must not run at module load (opening connections, timers), use `onStart`, not the constructor: `cell check` creates the components without starting the application. ## Logging ```ts import { Autowired, Component, Logger } from '@celljs/core'; @Component() export class TodoService { @Autowired(Logger) protected readonly logger: Logger; add(title: string) { this.logger.info(`added ${title}`); } } ``` The level comes from `logger.level` (`verbose`, `debug`, `info` (default), `warn`, `error`). `@celljs/logger` replaces the console logger with winston. ## Aspects (AOP) An aspect runs code around the methods of components created with `proxy: true` (controllers and RPC services are proxied by default): ```ts import { Aspect, MethodBeforeAdvice } from '@celljs/core'; @Aspect({ id: MethodBeforeAdvice, pointcut: 'Audited' }) export class AuditAdvice implements MethodBeforeAdvice { async before(method: string | number | symbol, args: any[], target: any): Promise { console.log('call', String(method), args); } } @Component({ proxy: true, sysTags: ['Audited'] }) export class AccountService { /* ... */ } ``` `AfterReturningAdvice` and `AfterThrowsAdvice` run after a method returns or throws. Without `pointcut` an advice applies to every proxied component. A proxied method always returns a Promise, also when it is synchronous. `aop.enabled: false` turns proxies off. ## Outside the container There is no global container. A component that picks components at run time injects the container (`@Autowired(Container)`); code that is not a component (a script, a test) uses the container it created: `ApplicationFactory.createContainer(props, ...modules)` (`@celljs/core/lib/common/application/application-factory`) returns it, and [`createTestApp()`](https://cell.cellbang.com/guide/testing) has `app.get()`. A `{ detached: true }` property is read from the container that created the instance, so it only works on components. ## When wiring fails The container reports wiring problems as structured errors, and `npx cell check` finds them without starting the application: | Code | Meaning | Usual fix | | --- | --- | --- | | `CELL_DI_NO_BINDING` | Nothing is registered for an identifier | Import the file from a DI module; add `@Component(Id)`; install the package | | `CELL_DI_AMBIGUOUS` | Several bindings match a single injection | Inject an array, or use `@Named` / `@Tagged` / `default: true`, or `rebind: true` | | `CELL_DI_CIRCULAR` | Components need each other while being created | Get one side lazily (`@AutowiredProvider`, or `@Autowired({ detached: true })` on a property), or move the shared part into another component | | `CELL_DI_MISSING_INJECT` | A constructor parameter has no identifier | Add `@Autowired(Id)` | The message names the identifier, who needs it and the resolution chain. The [core reference](https://cell.cellbang.com/packages/core) covers every decorator in detail. # HTTP APIs HTTP in Cell is two packages: `@celljs/web` (server, middleware chain, body parsing, cookies, sessions, CORS, errors) and `@celljs/mvc` (controllers on top of it). Both run on Node's `node:http` with no third-party framework. Import the decorators from `@celljs/mvc/lib/node` and the request-level types from `@celljs/web/lib/node`. ## Controllers and routes ```ts import { Body, Controller, Delete, Get, Param, Patch, Post, Query } from '@celljs/mvc/lib/node'; @Controller('users') // prefix: /users export class UserController { @Get() // GET /users?role=admin list(@Query('role') role?? string) { /* ... */ } @Get(':id') // GET /users/42 get(@Param('id') id: number) { /* ... */ } @Post() create(@Body() user: CreateUser) { /* ... */ } @Patch(':id') rename(@Param('id') id: number, @Body('name') name: string) { /* ... */ } @Delete(':id') remove(@Param('id') id: number): void { /* ... */ } } ``` - Method decorators: `@Get`, `@Post`, `@Put`, `@Patch`, `@Delete`, `@Head`, `@Options`, `@All` (every method). Without a path the method answers the controller path itself. - Paths: `:name` is one segment, `*` matches anything, `(...)` is optional. `server.path` (default `/`) prefixes every route. - A request no route matches gets `404`. - Controllers are components: inject services with `@Autowired`, read configuration with `@Value`. Import the controller's file from `src/node/module.ts`. ## Parameters | Decorator | Value | | --- | --- | | `@Param('id')` / `@Param()` | One path parameter / all of them | | `@Query('q')` / `@Query()` | One query parameter / the parsed query (`?a[b]=1` → `{ a: { b: '1' } }`) | | `@Body('name')` / `@Body()` | One field / the whole body | | `@Header('x-api-key')` / `@Header()` | One request header / all headers | | `@Cookie('sid')` | A request cookie | | `@Session('cart')` | A value of the session | | `@Req()` / `@Res()` | Node's request and response (`IncomingMessage` / `ServerResponse` with extras) | Parameters typed `string`, `number`, `boolean` or `Date` are converted (`'42'` → `42`). The body is parsed from JSON, `text/plain`, `application/octet-stream` (a Buffer) or a URL-encoded form; gzip, deflate and br bodies are decompressed. Malformed JSON answers `400`, a body over `web.body.limit` (default `100kb`) answers `413`. ### Validating the body Add `@celljs/validation` and `class-validator`, then type the parameter with a class: ```ts import { IsString, Length } from 'class-validator'; export class CreateUser { @IsString() @Length(1, 20) name: string; } @Post() create(@Body() user: CreateUser) { /* user is a CreateUser instance and is valid */ } ``` An invalid body answers `400` with the errors as JSON; the method is not called. Options are under `validation` ([@celljs/validation](https://cell.cellbang.com/packages/validation)). ## Responses | Return / decorator | Response | | --- | --- | | an object, array, number, ... | JSON (`application/json`); the default view | | `@Text()` and a string | `text/plain` | | `@Html('page.mustache')` and an object | the Mustache template from `mvc.mustache.baseViewDir` (default `assets/views`) rendered with it | | `@File()` and a Buffer or a stream | the bytes, streamed | | `undefined` (`void`) | `200` with an empty body | | `@Header('cache-control', 'no-store')` | adds a response header | | `@Cookie('flavor', 'oat')` / `@Session('step', 'two')` | sets a cookie / a session value | `async` methods work the same way. To choose the status code, take the response: `@Res() res: Response` (with `import type { Response } from '@celljs/web/lib/node'`: it is an interface) or `Context.getResponse()`, and set `res.statusCode = 201`, then return the body as usual. ## Errors ```ts import { HttpError, NotFoundError } from '@celljs/web/lib/node'; throw new NotFoundError('No user 42'); // 404 throw new HttpError(409, 'Name already taken'); // any status throw new Error('boom'); // 500 ``` The status comes from `HttpError.statusCode`; any other error answers `500`. Stack traces are never sent to the client: a `500` is logged with its stack (and its `cause`) at the error level, a 4xx `HttpError` (or an error a handler answered with a redirect or a 4xx) only at the debug level. The client gets the message of a 4xx `HttpError`; in production a 5xx or any other error answers only its status text, unless the `HttpError` was created with `{ expose: true }` or `web.errors.exposeMessages` is set. **Per controller**, a method marked `@Catch(ErrorType)` handles the errors of that type thrown in the controller, and its return value is the response: ```ts @Catch(QuotaError) @Text() onQuota(err: QuotaError) { return `Quota exceeded: ${err.message}`; } ``` **For the whole application**, add an `ErrorHandler`. Handlers are asked in order of `priority` (highest first) whether they `canHandle` the error; the first that can handles it, and if its `handle` throws, the next one is tried. The built-in handlers have priorities up to `2000`, so give yours more to run first: ```ts import { Component } from '@celljs/core'; import { Context, ErrorHandler } from '@celljs/web/lib/node'; @Component(ErrorHandler) export class QuotaErrorHandler implements ErrorHandler { readonly priority = 2100; async canHandle(ctx: Context, err: Error) { return err instanceof QuotaError; } async handle(ctx: Context, err: Error) { ctx.response.statusCode = 429; ctx.response.setHeader('content-type', 'application/json'); ctx.response.end(JSON.stringify({ error: err.message })); } } ``` ## Middleware A middleware is a component bound to `Middleware`. It sees every request before the controllers; higher `priority` runs earlier. ```ts import { Component } from '@celljs/core'; import { Context, HTTP_MIDDLEWARE_PRIORITY, HttpError, Middleware } from '@celljs/web/lib/node'; @Component(Middleware) export class ApiKeyMiddleware implements Middleware { readonly priority = HTTP_MIDDLEWARE_PRIORITY - 10; // after trace, CORS and body parsing async handle(ctx: Context, next: () => Promise): Promise { if (ctx.request.path.startsWith('/admin') && ctx.request.get('x-api-key') !== process.env.API_KEY) { throw new HttpError(401, 'Missing or wrong API key'); } await next(); } } ``` Middleware written for express that only needs Node's `req` / `res` can be wrapped with `convertExpressMiddleware(fn)`. ### The request context Anywhere during a request, also in services far from the controller, `Context` from `@celljs/web/lib/node` gives the current request: `Context.getRequest()`, `Context.getResponse()`, `Context.getSession()`, `Context.getCookies()`, `Context.getTraceId()`, and request-scoped attributes with `Context.setAttr(key, value)` / `Context.getAttr(key)`. It follows `await`, timers and promise chains. ## Built in | Feature | Configuration | | --- | --- | | Trace id: every response has an `X-Cell-Trace-ID` header (`Context.getTraceId()`) | `web.trace` | | Health check: `GET /health` | `web.health` | | CORS headers, every origin by default | `web.cors` (`origin`, `methods`, `credentials`, ...) | | Signed cookies and sessions stored in a cookie | `web.cookies.keys`, `web.session`. **Set your own `web.cookies.keys` in production** | | Request body limits | `web.body` | The [@celljs/web reference](https://cell.cellbang.com/packages/web) has every option. ## More | Need | Package | | --- | --- | | OpenAPI document (`/openapi.json`) and Swagger UI (`/api-docs`) from the controllers | [`@celljs/openapi`](https://cell.cellbang.com/packages/openapi) | | Login, sessions, `@Authenticated()`, `@PreAuthorize()` | [`@celljs/security`](https://cell.cellbang.com/packages/security) | | JSON Web Tokens | [`@celljs/jwt`](https://cell.cellbang.com/packages/jwt) | | Serving the frontend build | [`@celljs/serve-static`](https://cell.cellbang.com/packages/serve-static) | | Typed calls from the frontend or other services | [Frontend and RPC](https://cell.cellbang.com/guide/frontend) | | gRPC services | [`@celljs/grpc`](https://cell.cellbang.com/packages/grpc) | # Frontend and RPC A Cell application can have a React frontend in the same project as its backend. `cell dev` serves both on one port (HMR for the frontend, hot reload for the backend), and `cell build` puts the frontend into `.cell/output/public/`, served by the backend through `@celljs/serve-static` or by the platform's CDN (Cloudflare, Vercel). ```bash npx @celljs/cli init fullstack my-app ``` The fullstack template depends on `@celljs/react` (which adds the `frontend` build target), `@celljs/rpc` and `@celljs/serve-static`. ## Pages Pages are the files of `src/browser/pages/`; the default export of each is the page component. ```text src/browser/pages/ ├── _layout.tsx around every page below it ├── index.tsx / ├── about.tsx /about └── users/ ├── index.tsx /users └── [id].tsx /users/:id ``` ```tsx // src/browser/pages/users/[id].tsx import { Link, useParams } from '@celljs/react'; export default function User() { const { id } = useParams(); return <>

User {id}

All users; } ``` ```tsx // src/browser/pages/_layout.tsx import { NavLink } from '@celljs/react'; import type { PropsWithChildren } from 'react'; export default function Layout({ children }: PropsWithChildren) { return <>{children}; } ``` - `[...rest].tsx` matches the rest of the path: `pages/[...rest].tsx` is the page for unknown URLs. - `_layout.tsx` gets the page as `children`. Layouts nest, outermost first, and stay mounted while the user moves between their pages; pages without one get the `@DefaultLayout`. - A page that throws, or whose chunk does not load, shows an error inside its layout instead of blanking the application, and is tried again when the user goes to another path. The nearest `_error.tsx` (its directory or above) is what shows, with `{ error, reset }` as props (`ErrorFallbackProps` of `@celljs/react`); without one, a message and a button to try again. - Files and directories whose name starts with `_`, and tests, are not pages: keep the components of a page next to it in `_components/`. - Every page is a chunk of its own, loaded when it is visited. Two files for one path (`about.tsx` and `about/index.tsx`) fail with `CELL_ROUTE_CONFLICT`. - No imports to add anywhere: `cell dev` picks up added and removed pages. The routes are generated into `.cell/generated/frontend/routes.js`, and `npx cell info --json` lists them (`frontend.routes`). Routing is React Router 7. `@celljs/react` re-exports what pages need: `Link`, `NavLink`, `Navigate`, `Outlet`, `useParams`, `useNavigate`, `useLocation`, `useSearchParams`, `useMatch`. `react.router.type` in `cell.yml` chooses `browser` (default) or `hash` routing. `@App`, `@DefaultLayout` and `@Router` from `@celljs/react` replace the root component, the default layout and the router; a package adds pages with `@View` (see the reference). `src/browser/module.ts` is the DI module of the frontend: it imports the files of your frontend services (see below). The frontend gets the configuration keys components mark public and your `app.public`; they are evaluated at build time and visible to anyone. In the browser, read it with `useValue('html.title')` in a React component and `@Value` in a DI component. ## Services in components Frontend services (state, API clients, anything shared) are DI components, as on the backend. React components get them, and configuration, with hooks from `@celljs/react`: ```tsx import { useInject, useValue } from '@celljs/react'; import { UserService } from './user-service'; // @Component(UserService) in a file module.ts imports function Header() { const userService = useInject(UserService); // resolved once per component instance const title = useValue('app.public.title', 'My app'); return
{title}: {userService.name}
; } ``` The shell provides the application's container through `ContainerContext`. To test a component, render it inside `` with a container of your own. Keep pages, layouts and contexts plain React components; what they use comes from the container. `useRpc(id)` gives a backend service (next section) and `useLocale()` gives `[locale, setLocale]`: the language the user selected among `widget.locales`, kept in `localStorage`. For translated messages, install react-intl and import `@celljs/react/lib/browser/intl` once in `src/browser/module.ts`; `useIntl()` and `` then follow the selected locale. The hooks are listed in the [README of @celljs/react](https://cell.cellbang.com/packages/react). ## Calling the backend: RPC Instead of writing HTTP endpoints and fetch calls for your own frontend, share an interface and call it like a local object. ```ts // src/common/greeting-protocol.ts: imported by both sides export const GreetingServer = Symbol('GreetingServer'); export interface GreetingServer { greet(name: string): Promise; } ``` ```ts // src/node/greeting-server.ts: imported by src/node/module.ts import { Rpc } from '@celljs/rpc'; import { GreetingServer } from '../common/greeting-protocol'; @Rpc(GreetingServer) export class GreetingServerImpl implements GreetingServer { async greet(name: string): Promise { return `Hello, ${name}!`; } } ``` ```tsx // src/browser/pages/index.tsx import { useRpc } from '@celljs/react'; import { GreetingServer } from '../../common/greeting-protocol'; export default function Home() { const greetingServer = useRpc(GreetingServer); const [message, setMessage] = React.useState('Loading…'); React.useEffect(() => { greetingServer.greet('browser').then(setMessage); }, [greetingServer]); return

{message}

; } ``` - `@Rpc(Id)` is a component (inject services into it as usual) and also an endpoint: JSON-RPC over HTTP `POST` at `//`, by default `/api/GreetingServer` (the description of the symbol). - Every method returns a Promise; arguments and results travel as JSON (JSON-RPC 2.0). Only the methods the class declares can be called. An error thrown on the server rejects the promise in the caller: an `ApplicationError` as itself, others as an `RpcError` with `status`, whose message is hidden in production unless it is a 4xx `HttpError`. - Parameters typed with a class are validated when `@celljs/validation` is installed, as for controllers. - In tests, `app.get(GreetingServer)` from [`createTestApp()`](https://cell.cellbang.com/guide/testing) calls the implementation directly. ### The backend calls the frontend: WebSocket ```yaml rpc: transport: websocket ``` Calls then go over one WebSocket connection per browser tab (`//ws`), through the same middlewares, session and security as over HTTP, and the backend can call the frontend back. Declare what the frontend offers with `@RpcClient` (an interface in `src/common/`, the class in `src/browser/`, imported by its module): ```ts // src/browser/notice-client.ts import { RpcClient } from '@celljs/rpc'; import { NoticeClient } from '../common/notice-protocol'; @RpcClient(NoticeClient) export class NoticeClientImpl implements NoticeClient { async notify(text: string): Promise { console.log(text); } } ``` ```ts // in an @Rpc service of the backend import { RpcContext } from '@celljs/rpc/lib/node'; const connection = RpcContext.requireConnection(); // the caller's tab await connection.client(NoticeClient).notify('order shipped'); connection.onClose(() => { /* the tab went away */ }); ``` `RpcConnectionManager` (`@celljs/rpc/lib/node`) lists every open connection, to notify all tabs. Where no connection can be made (the serverless presets, a proxy without WebSocket support) calls use HTTP, and `RpcContext.requireConnection()` throws `RpcNoConnectionError`. Pages of other origins cannot connect unless `rpc.websocket.allowedOrigins` lists them. The [reference](https://cell.cellbang.com/packages/rpc) has the other keys. ### From another backend A backend calls a service of another Cell backend with an injected proxy: ```ts import { Component } from '@celljs/core'; import { Autorpc } from '@celljs/rpc'; @Component() export class Greeter { @Autorpc(GreetingServer) protected readonly greetingServer: GreetingServer; } ``` ```yaml rpc: defaultEndpoint: "http://greetings.internal:3000{rpcPath}/{serviceIdentifier}" endpoint: # per service, wins over defaultEndpoint GreetingServer: "${env.GREETING_URL}" ``` `RpcUtil.create(endpoint, Id)` makes the same proxy without the container. ## Frontend-only and backend-only projects The targets follow the installed components: `@celljs/react` adds `frontend`, and `backend` is always there. `targets` in `cell.config.ts` sets them explicitly. The [@celljs/react reference](https://cell.cellbang.com/packages/react) lists the annotations and configuration keys. # Testing and checking Two tools close the loop after every change: `cell check` finds mistakes without running the application, and `createTestApp()` runs it inside the test process. ## `cell check` ```bash npx cell check # exit code 1 when there are errors npx cell check --json # one JSON document: passed, summary, issues ``` | Check | Finds | | --- | --- | | `config` | Missing entry, unknown `-m` modes, `CELL_*` environment variables that match no key | | `types` | `tsc --noEmit` of the project, each diagnostic with file, line and `TSxxxx` code | | `decorators` | `experimentalDecorators` / `emitDecoratorMetadata` missing in `tsconfig.json` | | `di` | Loads the backend container without starting it and resolves every binding: `CELL_DI_NO_BINDING`, `CELL_DI_AMBIGUOUS`, `CELL_DI_CIRCULAR`, ... | Each issue has a `code`, a `file` and `line` when known, and a `hint`: a one-sentence fix. `--skip ` leaves one out; components can add their own checks (`typeorm:entities`, for example). ## `createTestApp()` ```ts // test/users.spec.ts import { afterAll, beforeAll, describe, expect, it } from 'bun:test'; import { join } from 'node:path'; import { createTestApp, type TestApp } from '@celljs/cli'; describe('users', () => { let app: TestApp; beforeAll(async () => { app = await createTestApp({ root: join(__dirname, '..') }); }, 60000); // the first start compiles the sources afterAll(() => app?.close()); // stops the application: onStop runs it('creates a user over HTTP', async () => { const res = await app.fetch('/users', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Ada' }), }); expect(res.status).toBe(200); expect(await res.json()).toMatchObject({ name: 'Ada' }); }); it('calls a service directly', async () => { const { UserService } = await app.import('/src/node/user-service.ts'); // the module the application loaded expect(await app.get(UserService).count()).toBe(1); }); }); ``` - The backend is loaded from source the way `cell dev` loads it: nothing is built and no port is opened. `fetch(path, init)` sends the request through the whole HTTP chain (middleware, security, controllers, error handlers) and returns a standard `Response`. - `app.get(Id)` returns a component of the application, `app.container.getAll(Id)` all of them. The application runs in its own module instances: **take classes from `await app.import('/src/node/user-service.ts')`** (a package by its name), not from an `import` of the test file. A class or `Symbol` the test imports itself is matched by name, which works for symbols and for classes without injections, but a class with `@Value` / `@Autowired` imported into the test process fails with `CELL_DI_NO_BINDING`. - The active mode is `test`: put test-only configuration in `cell.test.yml`, for example an in-memory database. `createTestApp({ mode: ['seed'] })` adds modes after `test`. - Startup failures throw a `CellError` (`CELL_DEV_BACKEND_FAILED` with the failing file). `fetch` without an HTTP backend throws `CELL_TEST_APP_NO_HTTP`. Run the tests with `bun test` (the templates' `npm test` runs `bun test ./test`). Each test file gets one application; tests in a file share its state, so make them independent of order or reset data in `beforeEach`. ### Example: a test database ```yaml # cell.test.yml typeorm: ormConfig: - type: sqljs # in memory, from the sql.js package synchronize: true ``` ## Unit tests without the application Plain classes can be tested with `new`, passing collaborators by hand. Components that use property injection need the container: prefer `createTestApp()` over assembling one yourself. ## Debugging a running application While `cell dev` runs, `POST /_cell/mcp` on the same port is an MCP server for coding agents with `get_errors` (startup errors, failed reloads, 5xx requests, error logs), `list_routes`, `list_bindings`, `get_config`, `recent_requests` and `logs`. See [Working with coding agents](https://cell.cellbang.com/guide/coding-agents). # Building and deploying Your code does not change between platforms: a **preset**, chosen at build time, decides the shape of the output and how it is deployed. ```bash npx cell build # preset node: .cell/output npx cell build --preset cloudflare npx cell deploy --preset fc --dry-run # the plan; nothing is uploaded ``` The preset comes from `--preset`, else `preset` in `cell.config.ts`, else the `CELL_PRESET` variable, else the CI platform (`cloudflare` in Workers Builds, `vercel` on Vercel), else `node`. `npx cell info` shows which one applies and why. ## Presets | Preset | Output | Run locally | Deploy | | --- | --- | --- | --- | | `node` (default) | Node.js HTTP server, `server/index.mjs` | `cell preview` | Copy `.cell/output` anywhere Node.js 18+, Bun or Deno runs | | `cli` | A command-line program on Cell DI, no HTTP server | `cell preview` | Distribute the file or an executable | | `cloudflare` | Worker + static assets + `wrangler.json` | `cell preview` (workerd through Wrangler) | `cell deploy --preset cloudflare` | | `fc` | Alibaba Cloud Function Compute 3.0, custom runtime | `cell preview` | `cell deploy --preset fc` | | `scf` | Tencent Cloud SCF web function | `cell preview` | `cell deploy --preset scf` | | `lambda` | AWS Lambda with a function URL | `cell preview` | `cell deploy --preset lambda` | | `vercel` | Vercel Build Output API (`.vercel/output`) | `cell preview` | `cell deploy --preset vercel` | ## The output ```text .cell/output/ ├── server/index.mjs # the backend: one ES module with every dependency inlined ├── public/ # the frontend build, when there is one └── manifest.json # preset, format, targets: what cell preview and cell deploy read ``` The output does not need the project's `node_modules`. Packages that cannot be bundled (native modules, `build.external` in `cell.config.ts`) are copied into `server/node_modules`. | Option | Result | | --- | --- | | `--single` | The frontend is embedded in `server/index.mjs`: one file runs the whole application | | `--compile [bun\|node\|deno]` | An executable `.cell/output/app` that needs no Node.js on the host | | `-m ` | Also reads `cell..yml` (`production` is always active in builds) | | `--sourcemap`, `--format cjs`, `--out-dir ` | Source maps, CommonJS output, another directory | ## Configuration in production - **Backend configuration is evaluated when the server starts.** The same build runs everywhere; give each environment its values through environment variables, either referenced in `cell.yml` (`"${env.DATABASE_URL}"`) or as overrides (`CELL_SERVER__PORT=8080`). See [configuration](https://cell.cellbang.com/guide/project#application-configuration-cell-yml). - **Never mark a secret public.** Configuration is server-only unless a schema marks it public or it is under `app.public`; frontend values are evaluated at build time and shipped to browsers. - The server listens on `127.0.0.1:3000` by default. In a container set `CELL_SERVER__HOST=0.0.0.0`; when the platform gives the port in `PORT`, set `CELL_SERVER__PORT='${env.PORT}'`. - Set your own `web.cookies.keys` (signing key of cookies and sessions), e.g. `CELL_WEB__COOKIES__KEYS='["..."]'`. - **Security checks.** `cell check` lists settings that are unsafe in production (`CELL_SECURITY_*` warnings: the development cookie key, a short `jwt.secret`...). A production start (`NODE_ENV=production`, which `cell build` writes into the server bundle) logs the same warnings; set `securityChecks: fail` (or `CELL_SECURITY_CHECKS=fail`) to refuse to start instead, `off` to skip them. - **Error messages.** In production a server error answers with its status text only (`Internal Server Error`); its message and stack go to the log. 4xx `HttpError` messages are sent. `web.errors.exposeMessages` overrides this. ### A container image ```dockerfile FROM node:22-alpine AS build WORKDIR /app COPY package*.json ./ RUN npm install COPY . . RUN npx cell build FROM node:22-alpine ENV NODE_ENV=production ENV CELL_SERVER__HOST=0.0.0.0 WORKDIR /app COPY --from=build --chown=node:node /app/.cell/output ./ USER node EXPOSE 3000 CMD ["node", "server/index.mjs"] ``` ## `cell deploy` `cell deploy` builds with the preset, makes a plan (the resources it will create or update), then applies it. `--dry-run` stops after the plan and works without credentials; `--skip-build` deploys the existing output. | Preset | Credentials (environment) | Platform options in `cell.config.ts` | | --- | --- | --- | | `cloudflare` | `CLOUDFLARE_API_TOKEN` (and `CLOUDFLARE_ACCOUNT_ID`), or `npx wrangler login` | `wrangler.jsonc` in the project root: name, vars, bindings, routes | | `fc` | `ALIBABA_CLOUD_ACCESS_KEY_ID`, `ALIBABA_CLOUD_ACCESS_KEY_SECRET` | `fc: { name, region, memory, cpu, timeout, concurrency, env }` | | `scf` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` | `scf: { name, region, namespace, memory, timeout, env }` | | `lambda` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`, or `AWS_PROFILE` | `lambda: { name, region, architecture, memory, timeout, role, env }` | | `vercel` | `VERCEL_TOKEN`, or `npx vercel login` | `vercel: { project, scope, environment, memory, maxDuration, regions }` | ```ts // cell.config.ts import { defineConfig } from '@celljs/cli'; export default defineConfig({ preset: 'fc', fc: { region: 'cn-shanghai', memory: 512, env: { DATABASE_URL: process.env.DATABASE_URL ?? '' } }, }); ``` The function platforms (`fc`, `scf`, `lambda`) call the cloud APIs directly: no vendor SDK or CLI is installed. Wrangler and the Vercel CLI are used from the project when installed, otherwise through `npx`. Every failure has a code: `CELL_DEPLOY_CREDENTIALS_MISSING`, `CELL_DEPLOY_UNAUTHORIZED`, `CELL_DEPLOY_PACKAGE_TOO_LARGE`, ... Platform details (custom domains, runtimes, package size limits, what each deployer creates) are in the [CLI reference](https://cell.cellbang.com/cli#presets). ## Hooks Shell commands on toolchain events, for steps such as code generation: ```ts export default defineConfig({ hooks: { 'build:before': 'prisma generate', // a failure cancels the build 'output:done': 'cp extra/* "$CELL_OUT_DIR/server/"', }, }); ``` Extensions that need code (Vite plugins, extra output files, checks, MCP tools) are toolchain modules; see the [CLI reference](https://cell.cellbang.com/cli). # Working with coding agents Cell is designed so that a coding agent (Claude Code, Codex, Cursor, CodeBuddy, pi, ...) can use it correctly on the first try: one way to write each thing, structured errors with fixes, machine-readable commands and documentation that matches the installed version. This page is for the people who set agents up; the guide the agents themselves read is [AGENTS.md](https://cell.cellbang.com/agent-guide). ## What a project gets `cell init` writes these files, and `npx cell agent sync` rewrites the Cell part of them after you upgrade Cell or add a component: | File | For | | --- | --- | | `AGENTS.md` | The Cell guide of the installed `@celljs/cli` version, followed by the guide snippets of installed components. Cell only rewrites the part between `` and ``; write project rules outside it | | `CLAUDE.md` | `@AGENTS.md`, for Claude Code | | `.agents/skills/cell/SKILL.md` | The same guide as an agent skill | | `.mcp.json` | Starts `npx cell mcp` | Add `npx cell agent sync` to your upgrade routine, or run it in a `postinstall` script. ## MCP `npx cell mcp` is a stdio MCP server for the project. Its tools return the same JSON as the commands: | Tool | Command | Side effects | | --- | --- | --- | | `info` | `cell info` | none | | `check` | `cell check` | none | | `config_get` | `cell config get` | none | | `docs_search` | `cell docs` | none | | `build` | `cell build` | writes the output directory | | `deploy_plan` | `cell deploy --dry-run` | writes the output directory; may reach the network | There is deliberately no deploy tool: deploying stays with a person running `cell deploy`. ```jsonc // .mcp.json (Claude Code, Cursor, CodeBuddy) or .cursor/mcp.json { "mcpServers": { "cell": { "command": "npx", "args": ["cell", "mcp"] } } } ``` ```toml # Codex: ~/.codex/config.toml [mcp_servers.cell] command = "npx" args = ["cell", "mcp"] ``` While `cell dev` runs, `http://127.0.0.1:/_cell/mcp` is a second MCP server (HTTP) for the running application: `get_errors`, `list_routes`, `list_bindings`, `get_config`, `recent_requests` and `logs`. It only accepts requests from the local machine. ## Commands agents use | Command | Gives | | --- | --- | | `npx cell check --json` | Every type, configuration, decorator and DI problem with `code`, `file`, `line` and `hint` | | `npx cell info --json` | Preset, components, DI modules, entries, hooks, output state | | `npx cell config get --why` | A merged configuration value and where each layer came from | | `npx cell docs ` | Sections of the docs shipped with the installed packages (their READMEs, these guides and the CLI reference) | | `npx cell build --json`, `npx cell deploy --dry-run --json` | One-line results instead of progress output | Every command accepts `--json` (one document with `schemaVersion` on stdout, everything else on stderr), never prompts when not on a terminal, and side-effecting commands have `--dry-run`. Exit codes: 0 success, 1 failure, 2 usage error. ## Errors Every toolchain and runtime error has a stable code, a message, a one-sentence fix and a docs link: ```text [CELL_DI_NO_BINDING] No binding for Symbol(Mailer), needed by Signup.mailer (TodoController → Signup → Symbol(Mailer)). fix: Bind the identifier in a DI module (@Component() in a file the module imports), or mark the injection @Optional(). docs: https://cell.cellbang.com/errors/cell-di-no-binding ``` The [error code list](https://cell.cellbang.com/errors/) has a page per code. Codes and `--json` fields stay stable within 4.x. ## Documentation for models The docs site publishes [`llms.txt`](https://cell.cellbang.com/llms.txt) (an index of every page) and [`llms-full.txt`](https://cell.cellbang.com/llms-full.txt) (the guides, the CLI reference and the package references in one file) for agents that read the web. Inside a project, `cell docs` is better: it searches the version you have installed. ## Habits that work - Ask the agent to run `npx cell check` and the tests after every change; both are fast and their output is made for it. - Keep project rules in `AGENTS.md` outside the Cell markers. - Let the agent use `cell deploy --dry-run` freely; keep `cell deploy` itself, and the credentials it needs, for people. # Package catalog All packages are published under [`@celljs`](https://www.npmjs.com/org/celljs) with the same version; keep them on one version. Installing a component package is enough to use it: its DI modules and `cell.yml` are loaded automatically (see [components](https://cell.cellbang.com/guide/project#components)). Each name links to its reference. ## Which package do I need? | I want to | Add | | --- | --- | | Write REST controllers | `@celljs/mvc` (with `@celljs/express-adapter` as the server entry; both are in the templates) | | Validate request bodies | `@celljs/validation` + `class-validator` | | Publish OpenAPI / Swagger UI | `@celljs/openapi` | | Call my backend from my frontend with types | `@celljs/rpc` | | Build a React frontend | `@celljs/react` + `@celljs/serve-static` | | Use a SQL database | `@celljs/typeorm` + a driver (`mysql2`, `pg`, `better-sqlite3`, `sql.js`, ...) | | Cache values in memory or Redis | `@celljs/cache` | | Run cron jobs | `@celljs/schedule` | | Retry failing calls with back-off | `@celljs/retry` | | Separate commands, queries and events | `@celljs/cqrs` | | Log with winston | `@celljs/logger` | | Log users in, protect routes | `@celljs/security` | | Issue and verify JWTs | `@celljs/jwt` | | Sign in with GitHub, Google or another OAuth2 / OIDC provider | `@celljs/oauth2-client` (with `@celljs/security`) | | Call other HTTP APIs | `@celljs/http` (`RestOperations`) | | Serve or call gRPC services | `@celljs/grpc` | | Talk to LLMs | `@celljs/ai-core` + `@celljs/ai-pi` | | Build an agent whose tools are my services | `@celljs/ai-agent` | | Run a coding agent inside my application | `@celljs/ai-coding-agent` | | Build a terminal UI | `@celljs/tui` (with the `cli` preset) | | Store files in Alibaba Cloud OSS / Tencent Cloud COS | `@celljs/oss` / `@celljs/cos` | | Take payments with WeChat Pay, Alipay or Stripe | `@celljs/wechat-pay` / `@celljs/alipay` / `@celljs/stripe` | ## Runtime | Package | What it provides | | --- | --- | | [`@celljs/core`](https://cell.cellbang.com/packages/core) | DI container, `@Component` / `@Autowired` / `@Value`, AOP, configuration, application lifecycle, logger, request context. Every application depends on it | | [`@celljs/web`](https://cell.cellbang.com/packages/web) | HTTP server on `node:http`: middleware chain, body parsing, cookies, sessions, CORS, trace ids, health check, error handlers | | [`@celljs/mvc`](https://cell.cellbang.com/packages/mvc) | Controllers: `@Controller`, `@Get` / `@Post` / ..., parameter decorators, JSON / text / HTML / file responses, `@Catch` | | [`@celljs/express-adapter`](https://cell.cellbang.com/packages/express-adapter) | The backend entry that starts the application and listens on `server.host` / `server.port`. Despite its name it has no express dependency | | [`@celljs/http`](https://cell.cellbang.com/packages/http) | HTTP client (`RestOperations`, on `fetch`) and the shared `HttpStatus`, `HttpHeaders`, `MediaType` | | [`@celljs/validation`](https://cell.cellbang.com/packages/validation) | class-validator / class-transformer validation of controller and RPC parameters | | [`@celljs/openapi`](https://cell.cellbang.com/packages/openapi) | OpenAPI 3.0 document and Swagger UI built from the controllers | | [`@celljs/rpc`](https://cell.cellbang.com/packages/rpc) | `@Rpc` services and typed proxies (`useRpc` of `@celljs/react`, `@Autorpc`) over JSON-RPC on HTTP | | [`@celljs/serve-static`](https://cell.cellbang.com/packages/serve-static) | Serves the frontend build with ETags, ranges and single-page-application fallback | | [`@celljs/typeorm`](https://cell.cellbang.com/packages/typeorm) | TypeORM data sources, `OrmContext` repositories and `@Transactional()` | | [`@celljs/cache`](https://cell.cellbang.com/packages/cache) | Named cache managers on cache-manager (memory, Redis and other stores) | | [`@celljs/schedule`](https://cell.cellbang.com/packages/schedule) | `@Scheduler` classes with `@Cron` methods | | [`@celljs/retry`](https://cell.cellbang.com/packages/retry) | Retry templates with retry and back-off policies | | [`@celljs/cqrs`](https://cell.cellbang.com/packages/cqrs) | Command, query and event buses, sagas and aggregates | | [`@celljs/grpc`](https://cell.cellbang.com/packages/grpc) | gRPC servers and clients on `node:http2` with its own `.proto` parser, no code generation | | [`@celljs/logger`](https://cell.cellbang.com/packages/logger) | winston-backed `Logger` | | [`@celljs/security`](https://cell.cellbang.com/packages/security) | Form login, HTTP Basic, logout, `@Authenticated()`, `@PreAuthorize()`, policies, password hashing | | [`@celljs/jwt`](https://cell.cellbang.com/packages/jwt) | `JwtService`: sign and verify JSON Web Tokens with `node:crypto` | | [`@celljs/oauth2-core`](https://cell.cellbang.com/packages/oauth2-core) | OAuth 2.0 / OpenID Connect types shared by the OAuth2 packages | | [`@celljs/oauth2-client`](https://cell.cellbang.com/packages/oauth2-client) | Login with OAuth2 / OIDC providers; client credentials, password and refresh-token grants | | [`@celljs/oauth2-jose`](https://cell.cellbang.com/packages/oauth2-jose) | JWK sets and JWS / JWT decoding for the OAuth2 packages | | [`@celljs/wechat-pay`](https://cell.cellbang.com/packages/wechat-pay) | WeChat Pay API v3 client and a verified notification endpoint | | [`@celljs/alipay`](https://cell.cellbang.com/packages/alipay) | Alipay gateway client and a verified notification endpoint | | [`@celljs/stripe`](https://cell.cellbang.com/packages/stripe) | Stripe client and a verified webhook endpoint | ## Frontend | Package | What it provides | | --- | --- | | [`@celljs/react`](https://cell.cellbang.com/packages/react) | React frontend: pages of `src/browser/pages`, routing, layouts, hooks to the DI container, the application shell | | [`@celljs/widget`](https://cell.cellbang.com/packages/widget) | Frontend infrastructure `@celljs/react` builds on: widgets, themes, locales | | [`@celljs/tui`](https://cell.cellbang.com/packages/tui) | Terminal user interfaces on pi-tui, including a chat for agents | ## AI | Package | What it provides | | --- | --- | | [`@celljs/ai-core`](https://cell.cellbang.com/packages/ai-core) | Provider-neutral `ChatModel`, `EmbeddingModel`, `PromptTemplate` and the routing `ProxyChatModel` | | [`@celljs/ai-pi`](https://cell.cellbang.com/packages/ai-pi) | 20+ model providers through pi-ai, addressed as `pi:/` | | [`@celljs/ai-agent`](https://cell.cellbang.com/packages/ai-agent) | Tool-using agents whose tools are Cell components; events over SSE | | [`@celljs/ai-coding-agent`](https://cell.cellbang.com/packages/ai-coding-agent) | pi coding agent sessions managed by DI | ## Cloud | Package | What it provides | | --- | --- | | [`@celljs/cloud`](https://cell.cellbang.com/packages/cloud) | Shared cloud abstractions: credentials, region, account and the `ObjectStorageService` interface | | [`@celljs/oss`](https://cell.cellbang.com/packages/oss) | `ObjectStorageService` on Alibaba Cloud OSS | | [`@celljs/cos`](https://cell.cellbang.com/packages/cos) | `ObjectStorageService` on Tencent Cloud COS | Deploying to Function Compute, SCF, Lambda, Vercel or Cloudflare needs no package: choose the preset ([Building and deploying](https://cell.cellbang.com/guide/deployment)). ## Toolchain | Package | What it provides | | --- | --- | | [`@celljs/cli`](https://cell.cellbang.com/cli) | The `cell` command, presets, deployers, `createTestApp()`, these guides | | [`@celljs/vite`](https://cell.cellbang.com/packages/vite) | The Vite plugin and `CellBuilder` behind `cell build` and `cell dev` | | [`@celljs/kit`](https://cell.cellbang.com/packages/kit) | Shared types for toolchain modules and presets: `CellError`, events, `definePreset` | # @celljs/cli The Cell 4 command-line tool. Reads `cell.config.ts`, builds applications according to presets, runs the built application, and deploys build artifacts. For getting started and usage guides, see the [guide](https://cell.cellbang.com/guide/getting-started) (distributed with this package in `docs/`, searchable with `cell docs`). For architecture details, see [docs/design/architecture.md](https://github.com/cellbang/cell/blob/main/docs/design/architecture.md); for migrating from 3.x, see [docs/migration-4.md](https://github.com/cellbang/cell/blob/main/docs/migration-4.md). Requires Node.js `^20.19.0 || >=22.12.0`. Install in your project: ```bash npm install -D @celljs/cli npx cell build ``` ## Commands | Command | Purpose | | --- | --- | | `cell init [template] [dir]` | Create a project from a template (`backend`, `fullstack`), with AGENTS.md, cell skill, and `.mcp.json` included; lists templates when invoked without arguments | | `cell dev` | Development: frontend and backend on the same port, HMR for frontend, automatic backend reload on file changes, auto-restart on config changes | | `cell build` | Build to `.cell/output`: `server/index.mjs` (single file with inlined dependencies), `public/` (frontend), `manifest.json` | | `cell info` | Describe the project: preset and its source, mode, targets, components, DI modules, hooks, and build status. Read-only; does not build or execute hooks | | `cell check` | Perform config, TypeScript types, decorator setup, backend DI binding, and module registration checks; exits with code 1 on error | | `cell preview` | Run build artifacts according to `manifest.json` without rebuilding | | `cell deploy` | Build with the selected preset, then publish via its deployer; use `--dry-run` to show the plan only | | `cell config get ` | Get the merged value of a config key; use `--why` to see which files and environment variables set it | | `cell docs ` | Search the documentation shipped with installed components and modules, plus guides and this document from `@celljs/cli` (matching the project's Cell version) | | `cell mcp` | MCP server on stdio that exposes `info`, `check`, `config_get`, `docs_search`, `build`, `deploy_plan`, and tools registered by modules to coding agents | | `cell agent sync` | Rewrite the parts of AGENTS.md managed by Cell and `.agents/skills/cell/SKILL.md` according to installed versions | ## Presets | preset | Artifact | Run / preview | Deployer | | --- | --- | --- | --- | | `node` (default) | Node.js HTTP server: `node .cell/output/server/index.mjs` | `cell preview` runs the application with Node.js | None; deploy artifacts manually | | `cloudflare` | Cloudflare Workers: `server/worker.mjs` + `wrangler.json`, frontend as static assets | `cell preview` runs via `wrangler dev` in local workerd | `cloudflare` (Wrangler) | | `cli` | Node.js CLI program: `node .cell/output/server/index.mjs` starts the application without listening on a port; components (e.g., `ApplicationLifecycle`) perform the work. Does not require `@celljs/express-adapter` or report missing entry | `cell preview` runs with Node.js, inherits the current terminal | None | | `fc` | Alibaba Cloud Functions (3.0) custom runtime: artifact with `bootstrap` startup script, frontend served from `public/` by the server | Run with Node.js | `fc` (Functions API) | | `scf` | Tencent Cloud SCF Web functions: artifact with `scf_bootstrap` | Run with Node.js | `scf` (Cloud Functions API) | | `lambda` | AWS Lambda + function URL: artifact with `server/lambda.mjs` handler | Run with Node.js | `lambda` (AWS API) | | `vercel` | Vercel Build Output API: `.vercel/output`, frontend on CDN, backend as a single function | Run the function bundle with Node.js; frontend served from `static/` | `vercel` (Vercel CLI) | Options for each preset are written in `cell.config.ts` in fields matching the preset name (e.g., `fc: { region: 'cn-shanghai' }`), validated against the preset's schema, and unknown options raise `CELL_CONFIG_INVALID`. Switching presets preserves other preset fields without error. ### cloudflare ```bash cell build --preset cloudflare # Build to .cell/output cell preview # wrangler dev runs in workerd (Cloudflare runtime), default port 8787 cell deploy --preset cloudflare --dry-run # Build and run wrangler deploy --dry-run, show plan, do not upload cell deploy --preset cloudflare # Build and deploy, output workers.dev address ``` - **Runtime**: The Cell backend runs on Workers' `node:http` server (requires `nodejs_compat`), provided by `@celljs/express-adapter`'s `http` entry—same as the `node` preset. Cloudflare serves the frontend (`public/`) as static assets; only requests that don't match static files enter the Worker. Artifacts support ESM only; `--format cjs` raises `CELL_CONFIG_CONFLICT`. - **Startup timing**: Workers allow code generation only during module loading phase (`new Function`, which some dependencies use), and I/O only during request handling. Application modules load when the Worker starts; `Application.start()` and `onStart` execute on the first request (`ApplicationHost.beforeStart`). Module load failures cause Worker startup to fail; `wrangler dev` / `wrangler deploy` report the error directly. - **Cloudflare configuration**: Write configuration in `wrangler.jsonc` or `wrangler.json` at the project root (optional, following Cloudflare's format): `name`, `compatibility_date`, `vars`, KV / D1 / R2 bindings, `routes`, etc. go here. During build, Cell generates `.cell/output/wrangler.json` from this as a base and sets: `main` (ignored with a warning if you specified it), `assets.directory` if a frontend exists, and `nodejs_compat` in `compatibility_flags`. Without a config file, `name` defaults to the package name from package.json (e.g., `@acme/shop` → `acme-shop`), and `compatibility_date` defaults to `2026-03-17`, the earliest allowed date (`node:http` server and `node:tty` have been available by default since then). `wrangler.toml` is not supported; convert to `wrangler.jsonc`. - **Single-page applications**: With a frontend, `assets.not_found_handling: "single-page-application"` is set by default. When the browser navigates to a nonexistent path, Cloudflare returns `index.html` (same as `@celljs/serve-static`'s `spa`); other requests like API calls still enter the Worker. If you specify `not_found_handling` in `wrangler.jsonc`, your project setting takes precedence. Without frontend files in the Worker, `@celljs/serve-static` detects the missing frontend directory and passes through; unknown paths return a normal 404. - **Custom domains**: If your domain's DNS is hosted on Cloudflare, add `"routes": [{ "pattern": "app.example.com", "custom_domain": true }]` to `wrangler.jsonc`. Cloudflare automatically creates DNS records and certificates on deploy, and `deployment.url` reports the domain. Networks that cannot reach `*.workers.dev` require a custom domain for access. - **Application config**: `vars` and secrets (set via `wrangler secret put`) enter `process.env`, so `${env.X}` and `CELL_*` variables in `cell.yml` work as expected. - **Wrangler**: Not a Cell dependency. If Wrangler is installed in your project, Cell uses it; otherwise, `npx --yes wrangler@4` installs it on first use (cached by npm). Wrangler manages credentials: set `CLOUDFLARE_API_TOKEN` (and optionally `CLOUDFLARE_ACCOUNT_ID`), or run `npx wrangler login` once. Alternatively, skip `cell deploy` and run `npx wrangler deploy --config .cell/output/wrangler.json` directly. - **Workers Builds** (Cloudflare's CI; set `WORKERS_CI=1`): automatically selects the `cloudflare` preset, build command `npx cell build`, and deploy command `npx cell deploy --skip-build`. - `wrangler dev` creates `.wrangler/` in the project root; add it to `.gitignore`. ### fc, scf, lambda These three function platforms use the same approach. The Cell server runs unchanged; the artifact directory is zipped as function code. The deployer calls cloud APIs directly (Cell implements request signing: Alibaba Cloud ACS3, Tencent Cloud TC3, AWS SigV4); no cloud SDK or CLI is needed. Function name defaults to the package name (`@acme/shop` → `shop`), or specify with `name`. Environment variables are written to the function (also includes `NODE_ENV=production`), and `${env.X}` and `CELL_*` in `cell.yml` work as expected. | preset | Runtime | Options | Credentials (environment variables) | Deploy result | | --- | --- | --- | --- | --- | | `fc` | `custom.debian10` runtime; `bootstrap` uses Node.js 20 from the image runtime (`node: '18'` optional) on port 9000 (`FC_SERVER_PORT`) | `name`, `region` (defaults to `ALIBABA_CLOUD_REGION_ID` or `cn-hangzhou`), `node`, `memory` (MB, default 1024), `cpu` (default based on 2 GB/core), `timeout` (default 60), `concurrency` (single instance, default 20), `env` | `ALIBABA_CLOUD_ACCESS_KEY_ID`, `ALIBABA_CLOUD_ACCESS_KEY_SECRET` (optionally `ALIBABA_CLOUD_SECURITY_TOKEN`) | Create or update function; create an anonymous HTTP trigger `http` if none exists (existing triggers are unchanged); return the `fcapp.run` address | | `scf` | Web functions (`Type: HTTP`); `scf_bootstrap` uses Node.js 18 from the runtime (`runtime: 'Nodejs16.13'` optional) on port 9000; only `/tmp` is writable | `name`, `region` (defaults to `TENCENTCLOUD_REGION` or `ap-guangzhou`), `namespace` (default `default`), `runtime`, `memory` (default 512), `timeout` (default 60), `env` | `TENCENTCLOUD_SECRET_ID`, `TENCENTCLOUD_SECRET_KEY` (optionally `TENCENTCLOUD_SESSION_TOKEN`) | Create or update function (deploy code first, then config, wait for Active state); create a public, unauthenticated function URL if none exists | | `lambda` | `nodejs22.x`; initialize on loopback port; `server/lambda.handler` converts function URL / HTTP API (payload v2) events to requests; responses are returned as base64, with multiple cookies in `cookies` | `name`, `region` (defaults to `AWS_REGION` or `us-east-1`), `runtime`, `architecture` (`x86_64` / `arm64`), `memory` (default 1024), `timeout` (default 30), `role`, `env` | `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` (optionally `AWS_SESSION_TOKEN`), or `AWS_PROFILE` / default from `~/.aws/credentials` | Create or reuse execution role `cell-lambda-execution` (log permissions only) if `role` is not specified; create or update function; create an `AuthType: NONE` function URL and its two public invoke permissions if none exists | - Code packages are uploaded directly with requests. Size limits: FC 100 MB, SCF 20 MB, Lambda 50 MB (after zipping). Oversized packages raise `CELL_DEPLOY_PACKAGE_TOO_LARGE`. Large sizes usually come from packages left in `server/node_modules` by `build.external`. - Without credentials, `--dry-run` still shows a plan (action is `deploy`, with a warning that the function's existence is unknown). With credentials, the cloud is queried and the action is `create` or `update`. Deployment without credentials raises `CELL_DEPLOY_CREDENTIALS_MISSING`; cloud API errors raise `CELL_DEPLOY_API_ERROR` / `CELL_DEPLOY_UNAUTHORIZED` with the cloud provider's error code and request ID. - The default domain `fcapp.run` causes browsers to download HTML pages. Web applications should bind a custom domain; APIs are unaffected. - Runtimes include older Node.js versions (FC 20.10, SCF 18.15). Server bundles include patches for `import.meta.dirname` / `filename`. ### vercel ```bash cell build --preset vercel # .vercel/output (Build Output API v3) cell preview # Run function bundle with Node.js, frontend served from .vercel/output/static cell deploy --preset vercel # vercel deploy --prebuilt, no rebuild on Vercel ``` - The frontend is placed in `static/` and returned by Vercel CDN (`/assets/*` with one-year immutable cache headers). Other requests enter function `functions/index.func` (`nodejs22.x`), where the Cell server handles APIs and serves `public/index.html` for single-page fallback. Files in `.vercel/output` are cleared during build. - Options: `project` (defaults to package name), `scope` (team), `environment` (`production` default or `preview`), `runtime`, `memory`, `maxDuration`, `regions` (comma-separated). Output directory is fixed at `.vercel/output`; deploying with `--out-dir` elsewhere raises `CELL_DEPLOY_OUTPUT_MISMATCH`. `--single` / `--compile` are not accepted. - Vercel CLI is not a dependency. If installed in your project, Cell uses it; otherwise `npx --yes vercel@65` is used. Credentials: `VERCEL_TOKEN`, or run `npx vercel login` once. The project is auto-created if it doesn't exist (`--create-if-missing`). - In Vercel's build environment (`VERCEL=1`), the `vercel` preset is automatically selected. Add `.vercel/` to `.gitignore` ### cell dev | Option | Description | | --- | --- | | `--preset ` | Which preset's entry to use for running the backend; default is `node` | | `-m, --mode ` | Additional mode(s), repeatable; `development` is always added automatically | | `-p, --port ` | Port, defaults to `server.port` (considering `CELL_SERVER__PORT`), otherwise 3000; `0` means random port | | `--host ` | Listen address, defaults to `server.host`, otherwise `127.0.0.1` | - Single port: Vite provides frontend pages and modules (HMR), `/favicon.ico`, and component `assets`. Files in `public/` match their location in the build output, and remaining requests go to the backend. - The backend is not bundled. In Vite's server environment, it loads via module runner: Cell packages and project source pass through the Cell plugin (generated DI container and config written to `.cell/generated/`); other dependencies load directly via Node. The entry passes the request handler function to `cell dev` via `ApplicationHost` (`@celljs/core`), not listening on a port itself. - When backend files change, the old application stops (triggering `onStop`), then restarts from new modules and triggers `dev:reload`. On startup failure, requests return 500 with `CellError` (`CELL_DEV_BACKEND_FAILED`) and retry automatically after saving. - When project or component `cell*.yml`, `package.json`, or `cell.config.*` changes, the toolchain is re-parsed and restarts on the same port, triggering `dev:ready` again. Config errors are reported; the process waits for the next change. - `require()` in source code and CommonJS files in the project (e.g., generated Prisma client) work normally. - `POST /_cell/mcp`: MCP endpoint of the running application (Streamable HTTP, JSON response). Tools include `get_errors` (backend startup errors, failed reloads, 5xx requests, error logs), `list_routes`, `list_bindings`, `get_config` (`${...}` already evaluated), `recent_requests`, and `logs`. Only accepts local requests (no `Origin` or loopback address); returns 403 for others. - `--json`: outputs one-line result on stdout when ready (`url`, `port`, `mode`, `targets`, `mcp`, or `backendError` if backend failed). Application and Vite output go to stderr. On SIGINT / SIGTERM, stops the application and exits with 0. - React Fast Refresh and other framework plugins are provided by the component's toolchain module (after the module host is complete). Before that, frontend changes trigger a full page refresh and CSS hot replacement. ### cell build | Option | Description | | --- | --- | | `--preset ` | Build output format: `node` (default), `cloudflare`, `cli`, `fc`, `scf`, `lambda`, `vercel` | | `-m, --mode ` | Additional mode(s), reads `cell..yml`, repeatable; `production` is always added automatically | | `--format ` | Server bundle format, defaults by preset (`node` uses `esm`) | | `--single` | Embed frontend into `server/index.mjs`: run the whole app from one file (see below) | | `--compile [bun\|node\|deno]` | Compile into an executable `.cell/output/app`, default bun (see below) | | `--compile-target ` | Cross-compile, passed to bun (e.g., `bun-linux-x64`) or deno (e.g., `x86_64-unknown-linux-gnu`) | | `--sourcemap` | Generate source maps | | `--out-dir ` | Output directory, default `.cell/output` | **Frontend static files**: After building the frontend, copy files into `public/` without overwriting build artifacts. Include: `favicon.ico` from the project root; files from each component's (including the project itself) `public/` or `src/browser/public/` directories, placed as-is at the site root (web app manifest, Service Worker, `robots.txt`, etc.); project files take priority on conflict. If no favicon exists, Cell's favicon is used. Files from each component's `assets/`, `src/assets/`, or `src/browser/assets/` directories are placed under `/assets/`. **Single file (`--single`)**: Build the frontend first, then encode all files in `public/` (pages, scripts, favicon, component assets) as base64 and embed them in the server bundle. At startup, register them to `Symbol.for('@celljs/serve-static/assets')`; `@celljs/serve-static` serves them from memory (Content-Type, ETag / 304, and Cache-Control match disk serving; single-page apps fall back to `index.html`). Artifacts contain only `server/index.mjs` and `manifest.json` (`single: true`, no `public`). Copy this one file to any directory and run with `node index.mjs`. If the project has only a backend, behavior is the same as without `--single` (a message appears); packages left in `server/node_modules` by `build.external` must still be placed beside it. Alternatively, write `build: { single: true }` in `cell.config.ts`. **Executable (`--compile`)**: Compile the single-file bundle into `.cell/output/app` (Windows: `app.exe`). The result runs on machines without Node.js or node_modules. `manifest.json` records `executable`, and `cell preview` runs it directly. | Tool | Method | Requires | | --- | --- | --- | | `bun` (default) | `bun build --compile` | Bun installed locally | | `deno` | `deno compile --allow-all` | Deno installed locally | | `node` | Node.js Single Executable Application (SEA): the CommonJS bundle generates a blob, injected into node's copy via `npx postject`, and re-signed on macOS | Output format auto-switches to `cjs`; npx downloads postject on first use; cross-compilation is not supported | Packages left outside the bundle (`build.external`, component `optionalDependencies`, usually native modules) cannot be included in the executable; `CELL_COMPILE_EXTERNALS` is raised, listing them. `cloudflare` and `vercel` presets have frontends managed by platforms and do not accept `--single` / `--compile`. ### cell info Accepts the same `--preset`, `-m`, `--format`, and `--out-dir` options as `cell build`, describing what `cell build` would use with the same parameters. Additionally: | Option | Description | | --- | --- | | `--app-config` | Also output the application config for each target (`cell.yml`): backend has all keys with `${...}` preserved (evaluated at runtime); frontend has only public keys, already evaluated | Reads only package.json, `cell*.yml`, `cell.config.ts`, and `manifest.json`. Does not run project code or execute `hooks`. Key fields in `--json` result: | Field | Content | | --- | --- | | `name`, `version`, `cellVersion`, `configFile` | Project and CLI versions, `cell.config` file (relative to root) | | `preset` | `name`, `source` (`flag` / `config` / `env` / `ci` / `default`), `entry`, `format`, `runtime` (Cloudflare Workers use `workerd`), `deployer`, `available` | | `mode`, `targets`, `targetSources` | Active modes; build targets and their sources (`cell.config.ts`, `default`, or declaring component) | | `build` | Active `format`, `outDir`, `external`, `sourcemap`, `single`, `compile` | | `components` | In load order; `dir` and `configFiles` relative to root; `targets`, `entries`, `toolchain`, and `optionalDependencies` are shown only when declared by the component | | `frontend`, `backend` | `entry`, `entryComponent` providing it, `staticModules` / `modules` (DI modules, in load order), and `config` (only with `--app-config`). If the frontend has pages, also includes `routes` (`path`, `file`, `layouts` from `src/browser/pages`, plus `error` if an `_error` file exists) | | `hooks`, `modules`, `moduleOptions` | Hooks in `cell.config.ts`, `modules` entries, toolchain module option keys | | `output` | `dir`, `built`, and manifest fields (`preset`, `format`, `targets`, `server`, `public`) if outdated | | `frontend.envOverrides`, `backend.envOverrides` | `CELL_*` variables in the current environment: `applied` (variable → config path) and `problems` (key not found or type mismatch). Only present if such variables exist; values are not output | `warnings` point out settings that won't take effect or cause `cell build` to fail: no component provides the preset's entry, `-m` specifies a mode with no `cell..yml` (with spelling suggestions), or non-working `CELL_*` variables. ### cell check | Option | Description | | --- | --- | | `--preset `, `-m, --mode ` | Same as `cell build` | | `--skip ` | Skip a specific check (repeatable); unknown names raise `CELL_ARGUMENT_INVALID` and list available checks | | Check | Content | | --- | --- | | `config` | Same as `cell info` warnings: missing entry (`CELL_ENTRY_MISSING`), invalid mode (`CELL_MODE_UNKNOWN`), or ineffective or shadowed environment variables (`CELL_ENV_OVERRIDE_IGNORED` / `CELL_ENV_OVERRIDE_SHADOWS`) | | `types` | Run `tsc --noEmit -p tsconfig.json` with the project's TypeScript. Each diagnostic includes file, line/column, and `TSxxxx` code. Skipped if no tsconfig.json or TypeScript is not installed | | `decorators` | tsconfig must enable `experimentalDecorators` and `emitDecoratorMetadata` (`CELL_TSCONFIG_DECORATORS`) to match build-time decorator semantics | | `di` | Load the backend DI container the same way `cell dev` does (without starting the application), then resolve each binding. Container errors (`CELL_DI_NO_BINDING`, `CELL_DI_AMBIGUOUS`, `CELL_DI_CIRCULAR`, `CELL_DI_MISSING_INJECT`, `CELL_DI_INCOMPLETE_BINDING`) are reported as errors with resolution chain and fix suggestions; each cause is reported once. Bindings that can only be created at injection point or request time are info | | `security` | Settings reported by the backend `SecurityCheck` component as unsafe for production. All are warnings (do not fail the check), the same set handled at production startup by `securityChecks`: `CELL_SECURITY_DEFAULT_COOKIE_KEYS`, `CELL_SECURITY_COOKIE_KEYS_MISSING`, `CELL_SECURITY_WEAK_COOKIE_KEYS` (`@celljs/web`), and `CELL_SECURITY_WEAK_JWT_SECRET` (`@celljs/jwt`). Reads config for the selected mode. Shares one backend load with `di` | | Module-registered checks | Registered by toolchain modules via `addCheck`, named like `typeorm:entities` | `--json` result: `passed`, `summary` (errors / warnings / info counts), `issues` (each with `check`, `level`, `code`, `message`, and possibly `file`, `line`, `column`, `hint`), and `checks` (duration, problem counts, skip reason for each). Without `--json`, output is in compiler format `src/a.ts:3:5 error TS2322 …`; info is not shown. ### cell init ```bash npx @celljs/cli init backend my-app # or fullstack: React frontend + backend communicating via RPC cd my-app && npm install && npx cell agent sync && npx cell dev ``` | Option | Description | | --- | --- | | `--name ` | Package name, defaults to directory name (lowercased, spaces removed) | | `--force` | Allow writing to non-empty directory (same-name files overwritten) | Never prompts. Raises `CELL_INIT_NOT_EMPTY` if the directory is non-empty, or `CELL_TEMPLATE_NOT_FOUND` if the template doesn't exist (with spelling suggestions). Project dependencies on `@celljs/*` are set to `^`; tests use `bun test` + `createTestApp()`. Generates for coding agents: `AGENTS.md` (Cell guide matching version), `.agents/skills/cell/SKILL.md`, `CLAUDE.md` (`@AGENTS.md`), and `.mcp.json` (`npx cell mcp`). ### cell agent sync Writes the Cell guide from `@celljs/cli` and `AGENTS.md` fragments from installed components and toolchain modules (`addDocs` directories) into the `` … `` region of `AGENTS.md` (other content unchanged; if no markers exist, appends to the end). Also rewrites `.agents/skills/cell/SKILL.md`. Run once after upgrading Cell or changing components. ### cell config get ```bash cell config get server.port --why # server.port (backend) = 8080 # server-only, owned by @celljs/web # Port the server listens on. Override with CELL_SERVER__PORT. # node_modules/@celljs/web/cell.yml: 3000 # cell.yml: 8080 ``` | Option | Description | | --- | --- | | `-m, --mode ` | Additional mode(s), repeatable | | `--target ` | `backend` or `frontend` (only public keys), default is `backend` if present | | `--why` | List the key's owning component, whether it reaches frontend, description, and files and environment variables that set it in merge order | Keys can be written as `a.b.c` or `items[0].name`. `--json` result has `value`, `owners` (components declaring this key in `cell.schema.json`), `public` (whether frontend sees it), `description`, `layers` (`file`, `component`, `value`), `env` (variables setting this key or ancestors), and `expressions` (backend value contains `${...}`, evaluated at server startup). Raises `CELL_CONFIG_KEY_NOT_FOUND` if the key doesn't exist (with nearest keys). Querying server-only keys with `--target frontend` indicates they are server-only. ### cell docs `cell docs [--limit n]`: Search by title sections in each component's `README.md`, `docs/`, module `addDocs()` directories, and `@celljs/cli`'s included docs and guide (`docs/`: getting started, config, dependency injection, HTTP, RPC, testing, deploy, coding agent, component directory; English). Returns the best-matching sections and `file:line` (`source` is `@celljs/cli` for CLI docs). ### cell mcp Run an MCP server on stdio (one JSON-RPC message per line) until stdin closes. Tools correspond to same-named commands' `--json` results: | Tool | Corresponding command | Side effects | | --- | --- | --- | | `info` | `cell info` | Read-only | | `check` | `cell check` | Read-only | | `config_get` | `cell config get` | Read-only | | `docs_search` | `cell docs` | Read-only | | `build` | `cell build` | Write output directory | | `deploy_plan` | `cell deploy --dry-run` | Write output directory, deployer may access network; does not deploy | Deployment tools are not provided. Actual deployment is done by running `cell deploy`. Tools registered by toolchain modules via `addMcpTool` are also included (tools with the same name as built-ins are ignored with a warning); automatically reloaded after `cell.config.*`, package.json, or module files change. Each tool is marked with `readOnlyHint` / `openWorldHint`; parameters are validated against `inputSchema`, raising `isError` and `CellError` on failure. stdout contains only protocol messages; module and hook output goes to stderr. Configure in an agent (run from project root): ```jsonc // Claude Code / Cursor / CodeBuddy: .mcp.json or .cursor/mcp.json { "mcpServers": { "cell": { "command": "npx", "args": ["cell", "mcp"] } } } ``` ```toml # Codex: ~/.codex/config.toml [mcp_servers.cell] command = "npx" args = ["cell", "mcp"] ``` While `cell dev` is running, you can also configure `http://127.0.0.1:/_cell/mcp` as an HTTP MCP server to inspect the running application. ### cell preview | Option | Description | | --- | --- | | `-p, --port ` | Listen port, defaults to `server.port` (3000); equivalent to setting `CELL_SERVER__PORT`. For `cloudflare` artifacts, passed to `wrangler dev --port`, default 8787 | | `--out-dir ` | Artifact directory, default `.cell/output` | The artifact runs according to which preset built it: `node`, `cli`, `fc`, `scf`, `lambda` run `server/index.mjs` with Node.js (stdin/stdout inherited from the current terminal); `cloudflare` runs `wrangler dev`; `vercel` runs the function bundle with Node.js and serves the frontend from `static/`. ### cell deploy | Option | Description | | --- | --- | | `--preset ` | Deploy to which platform: `cloudflare`, `fc`, `scf`, `lambda`, `vercel`; `node`, `cli` have no deployers and raise `CELL_DEPLOY_UNSUPPORTED` (checked before build) | | `--dry-run` | Build and plan without uploading (`cloudflare`: `wrangler deploy --dry-run`; `fc` / `scf` / `lambda`: zip and check if function exists; `vercel`: list functions and static files) | | `--skip-build` | Deploy existing artifacts in `--out-dir`. Raises `CELL_DEPLOY_OUTPUT_MISMATCH` if the artifact preset doesn't match `--preset` | | `-m, --mode `, `--sourcemap`, `--out-dir ` | Same as `cell build` | Plan first, then apply. The plan lists resources to deploy (`resources`, each with `type`, `name`, `action`, `details`); `--dry-run` stops here. Then fire `deploy:before` (gate; command failures in hooks cancel deploy), `deploy:plan`, and `deploy:done` events. Deploy tool output (Wrangler, Vercel CLI) goes to stderr on `--json`. `--json` result: `preset`, `deployer`, `dryRun`, `plan`, and on actual deploy also `deployment` (`url` and `details.versionId`). ### Common options | Option | Description | | --- | --- | | `--root ` | Project root (default: current directory) | | `--json` | stdout outputs one-line JSON result; other output (progress, hook command output) goes to stderr | | `-h, --help` / `-v, --version` | Help or version | Commands never prompt interactively. Exit codes: success 0, execution failure 1, usage error (unknown command, option, or value) 2. ## --json output ```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":[]} ``` On failure: ```json {"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` remains constant; fields only grow and never change. ## cell.config.ts Optional. Contains toolchain configuration only; application config (read by `@Value`) goes in `cell.yml`. ```ts 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' }, }); ``` | Field | Description | | --- | --- | | `preset` | Build output format. Priority: `--preset` → this field → `CELL_PRESET` → CI environment detection → `node` | | `targets` | Build targets. Default is `backend` plus targets declared by components (installing `@celljs/react` adds `frontend` via `@celljs/widget`). Specifying this field overrides the default completely | | `modules` | `'!package-name'` excludes a component (and dependencies only imported via it); `{ source, runtime: false }` excludes its DI module but keeps config; `{ source, toolchain: false }` excludes its toolchain module; local paths or package names specify additional toolchain modules (see below) | | `build.format` | `esm` or `cjs` | | `build.external` | Packages not to inline; copied to `server/node_modules` in dependency order | | `build.sourcemap`, `build.outDir` | Same as command-line options | | `build.single`, `build.compile` | Same as `--single` / `--compile` (`compile` can be `true`, `bun`, `node`, or `deno`) | | `vite` | Merged into Vite config for each environment | | `hooks` | Shell commands executed on toolchain events (see below) | Other top-level fields are options for toolchain modules and must be objects. The file supports `.ts`, `.mts`, `.cts`, `.js`, `.mjs`, and `.cjs` extensions; multiple files raise `CELL_CONFIG_AMBIGUOUS`. Command-line arguments take priority over the config file. ## Override configuration with environment variables Any key already in `cell.yml` can be overridden with environment variables without editing: ```bash CELL_SERVER__PORT=8080 cell preview # Applied at server startup: server.port CELL_APP__PUBLIC__API_BASE=/api cell build # Applied at build time, reaches frontend: app.public.apiBase ``` After the `CELL_` prefix, use one underscore, then `__` between levels (e.g., `CELL_SERVER__PORT`). `CELL_PRESET`, `CELL_MODE`, `CELL_EVENT`, `CELL_OUT_DIR`, and `CELL_DEBUG` are toolchain variables, not treated as config. Single-segment variables that don't match top-level keys (like another tool's `CELL_REGION`) are ignored. Variables with `__` that don't match keys report an error; using the old `CELL__…` / `CELL_BACKEND__…` format suggests the correct format. `__` separates levels; one key can only be set by one variable. Variables are case-insensitive, and `_` / `-` are ignored. Values are converted by schema type or original value type (limited numbers in JSON notation, `true` / `false`, and objects/arrays as JSON). Values that are a single `${...}` are preserved until evaluation (e.g., `CELL_SERVER__PORT='${env.PORT}'`), taking effect before `${...}` evaluation. Only existing keys can be overridden. Non-existent or mistyped variables don't apply and warn: the server prints during startup; `cell build` and `cell info` also check. See [docs/design/config.md](https://github.com/cellbang/cell/blob/main/docs/design/config.md) for full rules. ## hooks ```ts hooks: { 'build:before': 'prisma generate', 'output:done': 'cp extra/* "$CELL_OUT_DIR/server/"', } ``` - Commands run in the shell from the project root and do not read stdin. PATH has project and parent `node_modules/.bin` at the front. - Environment variables: `CELL_EVENT`, `CELL_PRESET`, `CELL_MODE` (comma-separated), and `CELL_OUT_DIR` (absolute output directory path). - `cell dev` fires `modules:done` → `config:resolved` → `dev:ready`, then `dev:reload` on backend reload, and repeats after `close` on restart. - `cell build` fires in order: `modules:done` → `config:resolved` → `build:before` → build → `output:done` → `close`. - `build:before` is a gate event: non-zero exit cancels the build (`CELL_BUILD_CANCELLED`). Non-zero exit in other events raises `CELL_HOOK_FAILED`. - Only string commands are accepted. Extensions requiring code or event modification (like `build:done` or `deploy:plan`) should be written as toolchain modules. ## toolchain modules Write extensions that require code as toolchain modules: export a factory function that, given `cell`, registers capabilities (see [docs/design/modules.md](https://github.com/cellbang/cell/blob/main/docs/design/modules.md) for design details). ```ts // modules/audit.ts, write modules: ['./modules/audit'], audit: { url: '...' } in cell.config.ts 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 }); }); } ``` - Load order is fixed: components in dependencies (package.json's `cell.toolchain`, by dependency order) → `cell.config.ts` `modules` (local paths or package names, in order). `{ source, toolchain: false }` disables a component's module; `'!package-name'` removes the component too. - The factory only registers; it doesn't start processes, ports, listeners, or timers. `cell info` also executes the factory. - Top-level fields in `cell.config.ts` besides `preset`, `targets`, `modules`, `build`, `vite`, and `hooks` are module options and must be objects. Each must be claimed by some module via `options()` or `addConfigSchema()`, raising `CELL_CONFIG_UNKNOWN_OPTION` (with spelling suggestions) if not. `addConfigSchema` accepts Standard Schema (zod, valibot, etc.); invalid schemas raise `CELL_CONFIG_INVALID`. - Factory errors raise `CELL_MODULE_ERROR` (with module name); non-function default export raises `CELL_MODULE_INVALID`. Notification-type event handlers that error in `cell dev` report without interrupting; any handler that fails in `cell build` stops the build. - Each module's registrations are listed in `cell info --json` under `toolchainModules`. | API | Purpose | | --- | --- | | `command`, `mode`, `dev`, `ci`, `root`, `preset`, `components`, `logger` | Read-only info. `preset` is the name from command-line / config / environment; resolved preset is in the `config:resolved` event | | `options(key, defaults)`, `addConfigSchema(key, schema)` | Read and claim `key` from `cell.config.ts` (only callable synchronously in the factory) | | `updateConfig({ build: { external }, vite })` | Append external packages; merge Vite config below the project's config (project takes priority). Application config remains in the component's `cell.yml` | | `addVitePlugin(plugin, { env })` | Add a Vite plugin. `env` as `client` / `server` applies only to frontend / backend | | `addRuntimeModule(path, { target, static })`, `addVirtualModule(id, code)`, `setEntry(target, path)` | Append a DI module, provide a virtual module, or specify entry | | `addDevHandler(path, handler)`, `addWatchFile(path)` | In `cell dev`, handle a path before the backend; restart on file changes | | `addPreset(preset)`, `addOutputFile(path, content)` | Register a preset (participates in `--preset` and CI detection); write a file to the output directory after build (`build:done` `event.files` can add/remove/modify) | | `addCheck(def)`, `addMcpTool(def)`, `addDocs(dir)` | Supply tools for `cell check`, `cell mcp`, and `cell docs` | | `on(event, handler)`, `events` | Subscribe to toolchain events (composition rules in modules.md §3.2); event bus for inter-module communication | | `exec(cmd, args, opts)`, `resolve(spec)` | Run commands without shell; resolve modules from the project root | ## Error codes All failures output as `CellError { code, message, file?, line?, hint, docs }`, with `code` stable within 4.x. | Error code | Meaning | | --- | --- | | `CELL_ARGUMENT_INVALID` | Unknown command, option, or value (exit code 2) | | `CELL_PROJECT_NOT_FOUND`, `CELL_PACKAGE_JSON_INVALID` | No package.json in root, or a package.json cannot be read | | `CELL_CONFIG_LOAD`, `CELL_CONFIG_AMBIGUOUS` | `cell.config.ts` load or execution failure, multiple config files exist | | `CELL_CONFIG_INVALID`, `CELL_CONFIG_CONFLICT` | Config field error (with field path and suggestions), option conflicts | | `CELL_CONFIG_PARSE`, `CELL_CONFIG_NOT_OBJECT`, `CELL_CONFIG_EXPRESSION` | `cell.yml` not valid YAML, top level not a mapping, `${...}` expression error | | `CELL_CONFIG_INVALID` (cell.yml) | Top-level key with no owner, option not in component, type mismatch; with file, line, suggestions; put custom config under `app` | | `CELL_CONFIG_KEY_CONFLICT`, `CELL_CONFIG_SCHEMA_INVALID` | Two components' `cell.schema.json` declare same key, schema uses unsupported keywords, or project schema declares keys outside `app` | | `CELL_COMPONENT_NOT_FOUND`, `CELL_LOCAL_MODULE_NOT_FOUND` | Component or local module in `modules` not found | | `CELL_MODULE_NOT_FOUND`, `CELL_CORE_NOT_FOUND` | DI module declared by component cannot resolve, project doesn't depend on `@celljs/core` | | `CELL_ROUTE_CONFLICT`, `CELL_ROUTE_INVALID` | Two files in `src/browser/pages` map to same path (`about.tsx` and `about/index.tsx`), filename not valid path segment | | `CELL_CORE_EL_MISSING` | Project's `@celljs/core` predates 4.1.0, missing `@celljs/core/el` entry for build-time config evaluation | | `CELL_PRESET_NOT_FOUND`, `CELL_PRESET_DUPLICATE`, `CELL_PRESET_CYCLE` | Preset not found, duplicate name, `extends` cycles | | `CELL_BUILD_FAILED` | Build failed, with error file and location | | `CELL_BUILD_CANCELLED`, `CELL_HOOK_FAILED` | Hook or module cancelled build, hook command failed | | `CELL_MODULE_ERROR`, `CELL_MODULE_INVALID` | Toolchain module factory or handler error, default export not a factory | | `CELL_CHECK_FAILED`, `CELL_DI_MODULE_FAILED` | A check had internal error, `cell check` failed loading backend DI module | | `CELL_SECURITY_*`, `CELL_SECURITY_CHECK_FAILED` | Unsafe production settings (reported as warnings by `cell check` `security`); production startup refuses with `securityChecks: fail` | | `CELL_CONFIG_UNKNOWN_OPTION` | `cell.config.ts` has option fields unclaimed by any module | | `CELL_DEV_FAILED`, `CELL_DEV_BACKEND_FAILED`, `CELL_DEV_START_TIMEOUT` | `cell dev` cannot start (port, etc.), backend startup failed, backend entry timeout without ready | | `CELL_DEV_BACKEND_NOT_RUNNING` | dev MCP tool requires running backend, but backend failed or not started | | `CELL_CONFIG_KEY_NOT_FOUND` | `cell config get` / `get_config` key doesn't exist | | `CELL_COMPILE_TOOL_MISSING`, `CELL_COMPILE_FAILED` | `--compile` requires bun / deno / node not found, compiler error (last few lines of output) | | `CELL_COMPILE_EXTERNALS`, `CELL_COMPILE_NO_SERVER` | Packages left outside bundle, project has no backend to compile | | `CELL_TEMPLATE_NOT_FOUND`, `CELL_INIT_NOT_EMPTY` | `cell init` template not found, target directory non-empty | | `CELL_UNSUPPORTED` | Using a feature not yet available | | `CELL_OUTPUT_NOT_FOUND`, `CELL_PREVIEW_NO_SERVER` | No build before `cell preview` / `cell deploy --skip-build`, output has no server | | `CELL_DEPLOY_UNSUPPORTED`, `CELL_DEPLOY_OUTPUT_MISMATCH`, `CELL_DEPLOYER_NOT_FOUND` | Preset has no deployer, existing artifact not from this preset, deployer not found | | `CELL_DEPLOY_PLAN_FAILED`, `CELL_DEPLOY_FAILED`, `CELL_DEPLOY_CANCELLED` | Plan failed (e.g., build failed), deploy failed (hint shows how to set credentials), hook or module cancelled deploy | | `CELL_DEPLOY_CREDENTIALS_MISSING`, `CELL_DEPLOY_UNAUTHORIZED` | No cloud credentials, credentials invalid or insufficient permissions | | `CELL_DEPLOY_API_ERROR`, `CELL_DEPLOY_NETWORK`, `CELL_DEPLOY_TIMEOUT`, `CELL_DEPLOY_PACKAGE_TOO_LARGE` | Cloud API error (with cloud error code and request ID), cannot reach cloud API, function timeout, code package exceeds direct upload limit | | `CELL_CLOUDFLARE_CONFIG_INVALID`, `CELL_CLOUDFLARE_CONFIG_UNSUPPORTED`, `CELL_CLOUDFLARE_COMPATIBILITY_DATE` | `wrangler.json(c)` not valid JSON or field type wrong, using `wrangler.toml`, `compatibility_date` before `2026-03-17` | | `CELL_INTERNAL_ERROR` | Cell or module defect; set `CELL_DEBUG=1` to see stack trace | ## Testing: createTestApp ```ts import { createTestApp } from '@celljs/cli'; const app = await createTestApp({ root, mode: ['extra'] }); // mode always has test in front const res = await app.fetch('/users', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }); const { UserService } = await app.import('/src/user-service.ts'); // the backend-loaded module app.get(UserService); // object from DI container await app.close(); // stop application (onStop) ``` In `app.get(X)`, importing a class or `Symbol('X')` from project source gives a different module instance than the app uses (app runs in Vite's module runner), matched by class name or symbol description; raises `CELL_DI_NO_BINDING` / `CELL_DI_AMBIGUOUS` if missing or duplicate. To get the app's version (on duplicates, or to use other exports from the module), use `app.import()`: project files as `/src/...`, packages as package name. Classes with `@Value` / `@Autowired` must come from `app.import()`: importing these in the test file, then resolving by name, fails (raises `CELL_DI_NO_BINDING`). In the test process, backend loads from source like `cell dev` does, no build or port listening; requests go through the complete HTTP handler chain via in-memory req/res objects, `fetch` returns standard `Response`. Startup failure throws `CellError` (like `CELL_DEV_BACKEND_FAILED`, with error file); backend without HTTP entry raises `CELL_TEST_APP_NO_HTTP` on `fetch`. ## Programming interface ```ts import { CellCli } from '@celljs/cli'; const code = await new CellCli({ stdout, stderr, env }).run(['build', '--json']); ``` Also exports `defineConfig`, `ConfigResolver`, `PresetRegistry`, `DeployerRegistry`, `EventBus`, `HookRunner`, etc., for testing and tool writing. # Cell This project uses Cell 4.8.0: TypeScript with dependency injection, MVC controllers on Node's `http` server, and the `cell` toolchain (Vite). This guide matches the installed version; `npx cell agent sync` rewrites it after an upgrade. ## Layout - `src/node/` is the backend, `src/browser/` the frontend, `src/common/` code for both. `src//module.ts` is the DI module of its side: it imports every file that has components and ends with `export default autoBind();`. A file it does not import is not loaded. - `cell.yml` holds the application configuration: keys of the components at the top level (`server.port`), your own under `app`; only keys marked public and `app.public` reach the browser; a wrong key is a `CELL_CONFIG_INVALID` error. `cell..yml` adds to it per mode (`development` in `cell dev`, `production` in `cell build`, `test` in tests, more with `-m `). - `cell.config.ts` (optional) configures the toolchain only: `preset`, `modules`, `build`, `vite`, `hooks`. Application configuration never goes there. - A dependency whose package.json has a `cell` field is a component: installing it is enough, its DI modules and `cell.yml` load automatically. Keep every `@celljs/*` package on the same version. ## Writing code - Services: `@Component()` on the class, `@Autowired(Type)` on a property to inject. For an interface, export a `Symbol` with the same name and bind the class with `@Component(Symbol)`. - Configuration: `@Value('app.key')` reads your keys under `app` in `cell.yml`, `@Value('server.port')` a key of a component. `${env.NAME ?? 'default'}` is evaluated at startup and converted to the key's schema type. `CELL_SERVER__PORT=8080` overrides an existing key. `npx cell config get --why` tells the owner and whether the frontend sees it. - HTTP: `@Controller('path')` with `@Get()`, `@Post()`, `@Put(':id')`, `@Delete(':id')` from `@celljs/mvc/lib/node`; parameters `@Param('id')`, `@Query('q')`, `@Body()`. Returned objects are JSON; add `@Text()` for text. `String`, `Number`, `Boolean` and `Date` parameters are converted. To validate a body, add `@celljs/validation` (with `class-validator`) to the dependencies and type the `@Body()` parameter with a class that has class-validator decorators: invalid bodies get 400. - API docs: add `@celljs/openapi` to the dependencies; the app serves the OpenAPI document at `/openapi.json` and Swagger UI at `/api-docs`, built from the routes, parameter and return types and class-validator decorators. Async methods and arrays need `@ApiResponse({ type: [Pet] })`; properties without class-validator decorators need `@ApiProperty()`. - gRPC: add `@celljs/grpc`; `Proto.parse(source).service('pkg.Service')` from the `.proto` text (`import source from './x.proto?raw'`), `@GrpcService(Service)` on the class implementing its methods in lowerCamelCase (streams are async iterables), `new GrpcChannel('host:50051').createClient(Service)` to call one. Throw `GrpcError(GrpcStatus.X, 'details')` for statuses. The server listens on `grpc.server.port` (50051). - Errors: throw `HttpError(status, message)` or `NotFoundError(message)` from `@celljs/web/lib/node` for a status; other errors answer 500. `@Catch(ErrorType)` on a controller method handles that type for the controller; an `@Component(ErrorHandler)` with `priority` above 2000 for the whole app. - Middleware: `@Component(Middleware)` implementing `handle(ctx, next)` and `priority` (`@celljs/web/lib/node`); `Context.getRequest()` gives the current request anywhere. - Startup and shutdown work: `@Component(ApplicationLifecycle)` with `onStart` / `onStop`, not constructors. Logging: `@Autowired(Logger)`. - Payments: `@celljs/wechat-pay` (WeChat Pay API v3), `@celljs/alipay` (Alipay gateway) and `@celljs/stripe` (with the official `stripe` package) inject a client (`WechatPayClient`, `AlipayClient`, `StripeClient`) configured under `backend.wechatPay` / `alipay` / `stripe` with `${env.X}` secrets, and serve a notification endpoint (`/wechat-pay/notify`, `/alipay/notify`, `/stripe/webhook`) that verifies signatures and runs `@WechatPayNotificationHandler('TRANSACTION.SUCCESS')`, `@AlipayNotificationHandler('TRADE_SUCCESS')` or `@StripeWebhookHandler('checkout.session.completed')` components. Handlers must be idempotent; throwing makes the provider retry. WeChat amounts are integers in fen, Alipay amounts strings in yuan. - Frontend and backend: `@Rpc(Symbol)` on the backend class, `useRpc(Symbol)` (`@celljs/react`) in a frontend component, `@Autorpc(Symbol)` in a frontend DI component. With `rpc.transport: websocket` the backend calls `@RpcClient(Symbol)` components of the frontend through `RpcContext.requireConnection().client(Symbol)` (`@celljs/rpc/lib/node`). - Frontend pages are files of `src/browser/pages/` with the component as default export (`users/[id].tsx` is `/users/:id`, `_layout.tsx` wraps a directory); do not import them anywhere. Router hooks and `Link` come from `@celljs/react`. - React components get frontend DI components with `useInject(Id)` and configuration with `useValue(key)` from `@celljs/react`; there is no global container. A page that throws shows the nearest `src/browser/pages/_error.tsx` inside its layout. Tests render a component inside ``. - Packages for common needs: database `@celljs/typeorm` (+ driver; `autoBindEntities`, `OrmContext`, `@Transactional()`), cache `@celljs/cache`, cron `@celljs/schedule`, login `@celljs/security`, tokens `@celljs/jwt`, outgoing HTTP `@celljs/http`, LLMs `@celljs/ai-core` + `@celljs/ai-pi`, payments (above). Read a package's docs with `npx cell docs ` before using it. ## Verify - `npx cell check --json` checks types, configuration, decorator settings and DI bindings. Every issue has `code`, `file`, `line` and a `hint`. Run it after each change. - Tests run with `bun test`; `createTestApp()` from `@celljs/cli` starts the application in the test process: ```ts const app = await createTestApp({ root: join(__dirname, '..') }); const res = await app.fetch('/todos', { method: 'POST', headers: { 'content-type': 'application/json' }, body: '{}' }); const { TodoService } = await app.import('/src/node/todo-service.ts'); // classes from app.import(), not from a test import const service = app.get(TodoService); await app.close(); ``` - `npx cell dev` runs the application and reloads it on save; `npx cell build` builds into `.cell/output` (`--single`: one file, `--compile`: one executable); `npx cell preview` runs the build. - `npx cell deploy --preset --dry-run` builds and shows what a deployment would change, without credentials. Platform options go in `cell.config.ts` under the preset's name (`fc: { region: 'cn-shanghai' }`); credentials come from environment variables. Leave the real `cell deploy` to the user unless asked. ## Find out - `npx cell info --json`: components, DI modules, entries, preset, hooks, output. - `npx cell config get --why`: a merged configuration value and the files and variables that set it. - `npx cell docs `: the docs of the installed Cell packages and the Cell guides (configuration, DI, HTTP, RPC, testing, deployment), in the installed versions. - `npx cell mcp` serves these as MCP tools. While `cell dev` runs, `http://127.0.0.1:/_cell/mcp` has `get_errors`, `list_routes`, `list_bindings`, `get_config`, `recent_requests` and `logs` of the running application. - Errors read `[CELL_CODE] message`, then `fix:` and a docs link: do what the fix says. # Migrating from Cell 3.x to 4.0 This guide is for existing Cell 3.x projects and describes the changes required when upgrading to 4.0. The 4.0 toolchain is completely redesigned (see [design/architecture.md](https://github.com/cellbang/cell/blob/main/docs/design/architecture.md)) and is **not backward compatible with 3.x commands, hooks, config expressions, or output directories, with no compatibility layer provided**. Review each section below and apply the necessary changes. The runtime API (`@Component`, `@Autowired`, `@Value`, MVC, RPC, typeorm, etc.) remains unchanged, so business code generally requires no modification. ## 0. Capabilities included in 4.0 | Capability | Status | | --- | --- | | `cell dev`, `cell build`, `cell info`, `cell preview`, `cell.config.ts`, `hooks`, `--json` | Available | | `node` preset (Node.js HTTP service) | Available, default | | `cell check` | Available | | `cell mcp`, `cell dev` `/_cell/mcp` endpoint, `cell config get`, `cell docs` | Available | | `cloudflare` preset and `cell deploy` (Cloudflare Workers) | Available, see [@celljs/cli README](https://cell.cellbang.com/cli#cloudflare) | | Cloud platform presets and `cell deploy` (fc / scf / lambda / vercel) | Available, see [@celljs/cli README](https://cell.cellbang.com/cli#fc-scf-lambda) | | `--single`, `--compile` single-file and executable | Available | | `cell init` and project templates (`backend`, `fullstack`), `cell agent sync` | Available | | Override any config key with environment variables (`CELL_*`) | Available | | In-process test utility `createTestApp()` (`@celljs/cli`) | Available | ## 1. Environment - Node.js `^20.19.0 || >=22.12.0` (Vite 8 requirement). The built service bundle runs on Node.js 18+, Bun 1.2+, and Deno 2 (fc and scf images use older Node.js). Runtime packages have `engines` set to `>=18.0.0` (those based on pi—`ai-pi`, `ai-agent`, `ai-coding-agent`, `tui`—require `>=22.19.0`). - Package manager is flexible. The repository itself is developed with Bun. ## 2. Dependencies ```jsonc { "dependencies": { "@celljs/core": "^4.0.0", "@celljs/express-adapter": "^4.0.0", // New: provides HTTP entry point "@celljs/mvc": "^4.0.0" }, "devDependencies": { "@celljs/cli": "^4.0.0" // Install locally, no longer global } } ``` - **Add `@celljs/express-adapter`.** In 3.x, the HTTP entry point was implicitly provided by a runtime plugin. In 4.0, the preset selects an entry by name (the `node` preset uses `http`) from the `cell.entries` declared by components. This entry is provided by `@celljs/express-adapter`. If missing, `cell build` will issue a warning, and the bundle will not listen on a port when started. - **Remove** `@celljs/cli-service`, `@celljs/cli-common`, `@celljs/cli-runtime`, `@celljs/dev-server`, `@celljs/frameworks`, `@celljs/testing`, and `@celljs/*-plugin`, along with dependencies that only served webpack (various `*-loader`, `mini-css-extract-plugin`, babel, etc.). - When using Sass / Less / Stylus, install the preprocessor yourself (e.g., `sass`); Vite will detect and use it. - For native dependencies with install scripts (e.g., `better-sqlite3`), ensure your package manager runs their install script (Bun requires them in `trustedDependencies`). Otherwise, the build may succeed but runtime will fail with a `.node` file not found error. ## 3. Commands | 3.x | 4.0 | | --- | --- | | `cell serve` | `cell dev`: frontend and backend share one port, frontend HMR enabled, backend auto-reloads on file changes, auto-restarts on `cell.yml` / `cell.config.ts` / `package.json` changes | | `cell build -m prod` | `cell build -m prod` | | Run `.cell/dist/backend/index.js` | `cell preview`, or `node .cell/output/server/index.mjs` directly | | `cell deploy -m fc` | `cell deploy --preset fc` (also `scf`, `lambda`, `vercel`, `cloudflare`; `--dry-run` shows the plan). 3.x platform configs like `cell.fc.yml` become `fc: { region, memory, ... }` in `cell.config.ts`; credentials are read from environment variables, not from `~/.cell` or profile files | | `cell init` | `cell init