├── .github └── workflows │ ├── build.yml │ └── release.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Changelog.md ├── LICENSE ├── Readme.md ├── examples ├── extend.json ├── extend2.json ├── nested │ └── .env.json └── simple │ └── .env.json ├── npm ├── getBinary.js ├── run.js └── scripts.js ├── package-lock.json ├── package.json ├── rust-toolchain └── src ├── main.rs ├── run_on_cd.bash ├── run_on_cd.fish └── run_on_cd.zsh /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: 3 | pull_request: 4 | push: 5 | schedule: 6 | - cron: '00 01 * * *' 7 | 8 | jobs: 9 | check: 10 | name: Check 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout sources 14 | uses: actions/checkout@v2 15 | 16 | - name: Install stable toolchain 17 | uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | 23 | - uses: Swatinem/rust-cache@v1 24 | 25 | - name: Run cargo check 26 | uses: actions-rs/cargo@v1 27 | with: 28 | command: check 29 | 30 | test: 31 | name: Test Suite 32 | strategy: 33 | matrix: 34 | os: [ubuntu-latest, macos-latest, windows-latest] 35 | rust: [stable] 36 | runs-on: ${{ matrix.os }} 37 | steps: 38 | - name: Checkout sources 39 | uses: actions/checkout@v2 40 | 41 | - name: Install stable toolchain 42 | uses: actions-rs/toolchain@v1 43 | with: 44 | profile: minimal 45 | toolchain: ${{ matrix.rust }} 46 | override: true 47 | 48 | - name: Install aarch64-apple-darwin target 49 | uses: actions-rs/toolchain@v1 50 | with: 51 | target: aarch64-apple-darwin 52 | if: ${{matrix.target == 'aarch64-apple-darwin'}} 53 | 54 | - uses: Swatinem/rust-cache@v1 55 | 56 | - name: Run cargo test 57 | uses: actions-rs/cargo@v1 58 | with: 59 | command: test 60 | if: ${{matrix.target != 'aarch64-apple-darwin'}} 61 | 62 | 63 | lints: 64 | name: Lints 65 | runs-on: ubuntu-latest 66 | steps: 67 | - name: Checkout sources 68 | uses: actions/checkout@v2 69 | with: 70 | submodules: true 71 | 72 | - name: Install stable toolchain 73 | uses: actions-rs/toolchain@v1 74 | with: 75 | profile: minimal 76 | toolchain: stable 77 | override: true 78 | components: rustfmt, clippy 79 | 80 | - uses: Swatinem/rust-cache@v1 81 | 82 | - name: Run cargo fmt 83 | uses: actions-rs/cargo@v1 84 | with: 85 | command: fmt 86 | args: --all -- --check 87 | 88 | - name: Run cargo clippy 89 | uses: actions-rs/cargo@v1 90 | with: 91 | command: clippy 92 | args: -- -D warnings 93 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | # schedule: 4 | # - cron: '0 0 * * *' # midnight UTC 5 | 6 | push: 7 | tags: 8 | - 'v[0-9]+.[0-9]+.[0-9]+' 9 | ## - release 10 | 11 | env: 12 | BIN_NAME: json_env 13 | PROJECT_NAME: json_env 14 | REPO_NAME: brodo/json_env 15 | BREW_TAP: brodo/homebrew-json_env 16 | 17 | jobs: 18 | dist: 19 | name: Dist 20 | runs-on: ${{ matrix.os }} 21 | strategy: 22 | fail-fast: false # don't fail other jobs if one fails 23 | matrix: 24 | build: [x86_64-linux, aarch64-linux, x86_64-macos, x86_64-windows] #, x86_64-win-gnu, win32-msvc 25 | include: 26 | - build: x86_64-linux 27 | os: ubuntu-20.04 28 | rust: stable 29 | target: x86_64-unknown-linux-gnu 30 | cross: false 31 | - build: aarch64-linux 32 | os: ubuntu-20.04 33 | rust: stable 34 | target: aarch64-unknown-linux-gnu 35 | cross: true 36 | - build: x86_64-macos 37 | os: macos-latest 38 | rust: stable 39 | target: x86_64-apple-darwin 40 | cross: false 41 | - build: x86_64-windows 42 | os: windows-2019 43 | rust: stable 44 | target: x86_64-pc-windows-msvc 45 | cross: false 46 | - build: aarch64-macos 47 | os: macos-latest 48 | rust: stable 49 | target: aarch64-apple-darwin 50 | cross: true 51 | # - build: x86_64-win-gnu 52 | # os: windows-2019 53 | # rust: stable-x86_64-gnu 54 | # target: x86_64-pc-windows-gnu 55 | # - build: win32-msvc 56 | # os: windows-2019 57 | # rust: stable 58 | # target: i686-pc-windows-msvc 59 | 60 | steps: 61 | - name: Checkout sources 62 | uses: actions/checkout@v2 63 | with: 64 | submodules: true 65 | 66 | - name: Install ${{ matrix.rust }} toolchain 67 | uses: actions-rs/toolchain@v1 68 | with: 69 | profile: minimal 70 | toolchain: ${{ matrix.rust }} 71 | target: ${{ matrix.target }} 72 | override: true 73 | 74 | - name: Run cargo test 75 | uses: actions-rs/cargo@v1 76 | with: 77 | use-cross: ${{ matrix.cross }} 78 | command: test 79 | args: --release --target ${{ matrix.target }} 80 | if: ${{matrix.target != 'aarch64-apple-darwin'}} 81 | 82 | - name: Build release binary 83 | uses: actions-rs/cargo@v1 84 | with: 85 | use-cross: ${{ matrix.cross }} 86 | command: build 87 | args: --release --target ${{ matrix.target }} 88 | 89 | - name: Strip release binary (linux and macos) 90 | if: matrix.build == 'x86_64-linux' || matrix.build == 'x86_64-macos' 91 | run: strip "target/${{ matrix.target }}/release/$BIN_NAME" 92 | 93 | - name: Strip release binary (arm) 94 | if: matrix.build == 'aarch64-linux' 95 | run: | 96 | docker run --rm -v \ 97 | "$PWD/target:/target:Z" \ 98 | rustembedded/cross:${{ matrix.target }} \ 99 | aarch64-linux-gnu-strip \ 100 | /target/${{ matrix.target }}/release/$BIN_NAME 101 | 102 | - name: Build archive 103 | shell: bash 104 | run: | 105 | mkdir dist 106 | if [ "${{ matrix.os }}" = "windows-2019" ]; then 107 | cp "target/${{ matrix.target }}/release/$BIN_NAME.exe" "dist/" 108 | else 109 | cp "target/${{ matrix.target }}/release/$BIN_NAME" "dist/" 110 | fi 111 | 112 | - uses: actions/upload-artifact@v2.2.4 113 | with: 114 | name: bins-${{ matrix.build }} 115 | path: dist 116 | 117 | publish: 118 | name: Publish 119 | needs: [dist] 120 | runs-on: ubuntu-latest 121 | steps: 122 | - name: Checkout sources 123 | uses: actions/checkout@v2 124 | with: 125 | submodules: false 126 | 127 | - uses: actions/download-artifact@v2 128 | # with: 129 | # path: dist 130 | # - run: ls -al ./dist 131 | - run: ls -al bins-* 132 | 133 | - name: Calculate tag name 134 | run: | 135 | name=dev 136 | if [[ $GITHUB_REF == refs/tags/v* ]]; then 137 | name=${GITHUB_REF:10} 138 | fi 139 | echo ::set-output name=val::$name 140 | echo TAG=$name >> $GITHUB_ENV 141 | id: tagname 142 | 143 | - name: Build archive 144 | shell: bash 145 | run: | 146 | set -ex 147 | 148 | rm -rf tmp 149 | mkdir tmp 150 | mkdir dist 151 | 152 | for dir in bins-* ; do 153 | platform=${dir#"bins-"} 154 | unset exe 155 | if [[ $platform =~ "windows" ]]; then 156 | exe=".exe" 157 | fi 158 | pkgname=$PROJECT_NAME-$TAG-$platform 159 | mkdir tmp/$pkgname 160 | # cp LICENSE README.md tmp/$pkgname 161 | mv bins-$platform/$BIN_NAME$exe tmp/$pkgname 162 | chmod +x tmp/$pkgname/$BIN_NAME$exe 163 | 164 | if [ "$exe" = "" ]; then 165 | tar czf dist/$pkgname.tar.gz -C tmp $pkgname 166 | else 167 | (cd tmp && 7z a -r ../dist/$pkgname.zip $pkgname) 168 | fi 169 | done 170 | 171 | - name: Upload binaries to release 172 | uses: svenstaro/upload-release-action@v2 173 | with: 174 | repo_token: ${{ secrets.GITHUB_TOKEN }} 175 | file: dist/* 176 | file_glob: true 177 | tag: ${{ steps.tagname.outputs.val }} 178 | overwrite: true 179 | 180 | - name: Extract version 181 | id: extract-version 182 | run: | 183 | printf "::set-output name=%s::%s\n" tag-name "${GITHUB_REF#refs/tags/}" 184 | 185 | - uses: mislav/bump-homebrew-formula-action@v1 186 | with: 187 | formula-path: Formula/${{env.PROJECT_NAME}}.rb 188 | homebrew-tap: ${{ env.BREW_TAP }} 189 | download-url: "https://github.com/${{ env.REPO_NAME }}/archive/refs/tags/${{ steps.extract-version.outputs.tag-name }}.zip" 190 | commit-message: updating formula for ${{ env.PROJECT_NAME }} 191 | env: 192 | COMMITTER_TOKEN: ${{ secrets.COMMITTER_TOKEN }} 193 | # 194 | # you can use this initial file in your homebrew-tap if you don't have an initial formula: 195 | # .rb 196 | # 197 | # class < Formula 198 | # desc "A test formula" 199 | # homepage "http://www.example.com" 200 | # url "-----" 201 | # version "-----" 202 | # sha256 "-----" 203 | 204 | # def install 205 | # bin.install "" 206 | # end 207 | # end 208 | 209 | 210 | # Uncomment this section if you want to release your package to crates.io 211 | # Before publishing, make sure you have filled out the following fields: 212 | # license or license-file, description, homepage, documentation, repository, readme. 213 | # Read more: https://doc.rust-lang.org/cargo/reference/publishing.html 214 | 215 | - name: Install stable toolchain 216 | uses: actions-rs/toolchain@v1 217 | with: 218 | profile: minimal 219 | toolchain: stable 220 | target: x86_64-unknown-linux-gnu 221 | override: true 222 | components: cargo 223 | - run: cargo publish --token ${CRATES_TOKEN} 224 | env: 225 | CRATES_TOKEN: ${{ secrets.CRATES_TOKEN }} 226 | 227 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | dist/ 4 | tmp/ 5 | node_modules 6 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "0.7.19" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.65" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "98161a4e3e2184da77bb14f02184cdd111e83bbbcc9979dfee3c44b9a85f5602" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "bitflags" 33 | version = "1.3.2" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 36 | 37 | [[package]] 38 | name = "block-buffer" 39 | version = "0.10.3" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 42 | dependencies = [ 43 | "generic-array", 44 | ] 45 | 46 | [[package]] 47 | name = "cfg-if" 48 | version = "1.0.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 51 | 52 | [[package]] 53 | name = "clap" 54 | version = "4.0.8" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "5840cd9093aabeabf7fd932754c435b7674520fc3ddc935c397837050f0f1e4b" 57 | dependencies = [ 58 | "atty", 59 | "bitflags", 60 | "clap_derive", 61 | "clap_lex", 62 | "once_cell", 63 | "strsim", 64 | "termcolor", 65 | ] 66 | 67 | [[package]] 68 | name = "clap_derive" 69 | version = "4.0.8" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "92289ffc6fb4a85d85c246ddb874c05a87a2e540fb6ad52f7ca07c8c1e1840b1" 72 | dependencies = [ 73 | "heck", 74 | "proc-macro-error", 75 | "proc-macro2", 76 | "quote", 77 | "syn", 78 | ] 79 | 80 | [[package]] 81 | name = "clap_lex" 82 | version = "0.3.0" 83 | source = "registry+https://github.com/rust-lang/crates.io-index" 84 | checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" 85 | dependencies = [ 86 | "os_str_bytes", 87 | ] 88 | 89 | [[package]] 90 | name = "console" 91 | version = "0.15.2" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "c050367d967ced717c04b65d8c619d863ef9292ce0c5760028655a2fb298718c" 94 | dependencies = [ 95 | "encode_unicode", 96 | "lazy_static", 97 | "libc", 98 | "terminal_size", 99 | "unicode-width", 100 | "winapi", 101 | ] 102 | 103 | [[package]] 104 | name = "cpufeatures" 105 | version = "0.2.5" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 108 | dependencies = [ 109 | "libc", 110 | ] 111 | 112 | [[package]] 113 | name = "crypto-common" 114 | version = "0.1.6" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 117 | dependencies = [ 118 | "generic-array", 119 | "typenum", 120 | ] 121 | 122 | [[package]] 123 | name = "dialoguer" 124 | version = "0.10.2" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "a92e7e37ecef6857fdc0c0c5d42fd5b0938e46590c2183cc92dd310a6d078eb1" 127 | dependencies = [ 128 | "console", 129 | "tempfile", 130 | "zeroize", 131 | ] 132 | 133 | [[package]] 134 | name = "digest" 135 | version = "0.10.5" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "adfbc57365a37acbd2ebf2b64d7e69bb766e2fea813521ed536f5d0520dcf86c" 138 | dependencies = [ 139 | "block-buffer", 140 | "crypto-common", 141 | ] 142 | 143 | [[package]] 144 | name = "dirs" 145 | version = "4.0.0" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" 148 | dependencies = [ 149 | "dirs-sys", 150 | ] 151 | 152 | [[package]] 153 | name = "dirs-sys" 154 | version = "0.3.7" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" 157 | dependencies = [ 158 | "libc", 159 | "redox_users", 160 | "winapi", 161 | ] 162 | 163 | [[package]] 164 | name = "encode_unicode" 165 | version = "0.3.6" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" 168 | 169 | [[package]] 170 | name = "fastrand" 171 | version = "1.8.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 174 | dependencies = [ 175 | "instant", 176 | ] 177 | 178 | [[package]] 179 | name = "generic-array" 180 | version = "0.14.6" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 183 | dependencies = [ 184 | "typenum", 185 | "version_check", 186 | ] 187 | 188 | [[package]] 189 | name = "getrandom" 190 | version = "0.2.8" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 193 | dependencies = [ 194 | "cfg-if", 195 | "libc", 196 | "wasi", 197 | ] 198 | 199 | [[package]] 200 | name = "heck" 201 | version = "0.4.0" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 204 | 205 | [[package]] 206 | name = "hermit-abi" 207 | version = "0.1.19" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 210 | dependencies = [ 211 | "libc", 212 | ] 213 | 214 | [[package]] 215 | name = "instant" 216 | version = "0.1.12" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 219 | dependencies = [ 220 | "cfg-if", 221 | ] 222 | 223 | [[package]] 224 | name = "itoa" 225 | version = "1.0.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "6c8af84674fe1f223a982c933a0ee1086ac4d4052aa0fb8060c12c6ad838e754" 228 | 229 | [[package]] 230 | name = "json_env" 231 | version = "1.3.0" 232 | dependencies = [ 233 | "anyhow", 234 | "clap", 235 | "dialoguer", 236 | "dirs", 237 | "jsonpath-rust", 238 | "serde", 239 | "serde_json", 240 | ] 241 | 242 | [[package]] 243 | name = "jsonpath-rust" 244 | version = "0.2.0" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "6a253c3700953a3f08b978b5a24fa2bb1670b1e6b1a85383a847fa4277148872" 247 | dependencies = [ 248 | "pest", 249 | "pest_derive", 250 | "regex", 251 | "serde_json", 252 | ] 253 | 254 | [[package]] 255 | name = "lazy_static" 256 | version = "1.4.0" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 259 | 260 | [[package]] 261 | name = "libc" 262 | version = "0.2.138" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" 265 | 266 | [[package]] 267 | name = "memchr" 268 | version = "2.5.0" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 271 | 272 | [[package]] 273 | name = "once_cell" 274 | version = "1.15.0" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1" 277 | 278 | [[package]] 279 | name = "os_str_bytes" 280 | version = "6.3.0" 281 | source = "registry+https://github.com/rust-lang/crates.io-index" 282 | checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff" 283 | 284 | [[package]] 285 | name = "pest" 286 | version = "2.4.0" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "dbc7bc69c062e492337d74d59b120c274fd3d261b6bf6d3207d499b4b379c41a" 289 | dependencies = [ 290 | "thiserror", 291 | "ucd-trie", 292 | ] 293 | 294 | [[package]] 295 | name = "pest_derive" 296 | version = "2.4.0" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "60b75706b9642ebcb34dab3bc7750f811609a0eb1dd8b88c2d15bf628c1c65b2" 299 | dependencies = [ 300 | "pest", 301 | "pest_generator", 302 | ] 303 | 304 | [[package]] 305 | name = "pest_generator" 306 | version = "2.4.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "f4f9272122f5979a6511a749af9db9bfc810393f63119970d7085fed1c4ea0db" 309 | dependencies = [ 310 | "pest", 311 | "pest_meta", 312 | "proc-macro2", 313 | "quote", 314 | "syn", 315 | ] 316 | 317 | [[package]] 318 | name = "pest_meta" 319 | version = "2.4.0" 320 | source = "registry+https://github.com/rust-lang/crates.io-index" 321 | checksum = "4c8717927f9b79515e565a64fe46c38b8cd0427e64c40680b14a7365ab09ac8d" 322 | dependencies = [ 323 | "once_cell", 324 | "pest", 325 | "sha1", 326 | ] 327 | 328 | [[package]] 329 | name = "proc-macro-error" 330 | version = "1.0.4" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 333 | dependencies = [ 334 | "proc-macro-error-attr", 335 | "proc-macro2", 336 | "quote", 337 | "syn", 338 | "version_check", 339 | ] 340 | 341 | [[package]] 342 | name = "proc-macro-error-attr" 343 | version = "1.0.4" 344 | source = "registry+https://github.com/rust-lang/crates.io-index" 345 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 346 | dependencies = [ 347 | "proc-macro2", 348 | "quote", 349 | "version_check", 350 | ] 351 | 352 | [[package]] 353 | name = "proc-macro2" 354 | version = "1.0.46" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "94e2ef8dbfc347b10c094890f778ee2e36ca9bb4262e86dc99cd217e35f3470b" 357 | dependencies = [ 358 | "unicode-ident", 359 | ] 360 | 361 | [[package]] 362 | name = "quote" 363 | version = "1.0.21" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179" 366 | dependencies = [ 367 | "proc-macro2", 368 | ] 369 | 370 | [[package]] 371 | name = "redox_syscall" 372 | version = "0.2.16" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 375 | dependencies = [ 376 | "bitflags", 377 | ] 378 | 379 | [[package]] 380 | name = "redox_users" 381 | version = "0.4.3" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" 384 | dependencies = [ 385 | "getrandom", 386 | "redox_syscall", 387 | "thiserror", 388 | ] 389 | 390 | [[package]] 391 | name = "regex" 392 | version = "1.6.0" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b" 395 | dependencies = [ 396 | "aho-corasick", 397 | "memchr", 398 | "regex-syntax", 399 | ] 400 | 401 | [[package]] 402 | name = "regex-syntax" 403 | version = "0.6.27" 404 | source = "registry+https://github.com/rust-lang/crates.io-index" 405 | checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" 406 | 407 | [[package]] 408 | name = "remove_dir_all" 409 | version = "0.5.3" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 412 | dependencies = [ 413 | "winapi", 414 | ] 415 | 416 | [[package]] 417 | name = "ryu" 418 | version = "1.0.11" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 421 | 422 | [[package]] 423 | name = "serde" 424 | version = "1.0.145" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "728eb6351430bccb993660dfffc5a72f91ccc1295abaa8ce19b27ebe4f75568b" 427 | 428 | [[package]] 429 | name = "serde_json" 430 | version = "1.0.85" 431 | source = "registry+https://github.com/rust-lang/crates.io-index" 432 | checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44" 433 | dependencies = [ 434 | "itoa", 435 | "ryu", 436 | "serde", 437 | ] 438 | 439 | [[package]] 440 | name = "sha1" 441 | version = "0.10.5" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 444 | dependencies = [ 445 | "cfg-if", 446 | "cpufeatures", 447 | "digest", 448 | ] 449 | 450 | [[package]] 451 | name = "strsim" 452 | version = "0.10.0" 453 | source = "registry+https://github.com/rust-lang/crates.io-index" 454 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 455 | 456 | [[package]] 457 | name = "syn" 458 | version = "1.0.101" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "e90cde112c4b9690b8cbe810cba9ddd8bc1d7472e2cae317b69e9438c1cba7d2" 461 | dependencies = [ 462 | "proc-macro2", 463 | "quote", 464 | "unicode-ident", 465 | ] 466 | 467 | [[package]] 468 | name = "tempfile" 469 | version = "3.3.0" 470 | source = "registry+https://github.com/rust-lang/crates.io-index" 471 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 472 | dependencies = [ 473 | "cfg-if", 474 | "fastrand", 475 | "libc", 476 | "redox_syscall", 477 | "remove_dir_all", 478 | "winapi", 479 | ] 480 | 481 | [[package]] 482 | name = "termcolor" 483 | version = "1.1.3" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" 486 | dependencies = [ 487 | "winapi-util", 488 | ] 489 | 490 | [[package]] 491 | name = "terminal_size" 492 | version = "0.1.17" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" 495 | dependencies = [ 496 | "libc", 497 | "winapi", 498 | ] 499 | 500 | [[package]] 501 | name = "thiserror" 502 | version = "1.0.37" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 505 | dependencies = [ 506 | "thiserror-impl", 507 | ] 508 | 509 | [[package]] 510 | name = "thiserror-impl" 511 | version = "1.0.37" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 514 | dependencies = [ 515 | "proc-macro2", 516 | "quote", 517 | "syn", 518 | ] 519 | 520 | [[package]] 521 | name = "typenum" 522 | version = "1.15.0" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" 525 | 526 | [[package]] 527 | name = "ucd-trie" 528 | version = "0.1.5" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" 531 | 532 | [[package]] 533 | name = "unicode-ident" 534 | version = "1.0.4" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "dcc811dc4066ac62f84f11307873c4850cb653bfa9b1719cee2bd2204a4bc5dd" 537 | 538 | [[package]] 539 | name = "unicode-width" 540 | version = "0.1.10" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" 543 | 544 | [[package]] 545 | name = "version_check" 546 | version = "0.9.4" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 549 | 550 | [[package]] 551 | name = "wasi" 552 | version = "0.11.0+wasi-snapshot-preview1" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 555 | 556 | [[package]] 557 | name = "winapi" 558 | version = "0.3.9" 559 | source = "registry+https://github.com/rust-lang/crates.io-index" 560 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 561 | dependencies = [ 562 | "winapi-i686-pc-windows-gnu", 563 | "winapi-x86_64-pc-windows-gnu", 564 | ] 565 | 566 | [[package]] 567 | name = "winapi-i686-pc-windows-gnu" 568 | version = "0.4.0" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 571 | 572 | [[package]] 573 | name = "winapi-util" 574 | version = "0.1.5" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 577 | dependencies = [ 578 | "winapi", 579 | ] 580 | 581 | [[package]] 582 | name = "winapi-x86_64-pc-windows-gnu" 583 | version = "0.4.0" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 586 | 587 | [[package]] 588 | name = "zeroize" 589 | version = "1.5.7" 590 | source = "registry+https://github.com/rust-lang/crates.io-index" 591 | checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" 592 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "json_env" 3 | version = "1.3.0" 4 | edition = "2021" 5 | license = "Apache-2.0" 6 | description ="Loads an environment variables from JSON files (`.env.json` per default) and starts a subprocess with them." 7 | homepage = "https://github.com/brodo/json_env" 8 | repository = "https://github.com/brodo/json_env.git" 9 | readme = "Readme.md" 10 | categories = ["command-line-utilities", "development-tools"] 11 | 12 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 13 | 14 | [[bin]] 15 | name = "json_env" 16 | path = "src/main.rs" 17 | 18 | [dependencies] 19 | serde = "1.0" 20 | serde_json = "1.0" 21 | anyhow = "1.0.62" 22 | clap = { version = "4.0.8", features = ["derive"] } 23 | jsonpath-rust = "0.2.0" 24 | dirs = "4.0.0" 25 | dialoguer = "0.10.2" -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.3.0] - 2022-11-04 8 | 9 | ### Added 10 | - Better error messages 11 | - Support for multiple config files 12 | 13 | ### Changed 14 | 15 | - Throw error if a JSON path is provided, that does not point to a thing 16 | 17 | 18 | ## [1.2.0] - 2022-11-03 19 | 20 | ### Added 21 | 22 | - Support for using a different config file name, not only `.env.json` 23 | - Support for expanding environment variables inside the config file 24 | - Support for JSON Path 25 | 26 | ## [1.1.0] - 2022-09-30 27 | ### Added 28 | 29 | - Support for `--help` and `--version` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2022 Julian Dax 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # json_env 2 | 3 | > **Environment Variable Loader** 4 | 5 | [![npmjs.com](https://img.shields.io/npm/v/@brodo/json_env?)](https://www.npmjs.com/package/@brodo/json_env) 6 | [![Crates.io](https://img.shields.io/crates/v/json_env)](https://crates.io/crates/json_env) [![License](https://img.shields.io/badge/license-Apache%202.0-blue)](LICENSE) 7 | 8 | `json_env` is [dotenv](https://github.com/motdotla/dotenv), but with JSON. 9 | It loads environment variables from JSON files (`.env.json` per default) and starts a subprocess with them. 10 | Storing configuration in the environment separate from code is based on [The Twelve-Factor](http://12factor.net/config) App methodology. 11 | 12 | json_env is written in Rust, but a [npm package](https://www.npmjs.com/package/@brodo/json_env) is also provided, so you 13 | can also add it as a dev-dependency to your Node project. 14 | 15 | ## Use-Cases 16 | 17 | - Load different environment variables for different execution contexts (testing, staging, production) 18 | - Load secrets (e.g. connection strings) from a dedicated configuration file 19 | - Easily provide JSON values inside environment variables 20 | 21 | ## How to install 22 | 23 | With homebrew: 24 | ```shell 25 | $ brew tap brodo/json_env 26 | $ brew install json_env 27 | ``` 28 | 29 | With NPM 30 | ```shell 31 | $ npm i -g @brodo/json_env 32 | ``` 33 | 34 | With cargo: 35 | ```shell 36 | $ cargo install json_env 37 | ``` 38 | 39 | Or download the binaries for your platform on the [releases page](https://github.com/brodo/json_env/releases/) and 40 | put them in your $PATH. 41 | 42 | ## How to use 43 | 44 | Just run json_env with any program as a parameter: 45 | ```shell 46 | $ json_env my_program 47 | ``` 48 | 49 | Additional command line arguments that are passed to `json_env` are forwarded to the child process: 50 | ```shell 51 | $ json_env echo "Test" 52 | 53 | Test 54 | ``` 55 | 56 | ### Example 57 | .env.json: 58 | ```json 59 | { 60 | "NODE_ENV": "DEV", 61 | "MY_USER": "Carl", 62 | "NUM_USERS": 10, 63 | "nested": { 64 | "hello": "world", 65 | "boo": "far" 66 | } 67 | } 68 | ``` 69 | 70 | Shell: 71 | ```shell 72 | $ json_env env 73 | MY_USER=Carl 74 | NODE_ENV=DEV 75 | NUM_USERS=10 76 | nested={"boo":"far","hello":"world"} 77 | [...] 78 | ``` 79 | 80 | json_env searches for `.env.json` files in the current directory and all parent directories. 81 | 82 | ### Environment Variable Expansion 83 | 84 | You can include existing environment variables in your env file to expand them: 85 | 86 | .env.json: 87 | ```json 88 | { 89 | "MY_VAR": "$FOO", 90 | "MY_OTHER_VAR": "User:$USER" 91 | } 92 | ``` 93 | 94 | Shell: 95 | ```shell 96 | $ json_env -e env 97 | FOO=Bar 98 | MY_OTHER_VAR=User:Carl 99 | MY_VAR=Bar 100 | USER=Carl 101 | [...] 102 | ``` 103 | 104 | 105 | ### JSON Path support 106 | 107 | There are some use cases where you already have environment variables defined 108 | in a JSON file but not at the root level. Take this 109 | [Azure Function local.settings.json file](https://learn.microsoft.com/en-us/azure/azure-functions/functions-develop-local#local-settings-file) 110 | for example: 111 | 112 | ```json 113 | 114 | { 115 | "IsEncrypted": false, 116 | "Values": { 117 | "FUNCTIONS_WORKER_RUNTIME": "", 118 | "AzureWebJobsStorage": "", 119 | "MyBindingConnection": "", 120 | "AzureWebJobs.HttpExample.Disabled": "true" 121 | }, 122 | "Host": { 123 | "LocalHttpPort": 7071, 124 | "CORS": "*", 125 | "CORSCredentials": false 126 | }, 127 | "ConnectionStrings": { 128 | "SQLConnectionString": "" 129 | } 130 | } 131 | ``` 132 | 133 | The `Values` property contains the environment variables we are interested in. 134 | You can use this file to run `app.js` with the environment variables defined in `Values` 135 | by providing the [JSON Path](https://docs.rs/jsonpath-rust/latest/jsonpath_rust/) `.Values` using the `-p` flag: 136 | 137 | ```shell 138 | $ json_env -c local.settings.json -p .Values node app.js 139 | ``` 140 | 141 | 142 | ### Using multiple config files 143 | 144 | In some cases, it makes sense to use several config files. For example, one 145 | file can be checked into your VCS containing default values and a second one 146 | can be used to overwrite some of them: 147 | 148 | defaults.json 149 | ```json 150 | { 151 | "SERVER_URL": "https://example.com/foo", 152 | "USER": "TO_BE_OVERWRITTEN", 153 | "PASSWORD": "TO_BE_OVERWRITTEN" 154 | } 155 | ``` 156 | 157 | my_settings.json 158 | ```json 159 | { 160 | "USER": "admin", 161 | "PASSWORD": "hunter2" 162 | } 163 | ``` 164 | Use multiple `-c` flags to add several config files: 165 | 166 | ```shell 167 | $ json_env -c defaults.json -c my_settings.json env 168 | PASSWORD=hunter2 169 | SERVER_URL=https://example.com/foo 170 | USER=admin 171 | [...] 172 | ``` 173 | 174 | Later config files overwrite the earlier ones. You can also use multiple JSON paths, which are applied in order. 175 | 176 | ## License 177 | 178 | json_env is licensed under the Apache 2.0 license. 179 | 180 | -------------------------------------------------------------------------------- /examples/extend.json: -------------------------------------------------------------------------------- 1 | { 2 | "TEST": "$FOO", 3 | "TEST2": "FOO" 4 | } -------------------------------------------------------------------------------- /examples/extend2.json: -------------------------------------------------------------------------------- 1 | { 2 | "TEST": { 3 | "Test": "$FOO" 4 | }, 5 | "EASY": "Hard" 6 | } -------------------------------------------------------------------------------- /examples/nested/.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "NODE_ENV": "DEV", 3 | "MY_USER": "Carl", 4 | "NUM_USERS": 10, 5 | "nested": { 6 | "hello": "world", 7 | "boo": "far" 8 | } 9 | } -------------------------------------------------------------------------------- /examples/simple/.env.json: -------------------------------------------------------------------------------- 1 | { 2 | "NODE_ENV": "DEV", 3 | "MY_USER": "Carl" 4 | } -------------------------------------------------------------------------------- /npm/getBinary.js: -------------------------------------------------------------------------------- 1 | const {Binary} = require('binary-install'); 2 | const os = require('os'); 3 | 4 | function getPlatform() { 5 | const type = os.type(); 6 | const arch = os.arch(); 7 | 8 | if (type === 'Windows_NT' && arch === 'x64') { 9 | return 'x86_64-windows'; 10 | } 11 | 12 | if (type === 'Linux') { 13 | if (arch === 'x64') { 14 | return 'x86_64-linux'; 15 | } 16 | if (arch === 'arm64') { 17 | return 'aarch64-linux'; 18 | } 19 | } 20 | 21 | if (type === 'Darwin') { 22 | if(arch === 'x64') { 23 | return 'x86_64-macos' 24 | } 25 | if(arch === 'arm64') { 26 | return 'aarch64-macos'; 27 | } 28 | } 29 | throw new Error(`Unsupported platform: ${type} ${arch}. Please create an issue at https://github.com/brodo/json_env/issues`); 30 | } 31 | 32 | function getBinary() { 33 | const platform = getPlatform(); 34 | const version = require('../package.json').version; 35 | const extension = platform === 'x86_64-windows' ? 'zip' : 'tar.gz'; 36 | const url = `https://github.com/brodo/json_env/releases/download/v${version}/json_env-v${version}-${platform}.${extension}`; 37 | return new Binary('json_env', url); 38 | } 39 | 40 | module.exports = getBinary; -------------------------------------------------------------------------------- /npm/run.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const getBinary = require('./getBinary'); 3 | 4 | const binary = getBinary(); 5 | binary.run(); -------------------------------------------------------------------------------- /npm/scripts.js: -------------------------------------------------------------------------------- 1 | function getBinary({ fatal }) { 2 | try { 3 | return require('./getBinary')(); 4 | } catch (err) { 5 | if (fatal) throw err; 6 | } 7 | } 8 | 9 | const binary = getBinary({ fatal: true }); 10 | if (binary) binary.install(); 11 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@brodo/json_env", 3 | "version": "1.2.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@brodo/json_env", 9 | "version": "1.2.1", 10 | "hasInstallScript": true, 11 | "license": "Apache-2.0", 12 | "dependencies": { 13 | "binary-install": "^1.0.6" 14 | }, 15 | "bin": { 16 | "json_env": "npm/run.js" 17 | } 18 | }, 19 | "node_modules/axios": { 20 | "version": "0.26.1", 21 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 22 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 23 | "dependencies": { 24 | "follow-redirects": "^1.14.8" 25 | } 26 | }, 27 | "node_modules/balanced-match": { 28 | "version": "1.0.2", 29 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 30 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 31 | }, 32 | "node_modules/binary-install": { 33 | "version": "1.0.6", 34 | "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.0.6.tgz", 35 | "integrity": "sha512-h3K4jaC4jEauK3csXI9GxGBJldkpuJlHCIBv8i+XBNhPuxnlERnD1PWVczQYDqvhJfv0IHUbB3lhDrZUMHvSgw==", 36 | "dependencies": { 37 | "axios": "^0.26.1", 38 | "rimraf": "^3.0.2", 39 | "tar": "^6.1.11" 40 | }, 41 | "engines": { 42 | "node": ">=10" 43 | } 44 | }, 45 | "node_modules/brace-expansion": { 46 | "version": "1.1.11", 47 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 48 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 49 | "dependencies": { 50 | "balanced-match": "^1.0.0", 51 | "concat-map": "0.0.1" 52 | } 53 | }, 54 | "node_modules/chownr": { 55 | "version": "2.0.0", 56 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 57 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", 58 | "engines": { 59 | "node": ">=10" 60 | } 61 | }, 62 | "node_modules/concat-map": { 63 | "version": "0.0.1", 64 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 65 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 66 | }, 67 | "node_modules/follow-redirects": { 68 | "version": "1.15.2", 69 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 70 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", 71 | "funding": [ 72 | { 73 | "type": "individual", 74 | "url": "https://github.com/sponsors/RubenVerborgh" 75 | } 76 | ], 77 | "engines": { 78 | "node": ">=4.0" 79 | }, 80 | "peerDependenciesMeta": { 81 | "debug": { 82 | "optional": true 83 | } 84 | } 85 | }, 86 | "node_modules/fs-minipass": { 87 | "version": "2.1.0", 88 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 89 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 90 | "dependencies": { 91 | "minipass": "^3.0.0" 92 | }, 93 | "engines": { 94 | "node": ">= 8" 95 | } 96 | }, 97 | "node_modules/fs.realpath": { 98 | "version": "1.0.0", 99 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 100 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 101 | }, 102 | "node_modules/glob": { 103 | "version": "7.2.3", 104 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 105 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 106 | "dependencies": { 107 | "fs.realpath": "^1.0.0", 108 | "inflight": "^1.0.4", 109 | "inherits": "2", 110 | "minimatch": "^3.1.1", 111 | "once": "^1.3.0", 112 | "path-is-absolute": "^1.0.0" 113 | }, 114 | "engines": { 115 | "node": "*" 116 | }, 117 | "funding": { 118 | "url": "https://github.com/sponsors/isaacs" 119 | } 120 | }, 121 | "node_modules/inflight": { 122 | "version": "1.0.6", 123 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 124 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 125 | "dependencies": { 126 | "once": "^1.3.0", 127 | "wrappy": "1" 128 | } 129 | }, 130 | "node_modules/inherits": { 131 | "version": "2.0.4", 132 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 133 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 134 | }, 135 | "node_modules/minimatch": { 136 | "version": "3.1.2", 137 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 138 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 139 | "dependencies": { 140 | "brace-expansion": "^1.1.7" 141 | }, 142 | "engines": { 143 | "node": "*" 144 | } 145 | }, 146 | "node_modules/minipass": { 147 | "version": "3.3.4", 148 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", 149 | "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", 150 | "dependencies": { 151 | "yallist": "^4.0.0" 152 | }, 153 | "engines": { 154 | "node": ">=8" 155 | } 156 | }, 157 | "node_modules/minizlib": { 158 | "version": "2.1.2", 159 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 160 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 161 | "dependencies": { 162 | "minipass": "^3.0.0", 163 | "yallist": "^4.0.0" 164 | }, 165 | "engines": { 166 | "node": ">= 8" 167 | } 168 | }, 169 | "node_modules/mkdirp": { 170 | "version": "1.0.4", 171 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 172 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", 173 | "bin": { 174 | "mkdirp": "bin/cmd.js" 175 | }, 176 | "engines": { 177 | "node": ">=10" 178 | } 179 | }, 180 | "node_modules/once": { 181 | "version": "1.4.0", 182 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 183 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 184 | "dependencies": { 185 | "wrappy": "1" 186 | } 187 | }, 188 | "node_modules/path-is-absolute": { 189 | "version": "1.0.1", 190 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 191 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 192 | "engines": { 193 | "node": ">=0.10.0" 194 | } 195 | }, 196 | "node_modules/rimraf": { 197 | "version": "3.0.2", 198 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 199 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 200 | "dependencies": { 201 | "glob": "^7.1.3" 202 | }, 203 | "bin": { 204 | "rimraf": "bin.js" 205 | }, 206 | "funding": { 207 | "url": "https://github.com/sponsors/isaacs" 208 | } 209 | }, 210 | "node_modules/tar": { 211 | "version": "6.1.11", 212 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 213 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 214 | "dependencies": { 215 | "chownr": "^2.0.0", 216 | "fs-minipass": "^2.0.0", 217 | "minipass": "^3.0.0", 218 | "minizlib": "^2.1.1", 219 | "mkdirp": "^1.0.3", 220 | "yallist": "^4.0.0" 221 | }, 222 | "engines": { 223 | "node": ">= 10" 224 | } 225 | }, 226 | "node_modules/wrappy": { 227 | "version": "1.0.2", 228 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 229 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 230 | }, 231 | "node_modules/yallist": { 232 | "version": "4.0.0", 233 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 234 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 235 | } 236 | }, 237 | "dependencies": { 238 | "axios": { 239 | "version": "0.26.1", 240 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz", 241 | "integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==", 242 | "requires": { 243 | "follow-redirects": "^1.14.8" 244 | } 245 | }, 246 | "balanced-match": { 247 | "version": "1.0.2", 248 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 249 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" 250 | }, 251 | "binary-install": { 252 | "version": "1.0.6", 253 | "resolved": "https://registry.npmjs.org/binary-install/-/binary-install-1.0.6.tgz", 254 | "integrity": "sha512-h3K4jaC4jEauK3csXI9GxGBJldkpuJlHCIBv8i+XBNhPuxnlERnD1PWVczQYDqvhJfv0IHUbB3lhDrZUMHvSgw==", 255 | "requires": { 256 | "axios": "^0.26.1", 257 | "rimraf": "^3.0.2", 258 | "tar": "^6.1.11" 259 | } 260 | }, 261 | "brace-expansion": { 262 | "version": "1.1.11", 263 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 264 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 265 | "requires": { 266 | "balanced-match": "^1.0.0", 267 | "concat-map": "0.0.1" 268 | } 269 | }, 270 | "chownr": { 271 | "version": "2.0.0", 272 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", 273 | "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" 274 | }, 275 | "concat-map": { 276 | "version": "0.0.1", 277 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 278 | "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" 279 | }, 280 | "follow-redirects": { 281 | "version": "1.15.2", 282 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", 283 | "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" 284 | }, 285 | "fs-minipass": { 286 | "version": "2.1.0", 287 | "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", 288 | "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", 289 | "requires": { 290 | "minipass": "^3.0.0" 291 | } 292 | }, 293 | "fs.realpath": { 294 | "version": "1.0.0", 295 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 296 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" 297 | }, 298 | "glob": { 299 | "version": "7.2.3", 300 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 301 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 302 | "requires": { 303 | "fs.realpath": "^1.0.0", 304 | "inflight": "^1.0.4", 305 | "inherits": "2", 306 | "minimatch": "^3.1.1", 307 | "once": "^1.3.0", 308 | "path-is-absolute": "^1.0.0" 309 | } 310 | }, 311 | "inflight": { 312 | "version": "1.0.6", 313 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 314 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 315 | "requires": { 316 | "once": "^1.3.0", 317 | "wrappy": "1" 318 | } 319 | }, 320 | "inherits": { 321 | "version": "2.0.4", 322 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 323 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 324 | }, 325 | "minimatch": { 326 | "version": "3.1.2", 327 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 328 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 329 | "requires": { 330 | "brace-expansion": "^1.1.7" 331 | } 332 | }, 333 | "minipass": { 334 | "version": "3.3.4", 335 | "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.4.tgz", 336 | "integrity": "sha512-I9WPbWHCGu8W+6k1ZiGpPu0GkoKBeorkfKNuAFBNS1HNFJvke82sxvI5bzcCNpWPorkOO5QQ+zomzzwRxejXiw==", 337 | "requires": { 338 | "yallist": "^4.0.0" 339 | } 340 | }, 341 | "minizlib": { 342 | "version": "2.1.2", 343 | "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", 344 | "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", 345 | "requires": { 346 | "minipass": "^3.0.0", 347 | "yallist": "^4.0.0" 348 | } 349 | }, 350 | "mkdirp": { 351 | "version": "1.0.4", 352 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", 353 | "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" 354 | }, 355 | "once": { 356 | "version": "1.4.0", 357 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 358 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 359 | "requires": { 360 | "wrappy": "1" 361 | } 362 | }, 363 | "path-is-absolute": { 364 | "version": "1.0.1", 365 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 366 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" 367 | }, 368 | "rimraf": { 369 | "version": "3.0.2", 370 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", 371 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", 372 | "requires": { 373 | "glob": "^7.1.3" 374 | } 375 | }, 376 | "tar": { 377 | "version": "6.1.11", 378 | "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", 379 | "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", 380 | "requires": { 381 | "chownr": "^2.0.0", 382 | "fs-minipass": "^2.0.0", 383 | "minipass": "^3.0.0", 384 | "minizlib": "^2.1.1", 385 | "mkdirp": "^1.0.3", 386 | "yallist": "^4.0.0" 387 | } 388 | }, 389 | "wrappy": { 390 | "version": "1.0.2", 391 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 392 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" 393 | }, 394 | "yallist": { 395 | "version": "4.0.0", 396 | "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", 397 | "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" 398 | } 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@brodo/json_env", 3 | "version": "1.2.1", 4 | "description": "json_env is dotenv, but with JSON.", 5 | "directories": { 6 | "example": "examples" 7 | }, 8 | "bin": { 9 | "json_env": "npm/run.js" 10 | }, 11 | "scripts": { 12 | "postinstall": "node npm/scripts.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/brodo/json_env.git" 17 | }, 18 | "keywords": [ 19 | "json", 20 | "env", 21 | "dotenv", 22 | "dotfile", 23 | "cli" 24 | ], 25 | "author": "Julian Dax ", 26 | "license": "Apache-2.0", 27 | "bugs": { 28 | "url": "https://github.com/brodo/json_env/issues" 29 | }, 30 | "homepage": "https://github.com/brodo/json_env#readme", 31 | "dependencies": { 32 | "binary-install": "^1.0.6" 33 | }, 34 | "files": [ 35 | "npm/**/*" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "nightly" 3 | components = [ "rustfmt", "clippy" ] 4 | targets = [ "wasm32-unknown-unknown" ] -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::fmt::{Display, Formatter}; 3 | use std::fs::{File, OpenOptions}; 4 | use std::io::{Read, Write}; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::Command; 7 | use std::string::ToString; 8 | use std::{env, fs, process}; 9 | 10 | use anyhow::{Error, Result}; 11 | use clap::error::ErrorKind; 12 | use clap::CommandFactory; 13 | use clap::Parser; 14 | use dialoguer::Confirm; 15 | use dirs::home_dir; 16 | use jsonpath_rust::JsonPathFinder; 17 | use serde_json::Value; 18 | 19 | struct Shell { 20 | shell_type: ShellType, 21 | config_path: &'static str, 22 | script: &'static str, 23 | include_command: &'static str, 24 | } 25 | 26 | #[derive(Debug, clap::ValueEnum)] 27 | enum ShellType { 28 | Bash, 29 | Zsh, 30 | Fish, 31 | } 32 | 33 | impl Display for ShellType { 34 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 35 | match self { 36 | ShellType::Bash => write!(f, "bash"), 37 | ShellType::Zsh => write!(f, "zsh"), 38 | ShellType::Fish => write!(f, "fish"), 39 | } 40 | } 41 | } 42 | 43 | impl Clone for ShellType { 44 | fn clone(&self) -> Self { 45 | match self { 46 | ShellType::Bash => ShellType::Bash, 47 | ShellType::Zsh => ShellType::Zsh, 48 | ShellType::Fish => ShellType::Fish, 49 | } 50 | } 51 | } 52 | 53 | impl Copy for ShellType {} 54 | 55 | impl Clone for Shell { 56 | fn clone(&self) -> Self { 57 | Shell { 58 | shell_type: self.shell_type, 59 | config_path: self.config_path, 60 | script: self.script, 61 | include_command: self.include_command, 62 | } 63 | } 64 | } 65 | 66 | impl Display for Shell { 67 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 68 | write!(f, "{}", self.shell_type) 69 | } 70 | } 71 | 72 | static BASH: Shell = Shell { 73 | shell_type: ShellType::Bash, 74 | config_path: ".bashrc", 75 | script: include_str!("run_on_cd.bash"), 76 | include_command: "\neval \"$(json_env --init bash)\"\n", 77 | }; 78 | 79 | static FISH: Shell = Shell { 80 | shell_type: ShellType::Fish, 81 | config_path: ".config/fish/config.fish", 82 | script: include_str!("run_on_cd.fish"), 83 | include_command: "\neval \"$(json_env --init fish)\"\n", 84 | }; 85 | 86 | static ZSH: Shell = Shell { 87 | shell_type: ShellType::Zsh, 88 | config_path: ".zshrc", 89 | script: include_str!("run_on_cd.zsh"), 90 | include_command: "\neval \"$(json_env --init zsh)\"\n", 91 | }; 92 | 93 | #[derive(Parser, Debug)] 94 | #[command( 95 | author, 96 | version, 97 | about = "Reads a JSON file and runs a program with these environment variables." 98 | )] 99 | struct Args { 100 | /// Expand env variables 101 | #[arg(short, long, default_value_t = false)] 102 | expand: bool, 103 | /// Do not run an application but print export commands. (can be sourced) 104 | #[arg(long, default_value_t = false)] 105 | export: bool, 106 | /// The JSON files from which the environment variables are taken from 107 | #[arg(short, long)] 108 | config_files: Vec, 109 | /// A JSON paths into the config files, in order. For examples and spec, see https://docs.rs/jsonpath-rust/latest/jsonpath_rust/ 110 | #[arg(short, long, default_value = "$")] 111 | paths: Vec, 112 | /// The executable which should be started, with it's command line arguments. 113 | executable: Vec, 114 | /// add a script to your shell configuration that automatically exports variables defined in .env.json when changing into a directory that contains such a file. 115 | #[arg(long, default_value_t = false)] 116 | install: bool, 117 | /// Silent mode, do not report errors (useful for scripts). Implies 'yes' to all questions. 118 | #[arg(short, long, default_value_t = false)] 119 | silent: bool, 120 | /// Print the path of the .env.json file that is used. 121 | #[arg(long, default_value_t = false)] 122 | print_config_path: bool, 123 | /// Check if the current .env.json file is whitelisted 124 | #[arg(long, default_value_t = false)] 125 | is_whitelisted: bool, 126 | /// Whitelist the current .env.json file 127 | #[arg(long, default_value_t = false)] 128 | whitelist: bool, 129 | /// Print the init script for the supplied shell 130 | #[arg(long)] 131 | init: Option, 132 | } 133 | 134 | /// `json_env` is [dotenv](https://github.com/motdotla/dotenv), but with JSON. 135 | /// See the [readme](Readme.md) for more information. 136 | fn main() { 137 | let mut args: Args = Args::parse(); 138 | let mut cmd = Args::command(); 139 | 140 | if args.install { 141 | install_shell_completion(args.silent); 142 | return; 143 | } 144 | 145 | if let Some(shell) = args.init { 146 | let shell = match shell { 147 | ShellType::Bash => &BASH, 148 | ShellType::Zsh => &ZSH, 149 | ShellType::Fish => &FISH, 150 | }; 151 | println!("{}", shell.script); 152 | return; 153 | } 154 | 155 | if args.is_whitelisted { 156 | if let Some(config_path) = find_env_file() { 157 | if is_whitelisted(&config_path) { 158 | println!("'{}' is whitelisted", &config_path.to_str().unwrap()); 159 | return; 160 | } else { 161 | println!("'{}' is not whitelisted", &config_path.to_str().unwrap()); 162 | process::exit(1); 163 | } 164 | } else { 165 | println!("No .env.json file found"); 166 | process::exit(1); 167 | } 168 | } 169 | 170 | if args.whitelist { 171 | if let Some(config_path) = find_env_file() { 172 | if is_whitelisted(&config_path) { 173 | println!("Already whitelisted"); 174 | return; 175 | } else { 176 | whitelist(&config_path); 177 | return; 178 | } 179 | } else { 180 | println!("No .env.json file found"); 181 | process::exit(1); 182 | } 183 | } 184 | 185 | let mut env_vars: HashMap = HashMap::new(); 186 | if args.print_config_path { 187 | if let Some(config_path) = find_env_file() { 188 | println!("{}", config_path.to_str().unwrap()); 189 | return; 190 | } else { 191 | println!("No .env.json file found"); 192 | process::exit(1); 193 | } 194 | } 195 | 196 | if args.executable.is_empty() && !args.export { 197 | if args.silent { 198 | process::exit(1); 199 | } 200 | cmd.error( 201 | ErrorKind::TooFewValues, 202 | "You need to provide the name of an executable", 203 | ) 204 | .exit(); 205 | } 206 | 207 | if args.config_files.is_empty() { 208 | let env_json = find_env_file(); 209 | if let Some(path) = env_json { 210 | args.config_files.push(path.to_str().unwrap().to_string()); 211 | } else if args.silent { 212 | process::exit(1); 213 | } else { 214 | cmd.error( 215 | ErrorKind::TooFewValues, 216 | "You need to provide the name of a config file or be in a directory with a .env.json file in it or one of it's parents", 217 | ) 218 | .exit(); 219 | } 220 | } 221 | 222 | // Read the config files, and parse them as JSON 223 | for (i, file_name) in args.config_files.iter().enumerate() { 224 | let Ok(mut file) = File::open(file_name) else { 225 | if args.silent { 226 | return; 227 | } 228 | cmd.error( 229 | ErrorKind::InvalidValue, 230 | format!("Could not open '{file_name}'"), 231 | ).exit(); 232 | }; 233 | let mut contents = String::new(); 234 | let json_path = match args.paths.get(i) { 235 | Some(p) => p, 236 | None => "$", 237 | }; 238 | if file.read_to_string(&mut contents).is_ok() { 239 | match parse_and_extract(&contents, json_path) { 240 | Ok(val) => { 241 | if val.is_empty() { 242 | if args.silent { 243 | return; 244 | } 245 | cmd.error( 246 | ErrorKind::InvalidValue, 247 | format!("There is nothing in file '{file_name}' at path '{json_path}'"), 248 | ) 249 | .exit(); 250 | } 251 | add_values_to_map(&val, args.expand, &mut env_vars); 252 | } 253 | Err(e) => { 254 | if args.silent { 255 | return; 256 | } 257 | eprintln!("Error while parsing json or jsonpath: {e} in '{file_name}'"); 258 | process::exit(1); 259 | } 260 | } 261 | } else { 262 | if args.silent { 263 | return; 264 | } 265 | cmd.error( 266 | ErrorKind::InvalidValue, 267 | format!("Could not read JSON in '{file_name}'"), 268 | ) 269 | .exit(); 270 | } 271 | } 272 | 273 | if args.export { 274 | for (k, v) in &env_vars { 275 | println!("export {k}=\"{v}\""); 276 | } 277 | return; 278 | } 279 | 280 | execute( 281 | &env_vars, 282 | &args.executable[0], 283 | &(args.executable[1..]).to_vec(), 284 | ) 285 | } 286 | 287 | fn whitelist(config_path: &Path) { 288 | let mut config_dir = json_env_config_dir_path(false); 289 | // Create config dir if it doesn't exist 290 | if !config_dir.exists() && fs::create_dir_all(&config_dir).is_err() { 291 | println!("Could not create config dir"); 292 | process::exit(1); 293 | } 294 | config_dir.push("whitelist.json"); 295 | 296 | let contents = read_or_create_empty(&config_dir); 297 | 298 | let mut whitelist = match serde_json::from_str::>(&contents) { 299 | Ok(w) => w, 300 | Err(_) => Vec::new(), 301 | }; 302 | let config_path_str = config_path.to_str().unwrap(); 303 | for path in &whitelist { 304 | if path == config_path_str { 305 | println!("Already whitelisted {config_path_str}"); 306 | return; 307 | } 308 | } 309 | whitelist.push(config_path_str.to_string()); 310 | let Ok(whitelist_json) = serde_json::to_string_pretty(&whitelist) else { 311 | return; 312 | }; 313 | 314 | let Ok(mut output_file) = OpenOptions::new() 315 | .write(true) 316 | .truncate(true) 317 | .open(config_dir.clone()) else 318 | { 319 | println!("Could not open whitelist file"); 320 | process::exit(1); 321 | 322 | }; 323 | 324 | if output_file 325 | .write_all(format!("{whitelist_json}\n").as_bytes()) 326 | .is_ok() 327 | { 328 | println!("Whitelisted {config_path_str}"); 329 | } 330 | } 331 | 332 | fn read_or_create_empty(config_file: &Path) -> String { 333 | let mut read_file = match OpenOptions::new().read(true).open(config_file) { 334 | Ok(file) => file, 335 | Err(_) => { 336 | // Create file if it doesn't exist 337 | match OpenOptions::new() 338 | .write(true) 339 | .read(true) 340 | .create(true) 341 | .open(config_file) 342 | { 343 | Ok(file) => file, 344 | Err(_) => { 345 | println!("Could not create whitelist file"); 346 | process::exit(1); 347 | } 348 | } 349 | } 350 | }; 351 | 352 | let mut contents = String::new(); 353 | if read_file.read_to_string(&mut contents).is_err() { 354 | contents = "[]".to_string(); 355 | } 356 | contents 357 | } 358 | 359 | fn is_whitelisted(config_path: &Path) -> bool { 360 | let mut whitelist_dir = json_env_config_dir_path(true); 361 | whitelist_dir.push("whitelist.json"); 362 | let Ok(mut file) = File::open(whitelist_dir) else { 363 | return false; 364 | }; 365 | let mut contents = String::new(); 366 | if file.read_to_string(&mut contents).is_ok() { 367 | let Ok(whitelist) = serde_json::from_str::>(&contents) else { 368 | return false; 369 | }; 370 | let config_path_str = config_path.to_str().unwrap(); 371 | for path in whitelist { 372 | if path == config_path_str { 373 | return true; 374 | } 375 | } 376 | } 377 | false 378 | } 379 | 380 | fn json_env_config_dir_path(silent: bool) -> PathBuf { 381 | match home_dir() { 382 | Some(mut path) => { 383 | path.push(".config"); 384 | path.push("json_env"); 385 | path 386 | } 387 | None => { 388 | if !silent { 389 | println!("Could not determine home directory"); 390 | } 391 | process::exit(1); 392 | } 393 | } 394 | } 395 | 396 | fn install_shell_completion(silent: bool) { 397 | let Ok(shell) = get_shell() else { 398 | if !silent { 399 | println!("Could not determine shell"); 400 | } 401 | process::exit(1); 402 | }; 403 | 404 | // Get the appropriate script to append to the configuration file 405 | // for the user's current shell 406 | if !silent 407 | && !Confirm::new() 408 | .with_prompt(format!( 409 | "Your shell has been detected as: '{shell}', is that correct?" 410 | )) 411 | .interact() 412 | .unwrap_or(false) 413 | { 414 | println!("Please set your shell to one of the supported shells and try again."); 415 | return; 416 | } 417 | 418 | let shell_config_path = match home_dir() { 419 | Some(mut path) => { 420 | path.push(shell.config_path); 421 | path 422 | } 423 | None => { 424 | if !silent { 425 | println!("Could not determine home directory"); 426 | } 427 | process::exit(1); 428 | } 429 | }; 430 | if !silent { 431 | println!( 432 | "I am going to append the following lines to your shell configuration file at '{}':\n {}\n", 433 | shell_config_path.to_str().unwrap(), 434 | shell.include_command 435 | ); 436 | } 437 | 438 | if !silent 439 | && !Confirm::new() 440 | .with_prompt("Do you want me to do that? ") 441 | .interact() 442 | .unwrap_or(false) 443 | { 444 | println!("Please set your shell to one of the supported shells and try again."); 445 | return; 446 | } 447 | 448 | // Check if the config file exists and create it if it does not 449 | if !shell_config_path.exists() { 450 | let Ok(mut file) = File::create(&shell_config_path) else { 451 | if !silent { 452 | println!("Could not create file '{}'", shell_config_path.to_str().unwrap()); 453 | } 454 | process::exit(1); 455 | }; 456 | file.write_all(b"").unwrap(); 457 | } 458 | 459 | // Open the configuration file in append-only mode 460 | let mut file = match OpenOptions::new().append(true).open(&shell_config_path) { 461 | Ok(file) => file, 462 | Err(error) => { 463 | if !silent { 464 | println!( 465 | "Could not open file '{}': {}", 466 | shell_config_path.to_str().unwrap(), 467 | error 468 | ); 469 | } 470 | process::exit(1); 471 | } 472 | }; 473 | 474 | if let Err(error) = file.write_all(shell.include_command.as_bytes()) { 475 | if !silent { 476 | println!( 477 | "Could not write to file '{}': {}", 478 | shell_config_path.to_str().unwrap(), 479 | error 480 | ); 481 | } 482 | process::exit(1); 483 | } 484 | 485 | let mut json_env_config_dir_path = json_env_config_dir_path(silent); 486 | 487 | if !json_env_config_dir_path.exists() { 488 | if let Err(error) = fs::create_dir_all(&json_env_config_dir_path) { 489 | if !silent { 490 | println!( 491 | "Could not create directory '{}': {}", 492 | json_env_config_dir_path.to_str().unwrap(), 493 | error 494 | ); 495 | } 496 | process::exit(1); 497 | } 498 | } 499 | 500 | // Create a whitelist file in the ~/.config/json_env directory 501 | json_env_config_dir_path.push("whitelist.json"); 502 | if !json_env_config_dir_path.exists() { 503 | let Ok(mut file) = File::create(&json_env_config_dir_path) else { 504 | if !silent { 505 | println!("Could not create file '{}'", json_env_config_dir_path.to_str().unwrap()); 506 | } 507 | process::exit(1); 508 | }; 509 | file.write_all(b"{\"items\":[]}").unwrap(); 510 | } 511 | } 512 | 513 | /// Get the the user's current shell 514 | fn get_shell() -> Result { 515 | // Get the name of the user's current shell 516 | let shell_path = match env::var("SHELL") { 517 | Ok(shell) => PathBuf::from(shell), 518 | Err(error) => { 519 | return Err(format!("Error getting shell name: {error}")); 520 | } 521 | }; 522 | let Some(file_name_os_str) = shell_path.file_name() else { 523 | return Err("Error getting shell name".to_string()); 524 | }; 525 | let Some(file_name) = file_name_os_str.to_str() else { 526 | return Err("Error getting shell name".to_string()); 527 | }; 528 | match file_name { 529 | "bash" => Ok(BASH.clone()), 530 | "zsh" => Ok(ZSH.clone()), 531 | "fish" => Ok(FISH.clone()), 532 | _ => Err("Unknown shell".to_string()), 533 | } 534 | } 535 | 536 | fn parse_and_extract(json_str: &str, path: &str) -> Result> { 537 | let finder = JsonPathFinder::from_str(json_str, path).map_err(Error::msg)?; 538 | finder 539 | .find() 540 | .as_array() 541 | .cloned() 542 | .ok_or_else(|| Error::msg("Json path does not point to valid object.")) 543 | } 544 | 545 | /// Execute the given command with the given environment variables. 546 | fn execute(vars: &HashMap, command: &str, args: &Vec) { 547 | match Command::new(command).envs(vars).args(args).spawn() { 548 | Err(e) => { 549 | eprintln!("Could not start executable '{command}': {e}"); 550 | } 551 | Ok(mut child) => { 552 | if let Err(e) = child.wait() { 553 | eprintln!("Error when running executable '{command}: {e}"); 554 | } 555 | } 556 | } 557 | } 558 | 559 | fn add_values_to_map( 560 | values: &Vec, 561 | should_expand: bool, 562 | str_map: &mut HashMap, 563 | ) { 564 | for value in values { 565 | if value.is_object() { 566 | let in_val = value.as_object().unwrap(); 567 | for (key, val) in in_val { 568 | let mut val_str = "".to_string(); 569 | if val.is_array() { 570 | val_str = val.to_string(); 571 | } 572 | if val.is_boolean() { 573 | val_str = val.to_string(); 574 | } 575 | if val.is_object() { 576 | val_str = val.to_string(); 577 | } 578 | if val.is_null() { 579 | val_str = val.to_string(); 580 | } 581 | if val.is_number() { 582 | val_str = val.to_string(); 583 | } 584 | if val.is_string() { 585 | val_str = val.as_str().unwrap().to_string(); // the as_str is needed, because we get quotes otherwise 586 | } 587 | if should_expand { 588 | let mut expanded_val = val_str.clone(); 589 | for (env_key, env_value) in env::vars() { 590 | let env_key_dollar = format!("${env_key}"); 591 | if val_str.contains(&env_key_dollar) { 592 | expanded_val = val_str.replace(&env_key_dollar, &env_value); 593 | } 594 | } 595 | str_map.insert(key.to_string(), expanded_val); 596 | } else { 597 | str_map.insert(key.to_string(), val_str); 598 | } 599 | } 600 | } 601 | } 602 | } 603 | 604 | /// Recursively find the file '.env.json' in the current directory and all parent directories. 605 | fn find_env_file() -> Option { 606 | let mut current_dir = env::current_dir().unwrap(); 607 | loop { 608 | let mut env_file = current_dir.clone(); 609 | env_file.push(".env.json"); 610 | if env_file.exists() { 611 | return Some(env_file); 612 | } 613 | if !current_dir.pop() { 614 | return None; 615 | } 616 | } 617 | } 618 | 619 | #[cfg(test)] 620 | mod tests { 621 | // Note this useful idiom: importing names from outer (for mod tests) scope. 622 | use super::*; 623 | 624 | #[test] 625 | fn parse_simple() { 626 | let simple_json = include_str!("../examples/simple/.env.json"); 627 | let val = parse_and_extract(simple_json, "$"); 628 | assert!(val.is_ok()); 629 | let mut env_vars: HashMap = HashMap::new(); 630 | add_values_to_map(&val.unwrap(), false, &mut env_vars); 631 | let node_env = env_vars.get("NODE_ENV"); 632 | assert!(node_env.is_some()); 633 | assert_eq!(node_env.unwrap().to_string(), "DEV".to_string()); 634 | } 635 | 636 | #[test] 637 | fn expand() { 638 | let extendable_json = include_str!("../examples/extend.json"); 639 | env::set_var("FOO", "Bar"); 640 | let val = parse_and_extract(extendable_json, "$"); 641 | assert!(val.is_ok()); 642 | let mut env_vars: HashMap = HashMap::new(); 643 | add_values_to_map(&val.unwrap(), true, &mut env_vars); 644 | let node_env = env_vars.get("TEST"); 645 | assert!(node_env.is_some()); 646 | assert_eq!(node_env.unwrap().to_string(), "Bar".to_string()); 647 | } 648 | 649 | #[test] 650 | fn expand_nested() { 651 | let extendable_json = include_str!("../examples/extend2.json"); 652 | env::set_var("FOO", "Bar"); 653 | let val = parse_and_extract(extendable_json, "$"); 654 | assert!(val.is_ok()); 655 | let mut env_vars: HashMap = HashMap::new(); 656 | add_values_to_map(&val.unwrap(), true, &mut env_vars); 657 | let node_env = env_vars.get("TEST"); 658 | assert!(node_env.is_some()); 659 | assert!(node_env.unwrap().contains("Bar")); 660 | } 661 | 662 | #[test] 663 | fn use_json_path() { 664 | let nested_json = include_str!("../examples/nested/.env.json"); 665 | let val = parse_and_extract(nested_json, "$.nested"); 666 | assert!(val.is_ok()); 667 | let mut env_vars: HashMap = HashMap::new(); 668 | add_values_to_map(&val.unwrap(), true, &mut env_vars); 669 | let hello = env_vars.get("hello"); 670 | assert!(hello.is_some()); 671 | assert_eq!(hello.unwrap(), "world"); 672 | } 673 | } 674 | -------------------------------------------------------------------------------- /src/run_on_cd.bash: -------------------------------------------------------------------------------- 1 | # Define the json_env_hook function 2 | function json_env_hook() { 3 | # Save the exit code and the output of "json_env --print-config-path" in a variable 4 | local json_env_output 5 | json_env_output="$(json_env --print-config-path)" 6 | local json_env_exit_code=$? 7 | 8 | # If the exit code is 0, that means that we are in a folder that contains a ".env.json" 9 | # file or has a parent folder that contains a ".env.json" file 10 | if [ $json_env_exit_code -eq 0 ]; then 11 | # Save the exit code and the exit code of "json_env --is-whitelisted" in a variable 12 | (json_env --is-whitelisted >/dev/null) 13 | local json_env_whitelisted_exit_code 14 | json_env_whitelisted_exit_code=$? 15 | 16 | # If the exit code is 0, that means that the current folder is whitelisted 17 | if [ $json_env_whitelisted_exit_code -eq 0 ]; then 18 | # Source the output of executing the "json_env" program with the "--export" parameter 19 | eval "$(json_env --export)" 20 | else 21 | echo "json_env: The config file at $json_env_output is not whitelisted. Run 'json_env --whitelist' to whitelist it." 22 | fi 23 | fi 24 | } 25 | 26 | function cd () { builtin cd "$@" && json_env_hook; } -------------------------------------------------------------------------------- /src/run_on_cd.fish: -------------------------------------------------------------------------------- 1 | 2 | function json_env_hook 3 | # Check if the current directory contains a file called ".env.json" 4 | if test -f ".env.json" 5 | # Execute the "json_env" program with the "--export" parameter 6 | # and "source" the output in the current shell environment 7 | . (json_env --export) 8 | end 9 | end 10 | 11 | function json_env_postexec 12 | # Check if the previous command was the builtin cd function 13 | if test $prev_cmd = "cd" 14 | # Call the json_env_hook function 15 | json_env_hook 16 | end 17 | end 18 | 19 | # Use the fish_postexec function to run the json_env_postexec function 20 | # after each command is executed 21 | fish_postexec json_env_postexec 22 | 23 | -------------------------------------------------------------------------------- /src/run_on_cd.zsh: -------------------------------------------------------------------------------- 1 | # Define the json_env_hook function 2 | function json_env_hook() { 3 | # Save the exit code and the output of "json_env --print-config-path" in a variable 4 | local json_env_output 5 | json_env_output="$(json_env --print-config-path)" 6 | local json_env_exit_code=$? 7 | 8 | # If the exit code is 0, that means that we are in a folder that contains a ".env.json" 9 | # file or has a parent folder that contains a ".env.json" file 10 | if [ $json_env_exit_code -eq 0 ]; then 11 | # Save the exit code and the exit code of "json_env --is-whitelisted" in a variable 12 | (json_env --is-whitelisted > /dev/null) 13 | local json_env_whitelisted_exit_code 14 | json_env_whitelisted_exit_code=$? 15 | 16 | # If the exit code is 0, that means that the current folder is whitelisted 17 | if [ $json_env_whitelisted_exit_code -eq 0 ]; then 18 | # Source the output of executing the "json_env" program with the "--export" parameter 19 | eval "$(json_env --export)" 20 | else 21 | echo "json_env: The config file at $json_env_output is not whitelisted. Run 'json_env --whitelist' to whitelist it." 22 | fi 23 | fi 24 | } 25 | 26 | # Use the add-zsh-hook function to run the json_env_hook function 27 | # whenever the chpwd event is triggered 28 | add-zsh-hook chpwd json_env_hook 29 | --------------------------------------------------------------------------------