sea_orm_cli/
cli.rs

1use clap::{ArgAction, ArgGroup, Parser, Subcommand, ValueEnum};
2#[cfg(feature = "codegen")]
3use dotenvy::dotenv;
4
5#[cfg(feature = "codegen")]
6use crate::{handle_error, run_generate_command, run_migrate_command};
7
8#[derive(Parser, Debug)]
9#[command(
10    version,
11    author,
12    help_template = r#"{before-help}{name} {version}
13{about-with-newline}
14
15{usage-heading} {usage}
16
17{all-args}{after-help}
18
19AUTHORS:
20    {author}
21"#,
22    about = r#"
23   ____                 ___   ____   __  __        /\
24  / ___|   ___   __ _  / _ \ |  _ \ |  \/  |      {.-}
25  \___ \  / _ \ / _` || | | || |_) || |\/| |     ;_.-'\
26   ___) ||  __/| (_| || |_| ||  _ < | |  | |    {    _.}_
27  |____/  \___| \__,_| \___/ |_| \_\|_|  |_|     \.-' /  `,
28                                                  \  |    /
29  An async & dynamic ORM for Rust                  \ |  ,/
30  ===============================                   \|_/
31
32  Getting Started
33    - Documentation: https://www.sea-ql.org/SeaORM
34    - Tutorial: https://www.sea-ql.org/sea-orm-tutorial
35    - Examples: https://github.com/SeaQL/sea-orm/tree/master/examples
36    - Cookbook: https://www.sea-ql.org/sea-orm-cookbook
37
38  Join our Discord server to chat with others in the SeaQL community!
39    - Invitation: https://discord.com/invite/uCPdDXzbdv
40
41  SeaQL Community Survey 2025
42    - Link: https://www.sea-ql.org/community-survey
43
44  If you like what we do, consider starring, sharing and contributing!
45"#
46)]
47pub struct Cli {
48    #[arg(global = true, short, long, help = "Show debug messages")]
49    pub verbose: bool,
50
51    #[command(subcommand)]
52    pub command: Commands,
53}
54
55#[derive(Subcommand, PartialEq, Eq, Debug)]
56pub enum Commands {
57    #[command(
58        about = "Codegen related commands",
59        arg_required_else_help = true,
60        display_order = 10
61    )]
62    Generate {
63        #[command(subcommand)]
64        command: GenerateSubcommands,
65    },
66    #[command(about = "Migration related commands", display_order = 20)]
67    Migrate {
68        #[arg(
69            global = true,
70            short = 'd',
71            long,
72            env = "MIGRATION_DIR",
73            help = "Migration script directory.
74If your migrations are in their own crate,
75you can provide the root of that crate.
76If your migrations are in a submodule of your app,
77you should provide the directory of that submodule.",
78            default_value = "./migration"
79        )]
80        migration_dir: String,
81
82        #[arg(
83            global = true,
84            short = 's',
85            long,
86            env = "DATABASE_SCHEMA",
87            long_help = "Database schema\n \
88                        - For MySQL and SQLite, this argument is ignored.\n \
89                        - For PostgreSQL, this argument is optional with default value 'public'.\n"
90        )]
91        database_schema: Option<String>,
92
93        #[arg(
94            global = true,
95            short = 'u',
96            long,
97            env = "DATABASE_URL",
98            help = "Database URL",
99            hide_env_values = true
100        )]
101        database_url: Option<String>,
102
103        #[command(subcommand)]
104        command: Option<MigrateSubcommands>,
105    },
106}
107
108#[derive(Subcommand, PartialEq, Eq, Debug)]
109pub enum MigrateSubcommands {
110    #[command(about = "Initialize migration directory", display_order = 10)]
111    Init,
112    #[command(about = "Generate a new, empty migration", display_order = 20)]
113    Generate {
114        #[arg(required = true, help = "Name of the new migration")]
115        migration_name: String,
116
117        #[arg(
118            long,
119            default_value = "true",
120            help = "Generate migration file based on Utc time",
121            conflicts_with = "local_time",
122            display_order = 1001
123        )]
124        universal_time: bool,
125
126        #[arg(
127            long,
128            help = "Generate migration file based on Local time",
129            conflicts_with = "universal_time",
130            display_order = 1002
131        )]
132        local_time: bool,
133    },
134    #[command(
135        about = "Drop all tables from the database, then reapply all migrations",
136        display_order = 30
137    )]
138    Fresh,
139    #[command(
140        about = "Rollback all applied migrations, then reapply all migrations",
141        display_order = 40
142    )]
143    Refresh,
144    #[command(about = "Rollback all applied migrations", display_order = 50)]
145    Reset,
146    #[command(about = "Check the status of all migrations", display_order = 60)]
147    Status,
148    #[command(about = "Apply pending migrations", display_order = 70)]
149    Up {
150        #[arg(short, long, help = "Number of pending migrations to apply")]
151        num: Option<u32>,
152    },
153    #[command(about = "Rollback applied migrations", display_order = 80)]
154    Down {
155        #[arg(
156            short,
157            long,
158            default_value = "1",
159            help = "Number of applied migrations to be rolled back",
160            display_order = 90
161        )]
162        num: u32,
163    },
164}
165
166#[derive(Subcommand, PartialEq, Eq, Debug)]
167pub enum GenerateSubcommands {
168    #[command(about = "Generate entity")]
169    #[command(group(ArgGroup::new("formats").args(&["compact_format", "expanded_format", "frontend_format"])))]
170    #[command(group(ArgGroup::new("group-tables").args(&["tables", "include_hidden_tables"])))]
171    Entity {
172        #[arg(long, help = "Generate entity file of compact format")]
173        compact_format: bool,
174
175        #[arg(long, help = "Generate entity file of expanded format")]
176        expanded_format: bool,
177
178        #[arg(long, help = "Generate entity file of frontend format")]
179        frontend_format: bool,
180
181        #[arg(
182            long,
183            help = "Generate entity file for hidden tables (i.e. table name starts with an underscore)"
184        )]
185        include_hidden_tables: bool,
186
187        #[arg(
188            short = 't',
189            long,
190            value_delimiter = ',',
191            help = "Generate entity file for specified tables only (comma separated)"
192        )]
193        tables: Vec<String>,
194
195        #[arg(
196            long,
197            value_delimiter = ',',
198            default_value = "seaql_migrations",
199            help = "Skip generating entity file for specified tables (comma separated)"
200        )]
201        ignore_tables: Vec<String>,
202
203        #[arg(
204            long,
205            default_value = "1",
206            help = "The maximum amount of connections to use when connecting to the database."
207        )]
208        max_connections: u32,
209
210        #[arg(
211            long,
212            default_value = "30",
213            long_help = "Acquire timeout in seconds of the connection used for schema discovery"
214        )]
215        acquire_timeout: u64,
216
217        #[arg(
218            short = 'o',
219            long,
220            default_value = "./",
221            help = "Entity file output directory"
222        )]
223        output_dir: String,
224
225        #[arg(
226            short = 's',
227            long,
228            env = "DATABASE_SCHEMA",
229            long_help = "Database schema\n \
230                        - For MySQL, this argument is ignored.\n \
231                        - For PostgreSQL, this argument is optional with default value 'public'."
232        )]
233        database_schema: Option<String>,
234
235        #[arg(
236            short = 'u',
237            long,
238            env = "DATABASE_URL",
239            help = "Database URL",
240            hide_env_values = true
241        )]
242        database_url: String,
243
244        #[arg(
245            long,
246            default_value = "all",
247            help = "Generate prelude.rs file (all, none, all-allow-unused-imports)"
248        )]
249        with_prelude: String,
250
251        #[arg(
252            long,
253            default_value = "none",
254            help = "Automatically derive serde Serialize / Deserialize traits for the entity (none, \
255                serialize, deserialize, both)"
256        )]
257        with_serde: String,
258
259        #[arg(
260            long,
261            help = "Generate a serde field attribute, '#[serde(skip_deserializing)]', for the primary key fields to skip them during deserialization, this flag will be affective only when '--with-serde' is 'both' or 'deserialize'"
262        )]
263        serde_skip_deserializing_primary_key: bool,
264
265        #[arg(
266            long,
267            default_value = "false",
268            help = "Opt-in to add skip attributes to hidden columns (i.e. when 'with-serde' enabled and column name starts with an underscore)"
269        )]
270        serde_skip_hidden_column: bool,
271
272        #[arg(
273            long,
274            default_value = "false",
275            long_help = "Automatically derive the Copy trait on generated enums.\n\
276            Enums generated from a database don't have associated data by default, and as such can \
277            derive Copy.
278            "
279        )]
280        with_copy_enums: bool,
281
282        #[arg(
283            long,
284            default_value_t,
285            value_enum,
286            help = "The datetime crate to use for generating entities."
287        )]
288        date_time_crate: DateTimeCrate,
289
290        #[arg(
291            long,
292            short = 'l',
293            default_value = "false",
294            help = "Generate index file as `lib.rs` instead of `mod.rs`."
295        )]
296        lib: bool,
297
298        #[arg(
299            long,
300            value_delimiter = ',',
301            help = "Add extra derive macros to generated model struct (comma separated), e.g. `--model-extra-derives 'ts_rs::Ts','CustomDerive'`"
302        )]
303        model_extra_derives: Vec<String>,
304
305        #[arg(
306            long,
307            value_delimiter = ',',
308            help = r#"Add extra attributes to generated model struct, no need for `#[]` (comma separated), e.g. `--model-extra-attributes 'serde(rename_all = "camelCase")','ts(export)'`"#
309        )]
310        model_extra_attributes: Vec<String>,
311
312        #[arg(
313            long,
314            value_delimiter = ',',
315            help = "Add extra derive macros to generated enums (comma separated), e.g. `--enum-extra-derives 'ts_rs::Ts','CustomDerive'`"
316        )]
317        enum_extra_derives: Vec<String>,
318
319        #[arg(
320            long,
321            value_delimiter = ',',
322            help = r#"Add extra attributes to generated enums, no need for `#[]` (comma separated), e.g. `--enum-extra-attributes 'serde(rename_all = "camelCase")','ts(export)'`"#
323        )]
324        enum_extra_attributes: Vec<String>,
325
326        #[arg(
327            long,
328            default_value = "false",
329            long_help = "Generate helper Enumerations that are used by Seaography."
330        )]
331        seaography: bool,
332
333        #[arg(
334            long,
335            default_value = "true",
336            default_missing_value = "true",
337            num_args = 0..=1,
338            require_equals = true,
339            action = ArgAction::Set,
340            long_help = "Generate empty ActiveModelBehavior impls."
341        )]
342        impl_active_model_behavior: bool,
343    },
344}
345
346#[derive(Copy, Clone, Debug, PartialEq, Eq, ValueEnum, Default)]
347pub enum DateTimeCrate {
348    #[default]
349    Chrono,
350    Time,
351}
352
353/// Use this to build a local, version-controlled `sea-orm-cli` in dependent projects
354/// (see [example use case](https://github.com/SeaQL/sea-orm/discussions/1889)).
355#[cfg(feature = "codegen")]
356pub async fn main() {
357    dotenv().ok();
358
359    let cli = Cli::parse();
360    let verbose = cli.verbose;
361
362    match cli.command {
363        Commands::Generate { command } => {
364            run_generate_command(command, verbose)
365                .await
366                .unwrap_or_else(handle_error);
367        }
368        Commands::Migrate {
369            migration_dir,
370            database_schema,
371            database_url,
372            command,
373        } => run_migrate_command(
374            command,
375            &migration_dir,
376            database_schema,
377            database_url,
378            verbose,
379        )
380        .unwrap_or_else(handle_error),
381    }
382}