From 018065026fe47772cc982f95ff53e1dd4aa513dc Mon Sep 17 00:00:00 2001 From: = Date: Fri, 7 Nov 2025 00:03:43 -0500 Subject: [PATCH 01/10] Adds flag to publish to allow clearing on migration conflict. --- crates/cli/src/common_args.rs | 22 +++++- crates/cli/src/subcommands/dev.rs | 25 ++++++- crates/cli/src/subcommands/publish.rs | 90 ++++++++++++++++-------- crates/client-api-messages/src/name.rs | 13 +++- crates/client-api/src/routes/database.rs | 17 ++--- 5 files changed, 126 insertions(+), 41 deletions(-) diff --git a/crates/cli/src/common_args.rs b/crates/cli/src/common_args.rs index f07247d841c..6858d242b44 100644 --- a/crates/cli/src/common_args.rs +++ b/crates/cli/src/common_args.rs @@ -1,5 +1,12 @@ -use clap::Arg; use clap::ArgAction::SetTrue; +use clap::{value_parser, Arg, ValueEnum}; + +#[derive(Copy, Clone, Debug, ValueEnum)] +pub enum ClearMode { + Always, // parses as "always" + OnConflict, // parses as "on-conflict" + Never, // parses as "never" +} pub fn server() -> Arg { Arg::new("server") @@ -37,3 +44,16 @@ pub fn confirmed() -> Arg { .action(SetTrue) .help("Instruct the server to deliver only updates of confirmed transactions") } + +pub fn clear_database() -> Arg { + Arg::new("clear-database") + .long("delete-data") + .short('c') + .num_args(0..=1) + .value_parser(value_parser!(ClearMode)) + .require_equals(true) + .default_missing_value("always") + .help( + "When publishing to an existing database identity, first DESTROY all data associated with the module. With 'on-conflict': only when breaking schema changes occur." + ) +} diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 868df9c8fe8..9d02dbcb95f 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -1,3 +1,4 @@ +use crate::common_args::ClearMode; use crate::config::Config; use crate::generate::Language; use crate::subcommands::init; @@ -8,7 +9,7 @@ use crate::util::{ use crate::{common_args, generate}; use crate::{publish, tasks}; use anyhow::Context; -use clap::{Arg, ArgMatches, Command}; +use clap::{value_parser, Arg, ArgMatches, Command, ValueEnum}; use colored::Colorize; use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect}; use futures::stream::{self, StreamExt}; @@ -71,6 +72,7 @@ pub fn cli() -> Command { ) .arg(common_args::server().help("The nickname, host name or URL of the server to publish to")) .arg(common_args::yes()) + .arg(common_args::clear_database()) } #[derive(Deserialize)] @@ -89,6 +91,10 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let spacetimedb_project_path = args.get_one::("module-project-path").unwrap(); let module_bindings_path = args.get_one::("module-bindings-path").unwrap(); let client_language = args.get_one::("client-lang"); + let clear_database = args + .get_one::("clear-database") + .copied() + .unwrap_or(ClearMode::Never); let force = args.get_flag("force"); // If you don't specify a server, we default to your default server @@ -236,6 +242,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E &database_name, client_language, resolved_server, + clear_database, ) .await?; @@ -347,6 +354,7 @@ async fn generate_build_and_publish( database_name: &str, client_language: Option<&Language>, server: &str, + clear_database: ClearMode, ) -> Result<(), anyhow::Error> { let module_language = detect_module_language(spacetimedb_dir)?; let client_language = client_language.unwrap_or(match module_language { @@ -394,7 +402,20 @@ async fn generate_build_and_publish( let project_path_str = spacetimedb_dir.to_str().unwrap(); - let mut publish_args = vec!["publish", database_name, "--project-path", project_path_str, "--yes"]; + let clear_flag = match clear_database { + ClearMode::Always => "always", + ClearMode::Never => "never", + ClearMode::OnConflict => "on-conflict", + }; + let mut publish_args = vec![ + "publish", + database_name, + "--project-path", + project_path_str, + "--yes", + "--clear-database", + clear_flag, + ]; publish_args.extend_from_slice(&["--server", server]); let publish_cmd = publish::cli(); diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 91433d0c250..38c94234ba8 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -8,6 +8,7 @@ use spacetimedb_client_api_messages::name::{DatabaseNameError, PrePublishResult, use std::path::PathBuf; use std::{env, fs}; +use crate::common_args::ClearMode; use crate::config::Config; use crate::util::{add_auth_header_opt, get_auth_header, AuthHeader, ResponseExt}; use crate::util::{decode_identity, y_or_n}; @@ -17,12 +18,8 @@ pub fn cli() -> clap::Command { clap::Command::new("publish") .about("Create and update a SpacetimeDB database") .arg( - Arg::new("clear_database") - .long("delete-data") - .short('c') - .action(SetTrue) + common_args::clear_database() .requires("name|identity") - .help("When publishing to an existing database identity, first DESTROY all data associated with the module"), ) .arg( Arg::new("build_options") @@ -71,7 +68,7 @@ pub fn cli() -> clap::Command { Arg::new("break_clients") .long("break-clients") .action(SetTrue) - .help("Allow breaking changes when publishing to an existing database identity. This will break existing clients.") + .help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NOT delete any data in the database.") ) .arg( common_args::anonymous() @@ -109,7 +106,10 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let server = args.get_one::("server").map(|s| s.as_str()); let name_or_identity = args.get_one::("name|identity"); let path_to_project = args.get_one::("project_path").unwrap(); - let clear_database = args.get_flag("clear_database"); + let clear_database = args + .get_one::("clear-database") + .copied() + .unwrap_or(ClearMode::Never); let force = args.get_flag("force"); let anon_identity = args.get_flag("anon_identity"); let wasm_file = args.get_one::("wasm_file"); @@ -117,7 +117,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let database_host = config.get_host_url(server)?; let build_options = args.get_one::("build_options").unwrap(); let num_replicas = args.get_one::("num_replicas"); - let break_clients_flag = args.get_flag("break_clients"); + let force_break_clients = args.get_flag("break_clients"); let parent = args.get_one::("parent"); // If the user didn't specify an identity and we didn't specify an anonymous identity, then @@ -175,7 +175,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let domain = percent_encoding::percent_encode(name_or_identity.as_bytes(), encode_set); let mut builder = client.put(format!("{database_host}/v1/database/{domain}")); - if !clear_database { + if !(matches!(clear_database, ClearMode::Always)) { builder = apply_pre_publish_if_needed( builder, &client, @@ -184,7 +184,9 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E host_type, &program_bytes, &auth_header, - break_clients_flag, + clear_database, + force_break_clients, + force, ) .await?; } @@ -194,7 +196,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E client.post(format!("{database_host}/v1/database")) }; - if clear_database { + if matches!(clear_database, ClearMode::Always) || matches!(clear_database, ClearMode::OnConflict) { // Note: `name_or_identity` should be set, because it is `required` in the CLI arg config. println!( "This will DESTROY the current {} module, and ALL corresponding data.", @@ -336,25 +338,55 @@ async fn apply_pre_publish_if_needed( host_type: &str, program_bytes: &[u8], auth_header: &AuthHeader, - break_clients_flag: bool, + clear_database: ClearMode, + force_break_clients: bool, + force: bool, ) -> Result { - if let Some(pre) = call_pre_publish(client, base_url, domain, host_type, program_bytes, auth_header).await? { - println!("{}", pre.migrate_plan); - - if pre.break_clients - && !y_or_n( - break_clients_flag, - "The above changes will BREAK existing clients. Do you want to proceed?", - )? - { - println!("Aborting"); - // Early exit: return an error or a special signal. Here we bail out by returning Err. - anyhow::bail!("Publishing aborted by user"); + if let Some(pre) = call_pre_publish( + client, + base_url, + &domain.to_string(), + host_type, + program_bytes, + auth_header, + ) + .await? + { + match pre { + PrePublishResult::ManualMigrate(manual) => { + if matches!(clear_database, ClearMode::OnConflict) { + println!("{}", manual.reason); + println!("Proceeding with database clear due to --delete-data=on-conflict."); + } + if matches!(clear_database, ClearMode::Never) { + println!("{}", manual.reason); + println!("Aborting publish due to required manual migration."); + anyhow::bail!("Publishing aborted by user"); + } + } + PrePublishResult::AutoMigrate(auto) => { + println!("{}", auto.migrate_plan); + // If the automigration plan will break clients and the user has not selected + // ClearMode::Always or ClearMode::OnConflict and the user has not passed the + // `--force` flag, then we need to prompt the user to ask if it is ok to break clients. + // OnConflict is assumed to be okay to break clients because all manual migrations + // are assumed to break clients as well, so it is likely what the user intended. + if auto.break_clients + && matches!(clear_database, ClearMode::Never) + && !y_or_n( + force_break_clients || force, + "The above changes will BREAK existing clients. Do you want to proceed?", + )? + { + println!("Aborting"); + // Early exit: return an error or a special signal. Here we bail out by returning Err. + anyhow::bail!("Publishing aborted by user"); + } + builder = builder + .query(&[("token", auto.token)]) + .query(&[("policy", "BreakClients")]); + } } - - builder = builder - .query(&[("token", pre.token)]) - .query(&[("policy", "BreakClients")]); } Ok(builder) @@ -369,7 +401,7 @@ async fn call_pre_publish( auth_header: &AuthHeader, ) -> Result, anyhow::Error> { let mut builder = client.post(format!("{database_host}/v1/database/{domain}/pre_publish")); - let style = pretty_print_style_from_env(); + let style: PrettyPrintStyle = pretty_print_style_from_env(); builder = builder .query(&[("pretty_print_style", style)]) .query(&[("host_type", host_type)]); diff --git a/crates/client-api-messages/src/name.rs b/crates/client-api-messages/src/name.rs index dcf39551496..e8876612ffb 100644 --- a/crates/client-api-messages/src/name.rs +++ b/crates/client-api-messages/src/name.rs @@ -121,12 +121,23 @@ pub enum PrettyPrintStyle { } #[derive(serde::Serialize, serde::Deserialize, Debug)] -pub struct PrePublishResult { +pub enum PrePublishResult { + AutoMigrate(PrePublishAutoMigrateResult), + ManualMigrate(PrePublishManualMigrateResult), +} + +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PrePublishAutoMigrateResult { pub migrate_plan: Box, pub break_clients: bool, pub token: spacetimedb_lib::Hash, } +#[derive(serde::Serialize, serde::Deserialize, Debug)] +pub struct PrePublishManualMigrateResult { + pub reason: String, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub enum DnsLookupResponse { /// The lookup was successful and the domain and identity are returned. diff --git a/crates/client-api/src/routes/database.rs b/crates/client-api/src/routes/database.rs index e77183b8a38..030468332a0 100644 --- a/crates/client-api/src/routes/database.rs +++ b/crates/client-api/src/routes/database.rs @@ -33,7 +33,8 @@ use spacetimedb::identity::{AuthCtx, Identity}; use spacetimedb::messages::control_db::{Database, HostType}; use spacetimedb_client_api_messages::http::SqlStmtResult; use spacetimedb_client_api_messages::name::{ - self, DatabaseName, DomainName, MigrationPolicy, PrePublishResult, PrettyPrintStyle, PublishOp, PublishResult, + self, DatabaseName, DomainName, MigrationPolicy, PrePublishAutoMigrateResult, PrePublishManualMigrateResult, + PrePublishResult, PrettyPrintStyle, PublishOp, PublishResult, }; use spacetimedb_lib::db::raw_def::v9::RawModuleDefV9; use spacetimedb_lib::{sats, AlgebraicValue, Hash, ProductValue, Timestamp}; @@ -947,17 +948,17 @@ pub async fn pre_publish } .hash(); - Ok(PrePublishResult { + Ok(PrePublishResult::AutoMigrate(PrePublishAutoMigrateResult { token, migrate_plan: plan, break_clients: breaks_client, - }) + })) + } + MigratePlanResult::AutoMigrationError(e) => { + Ok(PrePublishResult::ManualMigrate(PrePublishManualMigrateResult { + reason: e.to_string(), + })) } - MigratePlanResult::AutoMigrationError(e) => Err(( - StatusCode::BAD_REQUEST, - format!("Automatic migration is not possible: {e}"), - ) - .into()), } .map(axum::Json) } From c5a68d4789712aef26e36bc24f96abe7b998fcab Mon Sep 17 00:00:00 2001 From: = Date: Fri, 7 Nov 2025 00:15:28 -0500 Subject: [PATCH 02/10] Small fixes" --- crates/cli/src/subcommands/dev.rs | 2 +- crates/cli/src/subcommands/publish.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 9d02dbcb95f..0bdcc101617 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -9,7 +9,7 @@ use crate::util::{ use crate::{common_args, generate}; use crate::{publish, tasks}; use anyhow::Context; -use clap::{value_parser, Arg, ArgMatches, Command, ValueEnum}; +use clap::{Arg, ArgMatches, Command}; use colored::Colorize; use dialoguer::{theme::ColorfulTheme, Confirm, FuzzySelect}; use futures::stream::{self, StreamExt}; diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 38c94234ba8..fe3fb1ea0ac 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -68,7 +68,7 @@ pub fn cli() -> clap::Command { Arg::new("break_clients") .long("break-clients") .action(SetTrue) - .help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NOT delete any data in the database.") + .help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NEVER delete any data in the database.") ) .arg( common_args::anonymous() From 265aa6c2140839368948b9a4f36c65875c7d26bc Mon Sep 17 00:00:00 2001 From: = Date: Fri, 7 Nov 2025 00:23:55 -0500 Subject: [PATCH 03/10] Careful wording clarification --- crates/cli/src/subcommands/publish.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index fe3fb1ea0ac..208daf21996 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -68,7 +68,7 @@ pub fn cli() -> clap::Command { Arg::new("break_clients") .long("break-clients") .action(SetTrue) - .help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NEVER delete any data in the database.") + .help("Allow breaking changes when publishing to an existing database identity. This will force publish even if it will break existing clients, but will NOT force publish if it would cause deletion of any data in the database. See --yes and --delete-data for details.") ) .arg( common_args::anonymous() From b5b05063c0342a2ab4a98ebdd7160c3e1d038d5b Mon Sep 17 00:00:00 2001 From: = Date: Fri, 7 Nov 2025 00:39:02 -0500 Subject: [PATCH 04/10] Small typo fix --- crates/cli/src/subcommands/dev.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 0bdcc101617..a87a27f9450 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -291,6 +291,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E &database_name, client_language, resolved_server, + clear_database, ) .await { From 993cfe3c4b302d1ef69dacd0a443029aaa35b44c Mon Sep 17 00:00:00 2001 From: = Date: Fri, 7 Nov 2025 00:48:51 -0500 Subject: [PATCH 05/10] Small logic fix --- crates/cli/src/subcommands/dev.rs | 1 + crates/cli/src/subcommands/publish.rs | 8 ++------ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index a87a27f9450..46c92126b9f 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -347,6 +347,7 @@ fn upsert_env_db_names_and_hosts(env_path: &Path, server_host_url: &str, databas Ok(()) } +#[allow(clippy::too_many_arguments)] async fn generate_build_and_publish( config: &Config, project_dir: &Path, diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 208daf21996..7b82cff4d71 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -366,13 +366,9 @@ async fn apply_pre_publish_if_needed( } PrePublishResult::AutoMigrate(auto) => { println!("{}", auto.migrate_plan); - // If the automigration plan will break clients and the user has not selected - // ClearMode::Always or ClearMode::OnConflict and the user has not passed the - // `--force` flag, then we need to prompt the user to ask if it is ok to break clients. - // OnConflict is assumed to be okay to break clients because all manual migrations - // are assumed to break clients as well, so it is likely what the user intended. + // We only arrive here if you have not specified ClearMode::Always AND there was no + // conflict that required manual migration. if auto.break_clients - && matches!(clear_database, ClearMode::Never) && !y_or_n( force_break_clients || force, "The above changes will BREAK existing clients. Do you want to proceed?", From f9a6c47efb32c7574e8fed79bd3fab6de2c69f18 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Tue, 11 Nov 2025 12:14:19 -0500 Subject: [PATCH 06/10] Update crates/cli/src/common_args.rs Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> Signed-off-by: Tyler Cloutier --- crates/cli/src/common_args.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/cli/src/common_args.rs b/crates/cli/src/common_args.rs index 6858d242b44..926e9a90b48 100644 --- a/crates/cli/src/common_args.rs +++ b/crates/cli/src/common_args.rs @@ -51,6 +51,9 @@ pub fn clear_database() -> Arg { .short('c') .num_args(0..=1) .value_parser(value_parser!(ClearMode)) + // Because we have a default value for this flag, invocations can be ambiguous between + //passing a value to this flag, vs using the default value and passing an anonymous arg + // to the rest of the command. Adding `require_equals` resolves this ambiguity. .require_equals(true) .default_missing_value("always") .help( From fab73cc3b32109e87878bcc3a0d97c2e2e10f988 Mon Sep 17 00:00:00 2001 From: = Date: Tue, 11 Nov 2025 12:16:40 -0500 Subject: [PATCH 07/10] Fixed PR notes --- crates/cli/src/subcommands/publish.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 7b82cff4d71..9fc8368779b 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -175,7 +175,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let domain = percent_encoding::percent_encode(name_or_identity.as_bytes(), encode_set); let mut builder = client.put(format!("{database_host}/v1/database/{domain}")); - if !(matches!(clear_database, ClearMode::Always)) { + if !(clear_database == ClearMode::Always) { builder = apply_pre_publish_if_needed( builder, &client, @@ -196,7 +196,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E client.post(format!("{database_host}/v1/database")) }; - if matches!(clear_database, ClearMode::Always) || matches!(clear_database, ClearMode::OnConflict) { + if clear_database == ClearMode::Always || clear_database == ClearMode::OnConflict { // Note: `name_or_identity` should be set, because it is `required` in the CLI arg config. println!( "This will DESTROY the current {} module, and ALL corresponding data.", @@ -361,7 +361,7 @@ async fn apply_pre_publish_if_needed( if matches!(clear_database, ClearMode::Never) { println!("{}", manual.reason); println!("Aborting publish due to required manual migration."); - anyhow::bail!("Publishing aborted by user"); + anyhow::bail!("Aborting because publishing would require manual migration or deletion of data and --delete-data was not specified."); } } PrePublishResult::AutoMigrate(auto) => { From b5375f695b4ed5e1d604635e792b7081b047423f Mon Sep 17 00:00:00 2001 From: Zeke Foppa Date: Tue, 11 Nov 2025 10:06:18 -0800 Subject: [PATCH 08/10] [tyler/clear-on-conflict]: add PartialEq --- crates/cli/src/common_args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/common_args.rs b/crates/cli/src/common_args.rs index 926e9a90b48..f1f43f6f976 100644 --- a/crates/cli/src/common_args.rs +++ b/crates/cli/src/common_args.rs @@ -1,7 +1,7 @@ use clap::ArgAction::SetTrue; use clap::{value_parser, Arg, ValueEnum}; -#[derive(Copy, Clone, Debug, ValueEnum)] +#[derive(Copy, Clone, Debug, ValueEnum, PartialEq)] pub enum ClearMode { Always, // parses as "always" OnConflict, // parses as "on-conflict" From 8d9c1238b2ecdc5eb189d62b726a1107e0eef583 Mon Sep 17 00:00:00 2001 From: Tyler Cloutier Date: Wed, 12 Nov 2025 12:08:26 -0500 Subject: [PATCH 09/10] Update crates/cli/src/subcommands/publish.rs Co-authored-by: Zeke Foppa <196249+bfops@users.noreply.github.com> Signed-off-by: Tyler Cloutier --- crates/cli/src/subcommands/publish.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 9fc8368779b..8829bfa757d 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -175,7 +175,7 @@ pub async fn exec(mut config: Config, args: &ArgMatches) -> Result<(), anyhow::E let domain = percent_encoding::percent_encode(name_or_identity.as_bytes(), encode_set); let mut builder = client.put(format!("{database_host}/v1/database/{domain}")); - if !(clear_database == ClearMode::Always) { + if clear_database != ClearMode::Always { builder = apply_pre_publish_if_needed( builder, &client, From b4e345852a18e097bec16338215656437c18855a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 12 Nov 2025 12:32:35 -0500 Subject: [PATCH 10/10] Fixed smoke tests --- crates/bindings/README.md | 2 +- crates/cli/src/common_args.rs | 1 + crates/cli/src/subcommands/dev.rs | 2 +- crates/cli/src/subcommands/publish.rs | 8 ++++++-- .../06-Server Module Languages/04-csharp-reference.md | 2 +- .../06-Server Module Languages/06-Typescript-reference.md | 2 +- smoketests/tests/modules.py | 4 ++-- smoketests/tests/permissions.py | 5 ++--- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/bindings/README.md b/crates/bindings/README.md index 607acd55ac6..4f0f1365aa2 100644 --- a/crates/bindings/README.md +++ b/crates/bindings/README.md @@ -578,7 +578,7 @@ The following changes are forbidden without a manual migration: - ❌ **Changing whether a table is used for [scheduling](#scheduled-reducers).** - ❌ **Adding `#[unique]` or `#[primary_key]` constraints.** This could result in existing tables being in an invalid state. -Currently, manual migration support is limited. The `spacetime publish --clear-database ` command can be used to **COMPLETELY DELETE** and reinitialize your database, but naturally it should be used with EXTREME CAUTION. +Currently, manual migration support is limited. The `spacetime publish --delete-data ` command can be used to **COMPLETELY DELETE** and reinitialize your database, but naturally it should be used with EXTREME CAUTION. [macro library]: https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/bindings-macro [module library]: https://github.com/clockworklabs/SpacetimeDB/tree/master/crates/lib diff --git a/crates/cli/src/common_args.rs b/crates/cli/src/common_args.rs index f1f43f6f976..2f2a9773b36 100644 --- a/crates/cli/src/common_args.rs +++ b/crates/cli/src/common_args.rs @@ -48,6 +48,7 @@ pub fn confirmed() -> Arg { pub fn clear_database() -> Arg { Arg::new("clear-database") .long("delete-data") + .alias("clear-database") .short('c') .num_args(0..=1) .value_parser(value_parser!(ClearMode)) diff --git a/crates/cli/src/subcommands/dev.rs b/crates/cli/src/subcommands/dev.rs index 46c92126b9f..14bdb222f26 100644 --- a/crates/cli/src/subcommands/dev.rs +++ b/crates/cli/src/subcommands/dev.rs @@ -415,7 +415,7 @@ async fn generate_build_and_publish( "--project-path", project_path_str, "--yes", - "--clear-database", + "--delete-data", clear_flag, ]; publish_args.extend_from_slice(&["--server", server]); diff --git a/crates/cli/src/subcommands/publish.rs b/crates/cli/src/subcommands/publish.rs index 8829bfa757d..e60bcb96840 100644 --- a/crates/cli/src/subcommands/publish.rs +++ b/crates/cli/src/subcommands/publish.rs @@ -354,15 +354,19 @@ async fn apply_pre_publish_if_needed( { match pre { PrePublishResult::ManualMigrate(manual) => { - if matches!(clear_database, ClearMode::OnConflict) { + if clear_database == ClearMode::OnConflict { println!("{}", manual.reason); println!("Proceeding with database clear due to --delete-data=on-conflict."); } - if matches!(clear_database, ClearMode::Never) { + if clear_database == ClearMode::Never { println!("{}", manual.reason); println!("Aborting publish due to required manual migration."); anyhow::bail!("Aborting because publishing would require manual migration or deletion of data and --delete-data was not specified."); } + if clear_database == ClearMode::Always { + println!("{}", manual.reason); + println!("Proceeding with database clear due to --delete-data=always."); + } } PrePublishResult::AutoMigrate(auto) => { println!("{}", auto.migrate_plan); diff --git a/docs/docs/06-Server Module Languages/04-csharp-reference.md b/docs/docs/06-Server Module Languages/04-csharp-reference.md index d12e824e711..dd83ea493e4 100644 --- a/docs/docs/06-Server Module Languages/04-csharp-reference.md +++ b/docs/docs/06-Server Module Languages/04-csharp-reference.md @@ -1003,7 +1003,7 @@ The following changes are forbidden without a manual migration: - ❌ **Changing whether a table is used for [scheduling](#scheduled-reducers).** - ❌ **Adding `[Unique]` or `[PrimaryKey]` constraints.** This could result in existing tables being in an invalid state. -Currently, manual migration support is limited. The `spacetime publish --clear-database ` command can be used to **COMPLETELY DELETE** and reinitialize your database, but naturally it should be used with EXTREME CAUTION. +Currently, manual migration support is limited. The `spacetime publish --delete-data ` command can be used to **COMPLETELY DELETE** and reinitialize your database, but naturally it should be used with EXTREME CAUTION. ## Other infrastructure diff --git a/docs/docs/06-Server Module Languages/06-Typescript-reference.md b/docs/docs/06-Server Module Languages/06-Typescript-reference.md index 1cb32e26b5c..81fe86ee4ba 100644 --- a/docs/docs/06-Server Module Languages/06-Typescript-reference.md +++ b/docs/docs/06-Server Module Languages/06-Typescript-reference.md @@ -573,7 +573,7 @@ The following deletes all data stored in the database. To fully reset your database and clear all data, run: ```bash -spacetime publish --clear-database +spacetime publish --delete-data # or spacetime publish -c ``` diff --git a/smoketests/tests/modules.py b/smoketests/tests/modules.py index 1b45ce793cb..d4f3e06feb0 100644 --- a/smoketests/tests/modules.py +++ b/smoketests/tests/modules.py @@ -65,7 +65,7 @@ class UpdateModule(Smoketest): def test_module_update(self): - """Test publishing a module without the --clear-database option""" + """Test publishing a module without the --delete-data option""" name = random_string() @@ -88,7 +88,7 @@ def test_module_update(self): self.write_module_code(self.MODULE_CODE_B) with self.assertRaises(CalledProcessError) as cm: self.publish_module(name, clear=False) - self.assertIn("Error: Pre-publish check failed", cm.exception.stderr) + self.assertIn("Error: Aborting because publishing would require manual migration", cm.exception.stderr) # Check that the old module is still running by calling say_hello self.call("say_hello") diff --git a/smoketests/tests/permissions.py b/smoketests/tests/permissions.py index 3b1068d0c36..bdb4a73e23e 100644 --- a/smoketests/tests/permissions.py +++ b/smoketests/tests/permissions.py @@ -54,10 +54,9 @@ def test_publish(self): self.new_identity() with self.assertRaises(Exception): - # TODO: This raises for the wrong reason - `--clear-database` doesn't exist anymore! - self.spacetime("publish", self.database_identity, "--project-path", self.project_path, "--clear-database", "--yes") + self.spacetime("publish", self.database_identity, "--project-path", self.project_path, "--delete-data", "--yes") - # Check that this holds without `--clear-database`, too. + # Check that this holds without `--delete-data`, too. with self.assertRaises(Exception): self.spacetime("publish", self.database_identity, "--project-path", self.project_path, "--yes")