From 439d39eda76f4e8fa5b38bc1ae395c1fe72d6ec2 Mon Sep 17 00:00:00 2001 From: jzeuzs <75403863+jzeuzs@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:03:59 +0800 Subject: [PATCH 1/4] Add option to set from stdin using `railway variables` --- src/commands/variables.rs | 42 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 4051790d2..7ee6e65b3 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -10,7 +10,11 @@ use crate::{ table::Table, }; use anyhow::bail; -use std::{collections::BTreeMap, time::Duration}; +use std::{ + collections::BTreeMap, + io::{stdin, BufRead}, + time::Duration, +}; /// Show variables for active environment #[derive(Parser)] @@ -34,6 +38,28 @@ pub struct Args { #[clap(long)] set: Vec, + /// Read environment variable pairs from stdin. + /// + /// Each line should contain exactly one "{KEY}={VALUE}"" pair. + /// Leading and trailing whitespace is trimmed. Empty lines are ignored. + /// If combined with --set, values from both sources are applied. + /// + /// Examples: + /// + /// # Read a single variable from stdin + /// + /// echo "FOO=bar" | railway variables --set-from-stdin + /// + /// # Read multiple variables, one per line + /// + /// printf "FOO=bar\nBAZ=qux\n" | railway variables --set-from-stdin + /// + /// # Load variables from a .env file + /// + /// cat .env | railway variables --set-from-stdin + #[clap(long)] + set_from_stdin: bool, + /// Output in JSON format #[clap(long)] json: bool, @@ -43,7 +69,7 @@ pub struct Args { skip_deploys: bool, } -pub async fn command(args: Args) -> Result<()> { +pub async fn command(mut args: Args) -> Result<()> { let configs = Configs::new()?; let client = GQLClient::new_authorized(&configs)?; let linked_project = configs.get_linked_project().await?; @@ -74,6 +100,18 @@ pub async fn command(args: Args) -> Result<()> { _ => bail!(RailwayError::NoServiceLinked), }; + if args.set_from_stdin { + let stdin = stdin(); + + for line in stdin.lock().lines() { + let line = line?; + + if !line.trim().is_empty() { + args.set.push(line); + } + } + } + if !args.set.is_empty() { set_variables( args.set, From 6330676f1a207515b33641795a7729ade0f62d51 Mon Sep 17 00:00:00 2001 From: jzeuzs <75403863+jzeuzs@users.noreply.github.com> Date: Sat, 27 Sep 2025 23:18:33 +0800 Subject: [PATCH 2/4] docs --- src/commands/variables.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 7ee6e65b3..bbd73c193 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -40,24 +40,23 @@ pub struct Args { /// Read environment variable pairs from stdin. /// - /// Each line should contain exactly one "{KEY}={VALUE}"" pair. - /// Leading and trailing whitespace is trimmed. Empty lines are ignored. - /// If combined with --set, values from both sources are applied. + /// Each line should contain exactly one "{KEY}={VALUE}"" pair. Leading and trailing whitespace is trimmed. + /// Empty lines are ignored. If combined with --set, values from both sources are applied. /// /// Examples: /// - /// # Read a single variable from stdin + /// # Read a single variable from stdin /// - /// echo "FOO=bar" | railway variables --set-from-stdin + /// echo "FOO=bar" | railway variables --set-from-stdin /// - /// # Read multiple variables, one per line + /// # Read multiple variables, one per line /// - /// printf "FOO=bar\nBAZ=qux\n" | railway variables --set-from-stdin + /// printf "FOO=bar\nBAZ=qux\n" | railway variables --set-from-stdin /// - /// # Load variables from a .env file + /// # Load variables from a .env file /// - /// cat .env | railway variables --set-from-stdin - #[clap(long)] + /// cat .env | railway variables --set-from-stdin + #[clap(long, verbatim_doc_comment)] set_from_stdin: bool, /// Output in JSON format From 350a64134275d17fb9580bb5a75e4a024587c04f Mon Sep 17 00:00:00 2001 From: jzeuzs <75403863+jzeuzs@users.noreply.github.com> Date: Sun, 28 Sep 2025 19:31:14 +0800 Subject: [PATCH 3/4] cargo fmt --- src/commands/variables.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/variables.rs b/src/commands/variables.rs index bbd73c193..7f5525863 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -40,7 +40,7 @@ pub struct Args { /// Read environment variable pairs from stdin. /// - /// Each line should contain exactly one "{KEY}={VALUE}"" pair. Leading and trailing whitespace is trimmed. + /// Each line should contain exactly one "{KEY}={VALUE}"" pair. Leading and trailing whitespace is trimmed. /// Empty lines are ignored. If combined with --set, values from both sources are applied. /// /// Examples: From 59078d6d8df045424b1bc7e13351580e2159dbb1 Mon Sep 17 00:00:00 2001 From: jzeuzs <75403863+jzeuzs@users.noreply.github.com> Date: Sat, 8 Nov 2025 18:21:44 +0800 Subject: [PATCH 4/4] Error if stdin has no input --- src/commands/variables.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/commands/variables.rs b/src/commands/variables.rs index 7f5525863..0e906d15a 100644 --- a/src/commands/variables.rs +++ b/src/commands/variables.rs @@ -12,7 +12,7 @@ use crate::{ use anyhow::bail; use std::{ collections::BTreeMap, - io::{stdin, BufRead}, + io::{stdin, BufRead, IsTerminal}, time::Duration, }; @@ -102,11 +102,15 @@ pub async fn command(mut args: Args) -> Result<()> { if args.set_from_stdin { let stdin = stdin(); + if stdin.is_terminal() { + bail!("--set-from-stdin requires input from stdin (e.g., via pipe or redirect)"); + } + for line in stdin.lock().lines() { let line = line?; if !line.trim().is_empty() { - args.set.push(line); + args.set.push(line.trim().to_string()); } } }