From c4fa40ab3e4d9743b0a5059dce915558a75b7bfe Mon Sep 17 00:00:00 2001 From: Billy Chan Date: Tue, 2 Aug 2022 16:30:49 +0800 Subject: [PATCH] [cli] migrator CLI handles init and generate commands --- .../src/{commands.rs => commands/generate.rs} | 225 +---------------- sea-orm-cli/src/commands/migrate.rs | 231 ++++++++++++++++++ sea-orm-cli/src/commands/mod.rs | 17 ++ sea-orm-cli/src/lib.rs | 2 - sea-orm-migration/src/cli.rs | 40 ++- 5 files changed, 277 insertions(+), 238 deletions(-) rename sea-orm-cli/src/{commands.rs => commands/generate.rs} (54%) create mode 100644 sea-orm-cli/src/commands/migrate.rs create mode 100644 sea-orm-cli/src/commands/mod.rs diff --git a/sea-orm-cli/src/commands.rs b/sea-orm-cli/src/commands/generate.rs similarity index 54% rename from sea-orm-cli/src/commands.rs rename to sea-orm-cli/src/commands/generate.rs index 70b13d01..0ff95f41 100644 --- a/sea-orm-cli/src/commands.rs +++ b/sea-orm-cli/src/commands/generate.rs @@ -1,14 +1,12 @@ -use chrono::Local; -use regex::Regex; use sea_orm_codegen::{ DateTimeCrate as CodegenDateTimeCrate, EntityTransformer, EntityWriterContext, OutputFile, WithSerde, }; -use std::{error::Error, fmt::Display, fs, io::Write, path::Path, process::Command, str::FromStr}; +use std::{error::Error, fs, io::Write, path::Path, process::Command, str::FromStr}; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; -use crate::{DateTimeCrate, GenerateSubcommands, MigrateSubcommands}; +use crate::{DateTimeCrate, GenerateSubcommands}; pub async fn run_generate_command( command: GenerateSubcommands, @@ -207,172 +205,6 @@ where .map_err(Into::into) } -pub fn run_migrate_command( - command: Option, - migration_dir: &str, - verbose: bool, -) -> Result<(), Box> { - 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("", &ver) - }); - write_file!("README.md"); - println!("Done!"); - // Early exit! - return Ok(()); - } - 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"), migration_name); - - create_new_migration(&migration_name, migration_dir)?; - update_migrator(&migration_name, migration_dir)?; - return Ok(()); - } - _ => { - 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()?; - } - } - - Ok(()) -} - -fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(), Box> { - let migration_filepath = Path::new(migration_dir) - .join("src") - .join(format!("{}.rs", &migration_name)); - println!("Creating migration file `{}`", migration_filepath.display()); - // TODO: make OS agnostic - let migration_template = - include_str!("../template/migration/src/m20220101_000001_create_table.rs"); - let mut migration_file = fs::File::create(migration_filepath)?; - migration_file.write_all(migration_template.as_bytes())?; - Ok(()) -} - -fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box> { - let migrator_filepath = Path::new(migration_dir).join("src").join("lib.rs"); - println!( - "Adding migration `{}` to `{}`", - migration_name, - migrator_filepath.display() - ); - let migrator_content = fs::read_to_string(&migrator_filepath)?; - 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.with_file_name("lib.rs.bak"); - fs::copy(&migrator_filepath, &migrator_backup_filepath)?; - let mut migrator_file = fs::File::create(&migrator_filepath)?; - - // find existing mod declarations, add new line - let mod_regex = Regex::new(r"mod\s+(?Pm\d{8}_\d{6}_\w+);")?; - let mods: Vec<_> = mod_regex.captures_iter(&migrator_content).collect(); - let mods_end = mods.last().unwrap().get(0).unwrap().end() + 1; - updated_migrator_content.insert_str(mods_end, format!("mod {};\n", migration_name).as_str()); - - // build new vector from declared migration modules - let mut migrations: Vec<&str> = mods - .iter() - .map(|cap| cap.name("name").unwrap().as_str()) - .collect(); - migrations.push(migration_name); - let mut boxed_migrations = migrations - .iter() - .map(|migration| format!(" Box::new({}::Migration),", migration)) - .collect::>() - .join("\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); - - migrator_file.write_all(updated_migrator_content.as_bytes())?; - fs::remove_file(&migrator_backup_filepath)?; - Ok(()) -} - -pub fn handle_error(error: E) -where - E: Display, -{ - eprintln!("{}", error); - ::std::process::exit(1); -} - impl From for CodegenDateTimeCrate { fn from(date_time_crate: DateTimeCrate) -> CodegenDateTimeCrate { match date_time_crate { @@ -489,57 +321,4 @@ mod tests { _ => unreachable!(), } } - - #[test] - fn test_create_new_migration() { - let migration_name = "test_name"; - let migration_dir = "/tmp/sea_orm_cli_test_new_migration/"; - fs::create_dir_all(format!("{}src", migration_dir)).unwrap(); - create_new_migration(migration_name, migration_dir).unwrap(); - let migration_filepath = Path::new(migration_dir) - .join("src") - .join(format!("{}.rs", migration_name)); - assert!(migration_filepath.exists()); - let migration_content = fs::read_to_string(migration_filepath).unwrap(); - assert_eq!( - &migration_content, - include_str!("../template/migration/src/m20220101_000001_create_table.rs") - ); - fs::remove_dir_all("/tmp/sea_orm_cli_test_new_migration/").unwrap(); - } - - #[test] - fn test_update_migrator() { - let migration_name = "test_name"; - let migration_dir = "/tmp/sea_orm_cli_test_update_migrator/"; - fs::create_dir_all(format!("{}src", migration_dir)).unwrap(); - let migrator_filepath = Path::new(migration_dir).join("src").join("lib.rs"); - fs::copy("./template/migration/src/lib.rs", &migrator_filepath).unwrap(); - update_migrator(migration_name, migration_dir).unwrap(); - assert!(&migrator_filepath.exists()); - let migrator_content = fs::read_to_string(&migrator_filepath).unwrap(); - let mod_regex = Regex::new(r"mod (?P\w+);").unwrap(); - let migrations: Vec<&str> = mod_regex - .captures_iter(&migrator_content) - .map(|cap| cap.name("name").unwrap().as_str()) - .collect(); - assert_eq!(migrations.len(), 2); - assert_eq!( - *migrations.first().unwrap(), - "m20220101_000001_create_table" - ); - assert_eq!(migrations.last().unwrap(), &migration_name); - let boxed_regex = Regex::new(r"Box::new\((?P\S+)::Migration\)").unwrap(); - let migrations: Vec<&str> = boxed_regex - .captures_iter(&migrator_content) - .map(|cap| cap.name("name").unwrap().as_str()) - .collect(); - assert_eq!(migrations.len(), 2); - assert_eq!( - *migrations.first().unwrap(), - "m20220101_000001_create_table" - ); - assert_eq!(migrations.last().unwrap(), &migration_name); - fs::remove_dir_all("/tmp/sea_orm_cli_test_update_migrator/").unwrap(); - } } diff --git a/sea-orm-cli/src/commands/migrate.rs b/sea-orm-cli/src/commands/migrate.rs new file mode 100644 index 00000000..8855c0cd --- /dev/null +++ b/sea-orm-cli/src/commands/migrate.rs @@ -0,0 +1,231 @@ +use chrono::Local; +use regex::Regex; +use std::{error::Error, fs, io::Write, path::Path, process::Command}; + +use crate::MigrateSubcommands; + +pub fn run_migrate_command( + command: Option, + migration_dir: &str, + verbose: bool, +) -> Result<(), Box> { + match command { + Some(MigrateSubcommands::Init) => run_migrate_init(migration_dir)?, + Some(MigrateSubcommands::Generate { migration_name }) => { + run_migrate_generate(migration_dir, &migration_name)? + } + _ => { + 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()?; + } + } + + Ok(()) +} + +pub fn run_migrate_init(migration_dir: &str) -> Result<(), Box> { + 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("", &ver) + }); + write_file!("README.md"); + println!("Done!"); + + Ok(()) +} + +pub fn run_migrate_generate( + migration_dir: &str, + migration_name: &str, +) -> Result<(), Box> { + 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"), migration_name); + + create_new_migration(&migration_name, migration_dir)?; + update_migrator(&migration_name, migration_dir)?; + + Ok(()) +} + +fn create_new_migration(migration_name: &str, migration_dir: &str) -> Result<(), Box> { + let migration_filepath = Path::new(migration_dir) + .join("src") + .join(format!("{}.rs", &migration_name)); + println!("Creating migration file `{}`", migration_filepath.display()); + // TODO: make OS agnostic + let migration_template = + include_str!("../../template/migration/src/m20220101_000001_create_table.rs"); + let mut migration_file = fs::File::create(migration_filepath)?; + migration_file.write_all(migration_template.as_bytes())?; + Ok(()) +} + +fn update_migrator(migration_name: &str, migration_dir: &str) -> Result<(), Box> { + let migrator_filepath = Path::new(migration_dir).join("src").join("lib.rs"); + println!( + "Adding migration `{}` to `{}`", + migration_name, + migrator_filepath.display() + ); + let migrator_content = fs::read_to_string(&migrator_filepath)?; + 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.with_file_name("lib.rs.bak"); + fs::copy(&migrator_filepath, &migrator_backup_filepath)?; + let mut migrator_file = fs::File::create(&migrator_filepath)?; + + // find existing mod declarations, add new line + let mod_regex = Regex::new(r"mod\s+(?Pm\d{8}_\d{6}_\w+);")?; + let mods: Vec<_> = mod_regex.captures_iter(&migrator_content).collect(); + let mods_end = mods.last().unwrap().get(0).unwrap().end() + 1; + updated_migrator_content.insert_str(mods_end, format!("mod {};\n", migration_name).as_str()); + + // build new vector from declared migration modules + let mut migrations: Vec<&str> = mods + .iter() + .map(|cap| cap.name("name").unwrap().as_str()) + .collect(); + migrations.push(migration_name); + let mut boxed_migrations = migrations + .iter() + .map(|migration| format!(" Box::new({}::Migration),", migration)) + .collect::>() + .join("\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); + + migrator_file.write_all(updated_migrator_content.as_bytes())?; + fs::remove_file(&migrator_backup_filepath)?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_create_new_migration() { + let migration_name = "test_name"; + let migration_dir = "/tmp/sea_orm_cli_test_new_migration/"; + fs::create_dir_all(format!("{}src", migration_dir)).unwrap(); + create_new_migration(migration_name, migration_dir).unwrap(); + let migration_filepath = Path::new(migration_dir) + .join("src") + .join(format!("{}.rs", migration_name)); + assert!(migration_filepath.exists()); + let migration_content = fs::read_to_string(migration_filepath).unwrap(); + assert_eq!( + &migration_content, + include_str!("../../template/migration/src/m20220101_000001_create_table.rs") + ); + fs::remove_dir_all("/tmp/sea_orm_cli_test_new_migration/").unwrap(); + } + + #[test] + fn test_update_migrator() { + let migration_name = "test_name"; + let migration_dir = "/tmp/sea_orm_cli_test_update_migrator/"; + fs::create_dir_all(format!("{}src", migration_dir)).unwrap(); + let migrator_filepath = Path::new(migration_dir).join("src").join("lib.rs"); + fs::copy("./template/migration/src/lib.rs", &migrator_filepath).unwrap(); + update_migrator(migration_name, migration_dir).unwrap(); + assert!(&migrator_filepath.exists()); + let migrator_content = fs::read_to_string(&migrator_filepath).unwrap(); + let mod_regex = Regex::new(r"mod (?P\w+);").unwrap(); + let migrations: Vec<&str> = mod_regex + .captures_iter(&migrator_content) + .map(|cap| cap.name("name").unwrap().as_str()) + .collect(); + assert_eq!(migrations.len(), 2); + assert_eq!( + *migrations.first().unwrap(), + "m20220101_000001_create_table" + ); + assert_eq!(migrations.last().unwrap(), &migration_name); + let boxed_regex = Regex::new(r"Box::new\((?P\S+)::Migration\)").unwrap(); + let migrations: Vec<&str> = boxed_regex + .captures_iter(&migrator_content) + .map(|cap| cap.name("name").unwrap().as_str()) + .collect(); + assert_eq!(migrations.len(), 2); + assert_eq!( + *migrations.first().unwrap(), + "m20220101_000001_create_table" + ); + assert_eq!(migrations.last().unwrap(), &migration_name); + fs::remove_dir_all("/tmp/sea_orm_cli_test_update_migrator/").unwrap(); + } +} diff --git a/sea-orm-cli/src/commands/mod.rs b/sea-orm-cli/src/commands/mod.rs new file mode 100644 index 00000000..08bc6688 --- /dev/null +++ b/sea-orm-cli/src/commands/mod.rs @@ -0,0 +1,17 @@ +use std::fmt::Display; + +#[cfg(feature = "codegen")] +pub mod generate; +pub mod migrate; + +#[cfg(feature = "codegen")] +pub use generate::*; +pub use migrate::*; + +pub fn handle_error(error: E) +where + E: Display, +{ + eprintln!("{}", error); + ::std::process::exit(1); +} diff --git a/sea-orm-cli/src/lib.rs b/sea-orm-cli/src/lib.rs index 9c552cca..366b8fa6 100644 --- a/sea-orm-cli/src/lib.rs +++ b/sea-orm-cli/src/lib.rs @@ -1,7 +1,5 @@ pub mod cli; -#[cfg(feature = "codegen")] pub mod commands; pub use cli::*; -#[cfg(feature = "codegen")] pub use commands::*; diff --git a/sea-orm-migration/src/cli.rs b/sea-orm-migration/src/cli.rs index 90031deb..09183f23 100644 --- a/sea-orm-migration/src/cli.rs +++ b/sea-orm-migration/src/cli.rs @@ -1,13 +1,15 @@ use clap::Parser; use dotenv::dotenv; -use std::{fmt::Display, process::exit}; +use std::{error::Error, fmt::Display, process::exit}; use tracing_subscriber::{prelude::*, EnvFilter}; use sea_orm::{Database, DbConn}; -use sea_orm_cli::MigrateSubcommands; +use sea_orm_cli::{run_migrate_generate, run_migrate_init, MigrateSubcommands}; use super::MigratorTrait; +const MIGRATION_DIR: &str = "./"; + pub async fn run_cli(migrator: M) where M: MigratorTrait, @@ -17,10 +19,17 @@ where let db = &Database::connect(&url).await.unwrap(); let cli = Cli::parse(); - run_migrate(migrator, db, cli.command, cli.verbose).await; + run_migrate(migrator, db, cli.command, cli.verbose) + .await + .unwrap_or_else(handle_error); } -pub async fn run_migrate(_: M, db: &DbConn, command: Option, verbose: bool) +pub async fn run_migrate( + _: M, + db: &DbConn, + command: Option, + verbose: bool, +) -> Result<(), Box> where M: MigratorTrait, { @@ -49,15 +58,20 @@ where }; 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); + 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?, + Some(MigrateSubcommands::Init) => run_migrate_init(MIGRATION_DIR)?, + Some(MigrateSubcommands::Generate { migration_name }) => { + run_migrate_generate(MIGRATION_DIR, &migration_name)? + } + _ => M::up(db, None).await?, + }; + + Ok(()) } #[derive(Parser)]