Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
67 changes: 67 additions & 0 deletions src/commands/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1411,4 +1411,71 @@ mod tests {
assert!(rendered.contains("source: image"));
assert!(rendered.contains("registryCredentials"));
}

#[test]
fn pull_renderer_declares_volumes_and_attachments() {
let graph: runner::DesiredGraph = serde_json::from_value(json!({
"project": { "name": "acme" },
"resources": [
{
"address": "service.web",
"type": "service",
"name": "web",
"source": { "type": "image", "image": "ghcr.io/acme/api:1.2.3" },
"deploy": { "startCommand": "node index.js" },
"volumeAttachments": {
"web": { "volume": "volume.web", "mountPath": "/data" }
}
},
{
"address": "database.db",
"type": "database",
"engine": "postgres",
"name": "db",
"deploy": { "requiredMountPath": "/var/lib/postgresql/data" }
},
{ "address": "volume.web", "type": "volume", "name": "web", "config": { "name": "web", "sizeMB": 1024 } },
{ "address": "volume.db-volume", "type": "volume", "name": "db-volume", "config": { "name": "db-volume", "sizeMB": 2048 } },
{ "address": "volume.scratch", "type": "volume", "name": "scratch", "config": { "name": "scratch", "sizeMB": 512 } }
]
}))
.expect("valid graph json");

let out = render_graph_as_railway_ts(&graph, false);

// Volume names colliding with service names, database volumes, and
// unattached volumes must all be declared and referenced.
assert!(out.contains("volume(\"web\""), "missing volume(web): {out}");
assert!(
out.contains("volume(\"db-volume\""),
"missing volume(db-volume): {out}"
);
assert!(
out.contains("volume(\"scratch\""),
"missing volume(scratch): {out}"
);
assert!(out.contains("volumeMounts"), "missing attachment: {out}");
}

#[test]
fn runner_response_surfaces_sdk_version() {
let with_version: runner::RunnerResponse = serde_json::from_value(json!({
"ok": true,
"command": "current",
"file": "railway.ts",
"sdkVersion": "3.5.3",
"diagnostics": []
}))
.expect("valid response");
assert_eq!(with_version.sdk_version.as_deref(), Some("3.5.3"));

let without_version: runner::RunnerResponse = serde_json::from_value(json!({
"ok": true,
"command": "current",
"file": "railway.ts",
"diagnostics": []
}))
.expect("valid response");
assert!(without_version.sdk_version.is_none());
}
}
24 changes: 24 additions & 0 deletions src/commands/config/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,9 @@ pub(super) struct RunnerResponse {
pub(super) ok: bool,
command: String,
file: String,
/// Absent when the installed railway SDK predates the version handshake.
#[serde(default)]
pub(super) sdk_version: Option<String>,
current_environment: Option<CurrentEnvironment>,
pub(super) change_set: Option<ChangeSet>,
diff: Option<String>,
Expand Down Expand Up @@ -441,9 +444,30 @@ async fn invoke_runner(
format!("IaC runner returned non-JSON output.\nstdout:\n{stdout}\nstderr:\n{stderr}")
})?;

warn_outdated_runner(&response);

Ok(response)
}

/// Responses without sdkVersion come from SDKs that predate the version
/// handshake. Warn once per invocation, on stderr so --json output stays clean.
fn warn_outdated_runner(response: &RunnerResponse) {
use colored::Colorize;

static WARN_ONCE: std::sync::Once = std::sync::Once::new();
if response.sdk_version.is_some() {
return;
}
WARN_ONCE.call_once(|| {
eprintln!(
"{} You are running an outdated version of the {} SDK, which may result in undesired behavior. Upgrade it with {}.",
"Warning:".yellow().bold(),
"railway".cyan(),
"npm install railway@latest".cyan(),
);
});
}

struct ResolvedRunner {
path: String,
source: RunnerSource,
Expand Down
Loading