railwayapp 4.42.0

Interact with Railway via CLI
use anyhow::Context;

use crate::client::{GQLClient, post_graphql};
use crate::config::Configs;
use crate::gql::mutations::{self, cli_event_track};

pub struct CliTrackEvent {
    pub command: String,
    pub sub_command: Option<String>,
    pub duration_ms: u64,
    pub success: bool,
    pub error_message: Option<String>,
    pub os: &'static str,
    pub arch: &'static str,
    pub cli_version: &'static str,
    pub is_ci: bool,
}

fn env_var_is_truthy(name: &str) -> bool {
    std::env::var(name)
        .map(|v| v == "1" || v.eq_ignore_ascii_case("true"))
        .unwrap_or(false)
}

#[derive(serde::Serialize, serde::Deserialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct Preferences {
    #[serde(default)]
    pub telemetry_disabled: bool,
    #[serde(default)]
    pub auto_update_disabled: bool,
}

impl Preferences {
    fn path() -> Option<std::path::PathBuf> {
        dirs::home_dir().map(|h| h.join(".railway/preferences.json"))
    }

    pub fn read() -> Self {
        Self::path()
            .and_then(|p| std::fs::read_to_string(p).ok())
            .and_then(|s| serde_json::from_str(&s).ok())
            .unwrap_or_default()
    }

    pub fn write(&self) -> anyhow::Result<()> {
        let path = Self::path().context("Failed to determine home directory")?;
        let contents = serde_json::to_string(self)?;
        crate::util::write_atomic(&path, &contents)
    }
}

pub fn is_telemetry_disabled_by_env() -> bool {
    env_var_is_truthy("DO_NOT_TRACK") || env_var_is_truthy("RAILWAY_NO_TELEMETRY")
}

pub fn is_auto_update_disabled_by_env() -> bool {
    env_var_is_truthy("RAILWAY_NO_AUTO_UPDATE")
}

pub fn is_auto_update_disabled() -> bool {
    is_auto_update_disabled_by_env()
        || Preferences::read().auto_update_disabled
        || crate::config::Configs::env_is_ci()
}

fn is_telemetry_disabled() -> bool {
    is_telemetry_disabled_by_env() || Preferences::read().telemetry_disabled
}

pub async fn send(event: CliTrackEvent) {
    if is_telemetry_disabled() {
        return;
    }

    let configs = match Configs::new() {
        Ok(c) => c,
        Err(_) => return,
    };

    let client = match GQLClient::new_authorized(&configs) {
        Ok(c) => c,
        Err(_) => return,
    };

    let vars = cli_event_track::Variables {
        input: cli_event_track::CliEventTrackInput {
            command: event.command,
            sub_command: event.sub_command,
            duration_ms: event.duration_ms as i64,
            success: event.success,
            error_message: event.error_message,
            os: event.os.to_string(),
            arch: event.arch.to_string(),
            cli_version: event.cli_version.to_string(),
            is_ci: event.is_ci,
        },
    };

    let _ = tokio::time::timeout(
        std::time::Duration::from_secs(3),
        post_graphql::<mutations::CliEventTrack, _>(&client, configs.get_backboard(), vars),
    )
    .await;
}