Skip to content

Latest commit

 

History

History
208 lines (147 loc) · 6.9 KB

File metadata and controls

208 lines (147 loc) · 6.9 KB

Architecture

This document covers two core parts of the runx architecture:

  • how command proxies are implemented and executed across platforms
  • how env files are discovered and merged when one or more --envfile options are used

Command Proxy Execution Architecture

runx implements command proxies differently depending on the platform. On Linux/macOS, proxies use shell functions for transparency and simplicity. On Windows, proxies use .cmd files to integrate with PATH, with support for both user-level and machine-level deployment.

Linux/macOS: Shell Functions

On Linux/macOS, proxies are implemented as shell functions in config files (~/.bashrc, ~/.zshrc, or ~/.config/fish/config.fish):

# generated by runx add terraform --alias=mytf
mytf() {
  local RUNX_PROXY_ACTIVE=1
  '/path/to/runx' exec --envfile='.env' 'terraform' "$@"
}

The RUNX_PROXY_ACTIVE flag tells runx exec that we're being called from a proxy function, so it uses command -v to bypass the function and find the real command.

Windows: .cmd Files

On Windows, proxies are implemented as wrapper .cmd files in one of two proxy types—User Proxy or Machine Proxy—based on where the original command exists:

User Proxy

Located in %LOCALAPPDATA%\runx\proxy\, no admin privileges required:

@echo off
REM generated by runx add (user proxy)
setlocal
set "RUNX_PROXY_DIR=%~dp0"
if defined RUNX_PROXY_DIRS (set "RUNX_PROXY_DIRS=%RUNX_PROXY_DIR%;%RUNX_PROXY_DIRS%") else (set "RUNX_PROXY_DIRS=%RUNX_PROXY_DIR%")

if exist "%~dp0\\..\\runx.exe" (
  "%~dp0\\..\\runx.exe" exec --envfile=.env terraform %*
  exit /b %ERRORLEVEL%
)

REM Fallback to original command if runx.exe not found
where /Q terraform 2>nul
if %ERRORLEVEL% EQU 0 (
  terraform %*
  exit /b %ERRORLEVEL%
)

echo Error: Neither runx.exe nor original 'terraform' command found. 1>&2
exit /b 9009

User proxies are added to the User PATH. They work when the original command is in User PATH or not found in Machine PATH.

Machine Proxy

Located in C:\ProgramData\runx\proxy\, requires administrator privileges.

Machine proxies are added to the Machine PATH (system-wide) and take precedence over User PATH entries. Use this when the original command is in System32 or other Machine PATH locations.

Choosing Between User and Machine Proxy

Windows has two PATH types with different precedence:

  • Machine PATH: System-wide, requires admin privileges, higher priority
  • User PATH: Per-user, no admin needed, lower priority
When to Use Each
Scenario Proxy Type Reason
Original in System32 Machine Proxy Required: User Proxy cannot override Machine PATH
Original in User PATH User Proxy Same priority level works
Command not found User Proxy Easier, no admin needed
Automatic Detection

runx add automatically detects where the original command exists and recommends the appropriate proxy type. If the original command is found in Machine PATH, runx will prompt to create a Machine Proxy:

┌────────────────────────────────────────────────────────────────┐
│ Machine PATH Detected                                          │
└────────────────────────────────────────────────────────────────┘

The original command is in Machine PATH:
  Command: git
  Location: C:\Program Files\Git\cmd\git.exe

Windows prioritizes Machine PATH over User PATH.
Creating a User proxy will not work - the Machine PATH entry will
always take precedence.

Recommendation: Create a Machine Proxy instead.
  • Requires administrator privileges
  • Will be placed in: C:\ProgramData\runx\proxy
  • Will be added to Machine PATH (system-wide)

Create Machine proxy now? (y/N):

Proxy Directory Exclusion

To prevent infinite recursion, runx exec excludes proxy directories when resolving commands:

  • Uses RUNX_PROXY_DIR and RUNX_PROXY_DIRS environment variables to track all proxy directories
  • Searches PATH while skipping these directories
  • Finds the real command executable

Env File Resolution and Merge

When multiple --envfile options are provided (for example in runx add or runx exec), runx processes them from left to right in the order they were specified.

Per-file Resolution

Each env file value is resolved independently:

  1. If the value is an absolute path, only that exact path is checked.
  2. If the value is a filename, lookup is performed at command execution time in this order:
  • current directory
  • each parent directory up to filesystem root
  • home directory

Example (--envfile=.env):

/
├── workspace/
│   └── work/
│       ├── .env                       # checked on parent walk
│       └── project/
│           ├── .env                   # checked on parent walk
│           └── service/               # current working directory
│               └── (no .env)
└── home/
    └── alice/
        └── .env                       # checked last (home fallback)

If the current working directory is /workspace/work/project/service, runx checks in this order:

  1. /workspace/work/project/service/.env
  2. /workspace/work/project/.env
  3. /workspace/work/.env
  4. /workspace/.env
  5. /home/alice/.env

In this tree, /workspace/work/project/.env is the first existing match, so that file is adopted for this envfile entry.

If a specified env file is not found, runx continues with the next specified env file. If a file is found but has invalid syntax, execution fails with an error.

Merge Semantics

Resolved env files are merged in the same left-to-right order as the CLI arguments.

  • First file provides initial values.
  • Later files override keys from earlier files.
  • Within one file, if the same key appears multiple times, the last occurrence in that file is used.

This is a standard "later wins" merge model.

Example:

runx add az --envfile=.base.env --envfile=.team.env --envfile=.local.env

For overlapping keys, precedence is:

  1. .base.env
  2. .team.env
  3. .local.env (highest)

Example file contents:

# .base.env
AZURE_CONFIG_DIR=~/.azure_default
AZURE_CORE_OUTPUT=table
HTTP_PROXY=http://proxy.base:8080
# .team.env
AZURE_CORE_OUTPUT=json
HTTP_PROXY=http://proxy.team:8080
TEAM_NAME=platform
# .local.env
AZURE_CONFIG_DIR=~/.azure_personal
TEAM_NAME=platform-dev

Final merged result at execution time:

AZURE_CONFIG_DIR=~/.azure_personal
AZURE_CORE_OUTPUT=json
HTTP_PROXY=http://proxy.team:8080
TEAM_NAME=platform-dev

In this result, values from later files override earlier ones, while keys that appear only once are kept as-is.