Tools

Command line

The appling command checks, formats, inspects, releases and generates code from your models.

The appling command line tool is the package @appling/cli. It validates .appling files, rewrites them into canonical form, prints the semantic graph, records releases and runs code generation. Every command reads the files you give it as one workspace, so references between modules resolve across files.

Install

npm install --save-dev @appling/cli

Or run it without installing:

npx @appling/cli check models

Usage

appling check [files or folders...]
appling fmt   [files or folders...]
appling graph [files or folders...]
appling release <version> [files or folders...]
appling generate [--plan | --migrate | --sync] [files or folders...]

How files are found

ArgumentMeaning
noneThe current folder
a fileThat file, whatever its extension
a folderEvery .appling file below it, searched recursively

Folder searches skip hidden folders (names starting with ., such as .appling or .git) and node_modules. All files found load into one workspace before anything else happens, so sales.appling can reference crm.Account from crm.appling.

IMPORTANT

release and generate work relative to the current folder, not to the paths you pass. release writes to ./.appling/releases/, and generate reads ./appling.config.json. Run them from your project root.

Diagnostics

check, graph, release and generate print one line per problem in the form file:line:column severity message, with the file relative to the current folder:

models/sales.appling:13:23 error A machine must govern an entity field marked "state" with an enum type.

The severity is error or warning. Only errors change the exit code. The full list of rules is on Validation.

check

Parses, links and validates every file, prints each diagnostic, then a summary line.

appling check examples
2 file(s), 0 error(s)

check prints diagnostics on stdout. It exits with 1 when there is at least one error.

fmt

Rewrites every file into canonical form: whitespace, indentation (two spaces) and line breaks are normalized, comments are kept. It prints formatted <file> for each file it changed and stays silent for files that were already canonical.

appling fmt examples

fmt only needs a file to parse. It skips files with syntax errors, leaves them untouched and reports them on stderr:

models/sales.appling: not formatted, fix syntax errors first (run "appling check")

fmt is idempotent: running it a second time changes nothing. The language server formats with the same printer, see Editors.

graph

Validates the workspace, then prints the semantic graph as JSON on stdout: every element as a node, every reference as an edge, and the module to module dependencies.

appling graph examples > graph.json
{
  "nodes": [{ "qn": "sales.Order.status", "kind": "Field", "layer": "domain", "level": "in", "module": "sales", "props": { "type": "OrderStatus", "modifiers": ["state"], "default": "draft" }, "data": { "...": "..." } }],
  "edges": [{ "from": "sales.Order.status", "to": "sales.OrderStatus", "kind": "type" }],
  "dependencies": [{ "from": "sales", "to": "crm", "count": 1 }]
}

When the model has errors, graph prints them on stderr, writes nothing to stdout and exits with 1. The JSON format is described field by field in Semantic graph.

release

Validates the workspace and writes a snapshot of its semantic graph to .appling/releases/<version>.json in the current folder.

appling release 1.0.0
released 1.0.0 to .appling/releases/1.0.0.json

The version must have the form major.minor.patch with numbers only, such as 1.2.3. release refuses a model with errors, an invalid version and a version that already exists; it never overwrites a release.

Once a release exists, the tools that edit the model (the IDE and the AI tools) record renames, type changes and removals of released domain elements as migration steps in one block per module named after the release, such as migration since_v1_0_0. See Edit operations and Migrations. The file format is on Configuration.

generate

Generates a draft project for every target in appling.config.json through the target’s code generation extension. The model must be free of errors.

{
  "generate": [
    { "extension": "@appling/codegen-django", "out": "build/shop", "options": { "name": "shop" } }
  ]
}
appling generate examples

For each target, generate:

  1. Loads the extension package named in extension, resolved from the current folder.
  2. Checks the tools the extension needs (for example uv for Django) and stops with a hint for each one that is missing.
  3. Refuses to run when the out folder exists and is not empty. Generation creates a new draft project once.
  4. Runs the planned steps into out, printing one line per step, and writes the task registry .appling/tasks.json into it.
  5. Prints a summary such as 12 element(s): 11 generated, 1 partial, 0 todo.

Without targets in the configuration, generate prints how to add one and exits with 1. The extensions are described in Code generation, the configuration in Configuration.

Plan

Prints the steps for every target without running anything, so no prerequisites are checked and no folder is touched. Each line names the element (or (project) for setup steps) and the step:

For the model on Semantic graph (the modules crm and sales) and the Django target above:

appling generate --plan
@appling/codegen-django -> build/shop
  (project): command: uv init --bare --name shop
  (project): command: uv add django~=6.1.0
  (project): command: uv run django-admin startproject config .
  crm: command: uv run python manage.py startapp crm
  crm: template: config/settings.py
  sales: command: uv run python manage.py startapp sales
  sales: template: config/settings.py
  crm.Account: template: crm/models.py
  sales.OrderStatus: template: sales/enums.py
  sales.Order: template: sales/models.py
  sales.Order.invariant["total never negative"]: template: sales/tests.py
  (project): command: uv run python manage.py makemigrations
12 element(s): 11 generated, 1 partial, 0 todo

partial means the element was generated with open tasks; here the invariant got a skipped test whose body is a registry task. See Task registry.

Migrate

Carries the model’s migration steps that a generated project has not applied yet into it. Renames, type changes and removals become framework migrations that keep the data (a rename stays a rename, not a drop and an add), the generated model code changes with them, and the rest of the work becomes registry tasks.

appling generate --migrate examples
build/shop: 2 migration step(s) applied, 1 task(s) added to the registry

The target must have been generated first (it needs .appling/tasks.json in out). Applied steps are recorded by id in the project’s .appling/migrations.json, so each step is applied once. When nothing is pending, it prints build/shop: no pending migration steps. See Migrations in generated projects.

Sync

Brings a generated project up to the current model and keeps the work done in it since. It applies pending migration steps first (as --migrate), generates the model afresh in a temporary folder, and merges every file three ways. See Sync.

appling generate --sync examples
conflicts: internal/sales/types.go
build/shop: synced, 1 file(s) with conflict markers (registry tasks)

The target must have been generated with a version of appling that stores the generation baseline in .appling/baseline/.

--plan, --migrate and --sync must come directly after generate, before any path.

simulate

appling simulate reach <machine> <state> [paths...]
appling simulate actors <action> [paths...]
appling simulate risks <action> [paths...]

Asks the simulator about the model’s behavior. The model must be valid.

$ appling simulate reach sales.OrderFlow paid examples
paid is reachable from draft:
  PlaceOrder -> placed
  PaymentReceived -> paid

$ appling simulate actors sales.Cancel examples
sales.Shopper: no

$ appling simulate risks sales.PlaceOrder examples
no invariant reads a field this action changes

Exit codes

CodeMeaning
0No errors
1check, graph, release or generate found errors in the model; release got an invalid or existing version; generate has no targets, a missing prerequisite, a non empty output folder, a failing step, or (with --migrate) a target that was not generated; fmt skipped a file with syntax errors
2Unknown command, release without a version, or simulate without a known question and its arguments

Exit code 2 also prints the usage.

Examples

Validate everything below the current folder in CI:

npx @appling/cli check

Format the models before committing:

appling fmt models

List every entity of the model with jq:

appling graph models | jq -r '.nodes[] | select(.kind == "Entity") | .qn'

Release, keep editing, then bring a generated project up to date:

appling release 1.0.0 models
# rename or retype released fields in the IDE or with the AI tools
appling generate --migrate models