The axion CLI
Code generation is a single command. It reads *.contract.ts files and emits committed,
readable Kotlin or Swift — no hidden directories, no Nitrogen for feature code. Generated
files are committed to the repo; there is no Gradle task or Xcode build phase that runs
the CLI automatically.
axion bridgekit generate --contracts 'src/**/*.contract.ts' --out-dir bridgekit/generated| Flag | Default | Purpose |
|---|---|---|
--contracts <glob> | **/*.contract.ts | Glob for contract files (cwd-relative; excludes node_modules, /dist/). |
--out-dir <path> | bridgekit/generated | Output directory for generated files and bridgekit.lock. Created recursively if absent. |
--platform <kotlin|swift> | kotlin | Target language. Drives the emitter. Any other value is a hard CliError at parse. |
--package <pkg> | derived from id | Kotlin package override (default axion.bridgekit.contracts.<id>). Ignored by the Swift emitter. |
--check | — | Diff against existing output; exit 1 on drift (CI mode). Writes nothing. |
--into <path> | — | After writing to --out-dir, cpSync every generated file (including the lock) to this path. Must pre-exist. |
What it emits
Section titled “What it emits”Per contract, the generator writes one <PascalName>Contract.kt containing:
- Shared object types (
data class,sealed class,enum class) interface <ClassName>— the provider interfaceinterface <ClassName>Client— the consumer/RN-side interfaceprivate object <ClassName>Codecs— encode/decode to plainMap<String, Any?>object <ClassName>Contract : BridgeContractDefinition<…>—id,contractHash,memberHashes,inbound(),outbound()
See Anatomy of generated Kotlin for a full dissection.
Per contract, the generator writes one <PascalName>Contract.swift containing:
- Shared object types (
public structwith memberwiseinit) public protocol <ClassName>: AnyObject— the provider protocolpublic protocol <ClassName>Client: AnyObject— the consumer protocolclass <ClassName>Contract: BridgeContractDefinition<any <ClassName>, any <ClassName>Client>—id,contractHash,memberHashes,inbound(_:),outbound(_:)
The committed Swift file is post-processed by sanitize-ios-public-module.py to produce the
clean public facade (explicit public modifiers, synthesized inits). See Anatomy of
generated Swift.
Both platforms share a single bridgekit.lock file. The lock carries a platform field
("kotlin" or "swift") that records which emitter produced it.
Examples
Section titled “Examples”# Default: Kotlin, bridgekit/generated/axion bridgekit generate
# Explicit contract glob + custom output diraxion bridgekit generate --contracts 'src/**/*.contract.ts' --out-dir android/bridgekit
# Swift outputaxion bridgekit generate --platform swift --out-dir ios/contracts/generated
# Cross-repo mirror (Kotlin)axion bridgekit generate --into ../Android.Application/features/lia/bridgekit
# Cross-repo mirror (Swift)axion bridgekit generate --platform swift \ --out-dir ios/contracts/generated \ --into ../iOS.Application/lia/contracts--into makes the cross-repo copy tool-owned rather than a manual cp. The directory
must pre-exist. See Drift & cross-repo delivery for the full workflow.
Two loading strategies
Section titled “Two loading strategies”The CLI detects the authoring style and loads each accordingly — both produce the same
serializable descriptor ($type tag + descriptorVersion, duck-typed, never instanceof).
Schema (t.*) contracts — imported
Section titled “Schema (t.*) contracts — imported”The CLI spawns a child ESM subprocess (--experimental-strip-types), imports the .ts file
via a worker, duck-types the exports for { descriptor, hash }, and serializes to JSON.
Marker contracts — statically parsed
Section titled “Marker contracts — statically parsed”Marker type parameters are erased at runtime, so they cannot be read by importing. Marker
contracts are detected by a regex for Sync<, Async<, Void<, Stream<, State< in
value position and parsed with ts-morph: it loads the type checker, injects a stub for
@axion/bridgekit/contract, then walks the defineContract AST to extract params/result
types from the generic arguments. No subprocess required.
The purity gate
Section titled “The purity gate”Before loading, the CLI walks the import graph. Any import other than
@axion/bridgekit/contract or a relative contract path is a hard error with file:line.
This guarantees contracts are safe to import under Node, Jest, and web builds.
CI usage
Section titled “CI usage”axion bridgekit generate \ --contracts 'src/**/*.contract.ts' \ --out-dir bridgekit/generated \ --checkaxion bridgekit generate \ --contracts 'src/**/*.contract.ts' \ --out-dir ios/contracts/generated \ --platform swift \ --check--check compares against the last generated files and exits non-zero on any drift. The
drift message labels the platform explicitly:
Drift detected in generated Kotlin bindings: AxionHostContract.kt: content differsRun `axion bridgekit generate` to regenerate and commit the result.Drift detected in generated Swift bindings: AxionHostContract.swift: content differsRun `axion bridgekit generate --platform swift` to regenerate and commit the result.Wire it into CI so contract changes and regenerated output always land together.