From d25b42052a8a2a5a4680594c74a2a737098b412e Mon Sep 17 00:00:00 2001 From: Sheng Kun Chang Date: Tue, 5 May 2026 08:49:16 +0800 Subject: [PATCH] chore: walk up from cwd to find .env MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Devs run \`mirrorstack\` from many places inside the workspace — the meta-repo root, a module subdirectory, a scaffold target. The previous \`dotenvy::dotenv()\` only checked cwd, so workspace-level .env files at \`MirrorStack-AI-V2/.env\` or \`mirrorstack-cli/.env\` were silently missed when invoked from sibling dirs. This walks from cwd upward to the filesystem root and loads the first .env it finds. Process env vars still take precedence (dotenvy's default is non-overriding), so per-invocation \`MIRRORSTACK_API_URL=...\` still works. Silently ignored when no .env exists anywhere on the path (the common case for installed users). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/main.rs | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 17b9e30..4cc3f2d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ //! Entry point. Argument parsing and top-level command dispatch live in //! `commands::Cli`. +use std::path::{Path, PathBuf}; use std::process::ExitCode; use clap::Parser; @@ -13,10 +14,7 @@ mod credentials; mod http; fn main() -> ExitCode { - // Load .env from CWD if present. Process env vars still take precedence - // over .env, so users can override per-invocation. Silently ignored - // when no .env exists (the common case for installed users). - let _ = dotenvy::dotenv(); + load_dotenv(); let cli = commands::Cli::parse(); match cli.run() { @@ -27,3 +25,34 @@ fn main() -> ExitCode { } } } + +/// Walk from cwd up to the filesystem root looking for the first `.env` and +/// load it. Process env vars still win over `.env` because dotenvy's default +/// is non-overriding. Silently ignored when no `.env` exists anywhere on the +/// path (the common case for installed users). +/// +/// Why upward search and not just `dotenvy::dotenv()`: developers run +/// `mirrorstack` from many places inside their workspace — meta-repo root, +/// a module subdirectory, a scaffold target. The cwd-only behavior surprised +/// users by silently missing the workspace `.env` that lived one or two +/// dirs up. +fn load_dotenv() { + if let Some(path) = find_dotenv_upward() { + let _ = dotenvy::from_path(&path); + } +} + +fn find_dotenv_upward() -> Option { + let cwd = std::env::current_dir().ok()?; + let mut dir: &Path = cwd.as_path(); + loop { + let candidate = dir.join(".env"); + if candidate.is_file() { + return Some(candidate); + } + match dir.parent() { + Some(parent) => dir = parent, + None => return None, + } + } +}