diff --git a/src/client.rs b/src/client.rs index b77fb2694..2de3da8aa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -114,7 +114,7 @@ fn get_security_url() -> String { Environment::Staging => "railway-staging.com", Environment::Dev => "railway-develop.com", }; - format!("https://{}/account/security", host) + format!("https://{host}/account/security") } pub(crate) fn auth_failure_error() -> RailwayError { diff --git a/src/commands/add.rs b/src/commands/add.rs index 194ca995e..02cdef46f 100644 --- a/src/commands/add.rs +++ b/src/commands/add.rs @@ -124,14 +124,14 @@ pub async fn command(args: Args) -> Result<()> { } }; if verbose { - println!("{:?}", type_of_create); + println!("{type_of_create:?}"); } match type_of_create { CreateKind::Database(databases) => { let is_single_db = databases.len() == 1; for db in databases { if verbose { - println!("iterating through databases to add: {:?}", db) + println!("iterating through databases to add: {db:?}") } deploy::fetch_and_create( &client, @@ -147,7 +147,7 @@ pub async fn command(args: Args) -> Result<()> { ) .await?; if verbose { - println!("successfully created {:?}", db) + println!("successfully created {db:?}") } } } diff --git a/src/commands/deploy.rs b/src/commands/deploy.rs index 56f6d14ed..ba3a0c286 100644 --- a/src/commands/deploy.rs +++ b/src/commands/deploy.rs @@ -237,7 +237,7 @@ pub async fn fetch_and_create( "templateName": details.template.name, }) }; - println!("{}", output); + println!("{output}"); } else if let Some(spinner) = spinner { let mut msg = format!("🎉 Added {} to project", template_name.green().bold()); if options.should_link && linked_project.service.is_none() && new_service.is_some() { diff --git a/src/commands/dev.rs b/src/commands/dev.rs index 991d25c16..10d4d60f4 100644 --- a/src/commands/dev.rs +++ b/src/commands/dev.rs @@ -335,10 +335,7 @@ async fn configure_command(args: ConfigureArgs) -> Result<()> { local_dev_config.save(&project_id)?; println!("{} Removed configuration for '{}'", "✓".green(), name); } else { - println!( - "{}", - format!("Service '{}' is not configured", name).yellow() - ); + println!("{}", format!("Service '{name}' is not configured").yellow()); } } @@ -403,7 +400,7 @@ async fn configure_command(args: ConfigureArgs) -> Result<()> { ); let suggested = generate_random_port(); let port_input = - prompt_text(&format!("Choose a different port [{}]:", suggested))?; + prompt_text(&format!("Choose a different port [{suggested}]:"))?; new_config.port = Some(if port_input.is_empty() { suggested } else { @@ -453,7 +450,7 @@ async fn configure_command(args: ConfigureArgs) -> Result<()> { .unwrap_or_else(|_| existing.directory.clone()); let input_path = prompt_path_with_default( - &format!("Directory for '{}' (relative to cwd):", name), + &format!("Directory for '{name}' (relative to cwd):"), &default_dir, )?; @@ -476,8 +473,7 @@ async fn configure_command(args: ConfigureArgs) -> Result<()> { let railway_port = svc.get_ports().first().map(|&p| p as u16); let current_port = existing.port.or(railway_port).unwrap_or(DEFAULT_PORT); - let port_input = - prompt_text(&format!("Port for '{}' [{}]:", name, current_port))?; + let port_input = prompt_text(&format!("Port for '{name}' [{current_port}]:"))?; let mut new_port = if port_input.is_empty() { current_port @@ -501,7 +497,7 @@ async fn configure_command(args: ConfigureArgs) -> Result<()> { ); let suggested = generate_random_port(); let port_input = - prompt_text(&format!("Choose a different port [{}]:", suggested))?; + prompt_text(&format!("Choose a different port [{suggested}]:"))?; new_port = if port_input.is_empty() { suggested } else { @@ -564,7 +560,7 @@ fn show_service_config_menu(name: &str, config: &CodeServiceConfig) -> Result config.directory.clone(), }; - println!("\n{}", format!("Service '{}'", name).cyan().bold()); + println!("\n{}", format!("Service '{name}'").cyan().bold()); println!(" {}: {}", "command".dimmed(), config.command); println!(" {}: {}", "directory".dimmed(), display_dir); if let Some(port) = config.port { @@ -588,17 +584,13 @@ fn prompt_service_config( svc: &ServiceInstance, existing: Option<&CodeServiceConfig>, ) -> Result { - println!("\n{}", format!("Configure '{}'", name).cyan().bold()); + println!("\n{}", format!("Configure '{name}'").cyan().bold()); let default_command = existing.map(|e| e.command.as_str()).unwrap_or(""); let command = if default_command.is_empty() { - prompt_text(&format!("Dev command for '{}':", name))? + prompt_text(&format!("Dev command for '{name}':"))? } else { - prompt_text(&format!( - "Dev command for '{}' [{}]:", - name, default_command - )) - .map(|s| { + prompt_text(&format!("Dev command for '{name}' [{default_command}]:")).map(|s| { if s.is_empty() { default_command.to_string() } else { @@ -618,7 +610,7 @@ fn prompt_service_config( .unwrap_or_else(|| ".".to_string()); let input_path = prompt_path_with_default( - &format!("Directory for '{}' (relative to current directory):", name), + &format!("Directory for '{name}' (relative to current directory):"), &default_dir, )?; @@ -635,7 +627,7 @@ fn prompt_service_config( let default_port = existing.and_then(|e| e.port).or(inferred_port); let port = if let Some(default) = default_port { - let port_input = prompt_text(&format!("Port for '{}' [{}]:", name, default))?; + let port_input = prompt_text(&format!("Port for '{name}' [{default}]:"))?; if port_input.is_empty() { Some(default) } else { @@ -702,7 +694,7 @@ fn prompt_initial_service_setup( conflicts.join(", ") ); let suggested = generate_random_port(); - let port_input = prompt_text(&format!("Choose a different port [{}]:", suggested))?; + let port_input = prompt_text(&format!("Choose a different port [{suggested}]:"))?; new_config.port = Some(if port_input.is_empty() { suggested } else { @@ -799,8 +791,7 @@ fn resolve_port_conflicts( if let Some(service_id) = service_id { let suggested = generate_random_port(); let port_input = prompt_text(&format!( - "New port for '{}' (currently {}) [{}]:", - service_name, port, suggested + "New port for '{service_name}' (currently {port}) [{suggested}]:" ))?; let new_port = if port_input.is_empty() { @@ -1306,7 +1297,7 @@ fn build_image_service_compose( for (vol_id, vol_mount) in &svc.volume_mounts { if let Some(mount_path) = &vol_mount.mount_path { let vol_name = volume_name(environment_id, vol_id); - service_volumes.push(format!("{}:{}", vol_name, mount_path)); + service_volumes.push(format!("{vol_name}:{mount_path}")); compose_volumes.insert(vol_name, DockerComposeVolume {}); } } diff --git a/src/commands/environment/changes/build_command.rs b/src/commands/environment/changes/build_command.rs index 5421a0f34..031f3e97a 100644 --- a/src/commands/environment/changes/build_command.rs +++ b/src/commands/environment/changes/build_command.rs @@ -26,7 +26,7 @@ pub fn parse_interactive( } Ok(vec![( - format!("services.{}.build.buildCommand", service_id), + format!("services.{service_id}.build.buildCommand"), serde_json::json!(build_command), )]) } diff --git a/src/commands/environment/changes/builder.rs b/src/commands/environment/changes/builder.rs index 12cb56c84..3a12facb7 100644 --- a/src/commands/environment/changes/builder.rs +++ b/src/commands/environment/changes/builder.rs @@ -49,12 +49,12 @@ pub fn parse_interactive( }; let mut entries: Vec = Vec::new(); - let base_path = format!("services.{}.build", service_id); + let base_path = format!("services.{service_id}.build"); match builder_type { BuilderType::Dockerfile => { entries.push(( - format!("{}.builder", base_path), + format!("{base_path}.builder"), serde_json::json!("DOCKERFILE"), )); @@ -66,7 +66,7 @@ pub fn parse_interactive( )? { if !dockerfile_path.is_empty() { entries.push(( - format!("{}.dockerfilePath", base_path), + format!("{base_path}.dockerfilePath"), serde_json::json!(dockerfile_path), )); } @@ -74,7 +74,7 @@ pub fn parse_interactive( } BuilderType::Railpack => { entries.push(( - format!("{}.builder", base_path), + format!("{base_path}.builder"), serde_json::json!("RAILPACK"), )); // Railpack doesn't have a config path option diff --git a/src/commands/environment/changes/healthcheck.rs b/src/commands/environment/changes/healthcheck.rs index 4573dc901..e99590a44 100644 --- a/src/commands/environment/changes/healthcheck.rs +++ b/src/commands/environment/changes/healthcheck.rs @@ -14,7 +14,7 @@ pub fn parse_interactive( let existing_healthcheck_timeout = existing_deploy.and_then(|d| d.healthcheck_timeout); let mut entries: Vec = Vec::new(); - let base_path = format!("services.{}.deploy", service_id); + let base_path = format!("services.{service_id}.deploy"); // Health check path let path_placeholder = existing_healthcheck_path.unwrap_or("None"); @@ -31,7 +31,7 @@ pub fn parse_interactive( } entries.push(( - format!("{}.healthcheckPath", base_path), + format!("{base_path}.healthcheckPath"), serde_json::json!(path), )); @@ -57,7 +57,7 @@ pub fn parse_interactive( match timeout_str.parse::() { Ok(timeout) if timeout > 0 => { entries.push(( - format!("{}.healthcheckTimeout", base_path), + format!("{base_path}.healthcheckTimeout"), serde_json::json!(timeout), )); break; diff --git a/src/commands/environment/changes/regions.rs b/src/commands/environment/changes/regions.rs index 5d9882328..49bbd3efe 100644 --- a/src/commands/environment/changes/regions.rs +++ b/src/commands/environment/changes/regions.rs @@ -56,10 +56,10 @@ pub fn parse_interactive( // Convert to patch entries let mut entries: Vec = Vec::new(); - let base_path = format!("services.{}.deploy.multiRegionConfig", service_id); + let base_path = format!("services.{service_id}.deploy.multiRegionConfig"); for (region, config) in region_map { - entries.push((format!("{}.{}", base_path, region), config)); + entries.push((format!("{base_path}.{region}"), config)); } Ok(entries) diff --git a/src/commands/environment/changes/restart_policy.rs b/src/commands/environment/changes/restart_policy.rs index b20f0a1ed..d04b85414 100644 --- a/src/commands/environment/changes/restart_policy.rs +++ b/src/commands/environment/changes/restart_policy.rs @@ -51,15 +51,15 @@ pub fn parse_interactive( return Ok(vec![]); }; - let base_path = format!("services.{}.deploy", service_id); + let base_path = format!("services.{service_id}.deploy"); let result = match policy_type { RestartPolicyType::Never => vec![( - format!("{}.restartPolicyType", base_path), + format!("{base_path}.restartPolicyType"), serde_json::json!("NEVER"), )], RestartPolicyType::Always => vec![( - format!("{}.restartPolicyType", base_path), + format!("{base_path}.restartPolicyType"), serde_json::json!("ALWAYS"), )], RestartPolicyType::OnFailure => { @@ -84,11 +84,11 @@ pub fn parse_interactive( Ok(max_retries) => { break vec![ ( - format!("{}.restartPolicyType", base_path), + format!("{base_path}.restartPolicyType"), serde_json::json!("ON_FAILURE"), ), ( - format!("{}.restartPolicyMaxRetries", base_path), + format!("{base_path}.restartPolicyMaxRetries"), serde_json::json!(max_retries), ), ]; diff --git a/src/commands/environment/changes/source.rs b/src/commands/environment/changes/source.rs index add60aa62..c3c3d781d 100644 --- a/src/commands/environment/changes/source.rs +++ b/src/commands/environment/changes/source.rs @@ -47,7 +47,7 @@ pub fn parse_interactive( return Ok(vec![]); }; - let base_path = format!("services.{}.source", service_id); + let base_path = format!("services.{service_id}.source"); let mut entries: Vec = Vec::new(); @@ -72,12 +72,12 @@ pub fn parse_interactive( break image; }; - entries.push((format!("{}.image", base_path), serde_json::json!(image))); + entries.push((format!("{base_path}.image"), serde_json::json!(image))); } SourceType::GitHub => { // GitHub repo (required) let repo_placeholder = match (existing_repo, existing_branch) { - (Some(repo), Some(branch)) => format!("{}/{}", repo, branch), + (Some(repo), Some(branch)) => format!("{repo}/{branch}"), _ => "".to_string(), }; let (repo, branch) = loop { @@ -106,8 +106,8 @@ pub fn parse_interactive( break (format!("{}/{}", parts[0], parts[1]), parts[2].to_string()); }; - entries.push((format!("{}.repo", base_path), serde_json::json!(repo))); - entries.push((format!("{}.branch", base_path), serde_json::json!(branch))); + entries.push((format!("{base_path}.repo"), serde_json::json!(repo))); + entries.push((format!("{base_path}.branch"), serde_json::json!(branch))); // Root directory (monorepos) let root_dir_placeholder = existing_root_dir.unwrap_or("/packages/backend"); @@ -117,7 +117,7 @@ pub fn parse_interactive( )? { if !root_dir.is_empty() { entries.push(( - format!("{}.rootDirectory", base_path), + format!("{base_path}.rootDirectory"), serde_json::json!(root_dir), )); } @@ -130,7 +130,7 @@ pub fn parse_interactive( check_suites_default, )? { entries.push(( - format!("{}.checkSuites", base_path), + format!("{base_path}.checkSuites"), serde_json::json!(check_suites), )); } diff --git a/src/commands/environment/changes/start_command.rs b/src/commands/environment/changes/start_command.rs index e2d72333c..edfbdf384 100644 --- a/src/commands/environment/changes/start_command.rs +++ b/src/commands/environment/changes/start_command.rs @@ -26,7 +26,7 @@ pub fn parse_interactive( } Ok(vec![( - format!("services.{}.deploy.startCommand", service_id), + format!("services.{service_id}.deploy.startCommand"), serde_json::json!(start_command), )]) } diff --git a/src/commands/environment/changes/watch_patterns.rs b/src/commands/environment/changes/watch_patterns.rs index 176b849bb..3b528cb82 100644 --- a/src/commands/environment/changes/watch_patterns.rs +++ b/src/commands/environment/changes/watch_patterns.rs @@ -39,7 +39,7 @@ pub fn parse_interactive( } Ok(vec![( - format!("services.{}.build.watchPatterns", service_id), + format!("services.{service_id}.build.watchPatterns"), serde_json::json!(patterns_vec), )]) } diff --git a/src/commands/environment/mod.rs b/src/commands/environment/mod.rs index a8ce52a51..8c2cdc4a7 100644 --- a/src/commands/environment/mod.rs +++ b/src/commands/environment/mod.rs @@ -159,7 +159,7 @@ impl EnvironmentConfigOptions { if let Some((key, value)) = key_value.split_once('=') { configs.push(service.clone()); - configs.push(format!("variables.{}.value", key)); + configs.push(format!("variables.{key}.value")); configs.push(value.to_string()); } } diff --git a/src/commands/environment/new.rs b/src/commands/environment/new.rs index 1e9bbb29a..05b7ed08b 100644 --- a/src/commands/environment/new.rs +++ b/src/commands/environment/new.rs @@ -155,7 +155,7 @@ pub fn parse_non_interactive_configs( configured_fields.insert(display_field); // Build full path with service ID - let full_path = format!("services.{}.{}", service_id, normalized_path); + let full_path = format!("services.{service_id}.{normalized_path}"); entries.push((full_path, json_value)); } @@ -230,7 +230,7 @@ pub async fn parse_interactive_configs( .and_then(|c| c.services.get(service_id)); let selected_changes = prompt_multi_options( - &format!("What do you want to configure for {}?", service_name), + &format!("What do you want to configure for {service_name}?"), Change::iter().collect(), )?; diff --git a/src/commands/service.rs b/src/commands/service.rs index 4d8de6e22..fe617c926 100644 --- a/src/commands/service.rs +++ b/src/commands/service.rs @@ -42,6 +42,9 @@ enum Commands { /// Scale a service across regions Scale(crate::commands::scale::Args), + + /// Configure service repo source and settings + Source(SourceArgs), } #[derive(Parser)] @@ -50,6 +53,33 @@ struct LinkArgs { service: Option, } +#[derive(Parser)] +struct SourceArgs { + /// GitHub repo to connect (format: owner/repo) + #[clap(short, long)] + repo: String, + + /// Branch name (optional, defaults to repo's default branch) + #[clap(short, long)] + branch: Option, + + /// Root directory path within the repo + #[clap(long)] + root_directory: Option, + + /// Watch paths/patterns to trigger deployments + #[clap(long)] + watch_paths: Vec, + + /// Service name or ID (defaults to linked service) + #[clap(short, long)] + service: Option, + + /// Environment (defaults to linked environment) + #[clap(short, long)] + environment: Option, +} + #[derive(Parser)] struct StatusArgs { /// Service name or ID to show status for (defaults to linked service) @@ -102,6 +132,7 @@ pub async fn command(args: Args) -> Result<()> { crate::commands::restart::command(restart_args).await } Some(Commands::Scale(scale_args)) => crate::commands::scale::command(scale_args).await, + Some(Commands::Source(source_args)) => source_command(source_args).await, None => unreachable!(), } } @@ -199,7 +230,7 @@ async fn status_command(args: StatusArgs) -> Result<()> { println!("{}", serde_json::to_string_pretty(&service_statuses)?); } else { if service_statuses.is_empty() { - println!("No services found in environment '{}'", environment_name); + println!("No services found in environment '{environment_name}'"); return Ok(()); } @@ -255,6 +286,130 @@ async fn status_command(args: StatusArgs) -> Result<()> { Ok(()) } +async fn source_command(args: SourceArgs) -> Result<()> { + let configs = Configs::new()?; + let client = GQLClient::new_authorized(&configs)?; + let linked_project = configs.get_linked_project().await?; + let project = get_project(&client, &configs, linked_project.project.clone()).await?; + + ensure_project_and_environment_exist(&client, &configs, &linked_project).await?; + + // Determine which environment to use + let environment_id = if let Some(env_name) = args.environment { + let env = get_matched_environment(&project, env_name)?; + env.id + } else { + linked_project.environment.clone() + }; + + // Resolve service ID + let service_id = if let Some(service_name) = args.service { + let service = project + .services + .edges + .iter() + .find(|s| s.node.id == service_name || s.node.name == service_name) + .ok_or_else(|| RailwayError::ServiceNotFound(service_name.clone()))?; + service.node.id.clone() + } else { + linked_project + .service + .clone() + .context("No service linked. Use --service flag to specify a service.")? + }; + + // Get the service name for display + let service_name = project + .services + .edges + .iter() + .find(|s| s.node.id == service_id) + .map(|s| s.node.name.clone()) + .unwrap_or_else(|| service_id.clone()); + + // Get branch - use provided or fetch default branch from GitHub + let branch = if let Some(branch) = args.branch { + branch + } else { + // Fetch default branch for the repo + let repos = post_graphql::( + &client, + &configs.get_backboard(), + queries::git_hub_repos::Variables {}, + ) + .await? + .github_repos; + + let repo_info = repos + .iter() + .find(|r| r.full_name == args.repo) + .ok_or_else(|| { + anyhow::anyhow!( + "Repo '{}' not found. Make sure you have access to this repository.", + args.repo + ) + })?; + + repo_info.default_branch.clone() + }; + + // Connect service to repo source + let connect_input = mutations::service_connect::ServiceConnectInput { + repo: Some(args.repo.clone()), + branch: Some(branch.clone()), + image: None, + }; + + post_graphql::( + &client, + &configs.get_backboard(), + mutations::service_connect::Variables { + id: service_id.clone(), + input: connect_input, + }, + ) + .await?; + + println!( + "Connected service {} to repo {} (branch: {})", + service_name.green().bold(), + args.repo.blue(), + branch.cyan() + ); + + // Update root directory and watch paths if provided + let has_instance_updates = args.root_directory.is_some() || !args.watch_paths.is_empty(); + + if has_instance_updates { + let watch_patterns = if args.watch_paths.is_empty() { + None + } else { + Some(args.watch_paths.clone()) + }; + + post_graphql::( + &client, + &configs.get_backboard(), + mutations::service_instance_update_source::Variables { + environment_id: environment_id.clone(), + service_id: service_id.clone(), + root_directory: args.root_directory.clone(), + watch_patterns, + }, + ) + .await?; + + if let Some(root_dir) = &args.root_directory { + println!("Set root directory to {}", root_dir.cyan()); + } + if !args.watch_paths.is_empty() { + println!("Set watch paths to {}", args.watch_paths.join(", ").cyan()); + } + } + + Ok(()) +} + fn format_status_display(status: &ServiceStatusOutput) -> colored::ColoredString { if status.stopped && status.status.as_deref() == Some("SUCCESS") { return "STOPPED".yellow(); diff --git a/src/commands/up.rs b/src/commands/up.rs index 6c4079d43..8d36abe4a 100644 --- a/src/commands/up.rs +++ b/src/commands/up.rs @@ -234,8 +234,7 @@ pub async fn command(args: Args) -> Result<()> { parz.finish()?; let mut url = Url::parse(&format!( - "https://backboard.{hostname}/project/{}/environment/{}/up", - project_id, environment_id, + "https://backboard.{hostname}/project/{project_id}/environment/{environment_id}/up", ))?; url.query_pairs_mut() diff --git a/src/commands/upgrade.rs b/src/commands/upgrade.rs index 9751987d8..cc320d1ec 100644 --- a/src/commands/upgrade.rs +++ b/src/commands/upgrade.rs @@ -130,7 +130,7 @@ fn run_upgrade_command(method: InstallMethod) -> Result<()> { let status = Command::new(program) .args(&args) .status() - .context(format!("Failed to execute {}", program))?; + .context(format!("Failed to execute {program}"))?; if !status.success() { bail!( diff --git a/src/controllers/config/patch.rs b/src/controllers/config/patch.rs index 7481979ea..d4540ecaf 100644 --- a/src/controllers/config/patch.rs +++ b/src/controllers/config/patch.rs @@ -34,9 +34,9 @@ impl std::fmt::Display for ExpectedType { ExpectedType::Integer => write!(f, "integer"), ExpectedType::Number => write!(f, "number"), ExpectedType::Boolean => write!(f, "boolean"), - ExpectedType::Array(inner) => write!(f, "array of {}", inner), + ExpectedType::Array(inner) => write!(f, "array of {inner}"), ExpectedType::Object => write!(f, "object"), - ExpectedType::Nullable(inner) => write!(f, "{} (nullable)", inner), + ExpectedType::Nullable(inner) => write!(f, "{inner} (nullable)"), ExpectedType::Any => write!(f, "any"), } } @@ -431,7 +431,7 @@ pub fn build_config(entries: Vec) -> Result { for (path, value) in entries { json.dot_set(&path, value) - .with_context(|| format!("Failed to set path: {}", path))?; + .with_context(|| format!("Failed to set path: {path}"))?; } serde_json::from_value(json).context("Failed to parse built config into EnvironmentConfig") diff --git a/src/controllers/develop/code_runner.rs b/src/controllers/develop/code_runner.rs index 9b4d5475f..bd0ea8f25 100644 --- a/src/controllers/develop/code_runner.rs +++ b/src/controllers/develop/code_runner.rs @@ -41,7 +41,7 @@ fn spawn_child( .kill_on_drop(true) .process_group(0) .spawn() - .with_context(|| format!("Failed to spawn '{}'", command))?; + .with_context(|| format!("Failed to spawn '{command}'"))?; #[cfg(windows)] let child = Command::new("cmd") @@ -111,7 +111,7 @@ impl ProcessManager { let cmd_log = LogLine { service_name: service_name.clone(), - message: format!("$ {}", command), + message: format!("$ {command}"), is_stderr: false, color, }; diff --git a/src/controllers/develop/https_proxy.rs b/src/controllers/develop/https_proxy.rs index dae97681b..e7b87ed08 100644 --- a/src/controllers/develop/https_proxy.rs +++ b/src/controllers/develop/https_proxy.rs @@ -25,7 +25,7 @@ pub fn is_project_proxy_on_443(project_id: &str) -> bool { .args([ "ps", "--filter", - &format!("name={}-railway-proxy", project_id), + &format!("name={project_id}-railway-proxy"), "--format", "{{.Ports}}", ]) @@ -107,7 +107,7 @@ pub fn get_existing_certs( output_dir: &Path, use_port_443: bool, ) -> HttpsConfig { - let base_domain = format!("{}.railway.localhost", project_slug); + let base_domain = format!("{project_slug}.railway.localhost"); HttpsConfig { project_slug: project_slug.to_string(), base_domain, @@ -122,8 +122,8 @@ pub fn generate_certs( output_dir: &Path, use_port_443: bool, ) -> Result { - let base_domain = format!("{}.railway.localhost", project_slug); - let wildcard_domain = format!("*.{}", base_domain); + let base_domain = format!("{project_slug}.railway.localhost"); + let wildcard_domain = format!("*.{base_domain}"); let cert_path = output_dir.join("cert.pem"); let key_path = output_dir.join("key.pem"); @@ -187,7 +187,7 @@ pub fn generate_caddyfile(services: &[ServicePort], https_config: &HttpsConfig) format!("{}:{}", https_config.base_domain, svc.external_port) }; - caddyfile.push_str(&format!("{} {{\n", site_address)); + caddyfile.push_str(&format!("{site_address} {{\n")); caddyfile.push_str(" tls /certs/cert.pem /certs/key.pem\n"); // Code services run on host network, image services run in Docker network @@ -197,7 +197,7 @@ pub fn generate_caddyfile(services: &[ServicePort], https_config: &HttpsConfig) format!("{}:{}", svc.slug, svc.internal_port) }; - caddyfile.push_str(&format!(" reverse_proxy {}\n", upstream)); + caddyfile.push_str(&format!(" reverse_proxy {upstream}\n")); caddyfile.push_str("}\n\n"); } diff --git a/src/controllers/develop/output.rs b/src/controllers/develop/output.rs index d41fdfeb7..0c28deb39 100644 --- a/src/controllers/develop/output.rs +++ b/src/controllers/develop/output.rs @@ -50,7 +50,7 @@ pub fn print_code_service_summary( } } None => { - println!(" http://localhost:{}", port); + println!(" http://localhost:{port}"); } } } diff --git a/src/controllers/develop/session.rs b/src/controllers/develop/session.rs index 2fb1577f9..5fd87f2bf 100644 --- a/src/controllers/develop/session.rs +++ b/src/controllers/develop/session.rs @@ -154,7 +154,7 @@ impl DevSession { let (private_url, public_url) = match (internal_port, proxy_port) { (Some(port), Some(pport)) => { - let private = format!("http://localhost:{}", port); + let private = format!("http://localhost:{port}"); let public = https_config.as_ref().map(|config| { let slug = slugify(&service_name); if config.use_port_443 { diff --git a/src/controllers/develop/tui/app.rs b/src/controllers/develop/tui/app.rs index f0949ad2d..e34d2ceb0 100644 --- a/src/controllers/develop/tui/app.rs +++ b/src/controllers/develop/tui/app.rs @@ -374,7 +374,7 @@ impl TuiApp { } let (_, service_name, message, _) = log_ref.parts(); - let full_line = format!("[{}] {}", service_name, message); + let full_line = format!("[{service_name}] {message}"); let line_chars: Vec = full_line.chars().collect(); let line_len = line_chars.len(); diff --git a/src/controllers/develop/tui/docker_logs.rs b/src/controllers/develop/tui/docker_logs.rs index 9b0615c5f..aa2faf2b4 100644 --- a/src/controllers/develop/tui/docker_logs.rs +++ b/src/controllers/develop/tui/docker_logs.rs @@ -82,8 +82,8 @@ fn parse_log_line(line: &str, service_mapping: &ServiceMapping) -> Option( vis_row: usize, selection: &Option, ) -> Line<'a> { - let prefix = format!("[{}] ", service_name); + let prefix = format!("[{service_name}] "); let prefix_len = prefix.chars().count(); let Some(sel) = selection else { @@ -202,7 +202,7 @@ fn render_log_line<'a>( ]); } - let full_line = format!("{}{}", prefix, message); + let full_line = format!("{prefix}{message}"); let chars: Vec = full_line.chars().collect(); let line_len = chars.len(); diff --git a/src/controllers/develop/variables.rs b/src/controllers/develop/variables.rs index e33ecc553..6527bee6f 100644 --- a/src/controllers/develop/variables.rs +++ b/src/controllers/develop/variables.rs @@ -89,7 +89,7 @@ impl LocalDevelopContext { }) } else { let port = config.port_mapping.values().next().copied()?; - Some(format!("localhost:{}", port)) + Some(format!("localhost:{port}")) } } @@ -214,7 +214,7 @@ fn replace_domain_refs( let port_mapping = ctx.port_mapping_for_slug(slug); // Replace {slug}.railway.internal:{port} patterns - let railway_domain = format!("{}.railway.internal", slug); + let railway_domain = format!("{slug}.railway.internal"); if result.contains(&railway_domain) { match ctx.mode { NetworkMode::Docker => { @@ -234,8 +234,8 @@ fn replace_domain_refs( if ctx.mode == NetworkMode::Host { if let Some(ports) = port_mapping { for (internal, external) in ports { - let old_pattern = format!("{}:{}", slug, internal); - let new_pattern = format!("localhost:{}", external); + let old_pattern = format!("{slug}:{internal}"); + let new_pattern = format!("localhost:{external}"); result = result.replace(&old_pattern, &new_pattern); } } @@ -246,8 +246,8 @@ fn replace_domain_refs( for (prod_domain, local_domain) in public_domain_mapping { if !ctx.https_enabled() { // When HTTPS disabled, also replace https://prod -> http://local - let https_prod = format!("https://{}", prod_domain); - let http_local = format!("http://{}", local_domain); + let https_prod = format!("https://{prod_domain}"); + let http_local = format!("http://{local_domain}"); result = result.replace(&https_prod, &http_local); } result = result.replace(prod_domain, local_domain); @@ -264,8 +264,8 @@ fn replace_domain_with_port_mapping( let mut result = value.to_string(); for (internal, external) in port_mapping { - let old_pattern = format!("{}:{}", domain, internal); - let new_pattern = format!("localhost:{}", external); + let old_pattern = format!("{domain}:{internal}"); + let new_pattern = format!("localhost:{external}"); result = result.replace(&old_pattern, &new_pattern); } diff --git a/src/gql/mutations/mod.rs b/src/gql/mutations/mod.rs index 0c9d24dac..8ec55a9da 100644 --- a/src/gql/mutations/mod.rs +++ b/src/gql/mutations/mod.rs @@ -175,6 +175,15 @@ pub struct VariableDelete; )] pub struct ServiceCreate; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/gql/schema.json", + query_path = "src/gql/mutations/strings/ServiceConnect.graphql", + response_derives = "Debug, Serialize, Clone", + skip_serializing_none +)] +pub struct ServiceConnect; + #[derive(GraphQLQuery)] #[graphql( schema_path = "src/gql/schema.json", @@ -235,6 +244,15 @@ pub struct EnvironmentDelete; )] pub struct FunctionUpdate; +#[derive(GraphQLQuery)] +#[graphql( + schema_path = "src/gql/schema.json", + query_path = "src/gql/mutations/strings/ServiceInstanceUpdateSource.graphql", + response_derives = "Debug, Serialize, Clone", + skip_serializing_none +)] +pub struct ServiceInstanceUpdateSource; + #[derive(GraphQLQuery)] #[graphql( schema_path = "src/gql/schema.json", diff --git a/src/gql/mutations/strings/ServiceConnect.graphql b/src/gql/mutations/strings/ServiceConnect.graphql new file mode 100644 index 000000000..26b5872e1 --- /dev/null +++ b/src/gql/mutations/strings/ServiceConnect.graphql @@ -0,0 +1,6 @@ +mutation ServiceConnect($id: String!, $input: ServiceConnectInput!) { + serviceConnect(id: $id, input: $input) { + id + name + } +} diff --git a/src/gql/mutations/strings/ServiceInstanceUpdateSource.graphql b/src/gql/mutations/strings/ServiceInstanceUpdateSource.graphql new file mode 100644 index 000000000..5f4cf5f9f --- /dev/null +++ b/src/gql/mutations/strings/ServiceInstanceUpdateSource.graphql @@ -0,0 +1,15 @@ +mutation ServiceInstanceUpdateSource( + $environmentId: String! + $serviceId: String! + $rootDirectory: String + $watchPatterns: [String!] +) { + serviceInstanceUpdate( + environmentId: $environmentId + serviceId: $serviceId + input: { + rootDirectory: $rootDirectory + watchPatterns: $watchPatterns + } + ) +} diff --git a/src/util/logs.rs b/src/util/logs.rs index 1b2258479..a934fba50 100644 --- a/src/util/logs.rs +++ b/src/util/logs.rs @@ -57,7 +57,7 @@ pub fn format_attr_log_string(log: &T, show_all_attributes: bool) -> .bold(); if others.is_empty() { - format!("{} {}", level_str, message) + format!("{level_str} {message}") } else { format!( "{} {} {} {}",