Skip to content

Conversation

@jbowers85
Copy link

Added functionality to recursively search parent directories for an .env file. Users can opt-in to this by setting recursive = TRUE. If recursive = TRUE, then load_dot_env will search the directory, and if no .env file is found, parent directories are searched until one is found.

found_file <- NA # Used to store the path of the found .env file

# Loop until the file is found or we cannot go further up in the directory tree
while (is.na(found_file) && !identical(path, normalizePath(dirname(path)))) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possibly to get into an infinite loop here with symbolic links? Should we have some limit (100?) for the number of directories to try?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, we can account for symbolic links by keeping track of the visited directories. Here is some updated logic. What are your thoughts?

load_dot_env <- function(file = ".env", recursive = FALSE) {
  # Normalize the initial file path for consistent path handling
  original_path <- normalizePath(file, mustWork = FALSE)
  
  # Initialize the visited directories tracker and the found file path
  visited <- character(0)
  found_file <- character(0)
  
  if (!file.exists(original_path)) {
    if (recursive) {
      # Start with the directory containing the original path
      path <- dirname(original_path)
      
      # Loop until we find the file or reach the root directory
      while (!identical(path, normalizePath(dirname(path)))) {
        # Normalize the current directory path for accurate detection
        normalized_path <- normalizePath(path, mustWork = FALSE)
        
        # Break the loop if a cycle is detected
        if (normalized_path %in% visited) break
        
        # Track the visited directory to prevent cycles
        visited <- c(visited, normalized_path)
        
        # Construct the expected .env file path for the current directory
        test_path <- file.path(normalized_path, basename(original_path))
        
        # If the .env file is found, update `found_file` and exit the loop
        if (file.exists(test_path)) {
          found_file <- test_path
          break
        }
        
        # Move up one directory level
        path <- dirname(normalized_path)
      }
      
      # If no file was found in the entire search, raise an error
      if (length(found_file) == 0) {
        stop(sprintf("No .env file found from '%s' up to the root directory", original_path), call. = TRUE)
      } else {
        file <- found_file
      }
    } else {
      # Raise an error if recursive is FALSE and the .env file does not exist in the specified location
      stop(sprintf(".env file does not exist in the specified path: '%s'", original_path), call. = TRUE)
    }
  }
  
  # Read and process the .env file
  tmp <- readLines(file)
  tmp <- ignore_comments(tmp)
  tmp <- ignore_empty_lines(tmp)
  
  # If no environment variables are found, return invisibly
  if (length(tmp) == 0) return(invisible())
  
  # Parse and set environment variables from the .env file
  tmp <- lapply(tmp, parse_dot_line)
  set_env(tmp)
}

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, that looks good to me.

@jbowers85
Copy link
Author

Friendly ping @gaborcsardi

@cswingle
Copy link

Add my +1 to this merge

@skr5k
Copy link

skr5k commented Aug 21, 2025

+1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants