From d8e69ed4a8edaf15984b9e3023e22ad18161f9b7 Mon Sep 17 00:00:00 2001 From: Zuri Klaschka Date: Tue, 29 Oct 2024 01:21:30 +0100 Subject: [PATCH] Initial commit --- .github/workflows/deno-ci.yml | 23 + .github/workflows/publish-jsr.yml | 25 ++ .gitignore | 12 + LICENSE | 21 + README.md | 56 +++ deno.json | 23 + deno.lock | 707 ++++++++++++++++++++++++++++++ mod.ts | 231 ++++++++++ mod_test.ts | 235 ++++++++++ scripts/release.ts | 6 + 10 files changed, 1339 insertions(+) create mode 100644 .github/workflows/deno-ci.yml create mode 100644 .github/workflows/publish-jsr.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 deno.json create mode 100644 deno.lock create mode 100644 mod.ts create mode 100644 mod_test.ts create mode 100644 scripts/release.ts diff --git a/.github/workflows/deno-ci.yml b/.github/workflows/deno-ci.yml new file mode 100644 index 0000000..f512758 --- /dev/null +++ b/.github/workflows/deno-ci.yml @@ -0,0 +1,23 @@ +name: Deno CI ๐Ÿฆ• + +on: + push: + branches: + - main + pull_request: + branches: + - main + +jobs: + publish: + name: Run Deno Tests ๐Ÿงช + runs-on: ubuntu-latest + steps: + - name: Checkout Repository ๐Ÿ›Ž๏ธ + uses: actions/checkout@v4 + - name: Setup Deno Environment ๐Ÿฆ• + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + - name: Execute Tests ๐Ÿงช + run: deno task test diff --git a/.github/workflows/publish-jsr.yml b/.github/workflows/publish-jsr.yml new file mode 100644 index 0000000..1ac85c2 --- /dev/null +++ b/.github/workflows/publish-jsr.yml @@ -0,0 +1,25 @@ +name: Publish to JSR ๐Ÿš€ + +on: + push: + branches: + - main + +jobs: + publish: + name: Publish JSR Package ๐Ÿ“ฆ + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # The OIDC ID token is used for authentication with JSR. + steps: + - name: Checkout Repository ๐Ÿ›Ž๏ธ + uses: actions/checkout@v4 + - name: Setup Deno ๐Ÿฆ• + uses: denoland/setup-deno@v2 + with: + deno-version: v2.x + - name: Run Tests ๐Ÿงช + run: deno task test + - name: Publish Package ๐Ÿš€ + run: deno publish diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b1d8672 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Logs +*.jsonl +*.log + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..28210ef --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 WรผSpace e. V. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d2efcdc --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# @wuespace/envar + +[![JSR Scope](https://jsr.io/badges/@wuespace)](https://jsr.io/@wuespace) +[![JSR](https://jsr.io/badges/@wuespace/envar)](https://jsr.io/@wuespace/envar) +[![JSR Score](https://jsr.io/badges/@wuespace/envar/score)](https://jsr.io/@wuespace/envar) +[![Deno CI](https://github.com/wuespace/envar/actions/workflows/deno-ci.yml/badge.svg)](https://github.com/wuespace/envar/actions/workflows/deno-ci.yml) +[![Publish Workflow](https://github.com/wuespace/envar/actions/workflows/publish-jsr.yml/badge.svg)](https://github.com/wuespace/envar/actions/workflows/publish-jsr.yml) + +๐Ÿš€ **Envar** makes it easy to add configurability to your Deno applications. +It supports loading configuration from environment variables as well as +specifying default values. +It even supports configuration values from files specified by environment +variables to provide first-class support for secrets and the like in your +Docker Swarm or Kubernetes deployments. + +## ๐Ÿ“ฆ Usage + +You can use Envar in your Deno application by importing it from the +`jsr:@wuespace/envar` module. + +```tsx +// Import the initVariable function from the module +import { initVariable, EnvNotSetError } from "jsr:@wuespace/envar"; +import { z } from "npm:zod"; + +// Initialize the application variables +await initVariable("PORT", z.string().match(/^[0-9]{1,5}$/), "8080"); +await initVariable("SECRET", z.string().min(32)); +// At this point, we can rest assured that we have valid values for +// PORT and SECRET. Otherwise, the promises would have been rejected. + +// Access the variables like you normally would. +// Everything's synchronous now, so it's quite easy. +console.log(Deno.env.get("PORT")); +console.log(Deno.env.get("SECRET")); + +// For type safety, you'll need to check if it's undefined: +const port = Deno.env.get("PORT"); +if (port == undefined) { + // Automatically generate a nice error message for the user + throw new EnvNotSetError("PORT"); +} + +// Alternatively, you can also use process.env in Deno 2.0+ +console.log(process.env.PORT); +``` + +## ๐Ÿ‘ฅ Authors + +This package was created and is maintained by [WรผSpace e. V.](https://github.com/wuespace) + +Its primary author is [Zuri Klaschka](https://github.com/pklaschka). + +## ๐Ÿ“„ License + +This package is licensed under the MIT license. See the [LICENSE](LICENSE) file for more information. diff --git a/deno.json b/deno.json new file mode 100644 index 0000000..bfa2a46 --- /dev/null +++ b/deno.json @@ -0,0 +1,23 @@ +{ + "name": "@wuespace/envar", + "version": "0.0.1", + "exports": "./mod.ts", + "license": "MIT", + "scopes": { + }, + "publish": { + "include": [ + "mod.ts", + "LICENSE", + "README.md" + ] + }, + "tasks": { + "dev": "deno task test --watch", + "test": "deno test --allow-env --allow-read --allow-write" + }, + "imports": { + "@std/assert": "jsr:@std/assert@1", + "@std/log": "jsr:@std/log@^0.224.9" + } +} diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..701c6f9 --- /dev/null +++ b/deno.lock @@ -0,0 +1,707 @@ +{ + "version": "4", + "specifiers": { + "jsr:@std/assert@1": "1.0.6", + "jsr:@std/fmt@^1.0.2": "1.0.3", + "jsr:@std/fs@^1.0.4": "1.0.5", + "jsr:@std/internal@^1.0.4": "1.0.4", + "jsr:@std/io@0.225": "0.225.0", + "jsr:@std/log@~0.224.9": "0.224.9", + "npm:@changesets/cli@*": "2.27.8", + "npm:@types/node@*": "22.5.4", + "npm:conventional-changelog-angular@*": "8.0.0", + "npm:conventional-recommended-bump@*": "10.0.0_conventional-commits-filter@5.0.0_conventional-commits-parser@6.0.0" + }, + "jsr": { + "@std/assert@1.0.6": { + "integrity": "1904c05806a25d94fe791d6d883b685c9e2dcd60e4f9fc30f4fc5cf010c72207", + "dependencies": [ + "jsr:@std/internal" + ] + }, + "@std/fmt@1.0.3": { + "integrity": "97765c16aa32245ff4e2204ecf7d8562496a3cb8592340a80e7e554e0bb9149f" + }, + "@std/fs@1.0.5": { + "integrity": "41806ad6823d0b5f275f9849a2640d87e4ef67c51ee1b8fb02426f55e02fd44e" + }, + "@std/internal@1.0.4": { + "integrity": "62e8e4911527e5e4f307741a795c0b0a9e6958d0b3790716ae71ce085f755422" + }, + "@std/io@0.225.0": { + "integrity": "c1db7c5e5a231629b32d64b9a53139445b2ca640d828c26bf23e1c55f8c079b3" + }, + "@std/log@0.224.9": { + "integrity": "419d04e4d93e6b1a0205ac3f94809621cfec3d328d09fec9ec59cafa3b3fcaee", + "dependencies": [ + "jsr:@std/fmt", + "jsr:@std/fs", + "jsr:@std/io" + ] + } + }, + "npm": { + "@babel/runtime@7.25.6": { + "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==", + "dependencies": [ + "regenerator-runtime" + ] + }, + "@changesets/apply-release-plan@7.0.5": { + "integrity": "sha512-1cWCk+ZshEkSVEZrm2fSj1Gz8sYvxgUL4Q78+1ZZqeqfuevPTPk033/yUZ3df8BKMohkqqHfzj0HOOrG0KtXTw==", + "dependencies": [ + "@changesets/config", + "@changesets/get-version-range-type", + "@changesets/git", + "@changesets/should-skip-package", + "@changesets/types@6.0.0", + "@manypkg/get-packages", + "detect-indent", + "fs-extra@7.0.1", + "lodash.startcase", + "outdent", + "prettier", + "resolve-from", + "semver" + ] + }, + "@changesets/assemble-release-plan@6.0.4": { + "integrity": "sha512-nqICnvmrwWj4w2x0fOhVj2QEGdlUuwVAwESrUo5HLzWMI1rE5SWfsr9ln+rDqWB6RQ2ZyaMZHUcU7/IRaUJS+Q==", + "dependencies": [ + "@changesets/errors", + "@changesets/get-dependents-graph", + "@changesets/should-skip-package", + "@changesets/types@6.0.0", + "@manypkg/get-packages", + "semver" + ] + }, + "@changesets/changelog-git@0.2.0": { + "integrity": "sha512-bHOx97iFI4OClIT35Lok3sJAwM31VbUM++gnMBV16fdbtBhgYu4dxsphBF/0AZZsyAHMrnM0yFcj5gZM1py6uQ==", + "dependencies": [ + "@changesets/types@6.0.0" + ] + }, + "@changesets/cli@2.27.8": { + "integrity": "sha512-gZNyh+LdSsI82wBSHLQ3QN5J30P4uHKJ4fXgoGwQxfXwYFTJzDdvIJasZn8rYQtmKhyQuiBj4SSnLuKlxKWq4w==", + "dependencies": [ + "@changesets/apply-release-plan", + "@changesets/assemble-release-plan", + "@changesets/changelog-git", + "@changesets/config", + "@changesets/errors", + "@changesets/get-dependents-graph", + "@changesets/get-release-plan", + "@changesets/git", + "@changesets/logger", + "@changesets/pre", + "@changesets/read", + "@changesets/should-skip-package", + "@changesets/types@6.0.0", + "@changesets/write", + "@manypkg/get-packages", + "@types/semver", + "ansi-colors", + "ci-info", + "enquirer", + "external-editor", + "fs-extra@7.0.1", + "mri", + "outdent", + "p-limit", + "package-manager-detector", + "picocolors", + "resolve-from", + "semver", + "spawndamnit", + "term-size" + ] + }, + "@changesets/config@3.0.3": { + "integrity": "sha512-vqgQZMyIcuIpw9nqFIpTSNyc/wgm/Lu1zKN5vECy74u95Qx/Wa9g27HdgO4NkVAaq+BGA8wUc/qvbvVNs93n6A==", + "dependencies": [ + "@changesets/errors", + "@changesets/get-dependents-graph", + "@changesets/logger", + "@changesets/types@6.0.0", + "@manypkg/get-packages", + "fs-extra@7.0.1", + "micromatch" + ] + }, + "@changesets/errors@0.2.0": { + "integrity": "sha512-6BLOQUscTpZeGljvyQXlWOItQyU71kCdGz7Pi8H8zdw6BI0g3m43iL4xKUVPWtG+qrrL9DTjpdn8eYuCQSRpow==", + "dependencies": [ + "extendable-error" + ] + }, + "@changesets/get-dependents-graph@2.1.2": { + "integrity": "sha512-sgcHRkiBY9i4zWYBwlVyAjEM9sAzs4wYVwJUdnbDLnVG3QwAaia1Mk5P8M7kraTOZN+vBET7n8KyB0YXCbFRLQ==", + "dependencies": [ + "@changesets/types@6.0.0", + "@manypkg/get-packages", + "picocolors", + "semver" + ] + }, + "@changesets/get-release-plan@4.0.4": { + "integrity": "sha512-SicG/S67JmPTrdcc9Vpu0wSQt7IiuN0dc8iR5VScnnTVPfIaLvKmEGRvIaF0kcn8u5ZqLbormZNTO77bCEvyWw==", + "dependencies": [ + "@changesets/assemble-release-plan", + "@changesets/config", + "@changesets/pre", + "@changesets/read", + "@changesets/types@6.0.0", + "@manypkg/get-packages" + ] + }, + "@changesets/get-version-range-type@0.4.0": { + "integrity": "sha512-hwawtob9DryoGTpixy1D3ZXbGgJu1Rhr+ySH2PvTLHvkZuQ7sRT4oQwMh0hbqZH1weAooedEjRsbrWcGLCeyVQ==" + }, + "@changesets/git@3.0.1": { + "integrity": "sha512-pdgHcYBLCPcLd82aRcuO0kxCDbw/yISlOtkmwmE8Odo1L6hSiZrBOsRl84eYG7DRCab/iHnOkWqExqc4wxk2LQ==", + "dependencies": [ + "@changesets/errors", + "@manypkg/get-packages", + "is-subdir", + "micromatch", + "spawndamnit" + ] + }, + "@changesets/logger@0.1.1": { + "integrity": "sha512-OQtR36ZlnuTxKqoW4Sv6x5YIhOmClRd5pWsjZsddYxpWs517R0HkyiefQPIytCVh4ZcC5x9XaG8KTdd5iRQUfg==", + "dependencies": [ + "picocolors" + ] + }, + "@changesets/parse@0.4.0": { + "integrity": "sha512-TS/9KG2CdGXS27S+QxbZXgr8uPsP4yNJYb4BC2/NeFUj80Rni3TeD2qwWmabymxmrLo7JEsytXH1FbpKTbvivw==", + "dependencies": [ + "@changesets/types@6.0.0", + "js-yaml" + ] + }, + "@changesets/pre@2.0.1": { + "integrity": "sha512-vvBJ/If4jKM4tPz9JdY2kGOgWmCowUYOi5Ycv8dyLnEE8FgpYYUo1mgJZxcdtGGP3aG8rAQulGLyyXGSLkIMTQ==", + "dependencies": [ + "@changesets/errors", + "@changesets/types@6.0.0", + "@manypkg/get-packages", + "fs-extra@7.0.1" + ] + }, + "@changesets/read@0.6.1": { + "integrity": "sha512-jYMbyXQk3nwP25nRzQQGa1nKLY0KfoOV7VLgwucI0bUO8t8ZLCr6LZmgjXsiKuRDc+5A6doKPr9w2d+FEJ55zQ==", + "dependencies": [ + "@changesets/git", + "@changesets/logger", + "@changesets/parse", + "@changesets/types@6.0.0", + "fs-extra@7.0.1", + "p-filter", + "picocolors" + ] + }, + "@changesets/should-skip-package@0.1.1": { + "integrity": "sha512-H9LjLbF6mMHLtJIc/eHR9Na+MifJ3VxtgP/Y+XLn4BF7tDTEN1HNYtH6QMcjP1uxp9sjaFYmW8xqloaCi/ckTg==", + "dependencies": [ + "@changesets/types@6.0.0", + "@manypkg/get-packages" + ] + }, + "@changesets/types@4.1.0": { + "integrity": "sha512-LDQvVDv5Kb50ny2s25Fhm3d9QSZimsoUGBsUioj6MC3qbMUCuC8GPIvk/M6IvXx3lYhAs0lwWUQLb+VIEUCECw==" + }, + "@changesets/types@6.0.0": { + "integrity": "sha512-b1UkfNulgKoWfqyHtzKS5fOZYSJO+77adgL7DLRDr+/7jhChN+QcHnbjiQVOz/U+Ts3PGNySq7diAItzDgugfQ==" + }, + "@changesets/write@0.3.2": { + "integrity": "sha512-kDxDrPNpUgsjDbWBvUo27PzKX4gqeKOlhibaOXDJA6kuBisGqNHv/HwGJrAu8U/dSf8ZEFIeHIPtvSlZI1kULw==", + "dependencies": [ + "@changesets/types@6.0.0", + "fs-extra@7.0.1", + "human-id", + "prettier" + ] + }, + "@conventional-changelog/git-client@1.0.1_conventional-commits-filter@5.0.0_conventional-commits-parser@6.0.0": { + "integrity": "sha512-PJEqBwAleffCMETaVm/fUgHldzBE35JFk3/9LL6NUA5EXa3qednu+UT6M7E5iBu3zIQZCULYIiZ90fBYHt6xUw==", + "dependencies": [ + "@types/semver", + "conventional-commits-filter", + "conventional-commits-parser", + "semver" + ] + }, + "@manypkg/find-root@1.1.0": { + "integrity": "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA==", + "dependencies": [ + "@babel/runtime", + "@types/node@12.20.55", + "find-up", + "fs-extra@8.1.0" + ] + }, + "@manypkg/get-packages@1.1.3": { + "integrity": "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==", + "dependencies": [ + "@babel/runtime", + "@changesets/types@4.1.0", + "@manypkg/find-root", + "fs-extra@8.1.0", + "globby", + "read-yaml-file" + ] + }, + "@nodelib/fs.scandir@2.1.5": { + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": [ + "@nodelib/fs.stat", + "run-parallel" + ] + }, + "@nodelib/fs.stat@2.0.5": { + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk@1.2.8": { + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": [ + "@nodelib/fs.scandir", + "fastq" + ] + }, + "@types/node@12.20.55": { + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==" + }, + "@types/node@22.5.4": { + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", + "dependencies": [ + "undici-types" + ] + }, + "@types/semver@7.5.8": { + "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==" + }, + "ansi-colors@4.1.3": { + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "argparse@1.0.10": { + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": [ + "sprintf-js" + ] + }, + "array-ify@1.0.0": { + "integrity": "sha512-c5AMf34bKdvPhQ7tBGhqkgKNUzMr4WUs+WDtC2ZUGOUncbxKMTvqxYctiseW3+L4bA8ec+GcZ6/A/FW4m8ukng==" + }, + "array-union@2.1.0": { + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==" + }, + "better-path-resolve@1.0.0": { + "integrity": "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g==", + "dependencies": [ + "is-windows" + ] + }, + "braces@3.0.3": { + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dependencies": [ + "fill-range" + ] + }, + "chardet@0.7.0": { + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "ci-info@3.9.0": { + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==" + }, + "compare-func@2.0.0": { + "integrity": "sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==", + "dependencies": [ + "array-ify", + "dot-prop" + ] + }, + "conventional-changelog-angular@8.0.0": { + "integrity": "sha512-CLf+zr6St0wIxos4bmaKHRXWAcsCXrJU6F4VdNDrGRK3B8LDLKoX3zuMV5GhtbGkVR/LohZ6MT6im43vZLSjmA==", + "dependencies": [ + "compare-func" + ] + }, + "conventional-changelog-preset-loader@5.0.0": { + "integrity": "sha512-SetDSntXLk8Jh1NOAl1Gu5uLiCNSYenB5tm0YVeZKePRIgDW9lQImromTwLa3c/Gae298tsgOM+/CYT9XAl0NA==" + }, + "conventional-commits-filter@5.0.0": { + "integrity": "sha512-tQMagCOC59EVgNZcC5zl7XqO30Wki9i9J3acbUvkaosCT6JX3EeFwJD7Qqp4MCikRnzS18WXV3BLIQ66ytu6+Q==" + }, + "conventional-commits-parser@6.0.0": { + "integrity": "sha512-TbsINLp48XeMXR8EvGjTnKGsZqBemisPoyWESlpRyR8lif0lcwzqz+NMtYSj1ooF/WYjSuu7wX0CtdeeMEQAmA==", + "dependencies": [ + "meow" + ] + }, + "conventional-recommended-bump@10.0.0_conventional-commits-filter@5.0.0_conventional-commits-parser@6.0.0": { + "integrity": "sha512-RK/fUnc2btot0oEVtrj3p2doImDSs7iiz/bftFCDzels0Qs1mxLghp+DFHMaOC0qiCI6sWzlTDyBFSYuot6pRA==", + "dependencies": [ + "@conventional-changelog/git-client", + "conventional-changelog-preset-loader", + "conventional-commits-filter", + "conventional-commits-parser", + "meow" + ] + }, + "cross-spawn@5.1.0": { + "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "dependencies": [ + "lru-cache", + "shebang-command", + "which" + ] + }, + "detect-indent@6.1.0": { + "integrity": "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==" + }, + "dir-glob@3.0.1": { + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dependencies": [ + "path-type" + ] + }, + "dot-prop@5.3.0": { + "integrity": "sha512-QM8q3zDe58hqUqjraQOmzZ1LIH9SWQJTlEKCH4kJ2oQvLZk7RbQXvtDM2XEq3fwkV9CCvvH4LA0AV+ogFsBM2Q==", + "dependencies": [ + "is-obj" + ] + }, + "enquirer@2.4.1": { + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "dependencies": [ + "ansi-colors", + "strip-ansi" + ] + }, + "esprima@4.0.1": { + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "extendable-error@0.1.7": { + "integrity": "sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==" + }, + "external-editor@3.1.0": { + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dependencies": [ + "chardet", + "iconv-lite", + "tmp" + ] + }, + "fast-glob@3.3.2": { + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": [ + "@nodelib/fs.stat", + "@nodelib/fs.walk", + "glob-parent", + "merge2", + "micromatch" + ] + }, + "fastq@1.17.1": { + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": [ + "reusify" + ] + }, + "fill-range@7.1.1": { + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dependencies": [ + "to-regex-range" + ] + }, + "find-up@4.1.0": { + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": [ + "locate-path", + "path-exists" + ] + }, + "fs-extra@7.0.1": { + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dependencies": [ + "graceful-fs", + "jsonfile", + "universalify" + ] + }, + "fs-extra@8.1.0": { + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dependencies": [ + "graceful-fs", + "jsonfile", + "universalify" + ] + }, + "glob-parent@5.1.2": { + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": [ + "is-glob" + ] + }, + "globby@11.1.0": { + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dependencies": [ + "array-union", + "dir-glob", + "fast-glob", + "ignore", + "merge2", + "slash" + ] + }, + "graceful-fs@4.2.11": { + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "human-id@1.0.2": { + "integrity": "sha512-UNopramDEhHJD+VR+ehk8rOslwSfByxPIZyJRfV739NDhN5LF1fa1MqnzKm2lGTQRjNrjK19Q5fhkgIfjlVUKw==" + }, + "iconv-lite@0.4.24": { + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": [ + "safer-buffer" + ] + }, + "ignore@5.3.2": { + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==" + }, + "is-extglob@2.1.1": { + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob@4.0.3": { + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": [ + "is-extglob" + ] + }, + "is-number@7.0.0": { + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "is-obj@2.0.0": { + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, + "is-subdir@1.2.0": { + "integrity": "sha512-2AT6j+gXe/1ueqbW6fLZJiIw3F8iXGJtt0yDrZaBhAZEG1raiTxKWU+IPqMCzQAXOUCKdA4UDMgacKH25XG2Cw==", + "dependencies": [ + "better-path-resolve" + ] + }, + "is-windows@1.0.2": { + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + }, + "isexe@2.0.0": { + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-yaml@3.14.1": { + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dependencies": [ + "argparse", + "esprima" + ] + }, + "jsonfile@4.0.0": { + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "dependencies": [ + "graceful-fs" + ] + }, + "locate-path@5.0.0": { + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": [ + "p-locate" + ] + }, + "lodash.startcase@4.4.0": { + "integrity": "sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==" + }, + "lru-cache@4.1.5": { + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dependencies": [ + "pseudomap", + "yallist" + ] + }, + "meow@13.2.0": { + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==" + }, + "merge2@1.4.1": { + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" + }, + "micromatch@4.0.8": { + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dependencies": [ + "braces", + "picomatch" + ] + }, + "mri@1.2.0": { + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==" + }, + "os-tmpdir@1.0.2": { + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + }, + "outdent@0.5.0": { + "integrity": "sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==" + }, + "p-filter@2.1.0": { + "integrity": "sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==", + "dependencies": [ + "p-map" + ] + }, + "p-limit@2.3.0": { + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": [ + "p-try" + ] + }, + "p-locate@4.1.0": { + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": [ + "p-limit" + ] + }, + "p-map@2.1.0": { + "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==" + }, + "p-try@2.2.0": { + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-manager-detector@0.2.0": { + "integrity": "sha512-E385OSk9qDcXhcM9LNSe4sdhx8a9mAPrZ4sMLW+tmxl5ZuGtPUcdFu+MPP2jbgiWAZ6Pfe5soGFMd+0Db5Vrog==" + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-type@4.0.0": { + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==" + }, + "picocolors@1.1.0": { + "integrity": "sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==" + }, + "picomatch@2.3.1": { + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, + "pify@4.0.1": { + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "prettier@2.8.8": { + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==" + }, + "pseudomap@1.0.2": { + "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + }, + "queue-microtask@1.2.3": { + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "read-yaml-file@1.1.0": { + "integrity": "sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==", + "dependencies": [ + "graceful-fs", + "js-yaml", + "pify", + "strip-bom" + ] + }, + "regenerator-runtime@0.14.1": { + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" + }, + "resolve-from@5.0.0": { + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==" + }, + "reusify@1.0.4": { + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "run-parallel@1.2.0": { + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dependencies": [ + "queue-microtask" + ] + }, + "safer-buffer@2.1.2": { + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "semver@7.6.3": { + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==" + }, + "shebang-command@1.2.0": { + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dependencies": [ + "shebang-regex" + ] + }, + "shebang-regex@1.0.0": { + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + }, + "signal-exit@3.0.7": { + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "slash@3.0.0": { + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" + }, + "spawndamnit@2.0.0": { + "integrity": "sha512-j4JKEcncSjFlqIwU5L/rp2N5SIPsdxaRsIv678+TZxZ0SRDJTm8JrxJMjE/XuiEZNEir3S8l0Fa3Ke339WI4qA==", + "dependencies": [ + "cross-spawn", + "signal-exit" + ] + }, + "sprintf-js@1.0.3": { + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": [ + "ansi-regex" + ] + }, + "strip-bom@3.0.0": { + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==" + }, + "term-size@2.2.1": { + "integrity": "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg==" + }, + "tmp@0.0.33": { + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dependencies": [ + "os-tmpdir" + ] + }, + "to-regex-range@5.0.1": { + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": [ + "is-number" + ] + }, + "undici-types@6.19.8": { + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, + "universalify@0.1.2": { + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "which@1.3.1": { + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": [ + "isexe" + ] + }, + "yallist@2.1.2": { + "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" + } + }, + "workspace": { + "dependencies": [ + "jsr:@std/assert@1", + "jsr:@std/log@~0.224.9" + ] + } +} diff --git a/mod.ts b/mod.ts new file mode 100644 index 0000000..89e0718 --- /dev/null +++ b/mod.ts @@ -0,0 +1,231 @@ +import { getLogger } from "@std/log"; + +/** + * @module setup-env + * + * This module provides functions to set up environment variables in a Deno application. + * + * The main function in this module is {@link initVariable}, which sets up an environment variable. + * The variable can have three different sources: + * 1. The environment variable itself; + * 2. A file specified by another environment variable with the name `[variable]_PATH`; or + * 3. A default value. + * + * The environment variable takes precedence over the file, which takes precedence over the default value. + * + * The function also validates the environment variable's value using a Zod schema. + * + * After this is set up, you can simply read values synchronously from the environment using `Deno.env.get`. + * + * If, at that point, the environment variable is not set, the application shall throw an {@link EnvNotSetError}. + */ + +/** + * Whereas `envVariable` is the name of an environment variable used in the application, + * this error shall be thrown when the application tries to access an environment variable + * that is not set. + * + * Note that such variables should be set using {@link initVariable} at the beginning + * of the application's execution. + */ +export class EnvNotSetError extends Error { + /** + * @param envVariable name of the environment variable + */ + constructor(envVariable: string, cause?: unknown) { + super( + `Environment variable ${envVariable} is not set.\n` + + `You can also set it by specifying a path to a file ` + + `with its value using ${envVariable}_PATH.`, + ); + this.cause = cause; + } +} + +/** + * Whereas: + * 1. `envVariable` is the name of an environment variable used in the application, + * 2. `defaultValue` is the default value for the environment variable (can be undefined), and + * 3. `validator` is a Zod schema used to validate the environment variable's value, + * + * this function will: + * + * 1. Look for the environment variable `envVariable`; + * 2. If it is not set, try to set it by reading the file specified by `${envVariable}_PATH`; + * 3. If `${envVariable}_PATH` is not set, set the environment variable to `defaultValue`; + * 4. If `defaultValue` is undefined, delete the environment variable; + * 5. Validate the environment variable's value using `validator`. + * + * @param envVariable name of the environment variable + * @param validator Zod schema used to validate the environment variable's value + * @param defaultValue default value for the environment variable + * + * @throws {ConfigParseError} If the final environment variable's value cannot be parsed using `validator`, + * this function will throw a `ConfigParseError`. + * @throws {ConfigFileReadError} If the file at the path specified by `${envVariable}_PATH` cannot be read, + * this function will throw a `ConfigFileReadError`. + */ +export async function initVariable( + envVariable: string, + validator: ZodSchemaCompat, + defaultValue?: string, +): Promise { + logger().debug(`(${envVariable}) Setting up environment variable.`, { + envVariable, + defaultValue, + }); + + let source = `Environment variable ${envVariable}`; + + if (!Deno.env.get(envVariable)) { + source = `File from ${envVariable}_PATH`; + await setFromFile(envVariable); + } + + if (!Deno.env.get(envVariable)) { + source = `Default value ${defaultValue ? `"${defaultValue}"` : "[none]"}`; + setFromDefault(envVariable, defaultValue); + } + + const { error: parseError } = validator.safeParse( + Deno.env.get(envVariable) || defaultValue, + ); + + if (parseError) { + logger().error(`Could not parse variable ${envVariable}.`, { + error: parseError, + value: Deno.env.get(envVariable), + }); + throw new ConfigParseError( + `Could not parse variable ${envVariable}. Details:\n${parseError}`, + ); + } + + logger().info(`Variable: ${envVariable} (using ${source})`, { + envVariable, + value: Deno.env.get(envVariable), + source, + required: validator.isOptional(), + }); +} + +/** + * Whereas + * 1. `envVariable` is the name of an environment variable used in the application not set, + * 2. `pathVariable` is the name of the environment variable that specifies the path to the + * file containing the desiredvalue of `envVariable`, and + * 3. `cause` is the error that occurred while trying to read the file at the path specified by `pathVariable`, + * + * this error shall be thrown when reading the file at the path specified by `pathVariable` fails. + */ +export class ConfigFileReadError extends Error { + constructor( + public readonly envVariable: string, + public readonly pathVariable: string, + cause: Error, + ) { + super( + `Could not read file "${ + Deno.env.get(pathVariable) + }" to set ${envVariable} (based on ${pathVariable}). Details:\n${cause}`, + ); + this.cause = cause; + } +} +export class ConfigParseError extends Error {} + +/** + * Set the environment variable from the default value. + * If the default value is not set, the environment variable is deleted. + * @param envVariable name of the environment variable + * @param defaultValue default value for the environment variable + */ +function setFromDefault(envVariable: string, defaultValue?: string) { + if (defaultValue === undefined) { + logger().debug( + `(${envVariable}) No default value set for environment variable.`, + { + envVariable, + }, + ); + return Deno.env.delete(envVariable); + } + + logger().debug( + `(${envVariable}) Setting environment variable from default.`, + { + envVariable, + defaultValue, + }, + ); + Deno.env.set(envVariable, defaultValue); +} + +/** + * Whereas `envVariable` is the name of an environment variable currently not set, this function will: + * + * 1. Look for an environment variable named `${envVariable}_PATH`. + * 2. If it exists, read the file at the path specified by `${envVariable}_PATH`. + * 3. Set the environment variable `envVariable` to the contents of the file. + * + * If `${envVariable}_PATH` is not set, this function will do nothing. + * @throws {ConfigFileReadError} If the file at the path specified by `${envVariable}_PATH` cannot be read, + * this function will throw a `ConfigFileReadError`. + * + * @param envVariable name of the environment variable + */ +async function setFromFile(envVariable: string): Promise { + const pathVariable = `${envVariable}_PATH`; + + logger().debug( + `(${envVariable}) Trying to read environment variable from file.`, + { + envVariable, + pathVariable, + }, + ); + + const configValuePath = Deno.env.get(`${envVariable}_PATH`); + + if (!configValuePath) { + logger().debug( + `(${envVariable}) No ${envVariable}_PATH environment variable set. Skipping.`, + ); + return; // No file to read + } + + const [fileContent, readFileError] = await Deno.readTextFile(configValuePath) + .then( + (content) => [content, null] as const, + ).catch((error) => [null, error as unknown as Error] as const); + + if (readFileError) { + logger().error(`Could not read file.`, { + envVariable, + pathVariable, + configValuePath, + readFileError, + }); + throw new ConfigFileReadError(envVariable, pathVariable, readFileError); + } + + logger().debug(`(${envVariable}) Setting environment variable from file.`, { + envVariable, + pathVariable, + configValuePath, + }); + + Deno.env.set(envVariable, fileContent); +} + +/** + * Get the logger for this module. + */ +function logger() { + return getLogger("@wuespace/envar"); +} + +type ZodSchemaCompat = { + safeParse: (value: unknown) => { error: Error | null }; + isOptional: () => boolean; +}; diff --git a/mod_test.ts b/mod_test.ts new file mode 100644 index 0000000..b76d578 --- /dev/null +++ b/mod_test.ts @@ -0,0 +1,235 @@ +import { + assertEquals, + assertExists, + assertIsError, + assertNotEquals, + assertRejects, + assertStringIncludes, +} from "@std/assert"; +import { ConsoleHandler, FileHandler, jsonFormatter, setup } from "@std/log"; +import process from "node:process"; +import { + ConfigFileReadError, + ConfigParseError, + EnvNotSetError, + initVariable, +} from "./mod.ts"; + +setup({ + handlers: { + console: new ConsoleHandler("DEBUG"), + file: new FileHandler("DEBUG", { + filename: "./log.jsonl", + mode: "a", + formatter: jsonFormatter, + }), + }, + loggers: { + default: { + level: "DEBUG", + handlers: ["console", "file"], + }, + "@wuespace/envar": { + level: "DEBUG", + handlers: ["file"], + }, + }, +}); + +const PARSE_ERROR = new Error("parse error"); + +const OPTIONAL_PASSING = { + isOptional: () => true, + safeParse: () => ({ error: null }), +}; +const OPTIONAL_FAILING = { + isOptional: () => true, + safeParse: () => ({ error: PARSE_ERROR }), +}; +const REQUIRED_PASSING = { + isOptional: () => false, + safeParse: (val: unknown) => ({ + error: typeof val === "string" ? null : PARSE_ERROR, + }), +}; +const REQUIRED_FAILING = { + isOptional: () => false, + safeParse: () => ({ error: PARSE_ERROR }), +}; + +Deno.test("EnvNotSetError", async (t) => { + await t.step("without cause", () => { + const err = new EnvNotSetError("TEST"); + assertStringIncludes(err.message, "TEST"); + assertStringIncludes(err.message, "TEST_PATH"); + assertIsError(err); + }); + + await t.step("with cause", () => { + const err = new EnvNotSetError("TEST", new Error("cause")); + assertStringIncludes(err.message, "TEST"); + assertStringIncludes(err.message, "TEST_PATH"); + assertEquals(err.cause, new Error("cause")); + assertIsError(err); + }); +}); + +Deno.test("initVariable", async (t) => { + const ENV_VAR = "TEST"; + + const EXISTING_FILE = import.meta.filename; + const NONEXISTING_FILE = import.meta.filename + ".nonexistent"; + + const DEFAULT_VALUE = "default"; + + function prepare(TEST?: string, TEST_PATH?: string) { + Deno.env.delete(ENV_VAR); + Deno.env.delete(ENV_VAR + "_PATH"); + TEST && Deno.env.set(ENV_VAR, TEST); + TEST_PATH && Deno.env.set(ENV_VAR + "_PATH", TEST_PATH); + } + + await t.step("Value from Environment", async (t) => { + prepare("value"); + await initVariable(ENV_VAR, REQUIRED_PASSING); + assertEquals( + Deno.env.get(ENV_VAR), + "value", + "Expected the value to match the Environment Variable", + ); + assertEquals( + process.env[ENV_VAR], + "value", + "Expected the value to match the Environment Variable in process.env", + ); + prepare("value2"); + await initVariable(ENV_VAR, REQUIRED_PASSING); + assertEquals( + Deno.env.get(ENV_VAR), + "value2", + "Expected the value to match the Environment Variable", + ); + assertEquals( + process.env[ENV_VAR], + "value2", + "Expected the value to match the Environment Variable in process.env", + ); + + prepare("value"); + assertRejects( + () => initVariable(ENV_VAR, REQUIRED_FAILING), + ConfigParseError, + undefined, + "Expected a ConfigParseError when the value cannot be parsed", + ); + assertRejects( + () => initVariable(ENV_VAR, OPTIONAL_FAILING), + ConfigParseError, + undefined, + "Expected a ConfigParseError when the value cannot be parsed", + ); + + prepare("value", EXISTING_FILE); + await initVariable(ENV_VAR, REQUIRED_PASSING); + assertEquals( + Deno.env.get(ENV_VAR), + "value", + `Expected the Environment Variable to take precedence over the ${ENV_VAR}_PATH file`, + ); + assertEquals( + process.env[ENV_VAR], + "value", + `Expected the Environment Variable to take precedence over the ${ENV_VAR}_PATH file in process.env`, + ); + prepare("value2"); + await initVariable(ENV_VAR, REQUIRED_PASSING, DEFAULT_VALUE); + assertEquals( + Deno.env.get(ENV_VAR), + "value2", + "Expected the Environment Variable to take precedence over the default value", + ); + assertEquals( + process.env[ENV_VAR], + "value2", + "Expected the Environment Variable to take precedence over the default value in process.env", + ); + }); + + await t.step(`Value from ${ENV_VAR}_PATH file`, async (t) => { + prepare(undefined, EXISTING_FILE); + await initVariable(ENV_VAR, REQUIRED_PASSING); + assertExists( + Deno.env.get(ENV_VAR), + `Expected the value to be filled from the ${ENV_VAR}_PATH file`, + ); + assertExists( + process.env[ENV_VAR], + `Expected the value to be filled from the ${ENV_VAR}_PATH file in process.env`, + ); + + prepare(undefined, EXISTING_FILE); + assertRejects( + () => initVariable(ENV_VAR, REQUIRED_FAILING), + ConfigParseError, + undefined, + `Expected a ConfigParseError when the value cannot be parsed`, + ); + + prepare(undefined, NONEXISTING_FILE); + assertRejects( + () => initVariable(ENV_VAR, REQUIRED_PASSING), + ConfigFileReadError, + undefined, + "Expected a ConfigFileReadError when the file does not exist", + ); + + prepare(undefined, EXISTING_FILE); + await initVariable(ENV_VAR, REQUIRED_PASSING, DEFAULT_VALUE); + assertNotEquals( + Deno.env.get(ENV_VAR), + DEFAULT_VALUE, + `Expected the ${ENV_VAR}_PATH file to take precedence over the default value`, + ); + assertNotEquals( + process.env[ENV_VAR], + DEFAULT_VALUE, + `Expected the ${ENV_VAR}_PATH file to take precedence over the default value in process.env`, + ); + }); + + await t.step("value from default", async (t) => { + prepare(); + await initVariable(ENV_VAR, REQUIRED_PASSING, DEFAULT_VALUE); + assertEquals( + Deno.env.get(ENV_VAR), + DEFAULT_VALUE, + "Expected the default value to be used", + ); + assertEquals( + process.env[ENV_VAR], + DEFAULT_VALUE, + "Expected the default value to be used in process.env", + ); + + prepare(); + assertRejects( + () => initVariable(ENV_VAR, REQUIRED_FAILING, DEFAULT_VALUE), + ConfigParseError, + undefined, + "Expected a ConfigParseError when the default value cannot be parsed", + ); + + prepare(); + await initVariable(ENV_VAR, OPTIONAL_PASSING, DEFAULT_VALUE); + assertEquals( + Deno.env.get(ENV_VAR), + DEFAULT_VALUE, + "Expected the default value to be used", + ); + assertEquals( + process.env[ENV_VAR], + DEFAULT_VALUE, + "Expected the default value to be used in process.env", + ); + }); +}); diff --git a/scripts/release.ts b/scripts/release.ts new file mode 100644 index 0000000..cae074b --- /dev/null +++ b/scripts/release.ts @@ -0,0 +1,6 @@ +import 'npm:conventional-changelog-angular'; +import {Bumper} from 'npm:conventional-recommended-bump'; + +const bumper = new Bumper(Deno.cwd()).loadPreset('angular'); +const bump = await bumper.bump(); +console.log(bump.releaseType); // major, minor, patch