effx
CLI

effx build

Check the project, then write the canonical IR, the manifest and the generated Effect files. Obsolete generated files are removed only if the previous manifest owned them.

effx build [--project <tsconfig>] [--config <file>] [--out-dir <dir>] [--strict-access | --no-strict-access] [--target <profile>] [--emit <mode>]

From the example package: bun run --cwd examples/users effx:build.

Source: build in packages/cli/src/commands.ts, packages/cli/src/manifest.ts, scripts/effx.ts.

Flags

Same shared flags as effx check. The CLI overview has the full table, and Project config gives the precedence of flag, effx.config.ts, tsconfig effx block and default.

FlagValuesDefault
--projectpath to a tsconfigtsconfig.json, or the config project
--configpath to an effx.config.ts moduleeffx.config.ts beside the selected tsconfig, if present
--out-dirpath, resolved from the working directory<tsconfig dir>/.effx/generated
--strict-access, --no-strict-accessboolean flagfalse
--targeteffect-4.0resolved from the project's installed Effect
--emitcontract, handlers, allall

--emit selects which projection is written. See Emit modes and targets. --out-dir (or config outDir, or tsconfig effx.outDir) moves only the generated files. ir.json, manifest.json and surface.json stay in <tsconfig dir>/.effx. A generators toggle in effx.config.ts drops the matching built-in's files from the build without changing the IR or the semantic hash (spec 0015). See Extensions.

Output custody

build and dev --build share output custody. They acquire .effx-output-owner.lock in each exact canonical generated and metadata/manifest directory. A competing writer, including a symlink alias, fails before artifact writes. There is no waiting or automatic stale-lock reclamation. The owner releases only its own token when its scope closes. A crash can leave a fail-closed lock; recover it only after you establish that no writer owns it. These locks cover exact directories, not every possible nested-output overlap. They are not distributed leases. Check-only dev and LSP acquire no output locks. Saved tsconfig output-policy changes refresh watch exclusions and migrate watch-build custody between completed write batches. The same exact-directory refusal policy applies to the new output; an active batch retains its custody until completion.

Source: packages/cli/src/output-owner.ts, acquireBuildOutput and writeCompileResult in packages/cli/src/commands.ts.

Steps

 check (same output and exit code as `effx check`)
   │  errors? → nothing is written, exit 1
   ▼
 read previous .effx/manifest.json (if any)
   ▼
 work out obsolete files: listed by the previous manifest,
   inside this generated dir, ending in .ts, not in this build
   ▼
 write generated files       <generated dir>/<file>
 remove obsolete files
 write .effx/ir.json         canonical IR + "\n"
 write .effx/surface.json    surface manifest (spec 0021, `writeSurface`)
 write .effx/manifest.json   pretty JSON + "\n"
   ▼
 print: wrote .effx/{ir.json, manifest.json, surface.json}; generated: <files>

Warnings and info do not block a build. Only a diagnostic with severity error does.

Files written

FileContent
.effx/generated/*.tsgenerated Effect files, sorted by path. The directory is the project's resolved output directory (--out-dir, config outDir, tsconfig effx.outDir), default .effx/generated.
.effx/ir.jsoncanonical JSON of the normalized IR (Semantic hash), with a trailing newline
.effx/surface.jsonthe surface manifest written by writeSurface (spec 0021). Its format is not described on this page.
.effx/manifest.jsonthe manifest below, pretty-printed with two spaces, with a trailing newline

The success line lists the generated files relative to .effx/, for example generated/http.ts. With an output directory outside .effx, the listed paths start with ../ because they are computed with path.relative from the .effx directory (build in commands.ts).

Builds are deterministic. Spec 0003 requires two builds to produce a byte-identical ir.json, and the CLI test checks it.

Manifest

.effx/manifest.json holds what the IR deliberately omits: source locations, compiler versions, the file list and diagnostics. It is a Schema-defined value (Manifest in packages/cli/src/manifest.ts).

FieldTypeMeaning
format"effx-manifest"literal
version1literal
compiler.effxstring@effx/cli package version
compiler.effectstringEffect package version bundled with the CLI
compiler.typescriptstringversion of the TypeScript the frontend used
semanticHashstringsha-256 of the canonical IR (Semantic hash)
emitcontract, handlers or allthe emit mode of this build
generatedstring arraygenerated file paths relative to .effx/
diagnosticsDiagnostic arrayevery diagnostic from every stage, in stage order
locationsrecord of StableId to { file, line, col }where each operation and model is declared

locations keys are operation ids and model ids. The file is relative to the project root: the tsconfig directory, or the directory set by effx.projectRoot in the tsconfig. This is the only place declaration locations are kept. They never enter the IR (The invariant).

generated follows the generator output order, which generate sorts by path.

Obsolete files

The previous manifest is the ownership record. When the emit mode or the set of operations changes, files the previous build wrote and this build does not are removed. The rules:

RuleDetail
Only listed filesA file is removed only if the previous manifest.json listed it. Unrelated files in the output directory are never swept.
Only inside the output dirThe listed file must be under the current generated directory.
Only .tsFiles with another extension are kept.
Only inside .effxIf any file would be removed and the real path of the generated directory has no .effx segment, the build fails before writing with a CompilerFault (cannot remove obsolete generated files outside .effx).
Corrupt manifestAn existing manifest that does not decode fails the build with a CompilerFault (invalid prior effx manifest).

Source: build in packages/cli/src/commands.ts, docs/specs/0010-target-profiles-and-emit-modes.md ("obsolete artifacts ... must not masquerade as this build's output").

Exit code

ConditionExit
At least one error diagnostic1. Nothing is written.
Success0

On this page