Skip to content
Manech Dubreil edited this page Oct 21, 2026 · 5 revisions

CLI

transf-orm is a single binary (crates/transf-orm) that embeds two modes: the CLI (crates/cli) and the HTTP server (crates/server). The binary only parses the global flags with clap's derive API, builds a shared GlobalConfig, and dispatches:

transf-orm [--cli | --serveur] [--debug <level>] [--verbose] [--perf] [--tts] [--daltonien]
           [--port <port>] [<command>]

Modes

Invocation Mode Entry point
transf-orm (no argument) Interactive CLI transf_orm_cli::run(&config, None)
transf-orm [--cli] <command> ... CLI, runs one command transf_orm_cli::run(&config, Some(command))
transf-orm --serveur [--port <port>] HTTP server transf_orm_server::run(&config, server_args)

--cli is the default and can be omitted.

  • Interactive mode is not implemented yet: running transf-orm with no command only prints Interactive mode is not implemented yet (see #48).
  • Server mode only logs Starting transf-orm server on port <port> and exits for now; the HTTP bootstrap (axum, /health, --ip/--host) comes with #91.

Global flags

Flag Effect Status
--cli Run the CLI (default mode). Conflicts with --serveur. ✅
--serveur Run the HTTP server. Cannot be combined with a CLI command. ✅ (HTTP with #91)
--debug <level> Print internal logs on stderr up to level: error, warn, info, debug or trace (case-insensitive). ✅
--verbose Print each step of the command on stderr, prefixed with [verbose]. ✅
--perf Report performance metrics. 📋 #56
--tts Read CLI feedback aloud. 📋 #57
--daltonien Use a colour-blind-friendly palette. 📋 #55
--port <port> Server mode only (requires --serveur): port to listen on, 0 = any free port. Default 3000. ✅
-h, --help List the modes, flags and commands. ✅
-V, --version Print transf-orm <version>. ✅

Click a flag for its full behavior, examples and implementation details.

--debug, --verbose, --perf, --tts and --daltonien are global: they can also be placed after the command (transf-orm convert schema.prisma --verbose). --verbose and --debug are implemented and write only to stderr, so stdout is unchanged; --perf, --tts and --daltonien are only stored in GlobalConfig for now and come with #56, #57 and #55.

Any invalid input — unknown flag, invalid --debug level, --cli with --serveur, --serveur with a command, --port without --serveur — prints an error and exits with status 2.

GlobalConfig

The flags are mapped to transf_orm_core::config::GlobalConfig, defined in core so the CLI and the server read the same settings without duplicating anything:

pub struct GlobalConfig {
    pub debug: Option<DebugLevel>, // Error | Warn | Info | Debug | Trace
    pub verbose: bool,
    pub perf: bool,
    pub tts: bool,
    pub daltonien: bool,
}

core does not depend on clap: DebugLevel implements FromStr, which clap uses to parse and validate --debug. Mode-specific flags stay in their crate — ServerArgs (--port) lives in crates/server, the subcommands (transf_orm_cli::Command) in crates/cli — and are flattened into the binary's parser, so --help lists everything.

transf-orm convert

transf-orm convert <path> [--from <orm>]
  • <path> — path to the schema file (e.g. schema.prisma).
  • --from <orm> — force the source ORM by CLI name, skipping detection. Valid values come from registry::names().

Behavior:

  1. If --from is given, look up the ORM by name via registry::find_by_name. Unknown names print the list of valid options and exit with status 1.
  2. Otherwise, call registry::detect(path), which matches the file extension against every registered OrmDescriptor. No match prints an error suggesting --from and exits.
  3. If the detected ORM is not certain (extension is ambiguous between multiple ORMs), the CLI prompts for an Enter-to-confirm before proceeding; any other input (or Ctrl+C) aborts.
  4. Reads the file, calls the ORM's make_importer().import(content).
  5. On success, prints schema.to_canonical_json() to stdout (pretty JSON, alphabetically sorted keys — see Pivot IR).
  6. On ImportError, prints error: <message> to stderr and exits with status 1.

Flowchart: transf-orm convert decision tree — checks for --from, otherwise auto-detects the ORM, confirms with the user if detection is uncertain, then imports the file and prints the schema or an error

The only registered ORM is Prisma, so --from is rarely needed in practice — .prisma extension detection is certain: true, so no confirmation prompt occurs either.

Registry

crates/core/src/registry.rs is the single source of truth for which ORMs the CLI knows about:

pub struct OrmDescriptor {
    pub name: &'static str,                                  // CLI name for --from/--to
    pub display_name: &'static str,                          // human-readable
    pub extensions: &'static [&'static str],                 // e.g. &["prisma"]
    pub certain: bool,                                        // true = extension is unambiguous
    pub make_importer: Option<fn() -> Box<dyn Importer>>,
    pub make_exporter: Option<fn() -> Box<dyn Exporter>>,
}

pub static ORMS: &[OrmDescriptor] = &[OrmDescriptor {
    name: "prisma",
    display_name: "Prisma",
    extensions: &["prisma"],
    certain: true,
    make_importer: Some(|| Box::new(PrismaImporter)),
    make_exporter: None,
}];

Helper functions:

  • detect(path) -> Option<&OrmDescriptor> — matches the path's file extension.
  • find_by_name(name) -> Option<&OrmDescriptor> — case-insensitive lookup by CLI name.
  • names() -> Vec<&str> — all registered CLI names, used for error messages.

Adding a new ORM to the CLI is exactly one new entry in ORMS — nothing else in the CLI needs to change. make_importer/make_exporter are Option so an ORM can be import-only, export-only, or both; every entry has make_exporter: None since no exporter exists (see Importers).

Planned commands

Per Architecture, the CLI will grow generate, diff, merge, validate, init, watch, and history subcommands as the exporter, versioning, and codemod layers are built. None of these exist in crates/cli yet.

Clone this wiki locally