diff --git a/src/config.rs b/src/config.rs index d5621a7..06cca40 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use std::{ path::PathBuf, }; -use anyhow::{Context, Result, anyhow}; +use anyhow::{Context, Result, anyhow, bail}; use colored::Colorize; use inquire::ui::{Attributes, RenderConfig, StyleSheet, Styled}; use serde::{Deserialize, Serialize}; @@ -123,6 +123,24 @@ impl Configs { std::env::var(consts::RAILWAY_API_TOKEN_ENV).ok() } + pub fn get_railway_project_id() -> Option { + std::env::var(consts::RAILWAY_PROJECT_ID_ENV).ok() + } + + pub fn get_railway_environment_id() -> Option { + std::env::var(consts::RAILWAY_ENVIRONMENT_ID_ENV).ok() + } + + pub fn get_railway_service_id() -> Option { + std::env::var(consts::RAILWAY_SERVICE_ID_ENV).ok() + } + + /// Returns true if either RAILWAY_PROJECT_ID or RAILWAY_ENVIRONMENT_ID env vars are set, + /// indicating the user intends to use env-var-based project targeting. + pub fn has_env_var_project_config() -> bool { + Self::get_railway_project_id().is_some() || Self::get_railway_environment_id().is_some() + } + /// Returns true if using token-based auth (RAILWAY_TOKEN or RAILWAY_API_TOKEN) /// rather than session-based auth from `railway login`. /// Token-based auth bypasses 2FA on the backend, so client-side 2FA checks are unnecessary. @@ -227,7 +245,7 @@ impl Configs { } pub fn get_closest_linked_project_directory(&self) -> Result { - if Self::get_railway_token().is_some() { + if Self::has_env_var_project_config() || Self::get_railway_token().is_some() { return self.get_current_directory(); } @@ -291,6 +309,41 @@ impl Configs { return Ok(project); } + let has_project_id = Self::get_railway_project_id().is_some(); + let has_environment_id = Self::get_railway_environment_id().is_some(); + + if has_project_id != has_environment_id { + bail!( + "Both RAILWAY_PROJECT_ID and RAILWAY_ENVIRONMENT_ID must be set together. {} is missing.", + if has_project_id { + "RAILWAY_ENVIRONMENT_ID" + } else { + "RAILWAY_PROJECT_ID" + } + ); + } + + if let (Some(project_id), Some(environment_id)) = ( + Self::get_railway_project_id(), + Self::get_railway_environment_id(), + ) { + if self.get_railway_auth_token().is_none() { + bail!(RailwayError::Unauthorized); + } + + let service_id = + Self::get_railway_service_id().or_else(|| project.cloned().and_then(|p| p.service)); + + return Ok(LinkedProject { + project_path: self.get_current_directory()?, + name: None, + project: project_id, + environment: environment_id, + environment_name: None, + service: service_id, + }); + } + project .cloned() .ok_or_else(|| RailwayError::NoLinkedProject.into()) diff --git a/src/consts.rs b/src/consts.rs index d2ac998..541c2e6 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -4,6 +4,9 @@ pub const fn get_user_agent() -> &'static str { pub const RAILWAY_TOKEN_ENV: &str = "RAILWAY_TOKEN"; pub const RAILWAY_API_TOKEN_ENV: &str = "RAILWAY_API_TOKEN"; +pub const RAILWAY_PROJECT_ID_ENV: &str = "RAILWAY_PROJECT_ID"; +pub const RAILWAY_ENVIRONMENT_ID_ENV: &str = "RAILWAY_ENVIRONMENT_ID"; +pub const RAILWAY_SERVICE_ID_ENV: &str = "RAILWAY_SERVICE_ID"; pub const TICK_STRING: &str = "⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏ "; pub const NON_INTERACTIVE_FAILURE: &str = "This command is only available in interactive mode"; diff --git a/src/controllers/project.rs b/src/controllers/project.rs index 5c9ec0e..b440a7b 100644 --- a/src/controllers/project.rs +++ b/src/controllers/project.rs @@ -77,7 +77,7 @@ pub async fn ensure_project_and_environment_exist( linked_project .environment_name .clone() - .unwrap_or("Production".to_string()), + .unwrap_or_else(|| linked_project.environment.clone()), ); match environment {