TL;DR: This guide shows you how to automate the setup of .env.example
and .json.example
files in a monorepo using a Python script. The script copies template files, prompts for user input to fill in placeholders, and skips files that are already set up.
Thank me by sharing on Twitter 🙏
Introduction
Managing environment variables is crucial for any development project. These variables allow us to configure our applications for different environments without changing the code. However, setting up these variables manually for multiple projects in a monorepo can be tedious. In this post, I’ll share a solution I developed to automate this process using Python. We’ll create a script that copies template files, prompts for user input to fill in placeholder values, and skips files that are already set up.
Why Automate Environment Setup?
In a monorepo, where multiple projects share the same repository, keeping track of environment variables can become complicated. Each project might have its own set of configuration files, often with placeholders for sensitive information like database passwords or API keys. Manually updating these files can lead to inconsistencies and potential security risks if not handled properly. Automating this process ensures that all required variables are set up correctly and uniformly across all projects.
Setting Up the Python Script
Let’s break down the process into manageable steps. We’ll cover:
- Defining the root folders and file types to search for.
- Prompting the user for values to replace placeholders in the configuration files.
- Copying template files and replacing placeholders with the user-provided values.
- Skipping files that are already set up to avoid unnecessary prompts and overwrites.
1. Defining Root Folders and File Types
First, we need to specify the root folders where our configuration files are located and the types of files we want to process. In my case, these were .env.example
and .json.example
files. Here’s how I defined them in Python:
HP 67XL Black High-yield Ink Cartridge | Works with HP DeskJet 1255, 2700, 4100 Series, HP ENVY 6000, 6400 Series | Eligible for Instant Ink | One Size | 3YM57AN
$29.89 (as of January 9, 2025 10:16 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)The Coming Wave: Technology, Power, and the Twenty-First Century's Greatest Dilemma
$17.72 (as of January 11, 2025 10:31 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)HP 910 Cyan, Magenta, Yellow Ink Cartridges | Works with HP OfficeJet 8010, 8020 Series, HP OfficeJet Pro 8020, 8030 Series | Eligible for Instant Ink | 3YN97AN, 3 Count (Pack of 1)
$39.89 (as of January 9, 2025 10:16 GMT +00:00 - More infoProduct prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on [relevant Amazon Site(s), as applicable] at the time of purchase will apply to the purchase of this product.)import os
import re
import shutil
ROOT_FOLDERS = ['project1', 'project2', 'project3']
FILE_SUFFIXES = ['.env.example', '.json.example']
2. Prompting for Values
Next, we need a way to prompt the user for values to replace placeholders in our configuration files. To ensure consistency, we’ll store these values in a dictionary and reuse them when the same placeholder appears in different files.
# Dictionary to store values for placeholders
values = {}
def prompt_for_value(value_name):
if value_name in values:
return values[value_name]
value = input(f"Please enter the value for {value_name}: ")
values[value_name] = value
return value
3. Copying and Processing Files
With our folders and file types defined, and a function to prompt for values, we can now copy the template files to their respective locations and replace placeholders. We’ll also handle file skipping to avoid overwriting existing files.
def process_file(file_path):
with open(file_path, 'r') as file:
content = file.read()
placeholders = re.findall(r'\${([^}]+)}', content)
unique_placeholders = set(placeholders)
for placeholder in unique_placeholders:
value = prompt_for_value(placeholder)
content = content.replace(f"${{{placeholder}}}", value)
with open(file_path, 'w') as file:
file.write(content)
def setup_environment():
for folder in ROOT_FOLDERS:
if not os.path.isdir(folder):
print(f"Folder {folder} does not exist.")
continue
matching_files_found = False
for file in os.listdir(folder):
if any(file.endswith(suffix) for suffix in FILE_SUFFIXES):
matching_files_found = True
example_path = os.path.join(folder, file)
output_path = os.path.join(folder, file.replace('.example', ''))
if os.path.exists(output_path):
print(f"Skipping {output_path} as it already exists.")
continue
shutil.copy(example_path, output_path)
print(f"Processing {output_path}")
process_file(output_path)
print(f"{output_path} file created with entered values.")
if not matching_files_found:
print(f"No matching example files found in {folder}")
if __name__ == '__main__':
setup_environment()
4. Running the Script
To run the setup, navigate to the root directory of your monorepo and execute the following command:
python setup_env.py
This script will copy the .env.example
and .json.example
files to their respective non-example versions in each specified root folder, find all placeholders, prompt the user to enter the values for each placeholder (reusing values when appropriate), and replace the placeholders with the entered values. It will skip any output files that already exist and print a message indicating that they were skipped.
Conclusion
Automating the setup of environment variables in a monorepo can significantly streamline your development process. By creating a Python script to handle this task, you ensure consistency and reduce the potential for errors. The script I’ve shared can be easily adapted to suit the specific needs of your projects, making it a versatile tool in your development toolkit.
Implementing this automation has saved me a lot of time and hassle, and I hope it does the same for you. Happy developing!