Command line
xoong is the command that goes with the framework. It reads your OpenAPI document and writes the table of operations the runtime uses, checks your HTML screens against it, serves the app while you work, and writes static files that run under a strict Content-Security-Policy. It runs in Node, in development and in your build; none of it is sent to the browser.
Install and run
The command is part of @xoong/cli. @xoong/xoong, the one install for the runtime and the admin set, depends on it, so either gives you xoong. Both need Node 22.12 or newer. The command reads no environment variables.
# the runtime, the admin set and the command in one install
npm install @xoong/xoong@next
# or only the command, as a development tool
npm install --save-dev @xoong/cli@next
npx xoong --version
npx xoong --helpnpx xoong runs the copy in node_modules. xoong --help (or -h, or xoong help, anywhere on the line) prints every command with every option and does nothing else; xoong --version (-v) prints the version. An option that does not exist is refused with the list of the ones that do, never ignored. The MCP server and the language server are separate programs: xoong-mcp and xoong-lsp.
The commands at a glance
| Command | What it does | Reads | Writes |
|---|---|---|---|
xoong sync | Turns an OpenAPI document into the files the runtime and the editor read. | An OpenAPI 3 or Swagger 2 document (a file or a URL), a hints file | .xoong/operations.json, api.d.ts, xoong.html-data.json |
xoong doctor | Audits the document and says what limits xoong, with a fix for each finding. | The document, a hints file | Nothing |
xoong new | Creates a project with a first list screen. | The document | A project folder |
xoong check | Checks screens against your API: operations that do not exist, attributes in the wrong place, missing names. | HTML, JavaScript and TypeScript files, operations.json | Nothing |
xoong render | Prerenders a page, with its data, into plain HTML. | A page, the API it reads from, operations.json | The HTML: a file, or standard output |
xoong build | Writes static files that run under a strict Content-Security-Policy. | Every page under the root and what they reference | dist/ and dist/build-report.json |
xoong i18n extract | Lists the translatable texts (xo-t) with their source text. | HTML and JavaScript files | A catalog (JSON): a file, or standard output |
xoong i18n check | Compares a translation with the source catalog. | Two catalog files | Nothing |
xoong dev | Serves the app while you work: static files, an API proxy, TypeScript. | The project folder | Nothing |
A project uses them in this order: sync when the API changes, check while you write screens, dev to look at them, build to ship them.
npx xoong sync openapi.json --hints xoong.hints.json # after the API changed
npx xoong check index.html src # as you write screens
npx xoong dev # http://127.0.0.1:5173
npx xoong build --out dist # what you deployxoong sync
Turns your OpenAPI document into what the rest of xoong reads. Run it once, and again whenever the API changes. It reads OpenAPI 3.x as JSON or YAML, from a file or an http(s) URL (Swagger 2.0 is converted first), and writes three files into .xoong/:
operations.json: the table of operations, with their parameters, request fields and entities.configure({ api })gives it to the runtime;check, the language server and the MCP server read it too.api.d.ts: TypeScript types for the same operations.xoong.html-data.json: completion data for the editor (Editor support).
xoong sync <openapi.json|yaml|url> [--out .xoong] [--hints hints.json]
| Option | Default | What it does |
|---|---|---|
--out | .xoong | The folder for the generated files (created when it is missing). |
--hints | none | A hints file (xoong.hints.json). It is never looked for on its own: pass it on every run. |
It prints one line with the counts. Errors print in full, with the code, the place, the message and the fix, and make the exit code 1. What the document asks for and xoong does not do (XO113) is one warning line per kind. Any other warnings are only counted: xoong doctor lists them.
$ npx xoong sync openapi.json --hints xoong.hints.json
synced 5 operations, 1 entities
$ npx xoong sync openapi.json
synced 1 operations, 0 entities
error XO150 #/components/schemas/Order
$ref points to something that does not exist
fix: Define the target or remove the reference
warning XO113: the required header X-Tenant-Id (listOrders) is not sent by xoong. Send it on every call: configure({ headers: () => ({ 'X-Tenant-Id': value }) })
The second run exits with 1 because of the error; the warning alone would not.
xoong doctor
A health check of the OpenAPI document, for when you do not own it. It prints what limits xoong (a missing operationId, undocumented errors, schemas that are entities in all but name), each with a fix, and writes nothing. The codes are the XO1xx ones in Error codes.
xoong doctor <openapi.json|yaml|url> [--hints hints.json] [--all]
| Option | Default | What it does |
|---|---|---|
--hints | none | The same hints file you give to sync, so the audit sees what sync sees. |
--all | off | Print every warning. By default each warning code shows its first five and counts the rest. |
Errors always print in full. Each warning code shows its first five findings and counts the rest, so a large API stays readable; --all prints everything. The exit code is 1 when there is an error, 0 otherwise.
$ npx xoong doctor openapi.json --hints xoong.hints.json
error XO150 #/components/schemas/Order
$ref points to something that does not exist
fix: Define the target or remove the reference
warn XO113 #/paths (parameters in: header)
the required header X-Tenant-Id (listOrders) is not sent by xoong
fix: Send it on every call: configure({ headers: () => ({ 'X-Tenant-Id': value }) })
1 operations, 0 entities, 1 errors, 1 warnings
xoong new
Creates a project folder from an OpenAPI document: a page with a table of the first list the API offers (the first GET operation without path parameters whose response is, or contains, a list of objects), a test for it, the configuration and the generated files. It refuses to run in a folder that already has a package.json.
xoong new <folder> --spec <openapi.json|yaml|url> [--title "My app"] [--link <xoong checkout>]
| Option | Default | What it does |
|---|---|---|
--spec | required | The OpenAPI document the project is made from (a file or a URL). It is copied into the project. |
--title | the title of the OpenAPI document | The heading of the first screen. |
--link | none | A checkout of the xoong repository: the project then uses its packages by path instead of from npm. |
It writes package.json (with the scripts dev, sync, check and test), xoong.config.json, xoong.hints.json, index.html, src/main.ts, src/app.css, vitest.config.mjs, test/index.spec.mjs, README.md, a copy of the document as openapi.json, and .xoong/ as sync would.
$ npx xoong new people-app --spec openapi.json
created /home/you/people-app
first screen: GET /api/people
next: cd people-app && npm install && npm run dev
(the @xoong/* packages come from npm; to use a checkout of the xoong repository instead, run again with --link <path to the xoong repository>)
xoong check
Finds the mistakes that would otherwise show up as a button that does nothing, before you open a browser. It reads HTML files and, in .js, .mjs, .ts and .mts files, the HTML inside html`...` templates and the operation named in a client.load, query, mutate or stream call. It compares what it finds with operations.json. Give it files and folders; folders are walked without node_modules, dist, build, coverage and folders that start with a dot.
xoong check <file|folder>... [--operations .xoong/operations.json] [--format text|json] [--exclude glob]...
| Option | Default | What it does |
|---|---|---|
--operations | .xoong/operations.json | The operations table that sync wrote. |
--format | text | text prints one finding with its fix per line; json prints the list of findings, for tools. One of text or json. |
--exclude | none | Skip the files and folders that match (paths relative to the current folder). It adds to check.exclude of xoong.config.json. May be given more than once. |
Every finding has a code, a place (file, line, column), a message and a fix. An error makes the exit code 1; a warning is printed and does not. Without .xoong/operations.json (or the file --operations names) it stops and tells you to run xoong sync. --exclude and "check": { "exclude": [] } of xoong.config.json take globs, see Configuration.
<xo-view get="GET /api/people">
<button xo-click="deletePersn" xo-confirm-kind="danger">Delete</button>
<xo-table columns="name,email" sortable></xo-table>
</xo-view>$ npx xoong check screens
screens/people.html:2:11 error XO201 unknown operation "deletePersn" (closest: "deletePerson")
fix: Use "deletePerson"
screens/people.html:2:34 error XO270 xo-confirm-kind does nothing without xo-confirm on the same element
fix: Add xo-confirm="Question?" beside it, or remove it
1 files, 2 errors, 0 warnings$ npx xoong check screens --format json
[
{
"level": "error",
"code": "XO201",
"file": "screens/people.html",
"line": 2,
"column": 11,
"message": "unknown operation \"deletePersn\" (closest: \"deletePerson\")",
"fix": "Use \"deletePerson\""
},
{
"level": "error",
"code": "XO270",
"file": "screens/people.html",
"line": 2,
"column": 34,
"message": "xo-confirm-kind does nothing without xo-confirm on the same element",
"fix": "Add xo-confirm=\"Question?\" beside it, or remove it"
}
]The rules come in families. Each code is explained on the Error codes page, and the MCP tool explain_error gives the same text to an assistant.
| Codes | What they find |
|---|---|
| XO201, XO202, XO203, XO204 | Operations in screens. A name in xo-click, xo-submit, get or op that is not in the synced API, or an operation used the wrong way round: a click that calls a read, a view that reads a write, and in JavaScript load on a write or mutate on a read. |
| XO210 | Path parameters. A view whose operation has a {param} in its path and no p-param to fill it. |
| XO220, XO221, XO222 | Forms and expressions. xo-submit away from a form, a form outside an xo-view (it never binds), an xo-each, xo-if or xo-body with no path. |
| XO230 | Unknown names. An xo-* element or attribute that xoong does not define. Nearly always a typo; the message names the closest one. |
| XO240, XO241, XO242, XO243 | Accessibility and images. A button without a name, a click that a keyboard cannot reach, an xo-image without alt, an img whose src is bound inside a view. |
| XO250, XO251, XO252, XO253, XO254, XO255 | Tables, pagers, generated forms and selection. A table or pager asking for sorting, paging or filters that the operation does not offer, or sitting outside a view; a generated form with nothing to generate; xo-select and xo-selected used wrongly. |
| XO260, XO261, XO262, XO263 | Routes and layouts. A route path that is missing, malformed or repeated, a layout that is not declared or has no outlet, a route parameter that the route does not have. |
| XO270, XO271, XO272, XO273, XO274, XO275, XO276 | Attributes in the wrong place. An attribute that does nothing where it stands, a value outside its set or malformed, a name that the operation does not have, an attribute outside the element that gives it meaning. |
xoong dev
Serves the app while you work. It needs no bundler and no configuration beyond the proxy:
- Static files from the root, with
Cache-Control: no-cache. There is no live reload yet: refresh the page. - An import map. An HTML page that has none gets one before its first script, listing the installed xoong packages (and those named in
packages), soimport '@xoong/data'works as it is. - TypeScript. A
.tsfile is served as JavaScript, with its types stripped (erasable syntax only: noenum,namespaceor parameter properties; lines and columns stay the same). Import your own files with the.tsextension. - A proxy for every prefix you give.
/api/peoplereacheshttp://localhost:8080as/people: the prefix is removed, so put it in the target (/api=http://localhost:8080/api) to keep it. Whole path segments only (/apidoes not match/apix), the longest prefix first, bodies streamed both ways (server-sent events pass through), and a backend that does not answer gives a 502 with the reason. --spaanswers a path without a file extension withindex.html, for an app with an<xo-router>.--base(orbasein the config, or theX-Forwarded-Prefixheader) prefixes the import map and root-absolute asset URLs, the same prefix asxoong build --base. A request that still carries the prefix is served from the root.- Safe on a shared network. It listens on 127.0.0.1 only, serves nothing outside the root, hides folders that start with a dot (except
.xoongand.well-known) and refuses aHostthat is not local (allowedHostsadds names;"*"turns the check off).
xoong dev [--root .] [--base /admin] [--port 5173] [--proxy /api=http://localhost:8080]... [--spa]
| Option | Default | What it does |
|---|---|---|
--root | . | The folder to serve. xoong.config.json is read from it. |
--base | none | The URL path the app is served under, the same value as build --base. The import map and root-absolute asset URLs are prefixed, and a request that still carries the prefix is served from the root. X-Forwarded-Prefix overrides it when the header is a safe path. |
--port | 5173 | The port, a number from 0 to 65535 (0 lets the system pick one). The server listens on 127.0.0.1 only. |
--proxy | none | Forward the requests under a prefix to a backend, written /prefix=http://host:port. It adds to proxy of xoong.config.json; for the same prefix the flag wins. May be given more than once. |
--spa | off | Answer every path without a file extension with index.html, for an app with an <xo-router>. |
$ npx xoong dev
xoong dev http://127.0.0.1:5173 (root: /home/you/people-app)
proxy /api -> http://localhost:8080
It runs until you stop it. It exits with 1 when it cannot start (a port in use, a --port that is not a number from 0 to 65535, an unreadable xoong.config.json). The inline import map is blocked by a strict policy, so never deploy what the dev server sends: use xoong build.
xoong build
Writes static files that run under a strict Content-Security-Policy: no import map, no inline script, no inline style. It starts from every .html page under the root and follows what each one references: scripts, stylesheets, images, the modules the scripts import, CSS url() and @import. Installed packages are found in node_modules and copied to the same URLs the dev server uses. Then:
- bare imports (
"@xoong/data") are rewritten to URLs, and the import map is dropped from the pages; - TypeScript is stripped and
.tsfiles are written as.js; src,poster, a<link>'shrefand every URL of asrcsetare copied and, with--base, given the prefix.<a href>is left as written on purpose: a link to another page is for the router, or a plain relative link, to resolve;- every page gets
<link rel="modulepreload">for the modules its scripts import, so the browser fetches the chain at once instead of one level at a time (--no-preloadturns it off); - with
--minify(orbuild.minify), scripts, stylesheets and JSON are minified by the esbuild your project has installed (xoong does not depend on it; without it the build warns and goes on). Two risks: esbuild renames local identifiers (code that readsFunction.nameor a class'snamesees short names), and without esbuild the flag is only a warning, so check that it is in yourdevDependencies. - with
--bundle(orbuild.bundle), that esbuild folds static imports into CSP-clean ESM. A dynamicimport()stays its own chunk. Without esbuild the build warns and copies every module. - an inline
<script>is removed (it cannot run underscript-src 'self').--inline-scripts erroralso exits 1. JSON and JSON-LD stay. When.xoong/operations.jsonexists it is also written at/operations.json, and an import of the dot path is rewritten to that. - the default still writes the files you wrote: nothing is minified or bundled unless you ask.
xoong build [--root .] [--out dist] [--base /admin] [--include path]... [--exclude glob]... [--inline-scripts remove|error] [--no-preload] [--minify] [--bundle] [--budget KB]
| Option | Default | What it does |
|---|---|---|
--root | . | The folder with the pages. Every .html file under it is a page. |
--out | dist | The output folder. It is never read as input, even when it sits inside the root. |
--base | none | The URL path the app is served under. Root-absolute URLs get this prefix. By default the app is served at /. |
--include | none | A file or folder (relative to the root) to publish although no page names it: data fetched from JavaScript, images named in code. May be given more than once. |
--exclude | none | Keep matching .html files out of the start list (partials, drafts). A file that a page references is still published. May be given more than once. |
--inline-scripts | remove | Inline JavaScript is removed, because script-src 'self' blocks it. error does the same and makes the build exit 1. JSON and JSON-LD scripts stay. One of remove or error. |
--no-preload | off | Do not add <link rel="modulepreload"> to the pages. |
--minify | off | Minify the scripts and stylesheets (and JSON) with the esbuild your project has installed; the output stays strict-CSP and the imports are rewritten as usual. Without esbuild installed the build warns and writes the files as they are. |
--bundle | off | Bundle scripts into CSP-clean ESM with the esbuild your project has installed. Static imports go into the entry; a dynamic import() stays its own chunk. Without esbuild the build warns and copies every module. CSS stays separate. |
--budget | none | Print the first load of every page (HTML, stylesheets, scripts and their static imports, gzip, no images or data) and exit 1 when one is above this many KB. Also "build": { "budget": 120 } in xoong.config.json. build-report.json always lists the sizes. |
HTML that is not a page (a partial, a fixture, a draft) is kept out with --exclude; a file that a page references is still published. What the build cannot see, such as a fetch() of a data file or an image named in JavaScript, is added with --include.
import { configure } from '@xoong/data';
import '@xoong/data/view';
import operations from '/.xoong/operations.json' with { type: 'json' };import { configure } from '/@xoong/data/src/index.js';
import '/@xoong/data/src/view.js';
import operations from '/.xoong/operations.json' with { type: 'json' };import { configure } from '/admin/@xoong/data/src/index.js';
import '/admin/@xoong/data/src/view.js';
import operations from '/admin/.xoong/operations.json' with { type: 'json' };The strict-CSP model
The build prints the policy its output is tested with, and writes it into dist/build-report.json next to the size of every file (raw and gzipped) and the warnings. It does not send the policy: your host sets the Content-Security-Policy header. The CSP model explains why the runtime needs nothing the policy forbids.
$ npx xoong build --out dist
built 23 files into /home/you/people-app/dist (135.5 KB, 47.6 KB gzip)
runs under: Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self' data:; font-src 'self'; base-uri 'none'; form-action 'self'
The build warns about what that policy would block in your own pages: an inline <script>, a style="" attribute, an inline <style>. It also warns about a bare import it cannot resolve and a referenced file that does not exist. A warning does not fail the build (the exit code stays 0); it is printed and written to the report, so a CI job can fail on it.
warning: /index.html:2: a style="" attribute is blocked by style-src 'self'; use a class
warning: /index.html:3: an inline <script> is blocked by script-src 'self'; move it to a file
{
"csp": "default-src 'none'; script-src 'self'; ...",
"files": [
{ "path": "/index.html", "bytes": 1927, "gzip": 551 },
{ "path": "/src/main.js", "bytes": 493, "gzip": 311 }
],
"warnings": []
}Serve dist as static files. With an <xo-router>, make the host answer every path under the base with the app's index.html. Recipes has the deployment notes.
xoong render
Prerenders a page into plain HTML that already shows its data, for crawlers, for people without JavaScript and for the first paint. It runs the real <xo-view> runtime in Node and fetches the data from --api; in the browser the view draws again from the saved template, so nothing is hydrated. It needs @xoong/ssr, which is not part of @xoong/xoong: npm install @xoong/ssr. Without it the command stops and says so.
xoong render <page.html> --api <url> [--url /users/7] [--operations .xoong/operations.json] [--out file]
| Option | Default | What it does |
|---|---|---|
--api | required | The base URL of the API the page reads its data from. |
--url | none | Render the route of this URL, for a page with an <xo-router>. |
--operations | .xoong/operations.json | The operations table that sync wrote. |
--out | none | Write the HTML to this file (its folders are created). Without it the HTML goes to standard output. |
For a page with an <xo-router>, --url renders the route of that URL, with its <title> and description. Public pages only: the data is as fresh as the last render, and a page that needs the visitor's session is drawn in the browser. build does not call it: run render for each public page, and publish the files.
$ npx xoong render index.html --api http://localhost:8080 --out out/index.html
rendered index.html -> out/index.html
See Prerendering with @xoong/ssr for what the output contains and its limits.
xoong i18n
Two sub-commands keep translations honest. The text written in the HTML is the default of a key (Language), so a catalog for the source language can be made from the pages themselves.
i18n extract
Lists the keys a project uses with their source text: xo-t in HTML (and in HTML written inside JavaScript template strings) and text('key', 'default') calls. The catalog is sorted by key. A key used with two different texts, or a text that is not a valid message, is reported on standard error and makes the exit code 1; the catalog is still written. Folders are walked without node_modules, dist and folders that start with a dot.
xoong i18n extract <html|js files or folders>... [--out catalogs/en.json]
| Option | Default | What it does |
|---|---|---|
--out | none | Write the catalog to this file (its folders are created). Without it the JSON goes to standard output. |
i18n check
Compares a translation with the source catalog and prints one line per problem: a key that is missing, a key the source does not use, a message that cannot be read, placeholders that differ from the source's. It ends with the number of problems, or ok, and exits with 1 when there are problems.
xoong i18n check <catalog.json> --source <en.json>
| Option | Default | What it does |
|---|---|---|
--source | required | The source-language catalog, the file that i18n extract wrote. The translation is checked against it. |
$ npx xoong i18n extract src --out catalogs/en.json
5 messages -> catalogs/en.json
$ npx xoong i18n check catalogs/vi.json --source catalogs/en.json
catalogs/vi.json: "home.count" uses {count} but the source uses {n}
catalogs/vi.json: missing "home.title"
catalogs/vi.json: missing "search.hint"
catalogs/vi.json: "extra.key" is not used by the source
4 problems
Configuration
xoong.config.json holds what you would otherwise type on every run. All keys are optional, and a flag wins over the file. dev and build read it from their root folder (--root, the current folder by default); check reads it from the current folder. The other commands do not read it. A file that cannot be read stops the command with a message.
| Key | Used by | Default | Meaning |
|---|---|---|---|
root | dev | . | The folder to serve. The --root flag wins. |
port | dev | 5173 | The port. The --port flag wins. |
proxy | dev | none | An object of prefixes and backends, { "/api": "http://localhost:8080" }. It adds to the --proxy flags; for the same prefix the flag wins. |
spa | dev | false | The same as --spa. |
base | dev, build | none | The URL path the app is served under, the same value as --base. Dev prefixes the import map and root-absolute asset URLs; a request that still carries the prefix is served from the root. X-Forwarded-Prefix overrides it when the header is a safe path. |
allowedHosts | dev | [] | Host names the dev server accepts besides localhost, 127.0.0.1 and ::1. Any other Host header gets a 403, which stops a web page from reaching your machine through DNS rebinding. Add a name when you reach the server through a tunnel. "*" turns the check off. X-Forwarded-Host is not trusted in place of Host. |
packages | dev, build | [] | Installed packages, besides xoong's own, whose browser code the app imports. They are served at /@name, listed in the import map and copied by the build. |
build.exclude | build | [] | Globs of HTML files that are not pages. They add to the --exclude flags of build. |
build.preload | build | true | false turns off the modulepreload links, like --no-preload. |
build.minify | build | false | true minifies scripts, stylesheets and JSON with the esbuild the project has installed, like --minify; without esbuild the build warns and does not minify. |
build.bundle | build | false | true bundles scripts into CSP-clean ESM with that esbuild, like --bundle. A dynamic import() stays its own chunk. Without esbuild the build warns and copies every module. |
build.inlineScripts | build | remove | error makes the build exit 1 when it removes an inline script, like --inline-scripts error. JSON and JSON-LD scripts stay either way. |
check.exclude | check | [] | Globs of files and folders that check skips. They add to its --exclude flags. |
{
"port": 5173,
"proxy": { "/api": "http://localhost:8080" },
"packages": ["rainlet"],
"build": { "exclude": ["screens/**", "*.draft.html"], "preload": true },
"check": { "exclude": ["vendor/"] }
}Globs
--exclude and the exclude keys take paths relative to the root (for check, the current folder) with / separators. * is any characters except /, ** any characters including /, ? one character, and a trailing / means the folder and everything in it. A pattern without a / matches that name at any depth (*.draft.html, partials). A folder that matches is skipped with all it holds.
Hints
xoong.hints.json lets the front end say what the OpenAPI document does not, without editing the backend's file. Give it to sync and doctor with --hints; it is not looked for on its own, so the same flag belongs in your sync script. A key that is not one of these is reported (XO171), because it would do nothing.
| Key | Example | What it does |
|---|---|---|
idNames | ["id", "gid"] | Property names that identify a record, besides id. A schema that has one is an entity: its records are stored once and shared between screens. |
schemas | { "SessionResponse": { "entity": "Session", "id": "session_id" } } | Which schemas are one entity (entity) and which property is its id (id), for documents you cannot edit. |
unwrap | "data" | Responses shaped { code, message, data } are read as their data. |
idempotencyKeys | { "sendMessage": "client_message_id" } | A fresh UUID is put into that field of the request body on every call of the operation. |
fields | { "Person.email": { "label": "E-mail", "order": 2 } } | How a generated form draws a field, keyed by Schema.property: label, hint, placeholder, order, group, hidden, readOnly and widget. See Forms. |
In CI
Three steps cover a project: sync so the table is current (or commit .xoong/ and skip it), check to fail on a screen that does not match the API, and build to prove the site builds. Each exits with 1 on a problem, which fails the job. --format json gives other tools the findings.
name: xoong
on: [push, pull_request]
jobs:
screens:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx xoong sync openapi.json --hints xoong.hints.json
- run: npx xoong check index.html src
- run: npx xoong build --out dist
# a warning of the build does not fail it: this step does
- run: jq -e '.warnings | length == 0' dist/build-report.jsonExit codes
| Code | Meaning |
|---|---|
0 | done |
1 | findings or a failure (also a refused option) |
2 | unknown command or a missing argument |
syncanddoctor: 1 when the document has an error. Warnings do not fail.check: 1 when a finding is an error. Warnings do not fail.i18n extractandi18n check: 1 when there are problems.build: 0 even with warnings; fail onwarningsinbuild-report.jsonif you want to.dev: runs until you stop it; 1 when it cannot start.- No command at all prints the help and exits with 0. An unknown command, a missing argument or a missing required option prints the help and exits with 2. A refused option exits with 1.
See also: MCP server · Editor support · Error codes · Install · Data and tables · Recipes