Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c730db1
tyler/fix-on-conflict
cloutiertyler Nov 21, 2025
b7ec6c4
Merge branch 'master' into tyler/fix-on-conflict
bfops Nov 22, 2025
9dcf58b
Added automated testing
cloutiertyler Nov 23, 2025
3c99430
cargo fmt
cloutiertyler Nov 23, 2025
ed4f330
Snap tests
cloutiertyler Nov 23, 2025
8af9cf0
added table_to_remove to the other module-tests
cloutiertyler Nov 24, 2025
239f366
[tyler/fix-on-conflict]: clear env vars to improve build time
bfops Nov 25, 2025
b27fe6a
[tyler/fix-on-conflict]: refactoring
bfops Nov 25, 2025
a1f0cb2
[tyler/fix-on-conflict]: more tests
bfops Nov 25, 2025
2c4e1ee
[tyler/fix-on-conflict]: clippy
bfops Nov 25, 2025
4179ad9
[tyler/fix-on-conflict]: Merge remote-tracking branch 'origin/master'…
bfops Nov 25, 2025
9953d98
[tyler/fix-on-conflict]: review
bfops Nov 25, 2025
071a66a
Update crates/cli/Cargo.toml
bfops Nov 25, 2025
67c57a3
[tyler/fix-on-conflict]: try fix?
bfops Nov 25, 2025
164526d
[tyler/fix-on-conflict]: Merge branch 'tyler/fix-on-conflict' of gith…
bfops Nov 25, 2025
3ed2f12
Remove alias --yes-break-clients
jdetter Nov 26, 2025
0016ef7
[tyler/fix-on-conflict]: hide --features
bfops Nov 26, 2025
64a3745
[tyler/fix-on-conflict]: Merge remote-tracking branch 'origin/master'…
bfops Nov 26, 2025
4724cb8
[tyler/fix-on-conflict]: try undoing the unsetting of CARGO_*
bfops Nov 26, 2025
c146cfe
[tyler/fix-on-conflict]: try revert
bfops Nov 26, 2025
76f0995
[tyler/fix-on-conflict]: rename
bfops Nov 26, 2025
6e4e25b
Fix clippy lint
jdetter Nov 26, 2025
e8f0dae
[tyler/fix-on-conflict]: TODO
bfops Nov 26, 2025
e8cc3fc
Merge branch 'master' into tyler/fix-on-conflict
bfops Nov 27, 2025
5374327
[tyler/fix-on-conflict]: fix
bfops Nov 27, 2025
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
81 changes: 81 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ notify.workspace = true

[dev-dependencies]
pretty_assertions.workspace = true
fs_extra.workspace = true
assert_cmd = "2"
predicates = "3"
portpicker = "0.1"
reqwest = { version = "0.12", features = ["blocking", "json"] }

[target.'cfg(not(target_env = "msvc"))'.dependencies]
tikv-jemallocator = { workspace = true }
Expand Down
10 changes: 9 additions & 1 deletion crates/cli/src/subcommands/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ pub fn cli() -> clap::Command {
.default_value("src")
.help("The directory to lint for nonfunctional print statements. If set to the empty string, skips linting.")
)
.arg(
Arg::new("features")
.long("features")
.value_parser(clap::value_parser!(OsString))
.required(false)
.help("Additional features to pass to the build process (e.g. `--features feature1,feature2` for Rust modules).")
)
.arg(
Arg::new("debug")
.long("debug")
Expand All @@ -33,6 +40,7 @@ pub fn cli() -> clap::Command {

pub async fn exec(_config: Config, args: &ArgMatches) -> Result<(PathBuf, &'static str), anyhow::Error> {
let project_path = args.get_one::<PathBuf>("project_path").unwrap();
let features = args.get_one::<OsString>("features");
let lint_dir = args.get_one::<OsString>("lint_dir").unwrap();
let lint_dir = if lint_dir.is_empty() {
None
Expand All @@ -56,7 +64,7 @@ pub async fn exec(_config: Config, args: &ArgMatches) -> Result<(PathBuf, &'stat
));
}

let result = crate::tasks::build(project_path, lint_dir.as_deref(), build_debug)?;
let result = crate::tasks::build(project_path, lint_dir.as_deref(), build_debug, features)?;
println!("Build finished successfully.");

Ok(result)
Expand Down
17 changes: 8 additions & 9 deletions crates/cli/src/subcommands/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ async fn generate_build_and_publish(

println!("{}", "Building...".cyan());
let (_path_to_program, _host_type) =
tasks::build(spacetimedb_dir, Some(Path::new("src")), false).context("Failed to build project")?;
tasks::build(spacetimedb_dir, Some(Path::new("src")), false, None).context("Failed to build project")?;
println!("{}", "Build complete!".green());

println!("{}", "Generating module bindings...".cyan());
Expand All @@ -413,15 +413,14 @@ async fn generate_build_and_publish(
ClearMode::OnConflict => "on-conflict",
};
let mut publish_args = vec![
"publish",
database_name,
"--project-path",
project_path_str,
"--yes",
"--delete-data",
clear_flag,
"publish".to_string(),
database_name.to_string(),
"--project-path".to_string(),
project_path_str.to_string(),
"--yes".to_string(),
format!("--delete-data={}", clear_flag),
];
publish_args.extend_from_slice(&["--server", server]);
publish_args.extend_from_slice(&["--server".to_string(), server.to_string()]);

let publish_cmd = publish::cli();
let publish_matches = publish_cmd
Expand Down
78 changes: 42 additions & 36 deletions crates/cli/src/subcommands/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub fn cli() -> clap::Command {
.arg(
Arg::new("break_clients")
.long("break-clients")
.alias("yes-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 force publish if it would cause deletion of any data in the database. See --yes and --delete-data for details.")
)
Expand Down Expand Up @@ -175,46 +176,26 @@ 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 {
builder = apply_pre_publish_if_needed(
builder,
&client,
&database_host,
&domain.to_string(),
host_type,
&program_bytes,
&auth_header,
clear_database,
force_break_clients,
force,
)
.await?;
}
builder = apply_pre_publish_if_needed(
builder,
&client,
&database_host,
name_or_identity,
&domain.to_string(),
host_type,
&program_bytes,
&auth_header,
clear_database,
force_break_clients,
force,
)
.await?;

builder
} else {
client.post(format!("{database_host}/v1/database"))
};

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.",
name_or_identity.unwrap()
);
if !y_or_n(
force,
format!(
"Are you sure you want to proceed? [deleting {}]",
name_or_identity.unwrap()
)
.as_str(),
)? {
println!("Aborting");
return Ok(());
}
builder = builder.query(&[("clear", true)]);
}
if let Some(n) = num_replicas {
eprintln!("WARNING: Use of unstable option `--num-replicas`.\n");
builder = builder.query(&[("num_replicas", *n)]);
Expand Down Expand Up @@ -334,6 +315,7 @@ async fn apply_pre_publish_if_needed(
mut builder: reqwest::RequestBuilder,
client: &reqwest::Client,
base_url: &str,
name_or_identity: &str,
domain: &String,
host_type: &str,
program_bytes: &[u8],
Expand Down Expand Up @@ -367,11 +349,35 @@ async fn apply_pre_publish_if_needed(
println!("{}", manual.reason);
println!("Proceeding with database clear due to --delete-data=always.");
}
println!(
"This will DESTROY the current {} module, and ALL corresponding data.",
name_or_identity
);
if !y_or_n(
force,
format!("Are you sure you want to proceed? [deleting {}]", name_or_identity).as_str(),
)? {
anyhow::bail!("Aborting");
}
builder = builder.query(&[("clear", true)]);
}
PrePublishResult::AutoMigrate(auto) => {
if clear_database == ClearMode::Always {
println!("Auto-migration, does NOT require clearing the database, but proceeding with database clear due to --delete-data=always.");
println!(
"This will DESTROY the current {} module, and ALL corresponding data.",
name_or_identity
);
if !y_or_n(
force,
format!("Are you sure you want to proceed? [deleting {}]", name_or_identity).as_str(),
)? {
anyhow::bail!("Aborting");
}
builder = builder.query(&[("clear", true)]);
return Ok(builder);
}
println!("{}", auto.migrate_plan);
// We only arrive here if you have not specified ClearMode::Always AND there was no
// conflict that required manual migration.
if auto.break_clients
&& !y_or_n(
force_break_clients || force,
Expand Down
6 changes: 5 additions & 1 deletion crates/cli/src/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,14 @@ pub fn build(
project_path: &Path,
lint_dir: Option<&Path>,
build_debug: bool,
features: Option<&std::ffi::OsString>,
) -> anyhow::Result<(PathBuf, &'static str)> {
let lang = util::detect_module_language(project_path)?;
if features.is_some() && lang != ModuleLanguage::Rust {
anyhow::bail!("The --features option is only supported for Rust modules.");
}
let output_path = match lang {
ModuleLanguage::Rust => build_rust(project_path, lint_dir, build_debug),
ModuleLanguage::Rust => build_rust(project_path, features, lint_dir, build_debug),
ModuleLanguage::Csharp => build_csharp(project_path, build_debug),
ModuleLanguage::Javascript => build_javascript(project_path, build_debug),
}?;
Expand Down
21 changes: 17 additions & 4 deletions crates/cli/src/tasks/rust.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ fn cargo_cmd(subcommand: &str, build_debug: bool, args: &[&str]) -> duct::Expres
)
}

pub(crate) fn build_rust(project_path: &Path, lint_dir: Option<&Path>, build_debug: bool) -> anyhow::Result<PathBuf> {
pub(crate) fn build_rust(
project_path: &Path,
features: Option<&std::ffi::OsString>,
lint_dir: Option<&Path>,
build_debug: bool,
) -> anyhow::Result<PathBuf> {
// Make sure that we have the wasm target installed
if !has_wasm32_target() {
if has_rust_up() {
Expand Down Expand Up @@ -75,9 +80,17 @@ pub(crate) fn build_rust(project_path: &Path, lint_dir: Option<&Path>, build_deb
);
}

let reader = cargo_cmd("build", build_debug, &["--message-format=json-render-diagnostics"])
.dir(project_path)
.reader()?;
let mut args = if let Some(features) = features {
vec![format!("--features={}", features.to_string_lossy())]
} else {
vec![]
};
args.push("--message-format=json-render-diagnostics".to_string());

// Convert Vec<String> to Vec<&str>
let args_str: Vec<&str> = args.iter().map(|s| s.as_str()).collect();

let reader = cargo_cmd("build", build_debug, &args_str).dir(project_path).reader()?;

let mut artifact = None;
for message in Message::parse_stream(io::BufReader::new(reader)) {
Expand Down
Loading
Loading