Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions sea-orm-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ tracing-subscriber = { version = "0.3.17", default-features = false, features =
"fmt",
] }
url = { version = "2.2", default-features = false }
toml = "0.9.8"
serde = { version = "1.0.228", features = ["derive"] }

[dev-dependencies]
smol = "1.2.5"
Expand Down
62 changes: 16 additions & 46 deletions sea-orm-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use clap::{ArgAction, ArgGroup, Parser, Subcommand, ValueEnum};
use dotenvy::dotenv;

#[cfg(feature = "codegen")]
use crate::{handle_error, run_generate_command, run_migrate_command};
use crate::{handle_error, run_config_command, run_generate_command, run_migrate_command};

#[derive(Parser, Debug)]
#[command(
Expand Down Expand Up @@ -53,31 +53,22 @@ pub struct Cli {
#[allow(clippy::large_enum_variant)]
#[derive(Subcommand, PartialEq, Eq, Debug)]
pub enum Commands {
#[command(about = "Config related commands", display_order = 10)]
Config {
#[command(subcommand)]
command: ConfigSubcommands,
},
#[command(
about = "Codegen related commands",
arg_required_else_help = true,
display_order = 10
display_order = 20
)]
Generate {
#[command(subcommand)]
command: GenerateSubcommands,
},
#[command(about = "Migration related commands", display_order = 20)]
#[command(about = "Migration related commands", display_order = 30)]
Migrate {
#[arg(
global = true,
short = 'd',
long,
env = "MIGRATION_DIR",
help = "Migration script directory.
If your migrations are in their own crate,
you can provide the root of that crate.
If your migrations are in a submodule of your app,
you should provide the directory of that submodule.",
default_value = "./migration"
)]
migration_dir: String,

#[arg(
global = true,
short = 's',
Expand All @@ -89,21 +80,17 @@ you should provide the directory of that submodule.",
)]
database_schema: Option<String>,

#[arg(
global = true,
short = 'u',
long,
env = "DATABASE_URL",
help = "Database URL",
hide_env_values = true
)]
database_url: Option<String>,

#[command(subcommand)]
command: Option<MigrateSubcommands>,
},
}

#[derive(Subcommand, PartialEq, Eq, Debug)]
pub enum ConfigSubcommands {
#[command(about = "Initialize config file", display_order = 10)]
Init,
}

#[derive(Subcommand, PartialEq, Eq, Debug)]
pub enum MigrateSubcommands {
#[command(about = "Initialize migration directory", display_order = 10)]
Expand Down Expand Up @@ -234,15 +221,6 @@ pub enum GenerateSubcommands {
)]
database_schema: Option<String>,

#[arg(
short = 'u',
long,
env = "DATABASE_URL",
help = "Database URL",
hide_env_values = true
)]
database_url: String,

#[arg(
long,
default_value = "all",
Expand Down Expand Up @@ -417,23 +395,15 @@ pub async fn main() {
let verbose = cli.verbose;

match cli.command {
Commands::Config { command } => run_config_command(command).unwrap_or_else(handle_error),
Commands::Generate { command } => {
run_generate_command(command, verbose)
.await
.unwrap_or_else(handle_error);
}
Commands::Migrate {
migration_dir,
database_schema,
database_url,
command,
} => run_migrate_command(
command,
&migration_dir,
database_schema,
database_url,
verbose,
)
.unwrap_or_else(handle_error),
} => run_migrate_command(command, database_schema, verbose).unwrap_or_else(handle_error),
}
}
95 changes: 95 additions & 0 deletions sea-orm-cli/src/commands/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use serde::Deserialize;
use std::path::{Path, PathBuf};
use std::{env, error::Error, fs};

use crate::ConfigSubcommands;

#[derive(Deserialize)]
pub struct Config {
database: Database,
migrations: Migrations,
}

#[derive(Deserialize)]
struct Database {
url: String,
}

#[derive(Deserialize)]
struct Migrations {
directory: String,
}

pub fn run_config_command(command: ConfigSubcommands) -> Result<(), Box<dyn Error>> {
match command {
ConfigSubcommands::Init => run_config_init(),
}
}

/// The config file is expected to be in the current directory or a parent directory
pub fn get_config() -> Result<Config, Box<dyn Error>> {
let config_path = find_config_file()?;

let file_content = fs::read_to_string(config_path)?;
let config: Config = toml::from_str(&file_content)?;
Ok(config)
}

pub fn get_database_url() -> Result<String, Box<dyn Error>> {
let config = get_config()?;
if config.database.url.starts_with("env:") {
let env_var = config.database.url.split(":").nth(1).unwrap();
let value = env::var(env_var)?;
Ok(value)
} else {
Ok(config.database.url)
}
}

pub fn get_migration_dir() -> Result<String, Box<dyn Error>> {
let config = get_config()?;

let migration_dir = config.migrations.directory;
if migration_dir.starts_with(".") {
let config_dir = get_config_dir()?;
let migration_dir = config_dir.join(migration_dir);
return Ok(migration_dir.to_string_lossy().to_string());
} else {
return Ok(migration_dir);
}
}

fn get_config_dir() -> Result<PathBuf, Box<dyn Error>> {
let config_path = find_config_file()?;
let config_dir = config_path.parent().unwrap_or(Path::new(".")).to_path_buf();
Ok(config_dir)
}

fn find_config_file() -> Result<PathBuf, Box<dyn Error>> {
let current_dir = std::env::current_dir()?;
let config_path = current_dir.join("sea-orm.toml");
if config_path.exists() {
return Ok(config_path);
}
let parent_dir = current_dir.parent();
if let Some(parent_dir) = parent_dir {
let config_path = parent_dir.join("sea-orm.toml");
if config_path.exists() {
return Ok(config_path);
}
}
Err(Box::new(std::io::Error::new(
std::io::ErrorKind::NotFound,
"SeaORM config file not found, use `sea-orm-cli config init` to create one",
)))
}

fn run_config_init() -> Result<(), Box<dyn Error>> {
let config_path = Path::new("sea-orm.toml");
let config_template = include_str!("../../template/sea-orm.toml");

fs::write(config_path, config_template.to_string())?;

println!("Config file created at {}", config_path.display());
Ok(())
}
4 changes: 3 additions & 1 deletion sea-orm-cli/src/commands/generate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ use std::{error::Error, fs, path::Path, process::Command, str::FromStr};
use tracing_subscriber::{EnvFilter, prelude::*};
use url::Url;

use crate::config::get_database_url;

pub async fn run_generate_command(
command: GenerateSubcommands,
verbose: bool,
Expand All @@ -26,7 +28,6 @@ pub async fn run_generate_command(
acquire_timeout,
output_dir,
database_schema,
database_url,
with_prelude,
with_serde,
serde_skip_deserializing_primary_key,
Expand Down Expand Up @@ -63,6 +64,7 @@ pub async fn run_generate_command(
.try_init();
}

let database_url = get_database_url()?;
// The database should be a valid URL that can be parsed
// protocol://username:password@host/database_name
let url = Url::parse(&database_url)?;
Expand Down
29 changes: 14 additions & 15 deletions sea-orm-cli/src/commands/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,34 @@ use std::{
};

#[cfg(feature = "cli")]
use crate::MigrateSubcommands;
use crate::{MigrateSubcommands, config::get_database_url, config::get_migration_dir};

#[cfg(feature = "cli")]
pub fn run_migrate_command(
command: Option<MigrateSubcommands>,
migration_dir: &str,
database_schema: Option<String>,
database_url: Option<String>,
verbose: bool,
) -> Result<(), Box<dyn Error>> {
let migration_dir = get_migration_dir()?;
let database_url = get_database_url()?;
match command {
Some(MigrateSubcommands::Init) => run_migrate_init(migration_dir)?,
Some(MigrateSubcommands::Init) => run_migrate_init(&migration_dir)?,
Some(MigrateSubcommands::Generate {
migration_name,
universal_time: _,
local_time,
}) => run_migrate_generate(migration_dir, &migration_name, !local_time)?,
}) => run_migrate_generate(&migration_dir, &migration_name, !local_time)?,
_ => {
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, num, verbose),
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, num, verbose),
Some(MigrateSubcommands::Down { num }) => {
("down", migration_dir, Some(num), verbose)
("down", &migration_dir, Some(num), verbose)
}
_ => ("up", migration_dir, None, verbose),
_ => ("up", &migration_dir, None, verbose),
};

// Construct the `--manifest-path`
Expand All @@ -57,9 +57,8 @@ pub fn run_migrate_command(
if !num.is_empty() {
args.extend(["-n", &num])
}
if let Some(database_url) = &database_url {
envs.push(("DATABASE_URL", database_url));
}
envs.push(("DATABASE_URL", &database_url));

if let Some(database_schema) = &database_schema {
envs.push(("DATABASE_SCHEMA", database_schema));
}
Expand Down
2 changes: 2 additions & 0 deletions sea-orm-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
use std::fmt::Display;

#[cfg(feature = "codegen")]
pub mod config;
pub mod generate;
pub mod migrate;

#[cfg(feature = "codegen")]
pub use config::*;
pub use generate::*;
pub use migrate::*;

Expand Down
5 changes: 5 additions & 0 deletions sea-orm-cli/template/sea-orm.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[database]
url = "env:DATABASE_URL" # Support env: prefix or direct URL

[migrations]
directory = "./migration" # Where migrations live, relative path