Bump clap version to 3.2 (#706)
* bump clap to 3.1.17 sea-orm-migration: bump clap version to 3.1.17 sea-orm-cli: use clap derive API instead of builder API sea-orm-migration: use clap derive * Fix clippy warnings * Migration CLI verbose with default value * bump clap to 3.2 Co-authored-by: Thanh Van <tvt@smonv.com> Co-authored-by: Billy Chan <ccw.billy.123@gmail.com>
This commit is contained in:
parent
ca3809a6d3
commit
580fa90023
@ -29,7 +29,7 @@ path = "src/bin/sea.rs"
|
||||
required-features = ["codegen"]
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "^2.33.3" }
|
||||
clap = { version = "^3.2", features = ["env", "derive"] }
|
||||
dotenv = { version = "^0.15" }
|
||||
async-std = { version = "^1.9", features = [ "attributes", "tokio1" ] }
|
||||
sea-orm-codegen = { version = "^0.8.0", path = "../sea-orm-codegen", optional = true }
|
||||
|
@ -1,17 +1,24 @@
|
||||
use clap::StructOpt;
|
||||
use dotenv::dotenv;
|
||||
use sea_orm_cli::*;
|
||||
use sea_orm_cli::{handle_error, run_generate_command, run_migrate_command, Cli, Commands};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
let matches = cli::build_cli().get_matches();
|
||||
let cli = Cli::parse();
|
||||
let verbose = cli.verbose;
|
||||
|
||||
match matches.subcommand() {
|
||||
("generate", Some(matches)) => run_generate_command(matches)
|
||||
.await
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
run_generate_command(command, verbose)
|
||||
.await
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
Commands::Migrate {
|
||||
migration_dir,
|
||||
command,
|
||||
} => run_migrate_command(command, migration_dir.as_str(), verbose)
|
||||
.unwrap_or_else(handle_error),
|
||||
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
|
||||
_ => unreachable!("You should never see this message"),
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,26 @@
|
||||
//! COPY FROM bin/main.rs
|
||||
|
||||
use clap::StructOpt;
|
||||
use dotenv::dotenv;
|
||||
use sea_orm_cli::*;
|
||||
use sea_orm_cli::{handle_error, run_generate_command, run_migrate_command, Cli, Commands};
|
||||
|
||||
#[async_std::main]
|
||||
async fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
let matches = cli::build_cli().get_matches();
|
||||
let cli = Cli::parse();
|
||||
let verbose = cli.verbose;
|
||||
|
||||
match matches.subcommand() {
|
||||
("generate", Some(matches)) => run_generate_command(matches)
|
||||
.await
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
run_generate_command(command, verbose)
|
||||
.await
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
Commands::Migrate {
|
||||
migration_dir,
|
||||
command,
|
||||
} => run_migrate_command(command, migration_dir.as_str(), verbose)
|
||||
.unwrap_or_else(handle_error),
|
||||
("migrate", Some(matches)) => run_migrate_command(matches).unwrap_or_else(handle_error),
|
||||
_ => unreachable!("You should never see this message"),
|
||||
}
|
||||
}
|
||||
|
@ -1,129 +1,160 @@
|
||||
use crate::migration::get_subcommands;
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
use clap::{ArgGroup, Parser, Subcommand};
|
||||
|
||||
pub fn build_cli() -> App<'static, 'static> {
|
||||
let entity_subcommand = SubCommand::with_name("generate")
|
||||
.about("Codegen related commands")
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.subcommand(
|
||||
SubCommand::with_name("entity")
|
||||
.about("Generate entity")
|
||||
.arg(
|
||||
Arg::with_name("DATABASE_URL")
|
||||
.long("database-url")
|
||||
.short("u")
|
||||
.help("Database URL")
|
||||
.takes_value(true)
|
||||
.required(true)
|
||||
.env("DATABASE_URL"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("DATABASE_SCHEMA")
|
||||
.long("database-schema")
|
||||
.short("s")
|
||||
.help("Database schema")
|
||||
.long_help("Database schema\n \
|
||||
- For MySQL, this argument is ignored.\n \
|
||||
- For PostgreSQL, this argument is optional with default value 'public'.")
|
||||
.takes_value(true)
|
||||
.env("DATABASE_SCHEMA"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("OUTPUT_DIR")
|
||||
.long("output-dir")
|
||||
.short("o")
|
||||
.help("Entity file output directory")
|
||||
.takes_value(true)
|
||||
.default_value("./"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("INCLUDE_HIDDEN_TABLES")
|
||||
.long("include-hidden-tables")
|
||||
.help("Generate entity file for hidden tables (i.e. table name starts with an underscore)")
|
||||
.takes_value(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("TABLES")
|
||||
.long("tables")
|
||||
.short("t")
|
||||
.use_delimiter(true)
|
||||
.help("Generate entity file for specified tables only (comma seperated)")
|
||||
.takes_value(true)
|
||||
.conflicts_with("INCLUDE_HIDDEN_TABLES"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("EXPANDED_FORMAT")
|
||||
.long("expanded-format")
|
||||
.help("Generate entity file of expanded format")
|
||||
.takes_value(false)
|
||||
.conflicts_with("COMPACT_FORMAT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("COMPACT_FORMAT")
|
||||
.long("compact-format")
|
||||
.help("Generate entity file of compact format")
|
||||
.takes_value(false)
|
||||
.conflicts_with("EXPANDED_FORMAT"),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("WITH_SERDE")
|
||||
.long("with-serde")
|
||||
.help("Automatically derive serde Serialize / Deserialize traits for the entity (none, serialize, deserialize, both)")
|
||||
.takes_value(true)
|
||||
.default_value("none")
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("MAX_CONNECTIONS")
|
||||
.long("max-connections")
|
||||
.help("The maximum amount of connections to use when connecting to the database.")
|
||||
.takes_value(true)
|
||||
.default_value("1")
|
||||
),
|
||||
)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp);
|
||||
#[derive(Parser, Debug)]
|
||||
#[clap(version)]
|
||||
pub struct Cli {
|
||||
#[clap(action, global = true, short, long, help = "Show debug messages")]
|
||||
pub verbose: bool,
|
||||
|
||||
let arg_migration_dir = Arg::with_name("MIGRATION_DIR")
|
||||
.long("migration-dir")
|
||||
.short("d")
|
||||
.help("Migration script directory")
|
||||
.takes_value(true)
|
||||
.default_value("./migration");
|
||||
let mut migrate_subcommands = SubCommand::with_name("migrate")
|
||||
.about("Migration related commands")
|
||||
.subcommand(
|
||||
SubCommand::with_name("init")
|
||||
.about("Initialize migration directory")
|
||||
.arg(arg_migration_dir.clone()),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("generate")
|
||||
.about("Generate a new, empty migration")
|
||||
.arg(
|
||||
Arg::with_name("MIGRATION_NAME")
|
||||
.help("Name of the new migation")
|
||||
.required(true)
|
||||
.takes_value(true),
|
||||
)
|
||||
.arg(arg_migration_dir.clone()),
|
||||
)
|
||||
.arg(arg_migration_dir.clone());
|
||||
for subcommand in get_subcommands() {
|
||||
migrate_subcommands =
|
||||
migrate_subcommands.subcommand(subcommand.arg(arg_migration_dir.clone()));
|
||||
}
|
||||
|
||||
App::new("sea-orm-cli")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.subcommand(entity_subcommand)
|
||||
.subcommand(migrate_subcommands)
|
||||
.arg(
|
||||
Arg::with_name("VERBOSE")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.help("Show debug messages")
|
||||
.takes_value(false)
|
||||
.global(true),
|
||||
)
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
#[clap(subcommand)]
|
||||
pub command: Commands,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Debug)]
|
||||
pub enum Commands {
|
||||
#[clap(about = "Codegen related commands")]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
Generate {
|
||||
#[clap(subcommand)]
|
||||
command: GenerateSubcommands,
|
||||
},
|
||||
#[clap(about = "Migration related commands")]
|
||||
Migrate {
|
||||
#[clap(
|
||||
value_parser,
|
||||
global = true,
|
||||
short = 'd',
|
||||
long,
|
||||
help = "Migration script directory",
|
||||
default_value = "./migration"
|
||||
)]
|
||||
migration_dir: String,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Option<MigrateSubcommands>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Debug)]
|
||||
pub enum MigrateSubcommands {
|
||||
#[clap(about = "Initialize migration directory")]
|
||||
Init,
|
||||
#[clap(about = "Generate a new, empty migration")]
|
||||
Generate {
|
||||
#[clap(
|
||||
value_parser,
|
||||
long,
|
||||
required = true,
|
||||
help = "Name of the new migration"
|
||||
)]
|
||||
migration_name: String,
|
||||
},
|
||||
#[clap(about = "Drop all tables from the database, then reapply all migrations")]
|
||||
Fresh,
|
||||
#[clap(about = "Rollback all applied migrations, then reapply all migrations")]
|
||||
Refresh,
|
||||
#[clap(about = "Rollback all applied migrations")]
|
||||
Reset,
|
||||
#[clap(about = "Check the status of all migrations")]
|
||||
Status,
|
||||
#[clap(about = "Apply pending migrations")]
|
||||
Up {
|
||||
#[clap(
|
||||
value_parser,
|
||||
short,
|
||||
long,
|
||||
default_value = "1",
|
||||
help = "Number of pending migrations to be rolled back"
|
||||
)]
|
||||
num: u32,
|
||||
},
|
||||
#[clap(value_parser, about = "Rollback applied migrations")]
|
||||
Down {
|
||||
#[clap(
|
||||
value_parser,
|
||||
short,
|
||||
long,
|
||||
default_value = "1",
|
||||
help = "Number of pending migrations to be rolled back"
|
||||
)]
|
||||
num: u32,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand, PartialEq, Debug)]
|
||||
pub enum GenerateSubcommands {
|
||||
#[clap(about = "Generate entity")]
|
||||
#[clap(arg_required_else_help = true)]
|
||||
#[clap(group(ArgGroup::new("formats").args(&["compact-format", "expanded-format"])))]
|
||||
#[clap(group(ArgGroup::new("group-tables").args(&["tables", "include-hidden-tables"])))]
|
||||
Entity {
|
||||
#[clap(action, long, help = "Generate entity file of compact format")]
|
||||
compact_format: bool,
|
||||
|
||||
#[clap(action, long, help = "Generate entity file of expanded format")]
|
||||
expanded_format: bool,
|
||||
|
||||
#[clap(
|
||||
action,
|
||||
long,
|
||||
help = "Generate entity file for hidden tables (i.e. table name starts with an underscore)"
|
||||
)]
|
||||
include_hidden_tables: bool,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
short = 't',
|
||||
long,
|
||||
use_value_delimiter = true,
|
||||
takes_value = true,
|
||||
help = "Generate entity file for specified tables only (comma separated)"
|
||||
)]
|
||||
tables: Option<String>,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
long,
|
||||
default_value = "1",
|
||||
help = "The maximum amount of connections to use when connecting to the database."
|
||||
)]
|
||||
max_connections: u32,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
short = 'o',
|
||||
long,
|
||||
default_value = "./",
|
||||
help = "Entity file output directory"
|
||||
)]
|
||||
output_dir: String,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
short = 's',
|
||||
long,
|
||||
env = "DATABASE_SCHEMA",
|
||||
default_value = "public",
|
||||
long_help = "Database schema\n \
|
||||
- For MySQL, this argument is ignored.\n \
|
||||
- For PostgreSQL, this argument is optional with default value 'public'."
|
||||
)]
|
||||
database_schema: String,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
short = 'u',
|
||||
long,
|
||||
env = "DATABASE_URL",
|
||||
help = "Database URL"
|
||||
)]
|
||||
database_url: String,
|
||||
|
||||
#[clap(
|
||||
value_parser,
|
||||
long,
|
||||
default_value = "none",
|
||||
help = "Automatically derive serde Serialize / Deserialize traits for the entity (none, serialize, deserialize, both)"
|
||||
)]
|
||||
with_serde: String,
|
||||
},
|
||||
}
|
||||
|
@ -1,23 +1,29 @@
|
||||
use chrono::Local;
|
||||
use clap::ArgMatches;
|
||||
use regex::Regex;
|
||||
use sea_orm_codegen::{EntityTransformer, OutputFile, WithSerde};
|
||||
use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr};
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
use url::Url;
|
||||
|
||||
pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
||||
match matches.subcommand() {
|
||||
("entity", Some(args)) => {
|
||||
let output_dir = args.value_of("OUTPUT_DIR").unwrap();
|
||||
let include_hidden_tables = args.is_present("INCLUDE_HIDDEN_TABLES");
|
||||
let tables = args
|
||||
.values_of("TABLES")
|
||||
.unwrap_or_default()
|
||||
.collect::<Vec<_>>();
|
||||
let expanded_format = args.is_present("EXPANDED_FORMAT");
|
||||
let with_serde = args.value_of("WITH_SERDE").unwrap();
|
||||
if args.is_present("VERBOSE") {
|
||||
use crate::{GenerateSubcommands, MigrateSubcommands};
|
||||
|
||||
pub async fn run_generate_command(
|
||||
command: GenerateSubcommands,
|
||||
verbose: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
match command {
|
||||
GenerateSubcommands::Entity {
|
||||
compact_format: _,
|
||||
expanded_format,
|
||||
include_hidden_tables,
|
||||
tables,
|
||||
max_connections,
|
||||
output_dir,
|
||||
database_schema,
|
||||
database_url,
|
||||
with_serde,
|
||||
} => {
|
||||
if verbose {
|
||||
let _ = tracing_subscriber::fmt()
|
||||
.with_max_level(tracing::Level::DEBUG)
|
||||
.with_test_writer()
|
||||
@ -35,18 +41,9 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
.try_init();
|
||||
}
|
||||
|
||||
let max_connections = args
|
||||
.value_of("MAX_CONNECTIONS")
|
||||
.map(str::parse::<u32>)
|
||||
.transpose()?
|
||||
.unwrap();
|
||||
|
||||
// The database should be a valid URL that can be parsed
|
||||
// protocol://username:password@host/database_name
|
||||
let url = Url::parse(
|
||||
args.value_of("DATABASE_URL")
|
||||
.expect("No database url could be found"),
|
||||
)?;
|
||||
let url = Url::parse(&database_url)?;
|
||||
|
||||
// Make sure we have all the required url components
|
||||
//
|
||||
@ -68,6 +65,11 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
}
|
||||
}
|
||||
|
||||
let tables = match tables {
|
||||
Some(t) => t,
|
||||
_ => "".to_string(),
|
||||
};
|
||||
|
||||
// Closures for filtering tables
|
||||
let filter_tables = |table: &str| -> bool {
|
||||
if !tables.is_empty() {
|
||||
@ -76,6 +78,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
|
||||
true
|
||||
};
|
||||
|
||||
let filter_hidden_tables = |table: &str| -> bool {
|
||||
if include_hidden_tables {
|
||||
true
|
||||
@ -149,7 +152,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
use sea_schema::postgres::discovery::SchemaDiscovery;
|
||||
use sqlx::Postgres;
|
||||
|
||||
let schema = args.value_of("DATABASE_SCHEMA").unwrap_or("public");
|
||||
let schema = &database_schema;
|
||||
let connection = connect::<Postgres>(max_connections, url.as_str()).await?;
|
||||
let schema_discovery = SchemaDiscovery::new(connection, schema);
|
||||
let schema = schema_discovery.discover().await;
|
||||
@ -165,9 +168,9 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
};
|
||||
|
||||
let output = EntityTransformer::transform(table_stmts)?
|
||||
.generate(expanded_format, WithSerde::from_str(with_serde).unwrap());
|
||||
.generate(expanded_format, WithSerde::from_str(&with_serde).unwrap());
|
||||
|
||||
let dir = Path::new(output_dir);
|
||||
let dir = Path::new(&output_dir);
|
||||
fs::create_dir_all(dir)?;
|
||||
|
||||
for OutputFile { name, content } in output.files.iter() {
|
||||
@ -184,8 +187,7 @@ pub async fn run_generate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dy
|
||||
.wait()?;
|
||||
}
|
||||
}
|
||||
_ => unreachable!("You should never see this message"),
|
||||
};
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@ -201,103 +203,106 @@ where
|
||||
.map_err(Into::into)
|
||||
}
|
||||
|
||||
pub fn run_migrate_command(matches: &ArgMatches<'_>) -> Result<(), Box<dyn Error>> {
|
||||
let migrate_subcommand = matches.subcommand();
|
||||
// If it's `migrate init`
|
||||
if let ("init", Some(args)) = migrate_subcommand {
|
||||
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
|
||||
let migration_dir = match migration_dir.ends_with('/') {
|
||||
true => migration_dir.to_string(),
|
||||
false => format!("{}/", migration_dir),
|
||||
};
|
||||
println!("Initializing migration directory...");
|
||||
macro_rules! write_file {
|
||||
($filename: literal) => {
|
||||
let fn_content = |content: String| content;
|
||||
write_file!($filename, $filename, fn_content);
|
||||
};
|
||||
($filename: literal, $template: literal, $fn_content: expr) => {
|
||||
let filepath = [&migration_dir, $filename].join("");
|
||||
println!("Creating file `{}`", filepath);
|
||||
let path = Path::new(&filepath);
|
||||
let prefix = path.parent().unwrap();
|
||||
fs::create_dir_all(prefix).unwrap();
|
||||
let mut file = fs::File::create(path)?;
|
||||
let content = include_str!(concat!("../template/migration/", $template));
|
||||
let content = $fn_content(content.to_string());
|
||||
file.write_all(content.as_bytes())?;
|
||||
pub fn run_migrate_command(
|
||||
command: Option<MigrateSubcommands>,
|
||||
migration_dir: &str,
|
||||
verbose: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
match command {
|
||||
Some(MigrateSubcommands::Init) => {
|
||||
let migration_dir = match migration_dir.ends_with('/') {
|
||||
true => migration_dir.to_string(),
|
||||
false => format!("{}/", migration_dir),
|
||||
};
|
||||
println!("Initializing migration directory...");
|
||||
macro_rules! write_file {
|
||||
($filename: literal) => {
|
||||
let fn_content = |content: String| content;
|
||||
write_file!($filename, $filename, fn_content);
|
||||
};
|
||||
($filename: literal, $template: literal, $fn_content: expr) => {
|
||||
let filepath = [&migration_dir, $filename].join("");
|
||||
println!("Creating file `{}`", filepath);
|
||||
let path = Path::new(&filepath);
|
||||
let prefix = path.parent().unwrap();
|
||||
fs::create_dir_all(prefix).unwrap();
|
||||
let mut file = fs::File::create(path)?;
|
||||
let content = include_str!(concat!("../template/migration/", $template));
|
||||
let content = $fn_content(content.to_string());
|
||||
file.write_all(content.as_bytes())?;
|
||||
};
|
||||
}
|
||||
write_file!("src/lib.rs");
|
||||
write_file!("src/m20220101_000001_create_table.rs");
|
||||
write_file!("src/main.rs");
|
||||
write_file!("Cargo.toml", "_Cargo.toml", |content: String| {
|
||||
let ver = format!(
|
||||
"^{}.{}.0",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR")
|
||||
);
|
||||
content.replace("<sea-orm-migration-version>", &ver)
|
||||
});
|
||||
write_file!("README.md");
|
||||
println!("Done!");
|
||||
// Early exit!
|
||||
return Ok(());
|
||||
}
|
||||
write_file!("src/lib.rs");
|
||||
write_file!("src/m20220101_000001_create_table.rs");
|
||||
write_file!("src/main.rs");
|
||||
write_file!("Cargo.toml", "_Cargo.toml", |content: String| {
|
||||
let ver = format!(
|
||||
"^{}.{}.0",
|
||||
env!("CARGO_PKG_VERSION_MAJOR"),
|
||||
env!("CARGO_PKG_VERSION_MINOR")
|
||||
);
|
||||
content.replace("<sea-orm-migration-version>", &ver)
|
||||
});
|
||||
write_file!("README.md");
|
||||
println!("Done!");
|
||||
// Early exit!
|
||||
return Ok(());
|
||||
} else if let ("generate", Some(args)) = migrate_subcommand {
|
||||
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
|
||||
let migration_name = args.value_of("MIGRATION_NAME").unwrap();
|
||||
println!("Generating new migration...");
|
||||
Some(MigrateSubcommands::Generate { migration_name }) => {
|
||||
println!("Generating new migration...");
|
||||
|
||||
// build new migration filename
|
||||
let now = Local::now();
|
||||
let migration_name = format!(
|
||||
"m{}_{}",
|
||||
now.format("%Y%m%d_%H%M%S").to_string(),
|
||||
migration_name
|
||||
);
|
||||
// build new migration filename
|
||||
let now = Local::now();
|
||||
let migration_name = format!("m{}_{}", now.format("%Y%m%d_%H%M%S"), migration_name);
|
||||
|
||||
create_new_migration(&migration_name, migration_dir)?;
|
||||
update_migrator(&migration_name, migration_dir)?;
|
||||
return Ok(());
|
||||
}
|
||||
let (subcommand, migration_dir, steps, verbose) = match migrate_subcommand {
|
||||
// Catch all command with pattern `migrate xxx`
|
||||
(subcommand, Some(args)) => {
|
||||
let migration_dir = args.value_of("MIGRATION_DIR").unwrap();
|
||||
let steps = args.value_of("NUM_MIGRATION");
|
||||
let verbose = args.is_present("VERBOSE");
|
||||
(subcommand, migration_dir, steps, verbose)
|
||||
create_new_migration(&migration_name, migration_dir)?;
|
||||
update_migrator(&migration_name, migration_dir)?;
|
||||
return Ok(());
|
||||
}
|
||||
// Catch command `migrate`, this will be treated as `migrate up`
|
||||
_ => {
|
||||
let migration_dir = matches.value_of("MIGRATION_DIR").unwrap();
|
||||
let verbose = matches.is_present("VERBOSE");
|
||||
("up", migration_dir, None, verbose)
|
||||
let (subcommand, migration_dir, steps, verbose) = match command {
|
||||
Some(MigrateSubcommands::Fresh) => ("fresh", migration_dir, None, verbose),
|
||||
Some(MigrateSubcommands::Refresh) => ("refresh", migration_dir, None, verbose),
|
||||
Some(MigrateSubcommands::Reset) => ("reset", migration_dir, None, verbose),
|
||||
Some(MigrateSubcommands::Status) => ("status", migration_dir, None, verbose),
|
||||
Some(MigrateSubcommands::Up { num }) => ("up", migration_dir, Some(num), verbose),
|
||||
Some(MigrateSubcommands::Down { num }) => {
|
||||
("down", migration_dir, Some(num), verbose)
|
||||
}
|
||||
_ => ("up", migration_dir, None, verbose),
|
||||
};
|
||||
|
||||
// Construct the `--manifest-path`
|
||||
let manifest_path = if migration_dir.ends_with('/') {
|
||||
format!("{}Cargo.toml", migration_dir)
|
||||
} else {
|
||||
format!("{}/Cargo.toml", migration_dir)
|
||||
};
|
||||
// Construct the arguments that will be supplied to `cargo` command
|
||||
let mut args = vec![
|
||||
"run",
|
||||
"--manifest-path",
|
||||
manifest_path.as_str(),
|
||||
"--",
|
||||
subcommand,
|
||||
];
|
||||
|
||||
let mut num: String = "".to_string();
|
||||
if let Some(steps) = steps {
|
||||
num = steps.to_string();
|
||||
}
|
||||
if !num.is_empty() {
|
||||
args.extend(["-n", num.as_str()])
|
||||
}
|
||||
if verbose {
|
||||
args.push("-v");
|
||||
}
|
||||
// Run migrator CLI on user's behalf
|
||||
println!("Running `cargo {}`", args.join(" "));
|
||||
Command::new("cargo").args(args).spawn()?.wait()?;
|
||||
}
|
||||
};
|
||||
// Construct the `--manifest-path`
|
||||
let manifest_path = if migration_dir.ends_with('/') {
|
||||
format!("{}Cargo.toml", migration_dir)
|
||||
} else {
|
||||
format!("{}/Cargo.toml", migration_dir)
|
||||
};
|
||||
// Construct the arguments that will be supplied to `cargo` command
|
||||
let mut args = vec![
|
||||
"run",
|
||||
"--manifest-path",
|
||||
manifest_path.as_str(),
|
||||
"--",
|
||||
subcommand,
|
||||
];
|
||||
if let Some(steps) = steps {
|
||||
args.extend(["-n", steps]);
|
||||
}
|
||||
if verbose {
|
||||
args.push("-v");
|
||||
}
|
||||
// Run migrator CLI on user's behalf
|
||||
println!("Running `cargo {}`", args.join(" "));
|
||||
Command::new("cargo").args(args).spawn()?.wait()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -310,7 +315,7 @@ fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(),
|
||||
let migration_template =
|
||||
include_str!("../template/migration/src/m20220101_000001_create_table.rs");
|
||||
let migration_content =
|
||||
migration_template.replace("m20220101_000001_create_table", &migration_name);
|
||||
migration_template.replace("m20220101_000001_create_table", migration_name);
|
||||
let mut migration_file = fs::File::create(migration_filepath)?;
|
||||
migration_file.write_all(migration_content.as_bytes())?;
|
||||
Ok(())
|
||||
@ -327,7 +332,7 @@ fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box<
|
||||
let mut updated_migrator_content = migrator_content.clone();
|
||||
|
||||
// create a backup of the migrator file in case something goes wrong
|
||||
let migrator_backup_filepath = migrator_filepath.clone().with_file_name("lib.rs.bak");
|
||||
let migrator_backup_filepath = migrator_filepath.with_file_name("lib.rs.bak");
|
||||
fs::copy(&migrator_filepath, &migrator_backup_filepath)?;
|
||||
let mut migrator_file = fs::File::create(&migrator_filepath)?;
|
||||
|
||||
@ -348,7 +353,7 @@ fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box<
|
||||
.map(|migration| format!(" Box::new({}::Migration),", migration))
|
||||
.collect::<Vec<String>>()
|
||||
.join("\n");
|
||||
boxed_migrations.push_str("\n");
|
||||
boxed_migrations.push('\n');
|
||||
let boxed_migrations = format!("vec![\n{} ]\n", boxed_migrations);
|
||||
let vec_regex = Regex::new(r"vec!\[[\s\S]+\]\n")?;
|
||||
let updated_migrator_content = vec_regex.replace(&updated_migrator_content, &boxed_migrations);
|
||||
@ -368,25 +373,30 @@ where
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use clap::StructOpt;
|
||||
|
||||
use super::*;
|
||||
use crate::cli;
|
||||
use clap::AppSettings;
|
||||
use crate::{Cli, Commands};
|
||||
|
||||
#[test]
|
||||
#[should_panic(
|
||||
expected = "called `Result::unwrap()` on an `Err` value: RelativeUrlWithoutBase"
|
||||
)]
|
||||
fn test_generate_entity_no_protocol() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"://root:root@localhost:3306/database",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"://root:root@localhost:3306/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -394,16 +404,20 @@ mod tests {
|
||||
expected = "There is no database name as part of the url path: postgresql://root:root@localhost:3306"
|
||||
)]
|
||||
fn test_generate_entity_no_database_section() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgresql://root:root@localhost:3306",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgresql://root:root@localhost:3306",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -411,61 +425,77 @@ mod tests {
|
||||
expected = "There is no database name as part of the url path: mysql://root:root@localhost:3306/"
|
||||
)]
|
||||
fn test_generate_entity_no_database_path() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:root@localhost:3306/",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:root@localhost:3306/",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "No username was found in the database url")]
|
||||
fn test_generate_entity_no_username() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://:root@localhost:3306/database",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://:root@localhost:3306/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: PoolTimedOut")]
|
||||
fn test_generate_entity_no_password() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:@localhost:3306/database",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"mysql://root:@localhost:3306/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: EmptyHost")]
|
||||
fn test_generate_entity_no_host() {
|
||||
let matches = cli::build_cli()
|
||||
.setting(AppSettings::NoBinaryName)
|
||||
.get_matches_from(vec![
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgres://root:root@/database",
|
||||
]);
|
||||
let cli = Cli::parse_from(vec![
|
||||
"sea-orm-cli",
|
||||
"generate",
|
||||
"entity",
|
||||
"--database-url",
|
||||
"postgres://root:root@/database",
|
||||
]);
|
||||
|
||||
smol::block_on(run_generate_command(matches.subcommand().1.unwrap())).unwrap();
|
||||
match cli.command {
|
||||
Commands::Generate { command } => {
|
||||
smol::block_on(run_generate_command(command, cli.verbose)).unwrap();
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn test_create_new_migration() {
|
||||
|
@ -1,7 +1,6 @@
|
||||
pub mod cli;
|
||||
#[cfg(feature = "codegen")]
|
||||
pub mod commands;
|
||||
pub mod migration;
|
||||
|
||||
pub use cli::*;
|
||||
#[cfg(feature = "codegen")]
|
||||
|
@ -1,49 +0,0 @@
|
||||
use clap::{App, AppSettings, Arg, SubCommand};
|
||||
|
||||
pub fn build_cli() -> App<'static, 'static> {
|
||||
let mut app = App::new("sea-schema-migration")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.arg(
|
||||
Arg::with_name("VERBOSE")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.help("Show debug messages")
|
||||
.takes_value(false)
|
||||
.global(true),
|
||||
);
|
||||
for subcommand in get_subcommands() {
|
||||
app = app.subcommand(subcommand);
|
||||
}
|
||||
app
|
||||
}
|
||||
|
||||
pub fn get_subcommands() -> Vec<App<'static, 'static>> {
|
||||
vec![
|
||||
SubCommand::with_name("fresh")
|
||||
.about("Drop all tables from the database, then reapply all migrations"),
|
||||
SubCommand::with_name("refresh")
|
||||
.about("Rollback all applied migrations, then reapply all migrations"),
|
||||
SubCommand::with_name("reset").about("Rollback all applied migrations"),
|
||||
SubCommand::with_name("status").about("Check the status of all migrations"),
|
||||
SubCommand::with_name("up")
|
||||
.about("Apply pending migrations")
|
||||
.arg(
|
||||
Arg::with_name("NUM_MIGRATION")
|
||||
.long("num")
|
||||
.short("n")
|
||||
.help("Number of pending migrations to be applied")
|
||||
.takes_value(true),
|
||||
),
|
||||
SubCommand::with_name("down")
|
||||
.about("Rollback applied migrations")
|
||||
.arg(
|
||||
Arg::with_name("NUM_MIGRATION")
|
||||
.long("num")
|
||||
.short("n")
|
||||
.help("Number of pending migrations to be rolled back")
|
||||
.takes_value(true)
|
||||
.default_value("1"),
|
||||
),
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@ path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
async-trait = { version = "^0.1" }
|
||||
clap = { version = "^2.33" }
|
||||
clap = { version = "^3.2", features = ["env", "derive"] }
|
||||
dotenv = { version = "^0.15" }
|
||||
sea-orm = { version = "^0.8.0", path = "../", default-features = false, features = ["macros"] }
|
||||
sea-orm-cli = { version = "^0.8.1", path = "../sea-orm-cli", default-features = false }
|
||||
|
@ -1,10 +1,10 @@
|
||||
use clap::{App, AppSettings, Arg};
|
||||
use clap::Parser;
|
||||
use dotenv::dotenv;
|
||||
use std::{fmt::Display, process::exit};
|
||||
use tracing_subscriber::{prelude::*, EnvFilter};
|
||||
|
||||
use sea_orm::{Database, DbConn};
|
||||
use sea_orm_cli::migration::get_subcommands;
|
||||
use sea_orm_cli::MigrateSubcommands;
|
||||
|
||||
use super::MigratorTrait;
|
||||
|
||||
@ -15,27 +15,26 @@ where
|
||||
dotenv().ok();
|
||||
let url = std::env::var("DATABASE_URL").expect("Environment variable 'DATABASE_URL' not set");
|
||||
let db = &Database::connect(&url).await.unwrap();
|
||||
let app = build_cli();
|
||||
get_matches(migrator, db, app).await;
|
||||
let cli = Cli::parse();
|
||||
|
||||
run_migrate(migrator, db, cli.command, cli.verbose).await;
|
||||
}
|
||||
|
||||
pub async fn get_matches<M>(_: M, db: &DbConn, app: App<'static, 'static>)
|
||||
where
|
||||
pub async fn run_migrate<M>(
|
||||
_: M,
|
||||
db: &DbConn,
|
||||
command: Option<MigrateSubcommands>,
|
||||
verbose: bool,
|
||||
) where
|
||||
M: MigratorTrait,
|
||||
{
|
||||
let matches = app.get_matches();
|
||||
let mut verbose = false;
|
||||
let filter = match matches.subcommand() {
|
||||
(_, None) => "sea_orm_migration=info",
|
||||
(_, Some(args)) => match args.is_present("VERBOSE") {
|
||||
true => {
|
||||
verbose = true;
|
||||
"debug"
|
||||
}
|
||||
false => "sea_orm_migration=info",
|
||||
},
|
||||
let filter = match verbose {
|
||||
true => "debug",
|
||||
false => "sea_orm_migration=info",
|
||||
};
|
||||
|
||||
let filter_layer = EnvFilter::try_new(filter).unwrap();
|
||||
|
||||
if verbose {
|
||||
let fmt_layer = tracing_subscriber::fmt::layer();
|
||||
tracing_subscriber::registry()
|
||||
@ -52,44 +51,27 @@ where
|
||||
.with(fmt_layer)
|
||||
.init()
|
||||
};
|
||||
match matches.subcommand() {
|
||||
("fresh", _) => M::fresh(db).await,
|
||||
("refresh", _) => M::refresh(db).await,
|
||||
("reset", _) => M::reset(db).await,
|
||||
("status", _) => M::status(db).await,
|
||||
("up", None) => M::up(db, None).await,
|
||||
("down", None) => M::down(db, Some(1)).await,
|
||||
("up", Some(args)) => {
|
||||
let str = args.value_of("NUM_MIGRATION").unwrap_or_default();
|
||||
let steps = str.parse().ok();
|
||||
M::up(db, steps).await
|
||||
}
|
||||
("down", Some(args)) => {
|
||||
let str = args.value_of("NUM_MIGRATION").unwrap();
|
||||
let steps = str.parse().ok().unwrap_or(1);
|
||||
M::down(db, Some(steps)).await
|
||||
}
|
||||
|
||||
match command {
|
||||
Some(MigrateSubcommands::Fresh) => M::fresh(db).await,
|
||||
Some(MigrateSubcommands::Refresh) => M::refresh(db).await,
|
||||
Some(MigrateSubcommands::Reset) => M::reset(db).await,
|
||||
Some(MigrateSubcommands::Status) => M::status(db).await,
|
||||
Some(MigrateSubcommands::Up { num }) => M::up(db, Some(num)).await,
|
||||
Some(MigrateSubcommands::Down { num }) => M::down(db, Some(num)).await,
|
||||
_ => M::up(db, None).await,
|
||||
}
|
||||
.unwrap_or_else(handle_error);
|
||||
}
|
||||
|
||||
pub fn build_cli() -> App<'static, 'static> {
|
||||
let mut app = App::new("sea-orm-migration")
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.setting(AppSettings::VersionlessSubcommands)
|
||||
.arg(
|
||||
Arg::with_name("VERBOSE")
|
||||
.long("verbose")
|
||||
.short("v")
|
||||
.help("Show debug messages")
|
||||
.takes_value(false)
|
||||
.global(true),
|
||||
);
|
||||
for subcommand in get_subcommands() {
|
||||
app = app.subcommand(subcommand);
|
||||
}
|
||||
app
|
||||
#[derive(Parser)]
|
||||
#[clap(version)]
|
||||
pub struct Cli {
|
||||
#[clap(action, short = 'v', long, global = true, help = "Show debug messages")]
|
||||
verbose: bool,
|
||||
|
||||
#[clap(subcommand)]
|
||||
command: Option<MigrateSubcommands>,
|
||||
}
|
||||
|
||||
fn handle_error<E>(error: E)
|
||||
|
Loading…
x
Reference in New Issue
Block a user