Skip to content

Shopify/shadowenv

Folders and files

NameName
Last commit message
Last commit date

Latest commit

5e07f9e · May 24, 2025
May 23, 2025
Oct 28, 2024
Oct 28, 2024
Apr 24, 2025
May 24, 2025
Oct 11, 2022
Jun 11, 2020
Oct 25, 2021
May 24, 2025
May 24, 2025
Mar 15, 2019
Jul 13, 2020
Nov 19, 2024
Oct 25, 2021

Repository files navigation

Shadowenv

Shadowenv provides a way to perform a set of manipulations to the process environment upon entering a directory in a shell. These manipulations are reversed when leaving the directory, and there is some limited ability to make the manipulations dynamic.

shadowenv in action

In order to use shadowenv, add a line to your shell profile (.zshrc, .bash_profile, or config.fish) reading:

eval "$(shadowenv init bash)" # for bash
eval "$(shadowenv init zsh)"  # for zsh
shadowenv init fish | source  # for fish

With this code loaded, upon entering a directory containing a .shadowenv.d directory, any *.lisp files in that directory will be executed and you will see "activated shadowenv." in your shell.

The syntax for the .shadowenv.d/*.lisp files is Shadowlisp, a minimal Scheme-like language. Unlike other tools like direnv, this has the interesting property of allowing us to do things like simulate chruby reset upon entry into a directory without the user having chruby installed (and undo these changes to the environment when cd'ing back out):

(provide "ruby")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
  (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
  (when-let ((gem-root (env/get "GEM_ROOT")))
    (env/remove-from-pathlist "PATH" (path-concat gem-root "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-root))
  (when-let ((gem-home (env/get "GEM_HOME")))
    (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))
    (env/remove-from-pathlist "GEM_PATH" gem-home)))

The intention isn't really for users to write these files directly, nor to commit them to repositories , but for other tool authors to generate configuration on the user's machine. Here's an example of a generated Shadowlisp file for activating ruby 2.7.1:

(provide "ruby" "2.7.1")

(when-let ((ruby-root (env/get "RUBY_ROOT")))
 (env/remove-from-pathlist "PATH" (path-concat ruby-root "bin"))
 (when-let ((gem-root (env/get "GEM_ROOT")))
   (env/remove-from-pathlist "PATH" (path-concat gem-root "bin")))
 (when-let ((gem-home (env/get "GEM_HOME")))
   (env/remove-from-pathlist "PATH" (path-concat gem-home "bin"))))

(env/set "GEM_PATH" ())
(env/set "GEM_HOME" ())
(env/set "RUBYOPT" ())

(env/set "RUBY_ROOT" "/opt/rubies/2.7.1")
(env/prepend-to-pathlist "PATH" "/opt/rubies/2.7.1/bin")
(env/set "RUBY_ENGINE" "ruby")
(env/set "RUBY_VERSION" "2.7.1")
(env/set "GEM_ROOT" "/opt/rubies/2.7.1/lib/ruby/gems/2.7.0")

(when-let ((gem-root (env/get "GEM_ROOT")))
  (env/prepend-to-pathlist "GEM_PATH" gem-root)
  (env/prepend-to-pathlist "PATH" (path-concat gem-root "bin")))

(let ((gem-home
      (path-concat (env/get "HOME") ".gem" (env/get "RUBY_ENGINE") (env/get "RUBY_VERSION"))))
  (do
    (env/set "GEM_HOME" gem-home)
    (env/prepend-to-pathlist "GEM_PATH" gem-home)
    (env/prepend-to-pathlist "PATH" (path-concat gem-home "bin"))))

.shadowenv.d

The .shadowenv.d directory will generally exist at the root of your repository (in the same directory as .git.

We strongly recommend creating gitignore'ing everything under .shadowenv.d (echo '*' > .shadowenv.d/.gitignore).

A .shadowenv.d will contain any number of *.lisp files. These are evaluated in the order in which the OS returns when reading the directory: generally alphabetically. We strongly recommend using a prefix like 090_something.lisp to make it easy to maintain ordering.

.shadowenv.d will also contain a .trust-<fingerprint> file if it has been marked as trusted. (see the trust section).

Language

See https://shopify.github.io/shadowenv/ for Shadowlisp documentation.

Integrations

Shadowenv has plugins for multiple editors and/or IDEs:

Trust

If you cd into a directory containing .shadowenv.d/*.lisp files, they will not be run and you will see a message indicating so. This is for security reasons: we don't want to enable random stuff downloaded from the internet to modify your PATH, for example.

You can run shadowenv trust to mark a directory as trusted.

Technically, running shadowenv trust will create a file at .shadowenv.d/.trust-<fingerprint>, indicating that it's okay for shadowenv to run this code. The .shadowenv.d/.trust-* file contains a cryptographic signature of the directory path. The key is generated the first time shadowenv is run, and the fingerprint is an identifier for the key.

About

reversible directory-local environment variable manipulations

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

No packages published