diff --git a/src/commands/config/mod.rs b/src/commands/config/mod.rs index fe62a39f0..fc56fe343 100644 --- a/src/commands/config/mod.rs +++ b/src/commands/config/mod.rs @@ -1510,10 +1510,10 @@ async fn ensure_config_initialized(args: &SharedArgs) -> Result<()> { } let cwd = std::env::current_dir().context("Unable to get current directory")?; - let railway_file = cwd.join(".railway").join("railway.ts"); - if railway_file.exists() { + if find_railway_file(&cwd).is_some() { return Ok(()); } + let railway_file = cwd.join(".railway").join("railway.ts"); println!(); println!("{}", "Railway configuration is not initialized yet.".bold()); @@ -1542,6 +1542,22 @@ async fn ensure_config_initialized(args: &SharedArgs) -> Result<()> { Ok(()) } +fn find_railway_file(start: &Path) -> Option { + for directory in start.ancestors() { + if directory.file_name().and_then(|name| name.to_str()) == Some(".railway") { + let direct = directory.join("railway.ts"); + if direct.is_file() { + return Some(direct); + } + } + let nested = directory.join(".railway").join("railway.ts"); + if nested.is_file() { + return Some(nested); + } + } + None +} + #[cfg(test)] mod tests { use super::*; @@ -1619,6 +1635,31 @@ mod tests { assert!(outside.exists()); } + #[test] + fn config_discovery_works_from_project_root_and_railway_directory() { + let root = tempfile::tempdir().unwrap(); + let railway_dir = root.path().join(".railway"); + fs::create_dir_all(&railway_dir).unwrap(); + let config = railway_dir.join("railway.ts"); + fs::write(&config, "export default {};").unwrap(); + + assert_eq!(find_railway_file(root.path()), Some(config.clone())); + assert_eq!(find_railway_file(&railway_dir), Some(config)); + } + + #[test] + fn config_discovery_walks_up_from_nested_project_directories() { + let root = tempfile::tempdir().unwrap(); + let railway_dir = root.path().join(".railway"); + let nested = root.path().join("src").join("Backend.Api"); + fs::create_dir_all(&railway_dir).unwrap(); + fs::create_dir_all(&nested).unwrap(); + let config = railway_dir.join("railway.ts"); + fs::write(&config, "export default {};").unwrap(); + + assert_eq!(find_railway_file(&nested), Some(config)); + } + fn service_resource( source: serde_json::Value, deploy: serde_json::Value, @@ -1669,6 +1710,56 @@ mod tests { assert!(!rendered.contains("registryCredentials")); } + #[test] + fn pull_renderer_preserves_image_resource_limits() { + let resource = service_resource( + json!({ "image": "nginx:latest" }), + json!({ + "limitOverride": { + "containers": { "cpu": 2, "memoryBytes": 2_000_000_000_i64 } + } + }), + ); + + let rendered = render_service_body( + &resource, + &std::collections::BTreeMap::new(), + &std::collections::HashMap::new(), + true, + ); + + assert!(rendered.contains("source: image(\"nginx:latest\")")); + assert!(rendered.contains("cpu: 2")); + assert!(rendered.contains("memoryBytes: 2000000000")); + } + + #[test] + fn pull_renderer_preserves_quoted_bucket_references_when_values_are_visible() { + let mut resource = service_resource(json!({ "image": "nginx:latest" }), json!({})); + resource.variables = Some( + json!({ + "InspectionsBucket__BucketName": { + "type": "literal", + "value": "${{inspections_media.BUCKET}}" + } + }) + .as_object() + .unwrap() + .clone(), + ); + + let rendered = render_service_body( + &resource, + &std::collections::BTreeMap::new(), + &std::collections::HashMap::new(), + false, + ); + + assert!( + rendered.contains("InspectionsBucket__BucketName: \"${{inspections_media.BUCKET}}\"") + ); + } + #[test] fn pull_renderer_preserves_supported_image_auto_updates_only() { let policy = json!({ diff --git a/src/commands/config/runner.rs b/src/commands/config/runner.rs index f34011b32..91e8bb5c1 100644 --- a/src/commands/config/runner.rs +++ b/src/commands/config/runner.rs @@ -471,7 +471,9 @@ fn resolve_runner(explicit_runner: Option<&str>, cwd: &std::path::Path) -> Resol }; } - if let Some(runner) = find_project_runner(cwd) { + if let Some(runner) = + find_project_runner(cwd).or_else(|| find_project_runner(&cwd.join(".railway"))) + { return ResolvedRunner { path: runner.to_string_lossy().to_string(), source: RunnerSource::ProjectDependency, @@ -895,6 +897,20 @@ mod runner_discovery_tests { path } + #[test] + fn resolves_a_runner_installed_beside_the_railway_config() { + let root = tempfile::tempdir().unwrap(); + fs::create_dir_all(root.path().join(".git")).unwrap(); + let railway_dir = root.path().join(".railway"); + fs::create_dir_all(&railway_dir).unwrap(); + let expected = make_runner(&railway_dir); + + let resolved = resolve_runner(None, root.path()); + + assert_eq!(PathBuf::from(resolved.path), expected); + assert!(matches!(resolved.source, RunnerSource::ProjectDependency)); + } + #[test] fn finds_a_runner_inside_the_repository() { let root = tempfile::tempdir().unwrap();