├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── dependabot.yml └── workflows │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .vscode ├── extensions.json └── settings.json ├── CHANGELOG.md ├── LICENSE ├── README.md ├── biome.json ├── package.json ├── pnpm-lock.yaml ├── src └── index.ts ├── test ├── index.test.ts └── types.test.ts ├── tsconfig.json └── tsdown.config.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at any of 63 | their social media. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 126 | at [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | If you wish to contribute to the project, feel free to fork the repository and submit a 4 | pull request. We use Biome to enforce a consistent coding style, so having that set up in your editor of choice 5 | is a great boon to your development process. 6 | 7 | ## Setup 8 | 9 | To get ready to work on the codebase, please do the following: 10 | 11 | 1. Fork & clone the repository, and make sure you're on the **main** branch 12 | 2. Run `pnpm install --frozen-lockfile` 13 | 3. Code! 14 | 4. Run `node --run check`, `node --run typecheck` and `node --run test` 15 | 5. [Submit a pull request](https://github.com/SuperchupuDev/neon-env/compare) 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | 8 | - package-ecosystem: 'github-actions' 9 | directory: '/' 10 | schedule: 11 | interval: 'weekly' 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: [main] 5 | pull_request: 6 | branches: [main] 7 | jobs: 8 | biome: 9 | name: Biome 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Setup Biome 16 | uses: biomejs/setup-biome@v2 17 | 18 | - name: Run Biome 19 | run: biome ci 20 | 21 | tests: 22 | name: Tests 23 | runs-on: ubuntu-latest 24 | steps: 25 | - name: Checkout repository 26 | uses: actions/checkout@v4 27 | 28 | - name: Install pnpm 29 | uses: pnpm/action-setup@v4 30 | 31 | - name: Install Node 24 32 | uses: actions/setup-node@v4 33 | with: 34 | node-version: 24 35 | cache: 'pnpm' 36 | 37 | - name: Install dependencies 38 | run: pnpm install --frozen-lockfile 39 | 40 | - name: Run tests 41 | run: node --run test 42 | 43 | typescript: 44 | name: TypeScript 45 | runs-on: ubuntu-latest 46 | steps: 47 | - name: Checkout repository 48 | uses: actions/checkout@v4 49 | 50 | - name: Install pnpm 51 | uses: pnpm/action-setup@v4 52 | 53 | - name: Install Node 24 54 | uses: actions/setup-node@v4 55 | with: 56 | node-version: 24 57 | cache: 'pnpm' 58 | 59 | - name: Install dependencies 60 | run: pnpm install --frozen-lockfile 61 | 62 | - name: Run TypeScript compiler 63 | run: node --run typecheck 64 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Package 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | permissions: 9 | contents: read 10 | id-token: write 11 | steps: 12 | - name: Checkout repository 13 | uses: actions/checkout@v4 14 | 15 | - name: Install pnpm 16 | uses: pnpm/action-setup@v4 17 | 18 | - name: Install Node 24 19 | uses: actions/setup-node@v4 20 | with: 21 | node-version: 24 22 | registry-url: 'https://registry.npmjs.org' 23 | cache: 'pnpm' 24 | 25 | - name: Install dependencies 26 | run: pnpm install --frozen-lockfile 27 | 28 | - name: Build 29 | run: node --run build 30 | 31 | - name: Publish to npm 32 | run: pnpm publish --provenance --no-git-checks 33 | env: 34 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Packages 2 | node_modules/ 3 | 4 | # Log files 5 | logs/ 6 | *.log 7 | npm-debug.log* 8 | 9 | # Runtime data 10 | pids 11 | *.pid 12 | *.seed 13 | 14 | # Env 15 | .env 16 | 17 | # Dist 18 | dist/ 19 | 20 | # Miscellaneous 21 | .tmp/ 22 | .vscode/* 23 | !.vscode/extensions.json 24 | !.vscode/settings.json 25 | .idea/ 26 | .DS_Store 27 | .turbo 28 | tsconfig.tsbuildinfo 29 | coverage/ 30 | 31 | # yarn 32 | .pnp.* 33 | .yarn/* 34 | !.yarn/patches 35 | !.yarn/plugins 36 | !.yarn/releases 37 | !.yarn/sdks 38 | !.yarn/versions 39 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "biomejs.biome", 4 | "christian-kohler.npm-intellisense", 5 | "christian-kohler.path-intellisense", 6 | "codezombiech.gitignore", 7 | "connor4312.nodejs-testing", 8 | "eamodio.gitlens", 9 | "github.vscode-pull-request-github" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll": "explicit", 4 | "source.organizeImports.biome": "explicit" 5 | }, 6 | "editor.defaultFormatter": "biomejs.biome", 7 | "editor.formatOnSave": true, 8 | "files.eol": "\n", 9 | "files.insertFinalNewline": true, 10 | "nodejs-testing.extensions": [ 11 | { 12 | "extensions": ["ts"], 13 | "parameters": ["--experimental-strip-types"] 14 | } 15 | ], 16 | "npm.packageManager": "pnpm", 17 | "typescript.tsdk": "node_modules/typescript/lib" 18 | } 19 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 4 | 5 | Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog). 6 | 7 | #### [0.2.3](https://github.com/SuperchupuDev/neon-env/compare/0.2.2...0.2.3) 8 | 9 | - update project structure [`60cf251`](https://github.com/SuperchupuDev/neon-env/commit/60cf2515eb6036db160c51d7ead2037aea4d3e9b) 10 | 11 | #### [0.2.2](https://github.com/SuperchupuDev/neon-env/compare/0.2.1...0.2.2) 12 | 13 | > 7 August 2024 14 | 15 | - Bump braces from 3.0.2 to 3.0.3 in the npm_and_yarn group [`#198`](https://github.com/SuperchupuDev/neon-env/pull/198) 16 | - Bump pnpm/action-setup from 3 to 4 [`#188`](https://github.com/SuperchupuDev/neon-env/pull/188) 17 | - bump pnpm to v9 [`ee457e0`](https://github.com/SuperchupuDev/neon-env/commit/ee457e0eebbeb3498cb2b5636a7f18d4ee29047a) 18 | - switch to node test runner [`efa73c1`](https://github.com/SuperchupuDev/neon-env/commit/efa73c18dc9c6f54216b54fc9f5aaebaf29951a2) 19 | - replace eslint with biome linter [`28ae0d9`](https://github.com/SuperchupuDev/neon-env/commit/28ae0d9c5bf6f729160b2268edeb5ec4b137025a) 20 | - bump deps [`bc4ae9d`](https://github.com/SuperchupuDev/neon-env/commit/bc4ae9df774a826a1710dbf2baf127fc14e144d3) 21 | - fix publish workflow and bump deps [`3a16a65`](https://github.com/SuperchupuDev/neon-env/commit/3a16a65318770062d63aece401ecaad0bf88171c) 22 | - add publish workflow [`e878608`](https://github.com/SuperchupuDev/neon-env/commit/e87860885c897e9d6d598ba3e952fe3911e82f7a) 23 | - batch errors instead of throwing the first one [`5d38561`](https://github.com/SuperchupuDev/neon-env/commit/5d38561cd51daa150f30c09fe40cd9b1240d630a) 24 | - set `sideEffects` field [`26e7da4`](https://github.com/SuperchupuDev/neon-env/commit/26e7da44d0763d7b7ecbd78302c8ab90bb75ae79) 25 | - update license to claim copyright over the modifications from this fork [`823f07b`](https://github.com/SuperchupuDev/neon-env/commit/823f07b72a877a2e9d0bab235a10b8ad3db06dcf) 26 | 27 | #### [0.2.1](https://github.com/SuperchupuDev/neon-env/compare/0.2.0...0.2.1) 28 | 29 | > 18 February 2024 30 | 31 | - Bump vite from 4.3.9 to 4.5.2 [`#150`](https://github.com/SuperchupuDev/neon-env/pull/150) 32 | - Bump word-wrap from 1.2.3 to 1.2.5 [`#127`](https://github.com/SuperchupuDev/neon-env/pull/127) 33 | - fix exported types by bumping `tsup`, update project structure [`900d538`](https://github.com/SuperchupuDev/neon-env/commit/900d538a0c130af5c1e042c0efba3447ae1e05fb) 34 | - bump deps [`9756cf6`](https://github.com/SuperchupuDev/neon-env/commit/9756cf6173080129d4bcd2b12728ea8d675d01ec) 35 | - add prettier to workflow [`e24b2e3`](https://github.com/SuperchupuDev/neon-env/commit/e24b2e3746e4c5d144c2eae24724a3104247fa44) 36 | - re-enable recommended type checking eslint rules [`2e1f38a`](https://github.com/SuperchupuDev/neon-env/commit/2e1f38a03df64d4d79f42f4ba2d3206339ffc013) 37 | - fix version typo [`a655f80`](https://github.com/SuperchupuDev/neon-env/commit/a655f800260feb0e7e91368ed889a3086e5333ed) 38 | 39 | #### [0.2.0](https://github.com/SuperchupuDev/neon-env/compare/0.1.3...0.2.0) 40 | 41 | > 19 March 2023 42 | 43 | - use const type parameters, bump typescript to 5.0 [`a04525a`](https://github.com/SuperchupuDev/neon-env/commit/a04525ab1d57f5e40acb9df441c5f909e58af27c) 44 | 45 | #### [0.1.3](https://github.com/SuperchupuDev/neon-env/compare/0.1.2...0.1.3) 46 | 47 | > 18 December 2022 48 | 49 | - switch to tsup for better cjs/esm support [`efb61b8`](https://github.com/SuperchupuDev/neon-env/commit/efb61b82f39c783c3deff608eb4af91180d78584) 50 | 51 | #### [0.1.2](https://github.com/SuperchupuDev/neon-env/compare/0.1.1...0.1.2) 52 | 53 | > 18 December 2022 54 | 55 | - Add strict number validation [`#6`](https://github.com/SuperchupuDev/neon-env/pull/6) 56 | - enable source maps [`6ab981d`](https://github.com/SuperchupuDev/neon-env/commit/6ab981d90fd85c6804f4b2db385c16972ce5a5c5) 57 | - bump deps [`31f292e`](https://github.com/SuperchupuDev/neon-env/commit/31f292e73cf2c4834f71132762a4f6d04da98283) 58 | 59 | #### [0.1.1](https://github.com/SuperchupuDev/neon-env/compare/0.1.0...0.1.1) 60 | 61 | > 30 October 2022 62 | 63 | - chore: support node v14.18 or later but node v15 [`#2`](https://github.com/SuperchupuDev/neon-env/pull/2) 64 | 65 | #### 0.1.0 66 | 67 | > 25 October 2022 68 | 69 | - initial commit [`3f77290`](https://github.com/SuperchupuDev/neon-env/commit/3f7729006640e2ab6908c4d6e4efab68b68e66b5) 70 | - Initial Commit [`1309f0c`](https://github.com/SuperchupuDev/neon-env/commit/1309f0c2e4481e302321391f8537388d4478ee6b) 71 | - rename `config` to `createEnv`, add tests [`8d947bf`](https://github.com/SuperchupuDev/neon-env/commit/8d947bf3ad46dcfb2835fe6097bcbf41b214252e) 72 | - Initial commit [`c6c8ddc`](https://github.com/SuperchupuDev/neon-env/commit/c6c8ddccd14485f353f11d299ee7710ef49c70ce) 73 | - Add keywords [`c235e8e`](https://github.com/SuperchupuDev/neon-env/commit/c235e8e7ae5d077f56b7a2b6e6f26c571144d222) 74 | - Add support for boolean [`83548f5`](https://github.com/SuperchupuDev/neon-env/commit/83548f5e59bd8babfccc67cb27371377b024ae1e) 75 | - support space after commas in arrays [`436c611`](https://github.com/SuperchupuDev/neon-env/commit/436c6112c07eb99eb2011f55cdc1b4872d94cc76) 76 | - use correct warn syntax [`9d497a6`](https://github.com/SuperchupuDev/neon-env/commit/9d497a66a12c4943d4456dfbfe057611ebf00895) 77 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Madeline Gurriarán 4 | Copyright (c) 2020 Luke James 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # neon-env 2 | 3 | ## Merged with [typed-env](https://www.npmjs.com/package/typed-env), development will continue there, use that instead. 4 | 5 | > Basically [typed-env](https://www.npmjs.com/package/typed-env), but with support for choices and maintained. 6 | 7 | A typed environment variable parser with support for choices, custom parsers, and more. 8 | 9 | ## Installation 10 | 11 | > Node.js ^14.18.0, v16 or newer is required. [See issue #1](https://github.com/SuperchupuDev/neon-env/issues/1#issuecomment-1296366710). 12 | 13 | ```sh-session 14 | npm i neon-env 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```ts 20 | import { createEnv } from 'neon-env'; 21 | 22 | const env = createEnv({ 23 | PORT: { type: 'number', default: 80 } 24 | }); 25 | 26 | env.PORT; // number 27 | ``` 28 | 29 | ## Features 30 | 31 | - Strongly typed 32 | - Supports [custom parsers](#parser) 33 | - Supports optional environment variables 34 | - Supports limiting the possible values (see [Choices](#choices)) 35 | - Supports passing custom environments (see [Options](#options)) 36 | 37 | ### Choices 38 | 39 | ```ts 40 | import { createEnv } from 'neon-env'; 41 | 42 | const env = createEnv({ 43 | NODE_ENV: { 44 | type: 'string', 45 | choices: ['development', 'production'] 46 | } 47 | }); 48 | 49 | env.NODE_ENV; // 'development' | 'production' 50 | ``` 51 | 52 | As of 0.2.0, you no longer need to add `as const` to the choices array to get the best type safety. 53 | 54 | ### Parser 55 | 56 | You can pass a `parser` function to return your own custom type 57 | 58 | ```ts 59 | import { createEnv } from 'neon-env'; 60 | 61 | const env = createEnv({ 62 | HOMEPAGE: { parser: url => new URL(url) } 63 | }); 64 | 65 | env.HOMEPAGE; // URL 66 | ``` 67 | 68 | ### Options 69 | 70 | If you want to use a custom env, pass `env` in the `options` parameter, otherwise it will load from `process.env` 71 | 72 | ```ts 73 | interface Options { 74 | env?: Record | NodeJS.ProcessEnv; 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "vcs": { 4 | "enabled": true, 5 | "clientKind": "git", 6 | "defaultBranch": "main", 7 | "useIgnoreFile": true 8 | }, 9 | "formatter": { 10 | "indentStyle": "space", 11 | "indentWidth": 2, 12 | "lineWidth": 120 13 | }, 14 | "javascript": { 15 | "formatter": { 16 | "arrowParentheses": "asNeeded", 17 | "quoteStyle": "single", 18 | "semicolons": "always", 19 | "trailingCommas": "none" 20 | } 21 | }, 22 | "linter": { 23 | "rules": { 24 | "recommended": true, 25 | "correctness": { 26 | "noNewSymbol": "error", 27 | "noUndeclaredVariables": "error", 28 | "noUnusedPrivateClassMembers": "error", 29 | "noUnusedVariables": "error" 30 | }, 31 | "style": { 32 | "noNegationElse": "error", 33 | "useBlockStatements": "error", 34 | "useConsistentArrayType": "error", 35 | "useEnumInitializers": "off", 36 | "useForOf": "error", 37 | "useNodeAssertStrict": "error", 38 | "useShorthandAssign": "error" 39 | }, 40 | "suspicious": { 41 | "noEmptyBlockStatements": "error", 42 | "useAwait": "error" 43 | } 44 | } 45 | }, 46 | "organizeImports": { 47 | "enabled": true 48 | }, 49 | "overrides": [ 50 | { 51 | "include": ["package.json"], 52 | "formatter": { 53 | "lineWidth": 1 54 | } 55 | } 56 | ] 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "neon-env", 3 | "version": "0.2.3", 4 | "description": "Typed env parser", 5 | "scripts": { 6 | "build": "tsdown", 7 | "changelog": "auto-changelog -p --commit-limit false", 8 | "check": "biome check", 9 | "format": "biome format --write", 10 | "lint": "biome lint", 11 | "lint:fix": "biome lint --fix --unsafe", 12 | "test": "node --test", 13 | "typecheck": "tsc" 14 | }, 15 | "main": "dist/index.js", 16 | "module": "dist/index.mjs", 17 | "types": "dist/index.d.ts", 18 | "exports": { 19 | "import": "./dist/index.mjs", 20 | "require": "./dist/index.js", 21 | "types": "./dist/index.d.ts" 22 | }, 23 | "sideEffects": "false", 24 | "files": [ 25 | "dist" 26 | ], 27 | "author": "Superchupu", 28 | "license": "MIT", 29 | "keywords": [ 30 | "environment variables", 31 | "env", 32 | "parser", 33 | "env parser", 34 | "typescript", 35 | "typed", 36 | "config" 37 | ], 38 | "repository": { 39 | "type": "git", 40 | "url": "git+https://github.com/SuperchupuDev/neon-env.git" 41 | }, 42 | "bugs": { 43 | "url": "https://github.com/SuperchupuDev/neon-env/issues" 44 | }, 45 | "homepage": "https://github.com/SuperchupuDev/neon-env#readme", 46 | "devDependencies": { 47 | "@biomejs/biome": "^1.9.4", 48 | "@types/node": "^22.15.21", 49 | "auto-changelog": "^2.5.0", 50 | "tsdown": "^0.12.3", 51 | "typescript": "^5.8.3" 52 | }, 53 | "engines": { 54 | "node": "^14.18.0 || >=16.0.0" 55 | }, 56 | "publishConfig": { 57 | "access": "public", 58 | "provenance": true 59 | }, 60 | "packageManager": "pnpm@10.11.0" 61 | } 62 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '9.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: 10 | devDependencies: 11 | '@biomejs/biome': 12 | specifier: ^1.9.4 13 | version: 1.9.4 14 | '@types/node': 15 | specifier: ^22.15.21 16 | version: 22.15.21 17 | auto-changelog: 18 | specifier: ^2.5.0 19 | version: 2.5.0 20 | tsdown: 21 | specifier: ^0.12.3 22 | version: 0.12.3(typescript@5.8.3) 23 | typescript: 24 | specifier: ^5.8.3 25 | version: 5.8.3 26 | 27 | packages: 28 | 29 | '@babel/generator@7.27.1': 30 | resolution: {integrity: sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==} 31 | engines: {node: '>=6.9.0'} 32 | 33 | '@babel/helper-string-parser@7.27.1': 34 | resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} 35 | engines: {node: '>=6.9.0'} 36 | 37 | '@babel/helper-validator-identifier@7.27.1': 38 | resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} 39 | engines: {node: '>=6.9.0'} 40 | 41 | '@babel/parser@7.27.2': 42 | resolution: {integrity: sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==} 43 | engines: {node: '>=6.0.0'} 44 | hasBin: true 45 | 46 | '@babel/types@7.27.1': 47 | resolution: {integrity: sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==} 48 | engines: {node: '>=6.9.0'} 49 | 50 | '@biomejs/biome@1.9.4': 51 | resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==} 52 | engines: {node: '>=14.21.3'} 53 | hasBin: true 54 | 55 | '@biomejs/cli-darwin-arm64@1.9.4': 56 | resolution: {integrity: sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==} 57 | engines: {node: '>=14.21.3'} 58 | cpu: [arm64] 59 | os: [darwin] 60 | 61 | '@biomejs/cli-darwin-x64@1.9.4': 62 | resolution: {integrity: sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==} 63 | engines: {node: '>=14.21.3'} 64 | cpu: [x64] 65 | os: [darwin] 66 | 67 | '@biomejs/cli-linux-arm64-musl@1.9.4': 68 | resolution: {integrity: sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==} 69 | engines: {node: '>=14.21.3'} 70 | cpu: [arm64] 71 | os: [linux] 72 | 73 | '@biomejs/cli-linux-arm64@1.9.4': 74 | resolution: {integrity: sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==} 75 | engines: {node: '>=14.21.3'} 76 | cpu: [arm64] 77 | os: [linux] 78 | 79 | '@biomejs/cli-linux-x64-musl@1.9.4': 80 | resolution: {integrity: sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==} 81 | engines: {node: '>=14.21.3'} 82 | cpu: [x64] 83 | os: [linux] 84 | 85 | '@biomejs/cli-linux-x64@1.9.4': 86 | resolution: {integrity: sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==} 87 | engines: {node: '>=14.21.3'} 88 | cpu: [x64] 89 | os: [linux] 90 | 91 | '@biomejs/cli-win32-arm64@1.9.4': 92 | resolution: {integrity: sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==} 93 | engines: {node: '>=14.21.3'} 94 | cpu: [arm64] 95 | os: [win32] 96 | 97 | '@biomejs/cli-win32-x64@1.9.4': 98 | resolution: {integrity: sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==} 99 | engines: {node: '>=14.21.3'} 100 | cpu: [x64] 101 | os: [win32] 102 | 103 | '@emnapi/core@1.4.3': 104 | resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} 105 | 106 | '@emnapi/runtime@1.4.3': 107 | resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} 108 | 109 | '@emnapi/wasi-threads@1.0.2': 110 | resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} 111 | 112 | '@jridgewell/gen-mapping@0.3.8': 113 | resolution: {integrity: sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==} 114 | engines: {node: '>=6.0.0'} 115 | 116 | '@jridgewell/resolve-uri@3.1.2': 117 | resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} 118 | engines: {node: '>=6.0.0'} 119 | 120 | '@jridgewell/set-array@1.2.1': 121 | resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} 122 | engines: {node: '>=6.0.0'} 123 | 124 | '@jridgewell/sourcemap-codec@1.5.0': 125 | resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} 126 | 127 | '@jridgewell/trace-mapping@0.3.25': 128 | resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} 129 | 130 | '@napi-rs/wasm-runtime@0.2.10': 131 | resolution: {integrity: sha512-bCsCyeZEwVErsGmyPNSzwfwFn4OdxBj0mmv6hOFucB/k81Ojdu68RbZdxYsRQUPc9l6SU5F/cG+bXgWs3oUgsQ==} 132 | 133 | '@oxc-project/runtime@0.71.0': 134 | resolution: {integrity: sha512-QwoF5WUXIGFQ+hSxWEib4U/aeLoiDN9JlP18MnBgx9LLPRDfn1iICtcow7Jgey6HLH4XFceWXQD5WBJ39dyJcw==} 135 | engines: {node: '>=6.9.0'} 136 | 137 | '@oxc-project/types@0.71.0': 138 | resolution: {integrity: sha512-5CwQ4MI+P4MQbjLWXgNurA+igGwu/opNetIE13LBs9+V93R64MLvDKOOLZIXSzEfovU3Zef3q3GjPnMTgJTn2w==} 139 | 140 | '@quansync/fs@0.1.3': 141 | resolution: {integrity: sha512-G0OnZbMWEs5LhDyqy2UL17vGhSVHkQIfVojMtEWVenvj0V5S84VBgy86kJIuNsGDp2p7sTKlpSIpBUWdC35OKg==} 142 | engines: {node: '>=20.0.0'} 143 | 144 | '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': 145 | resolution: {integrity: sha512-Mp0/gqiPdepHjjVm7e0yL1acWvI0rJVVFQEADSezvAjon9sjQ7CEg9JnXICD4B1YrPmN9qV/e7cQZCp87tTV4w==} 146 | cpu: [arm64] 147 | os: [darwin] 148 | 149 | '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': 150 | resolution: {integrity: sha512-40re4rMNrsi57oavRzIOpRGmg3QRlW6Ea8Q3znaqgOuJuKVrrm2bIQInTfkZJG7a4/5YMX7T951d0+toGLTdCA==} 151 | cpu: [x64] 152 | os: [darwin] 153 | 154 | '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': 155 | resolution: {integrity: sha512-8BDM939bbMariZupiHp3OmP5N+LXPT4mULA0hZjDaq970PCxv4krZOSMG+HkWUUwmuQROtV+/00xw39EO0P+8g==} 156 | cpu: [x64] 157 | os: [freebsd] 158 | 159 | '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': 160 | resolution: {integrity: sha512-sntsPaPgrECpBB/+2xrQzVUt0r493TMPI+4kWRMhvMsmrxOqH1Ep5lM0Wua/ZdbfZNwm1aVa5pcESQfNfM4Fhw==} 161 | cpu: [arm] 162 | os: [linux] 163 | 164 | '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': 165 | resolution: {integrity: sha512-5clBW/I+er9F2uM1OFjJFWX86y7Lcy0M+NqsN4s3o07W+8467Zk8oQa4B45vdaXoNUF/yqIAgKkA/OEdQDxZqA==} 166 | cpu: [arm64] 167 | os: [linux] 168 | 169 | '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': 170 | resolution: {integrity: sha512-wv+rnAfQDk9p/CheX8/Kmqk2o1WaFa4xhWI9gOyDMk/ljvOX0u0ubeM8nI1Qfox7Tnh71eV5AjzSePXUhFOyOg==} 171 | cpu: [arm64] 172 | os: [linux] 173 | 174 | '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': 175 | resolution: {integrity: sha512-gxD0/xhU4Py47IH3bKZbWtvB99tMkUPGPJFRfSc5UB9Osoje0l0j1PPbxpUtXIELurYCqwLBKXIMTQGifox1BQ==} 176 | cpu: [x64] 177 | os: [linux] 178 | 179 | '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': 180 | resolution: {integrity: sha512-HotuVe3XUjDwqqEMbm3o3IRkP9gdm8raY/btd/6KE3JGLF/cv4+3ff1l6nOhAZI8wulWDPEXPtE7v+HQEaTXnA==} 181 | cpu: [x64] 182 | os: [linux] 183 | 184 | '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': 185 | resolution: {integrity: sha512-8Cx+ucbd8n2dIr21FqBh6rUvTVL0uTgEtKR7l+MUZ5BgY4dFh1e4mPVX8oqmoYwOxBiXrsD2JIOCz4AyKLKxWA==} 186 | engines: {node: '>=14.21.3'} 187 | cpu: [wasm32] 188 | 189 | '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': 190 | resolution: {integrity: sha512-Vhq5vikrVDxAa75fxsyqj0c0Y/uti/TwshXI71Xb8IeUQJOBnmLUsn5dgYf5ljpYYkNa0z9BPAvUDIDMmyDi+w==} 191 | cpu: [arm64] 192 | os: [win32] 193 | 194 | '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': 195 | resolution: {integrity: sha512-lN7RIg9Iugn08zP2aZN9y/MIdG8iOOCE93M1UrFlrxMTqPf8X+fDzmR/OKhTSd1A2pYNipZHjyTcb5H8kyQSow==} 196 | cpu: [ia32] 197 | os: [win32] 198 | 199 | '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': 200 | resolution: {integrity: sha512-7/7cLIn48Y+EpQ4CePvf8reFl63F15yPUlg4ZAhl+RXJIfydkdak1WD8Ir3AwAO+bJBXzrfNL+XQbxm0mcQZmw==} 201 | cpu: [x64] 202 | os: [win32] 203 | 204 | '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': 205 | resolution: {integrity: sha512-8sExkWRK+zVybw3+2/kBkYBFeLnEUWz1fT7BLHplpzmtqkOfTbAQ9gkt4pzwGIIZmg4Qn5US5ACjUBenrhezwQ==} 206 | 207 | '@tybys/wasm-util@0.9.0': 208 | resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} 209 | 210 | '@types/node@22.15.21': 211 | resolution: {integrity: sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ==} 212 | 213 | ansis@4.0.0: 214 | resolution: {integrity: sha512-P8nrHI1EyW9OfBt1X7hMSwGN2vwRuqHSKJAT1gbLWZRzDa24oHjYwGHvEgHeBepupzk878yS/HBZ0NMPYtbolw==} 215 | engines: {node: '>=14'} 216 | 217 | ast-kit@2.0.0: 218 | resolution: {integrity: sha512-P63jzlYNz96MF9mCcprU+a7I5/ZQ5QAn3y+mZcPWEcGV3CHF/GWnkFPj3oCrWLUjL47+PD9PNiCUdXxw0cWdsg==} 219 | engines: {node: '>=20.18.0'} 220 | 221 | auto-changelog@2.5.0: 222 | resolution: {integrity: sha512-UTnLjT7I9U2U/xkCUH5buDlp8C7g0SGChfib+iDrJkamcj5kaMqNKHNfbKJw1kthJUq8sUo3i3q2S6FzO/l/wA==} 223 | engines: {node: '>=8.3'} 224 | hasBin: true 225 | 226 | birpc@2.3.0: 227 | resolution: {integrity: sha512-ijbtkn/F3Pvzb6jHypHRyve2QApOCZDR25D/VnkY2G/lBNcXCTsnsCxgY4k4PkVB7zfwzYbY3O9Lcqe3xufS5g==} 228 | 229 | cac@6.7.14: 230 | resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} 231 | engines: {node: '>=8'} 232 | 233 | chokidar@4.0.3: 234 | resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} 235 | engines: {node: '>= 14.16.0'} 236 | 237 | commander@7.2.0: 238 | resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} 239 | engines: {node: '>= 10'} 240 | 241 | debug@4.4.1: 242 | resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} 243 | engines: {node: '>=6.0'} 244 | peerDependencies: 245 | supports-color: '*' 246 | peerDependenciesMeta: 247 | supports-color: 248 | optional: true 249 | 250 | defu@6.1.4: 251 | resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} 252 | 253 | diff@8.0.2: 254 | resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} 255 | engines: {node: '>=0.3.1'} 256 | 257 | dts-resolver@2.0.1: 258 | resolution: {integrity: sha512-Pe2kqaQTNVxleYpt9Q9658fn6rEpoZbMbDpEBbcU6pnuGM3Q0IdM+Rv67kN6qcyp8Bv2Uv9NYy5Y1rG1LSgfoQ==} 259 | engines: {node: '>=20.18.0'} 260 | peerDependencies: 261 | oxc-resolver: ^9.0.2 262 | peerDependenciesMeta: 263 | oxc-resolver: 264 | optional: true 265 | 266 | empathic@1.1.0: 267 | resolution: {integrity: sha512-rsPft6CK3eHtrlp9Y5ALBb+hfK+DWnA4WFebbazxjWyx8vSm3rZeoM3z9irsjcqO3PYRzlfv27XIB4tz2DV7RA==} 268 | engines: {node: '>=14'} 269 | 270 | fdir@6.4.4: 271 | resolution: {integrity: sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==} 272 | peerDependencies: 273 | picomatch: ^3 || ^4 274 | peerDependenciesMeta: 275 | picomatch: 276 | optional: true 277 | 278 | get-tsconfig@4.10.1: 279 | resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} 280 | 281 | handlebars@4.7.8: 282 | resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} 283 | engines: {node: '>=0.4.7'} 284 | hasBin: true 285 | 286 | hookable@5.5.3: 287 | resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} 288 | 289 | import-cwd@3.0.0: 290 | resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} 291 | engines: {node: '>=8'} 292 | 293 | import-from@3.0.0: 294 | resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} 295 | engines: {node: '>=8'} 296 | 297 | jiti@2.4.2: 298 | resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} 299 | hasBin: true 300 | 301 | jsesc@3.1.0: 302 | resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} 303 | engines: {node: '>=6'} 304 | hasBin: true 305 | 306 | minimist@1.2.8: 307 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 308 | 309 | ms@2.1.3: 310 | resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} 311 | 312 | neo-async@2.6.2: 313 | resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} 314 | 315 | node-fetch@2.7.0: 316 | resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} 317 | engines: {node: 4.x || >=6.0.0} 318 | peerDependencies: 319 | encoding: ^0.1.0 320 | peerDependenciesMeta: 321 | encoding: 322 | optional: true 323 | 324 | parse-github-url@1.0.3: 325 | resolution: {integrity: sha512-tfalY5/4SqGaV/GIGzWyHnFjlpTPTNpENR9Ea2lLldSJ8EWXMsvacWucqY3m3I4YPtas15IxTLQVQ5NSYXPrww==} 326 | engines: {node: '>= 0.10'} 327 | hasBin: true 328 | 329 | pathe@2.0.3: 330 | resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} 331 | 332 | picomatch@4.0.2: 333 | resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 334 | engines: {node: '>=12'} 335 | 336 | quansync@0.2.10: 337 | resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} 338 | 339 | readdirp@4.1.2: 340 | resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} 341 | engines: {node: '>= 14.18.0'} 342 | 343 | resolve-from@5.0.0: 344 | resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} 345 | engines: {node: '>=8'} 346 | 347 | resolve-pkg-maps@1.0.0: 348 | resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} 349 | 350 | rolldown-plugin-dts@0.13.4: 351 | resolution: {integrity: sha512-2+3GnKj6A3wKfyomUKfONRHjgKE85X4PcgW1b84KkHvuN3mUuUiOMseLKafFLMF6NkqQPAJ3FErwtC4HuwIswg==} 352 | engines: {node: '>=20.18.0'} 353 | peerDependencies: 354 | rolldown: ^1.0.0-beta.9 355 | typescript: ^5.0.0 356 | vue-tsc: ~2.2.0 357 | peerDependenciesMeta: 358 | typescript: 359 | optional: true 360 | vue-tsc: 361 | optional: true 362 | 363 | rolldown@1.0.0-beta.9-commit.d91dfb5: 364 | resolution: {integrity: sha512-FHkj6gGEiEgmAXQchglofvUUdwj2Oiw603Rs+zgFAnn9Cb7T7z3fiaEc0DbN3ja4wYkW6sF2rzMEtC1V4BGx/g==} 365 | hasBin: true 366 | 367 | semver@7.7.2: 368 | resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} 369 | engines: {node: '>=10'} 370 | hasBin: true 371 | 372 | source-map@0.6.1: 373 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 374 | engines: {node: '>=0.10.0'} 375 | 376 | tinyexec@1.0.1: 377 | resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==} 378 | 379 | tinyglobby@0.2.14: 380 | resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} 381 | engines: {node: '>=12.0.0'} 382 | 383 | tr46@0.0.3: 384 | resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} 385 | 386 | tsdown@0.12.3: 387 | resolution: {integrity: sha512-GFqQc6rP8EzsIDs4biOdh7sTveQpMVUv2GUgv7O0Z7yjnlGK050yw0PfUHBHi2xaSyN8K/ztU83DXyEl2upeGw==} 388 | engines: {node: '>=18.0.0'} 389 | hasBin: true 390 | peerDependencies: 391 | publint: ^0.3.0 392 | typescript: ^5.0.0 393 | unplugin-lightningcss: ^0.4.0 394 | unplugin-unused: ^0.5.0 395 | peerDependenciesMeta: 396 | publint: 397 | optional: true 398 | typescript: 399 | optional: true 400 | unplugin-lightningcss: 401 | optional: true 402 | unplugin-unused: 403 | optional: true 404 | 405 | tslib@2.8.1: 406 | resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 407 | 408 | typescript@5.8.3: 409 | resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} 410 | engines: {node: '>=14.17'} 411 | hasBin: true 412 | 413 | uglify-js@3.19.3: 414 | resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} 415 | engines: {node: '>=0.8.0'} 416 | hasBin: true 417 | 418 | unconfig@7.3.2: 419 | resolution: {integrity: sha512-nqG5NNL2wFVGZ0NA/aCFw0oJ2pxSf1lwg4Z5ill8wd7K4KX/rQbHlwbh+bjctXL5Ly1xtzHenHGOK0b+lG6JVg==} 420 | 421 | undici-types@6.21.0: 422 | resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} 423 | 424 | webidl-conversions@3.0.1: 425 | resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} 426 | 427 | whatwg-url@5.0.0: 428 | resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} 429 | 430 | wordwrap@1.0.0: 431 | resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} 432 | 433 | snapshots: 434 | 435 | '@babel/generator@7.27.1': 436 | dependencies: 437 | '@babel/parser': 7.27.2 438 | '@babel/types': 7.27.1 439 | '@jridgewell/gen-mapping': 0.3.8 440 | '@jridgewell/trace-mapping': 0.3.25 441 | jsesc: 3.1.0 442 | 443 | '@babel/helper-string-parser@7.27.1': {} 444 | 445 | '@babel/helper-validator-identifier@7.27.1': {} 446 | 447 | '@babel/parser@7.27.2': 448 | dependencies: 449 | '@babel/types': 7.27.1 450 | 451 | '@babel/types@7.27.1': 452 | dependencies: 453 | '@babel/helper-string-parser': 7.27.1 454 | '@babel/helper-validator-identifier': 7.27.1 455 | 456 | '@biomejs/biome@1.9.4': 457 | optionalDependencies: 458 | '@biomejs/cli-darwin-arm64': 1.9.4 459 | '@biomejs/cli-darwin-x64': 1.9.4 460 | '@biomejs/cli-linux-arm64': 1.9.4 461 | '@biomejs/cli-linux-arm64-musl': 1.9.4 462 | '@biomejs/cli-linux-x64': 1.9.4 463 | '@biomejs/cli-linux-x64-musl': 1.9.4 464 | '@biomejs/cli-win32-arm64': 1.9.4 465 | '@biomejs/cli-win32-x64': 1.9.4 466 | 467 | '@biomejs/cli-darwin-arm64@1.9.4': 468 | optional: true 469 | 470 | '@biomejs/cli-darwin-x64@1.9.4': 471 | optional: true 472 | 473 | '@biomejs/cli-linux-arm64-musl@1.9.4': 474 | optional: true 475 | 476 | '@biomejs/cli-linux-arm64@1.9.4': 477 | optional: true 478 | 479 | '@biomejs/cli-linux-x64-musl@1.9.4': 480 | optional: true 481 | 482 | '@biomejs/cli-linux-x64@1.9.4': 483 | optional: true 484 | 485 | '@biomejs/cli-win32-arm64@1.9.4': 486 | optional: true 487 | 488 | '@biomejs/cli-win32-x64@1.9.4': 489 | optional: true 490 | 491 | '@emnapi/core@1.4.3': 492 | dependencies: 493 | '@emnapi/wasi-threads': 1.0.2 494 | tslib: 2.8.1 495 | optional: true 496 | 497 | '@emnapi/runtime@1.4.3': 498 | dependencies: 499 | tslib: 2.8.1 500 | optional: true 501 | 502 | '@emnapi/wasi-threads@1.0.2': 503 | dependencies: 504 | tslib: 2.8.1 505 | optional: true 506 | 507 | '@jridgewell/gen-mapping@0.3.8': 508 | dependencies: 509 | '@jridgewell/set-array': 1.2.1 510 | '@jridgewell/sourcemap-codec': 1.5.0 511 | '@jridgewell/trace-mapping': 0.3.25 512 | 513 | '@jridgewell/resolve-uri@3.1.2': {} 514 | 515 | '@jridgewell/set-array@1.2.1': {} 516 | 517 | '@jridgewell/sourcemap-codec@1.5.0': {} 518 | 519 | '@jridgewell/trace-mapping@0.3.25': 520 | dependencies: 521 | '@jridgewell/resolve-uri': 3.1.2 522 | '@jridgewell/sourcemap-codec': 1.5.0 523 | 524 | '@napi-rs/wasm-runtime@0.2.10': 525 | dependencies: 526 | '@emnapi/core': 1.4.3 527 | '@emnapi/runtime': 1.4.3 528 | '@tybys/wasm-util': 0.9.0 529 | optional: true 530 | 531 | '@oxc-project/runtime@0.71.0': {} 532 | 533 | '@oxc-project/types@0.71.0': {} 534 | 535 | '@quansync/fs@0.1.3': 536 | dependencies: 537 | quansync: 0.2.10 538 | 539 | '@rolldown/binding-darwin-arm64@1.0.0-beta.9-commit.d91dfb5': 540 | optional: true 541 | 542 | '@rolldown/binding-darwin-x64@1.0.0-beta.9-commit.d91dfb5': 543 | optional: true 544 | 545 | '@rolldown/binding-freebsd-x64@1.0.0-beta.9-commit.d91dfb5': 546 | optional: true 547 | 548 | '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.9-commit.d91dfb5': 549 | optional: true 550 | 551 | '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.9-commit.d91dfb5': 552 | optional: true 553 | 554 | '@rolldown/binding-linux-arm64-musl@1.0.0-beta.9-commit.d91dfb5': 555 | optional: true 556 | 557 | '@rolldown/binding-linux-x64-gnu@1.0.0-beta.9-commit.d91dfb5': 558 | optional: true 559 | 560 | '@rolldown/binding-linux-x64-musl@1.0.0-beta.9-commit.d91dfb5': 561 | optional: true 562 | 563 | '@rolldown/binding-wasm32-wasi@1.0.0-beta.9-commit.d91dfb5': 564 | dependencies: 565 | '@napi-rs/wasm-runtime': 0.2.10 566 | optional: true 567 | 568 | '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.9-commit.d91dfb5': 569 | optional: true 570 | 571 | '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.9-commit.d91dfb5': 572 | optional: true 573 | 574 | '@rolldown/binding-win32-x64-msvc@1.0.0-beta.9-commit.d91dfb5': 575 | optional: true 576 | 577 | '@rolldown/pluginutils@1.0.0-beta.9-commit.d91dfb5': {} 578 | 579 | '@tybys/wasm-util@0.9.0': 580 | dependencies: 581 | tslib: 2.8.1 582 | optional: true 583 | 584 | '@types/node@22.15.21': 585 | dependencies: 586 | undici-types: 6.21.0 587 | 588 | ansis@4.0.0: {} 589 | 590 | ast-kit@2.0.0: 591 | dependencies: 592 | '@babel/parser': 7.27.2 593 | pathe: 2.0.3 594 | 595 | auto-changelog@2.5.0: 596 | dependencies: 597 | commander: 7.2.0 598 | handlebars: 4.7.8 599 | import-cwd: 3.0.0 600 | node-fetch: 2.7.0 601 | parse-github-url: 1.0.3 602 | semver: 7.7.2 603 | transitivePeerDependencies: 604 | - encoding 605 | 606 | birpc@2.3.0: {} 607 | 608 | cac@6.7.14: {} 609 | 610 | chokidar@4.0.3: 611 | dependencies: 612 | readdirp: 4.1.2 613 | 614 | commander@7.2.0: {} 615 | 616 | debug@4.4.1: 617 | dependencies: 618 | ms: 2.1.3 619 | 620 | defu@6.1.4: {} 621 | 622 | diff@8.0.2: {} 623 | 624 | dts-resolver@2.0.1: {} 625 | 626 | empathic@1.1.0: {} 627 | 628 | fdir@6.4.4(picomatch@4.0.2): 629 | optionalDependencies: 630 | picomatch: 4.0.2 631 | 632 | get-tsconfig@4.10.1: 633 | dependencies: 634 | resolve-pkg-maps: 1.0.0 635 | 636 | handlebars@4.7.8: 637 | dependencies: 638 | minimist: 1.2.8 639 | neo-async: 2.6.2 640 | source-map: 0.6.1 641 | wordwrap: 1.0.0 642 | optionalDependencies: 643 | uglify-js: 3.19.3 644 | 645 | hookable@5.5.3: {} 646 | 647 | import-cwd@3.0.0: 648 | dependencies: 649 | import-from: 3.0.0 650 | 651 | import-from@3.0.0: 652 | dependencies: 653 | resolve-from: 5.0.0 654 | 655 | jiti@2.4.2: {} 656 | 657 | jsesc@3.1.0: {} 658 | 659 | minimist@1.2.8: {} 660 | 661 | ms@2.1.3: {} 662 | 663 | neo-async@2.6.2: {} 664 | 665 | node-fetch@2.7.0: 666 | dependencies: 667 | whatwg-url: 5.0.0 668 | 669 | parse-github-url@1.0.3: {} 670 | 671 | pathe@2.0.3: {} 672 | 673 | picomatch@4.0.2: {} 674 | 675 | quansync@0.2.10: {} 676 | 677 | readdirp@4.1.2: {} 678 | 679 | resolve-from@5.0.0: {} 680 | 681 | resolve-pkg-maps@1.0.0: {} 682 | 683 | rolldown-plugin-dts@0.13.4(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.8.3): 684 | dependencies: 685 | '@babel/generator': 7.27.1 686 | '@babel/parser': 7.27.2 687 | '@babel/types': 7.27.1 688 | ast-kit: 2.0.0 689 | birpc: 2.3.0 690 | debug: 4.4.1 691 | dts-resolver: 2.0.1 692 | get-tsconfig: 4.10.1 693 | rolldown: 1.0.0-beta.9-commit.d91dfb5 694 | optionalDependencies: 695 | typescript: 5.8.3 696 | transitivePeerDependencies: 697 | - oxc-resolver 698 | - supports-color 699 | 700 | rolldown@1.0.0-beta.9-commit.d91dfb5: 701 | dependencies: 702 | '@oxc-project/runtime': 0.71.0 703 | '@oxc-project/types': 0.71.0 704 | '@rolldown/pluginutils': 1.0.0-beta.9-commit.d91dfb5 705 | ansis: 4.0.0 706 | optionalDependencies: 707 | '@rolldown/binding-darwin-arm64': 1.0.0-beta.9-commit.d91dfb5 708 | '@rolldown/binding-darwin-x64': 1.0.0-beta.9-commit.d91dfb5 709 | '@rolldown/binding-freebsd-x64': 1.0.0-beta.9-commit.d91dfb5 710 | '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.9-commit.d91dfb5 711 | '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.9-commit.d91dfb5 712 | '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.9-commit.d91dfb5 713 | '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.9-commit.d91dfb5 714 | '@rolldown/binding-linux-x64-musl': 1.0.0-beta.9-commit.d91dfb5 715 | '@rolldown/binding-wasm32-wasi': 1.0.0-beta.9-commit.d91dfb5 716 | '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.9-commit.d91dfb5 717 | '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.9-commit.d91dfb5 718 | '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.9-commit.d91dfb5 719 | 720 | semver@7.7.2: {} 721 | 722 | source-map@0.6.1: {} 723 | 724 | tinyexec@1.0.1: {} 725 | 726 | tinyglobby@0.2.14: 727 | dependencies: 728 | fdir: 6.4.4(picomatch@4.0.2) 729 | picomatch: 4.0.2 730 | 731 | tr46@0.0.3: {} 732 | 733 | tsdown@0.12.3(typescript@5.8.3): 734 | dependencies: 735 | ansis: 4.0.0 736 | cac: 6.7.14 737 | chokidar: 4.0.3 738 | debug: 4.4.1 739 | diff: 8.0.2 740 | empathic: 1.1.0 741 | hookable: 5.5.3 742 | rolldown: 1.0.0-beta.9-commit.d91dfb5 743 | rolldown-plugin-dts: 0.13.4(rolldown@1.0.0-beta.9-commit.d91dfb5)(typescript@5.8.3) 744 | semver: 7.7.2 745 | tinyexec: 1.0.1 746 | tinyglobby: 0.2.14 747 | unconfig: 7.3.2 748 | optionalDependencies: 749 | typescript: 5.8.3 750 | transitivePeerDependencies: 751 | - oxc-resolver 752 | - supports-color 753 | - vue-tsc 754 | 755 | tslib@2.8.1: 756 | optional: true 757 | 758 | typescript@5.8.3: {} 759 | 760 | uglify-js@3.19.3: 761 | optional: true 762 | 763 | unconfig@7.3.2: 764 | dependencies: 765 | '@quansync/fs': 0.1.3 766 | defu: 6.1.4 767 | jiti: 2.4.2 768 | quansync: 0.2.10 769 | 770 | undici-types@6.21.0: {} 771 | 772 | webidl-conversions@3.0.1: {} 773 | 774 | whatwg-url@5.0.0: 775 | dependencies: 776 | tr46: 0.0.3 777 | webidl-conversions: 3.0.1 778 | 779 | wordwrap@1.0.0: {} 780 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | interface ArgInfoType { 2 | type: 'number' | 'string' | 'array' | 'boolean'; 3 | } 4 | 5 | interface ArgInfoParser { 6 | parser: (input: string) => unknown; 7 | } 8 | 9 | interface ArgInfoOptional { 10 | optional?: boolean; 11 | } 12 | 13 | interface ArgInfoDefault { 14 | default?: unknown; 15 | } 16 | 17 | interface ArgInfoChoices { 18 | choices?: readonly unknown[]; 19 | } 20 | 21 | type ArgInfo = (ArgInfoType | ArgInfoParser) & ArgInfoOptional & ArgInfoDefault & ArgInfoChoices; 22 | 23 | interface MappedArgType { 24 | string: Choice extends (infer U)[] ? U : string; 25 | number: Choice extends (infer U)[] ? U : number; 26 | boolean: boolean; 27 | array: string[]; 28 | } 29 | 30 | type Mutable = { -readonly [P in keyof T]: T[P] }; 31 | 32 | type GetArgTypeInner = T extends ArgInfoParser 33 | ? ReturnType 34 | : T extends ArgInfoType 35 | ? MappedArgType>[T['type']] 36 | : never; 37 | 38 | type GetArgTypeOptional = T extends ArgInfoDefault ? T['default'] : undefined; 39 | 40 | type GetArgType = T extends ArgInfoOptional 41 | ? GetArgTypeInner | GetArgTypeOptional 42 | : GetArgTypeInner; 43 | 44 | type ArgReturnType> = { 45 | [K in keyof T]: GetArgType; 46 | }; 47 | 48 | export interface Options { 49 | env?: Record | NodeJS.ProcessEnv; 50 | } 51 | 52 | export type Config = Record; 53 | 54 | interface CustomEnvError { 55 | key: string; 56 | value: string; 57 | error: unknown; 58 | kind: 'custom'; 59 | } 60 | 61 | interface ChoiceEnvError { 62 | key: string; 63 | value: string; 64 | expected: readonly unknown[]; 65 | kind: 'choice'; 66 | } 67 | 68 | interface NumberEnvError { 69 | key: string; 70 | value: string; 71 | kind: 'number'; 72 | } 73 | 74 | interface MissingEnvError { 75 | key: string; 76 | kind: 'missing'; 77 | } 78 | 79 | type EnvError = CustomEnvError | ChoiceEnvError | NumberEnvError | MissingEnvError; 80 | 81 | export function createEnv(info: T, options?: Options): ArgReturnType { 82 | const env = options?.env ?? (typeof process !== 'undefined' ? process.env : null); 83 | 84 | if (!env) { 85 | throw new TypeError('No env specified'); 86 | } 87 | 88 | const errors: EnvError[] = []; 89 | const config: Record = {}; 90 | 91 | for (const [key, argInfo] of Object.entries(info)) { 92 | const value = env[key]; 93 | 94 | if (!value) { 95 | if ('default' in argInfo) { 96 | config[key] = argInfo.default; 97 | } else if (!argInfo.optional) { 98 | errors.push({ key, kind: 'missing' }); 99 | } 100 | 101 | continue; 102 | } 103 | 104 | if ('parser' in argInfo) { 105 | try { 106 | config[key] = argInfo.parser(value); 107 | } catch (error) { 108 | errors.push({ key, value, kind: 'custom', error }); 109 | } 110 | 111 | continue; 112 | } 113 | 114 | if (argInfo.choices && !argInfo.choices.includes(value)) { 115 | errors.push({ key, value, kind: 'choice', expected: argInfo.choices }); 116 | continue; 117 | } 118 | 119 | switch (argInfo.type) { 120 | case 'string': 121 | config[key] = value; 122 | break; 123 | case 'number': { 124 | const numberValue = Number(value); 125 | if (Number.isNaN(numberValue)) { 126 | errors.push({ key, value, kind: 'number' }); 127 | continue; 128 | } 129 | config[key] = numberValue; 130 | break; 131 | } 132 | case 'boolean': 133 | config[key] = value.toLowerCase() === 'true' || value === '1'; 134 | break; 135 | case 'array': 136 | config[key] = value.split(/, ?/gu); 137 | break; 138 | } 139 | } 140 | 141 | if (errors.length !== 0) { 142 | let message = 'The following environment variables are invalid:\n'; 143 | 144 | for (const error of errors) { 145 | switch (error.kind) { 146 | case 'missing': 147 | message += ` ${error.key} (missing)\n`; 148 | break; 149 | case 'number': 150 | message += ` ${error.key}: ${error.value} (expected number)\n`; 151 | break; 152 | case 'choice': 153 | message += ` ${error.key}: ${error.value} (expected one of ${error.expected.join(', ')})\n`; 154 | break; 155 | case 'custom': 156 | message += ` ${error.key}: ${error.value} (${error.error})\n`; 157 | break; 158 | } 159 | } 160 | throw new TypeError(message); 161 | } 162 | 163 | return config as ArgReturnType; 164 | } 165 | -------------------------------------------------------------------------------- /test/index.test.ts: -------------------------------------------------------------------------------- 1 | import assert from 'node:assert/strict'; 2 | import process from 'node:process'; 3 | import { describe, it } from 'node:test'; 4 | import { createEnv } from '../src/index.ts'; 5 | 6 | describe('env', () => { 7 | it('should work', () => { 8 | const env = { 9 | TEST_STRING: 'test', 10 | TEST_NUMBER: '123', 11 | TEST_ARRAY: 'a,b,c', 12 | TEST_ARRAY_2: 'd, e, f', 13 | TEST_BOOLEAN: 'true', 14 | TEST_BOOLEAN_2: '1', 15 | TEST_BOOLEAN_3: 'false', 16 | TEST_BOOLEAN_4: '0' 17 | }; 18 | 19 | const result = createEnv( 20 | { 21 | TEST_STRING: { type: 'string' }, 22 | TEST_STRING_DEFAULT: { type: 'string', default: 'default' }, 23 | TEST_NUMBER: { type: 'number' }, 24 | TEST_ARRAY: { type: 'array' }, 25 | TEST_ARRAY_2: { type: 'array' }, 26 | TEST_BOOLEAN: { type: 'boolean' }, 27 | TEST_BOOLEAN_2: { type: 'boolean' }, 28 | TEST_BOOLEAN_3: { type: 'boolean' }, 29 | TEST_BOOLEAN_4: { type: 'boolean' }, 30 | TEST_OPTIONAL: { type: 'string', optional: true } 31 | }, 32 | { env } 33 | ); 34 | 35 | assert.deepEqual(result, { 36 | TEST_STRING: 'test', 37 | TEST_STRING_DEFAULT: 'default', 38 | TEST_NUMBER: 123, 39 | TEST_ARRAY: ['a', 'b', 'c'], 40 | TEST_ARRAY_2: ['d', 'e', 'f'], 41 | TEST_BOOLEAN: true, 42 | TEST_BOOLEAN_2: true, 43 | TEST_BOOLEAN_3: false, 44 | TEST_BOOLEAN_4: false 45 | }); 46 | }); 47 | 48 | it('should work with choices', () => { 49 | const env = { 50 | TEST_ENV: 'production' 51 | }; 52 | 53 | const result = createEnv( 54 | { 55 | TEST_ENV: { type: 'string', choices: ['production', 'development'] } 56 | }, 57 | { env } 58 | ); 59 | 60 | assert.deepEqual(result, { 61 | TEST_ENV: 'production' 62 | }); 63 | }); 64 | 65 | it('should throw with no env', () => { 66 | const env = process.env; 67 | Object.defineProperty(process, 'env', { value: null }); 68 | 69 | assert.throws(() => 70 | createEnv({ 71 | TEST_NUMBER: { type: 'number' } 72 | }) 73 | ); 74 | 75 | process.env = env; 76 | }); 77 | 78 | it('should throw with invalid choice value', () => { 79 | const env = { 80 | TEST_STRING: 'test' 81 | }; 82 | 83 | assert.throws(() => 84 | createEnv( 85 | { 86 | TEST_STRING: { type: 'string', choices: ['hello', 'hi'] } 87 | }, 88 | { env } 89 | ) 90 | ); 91 | }); 92 | 93 | it('should throw with missing required field', () => { 94 | const env = { 95 | TEST_STRING: 'test' 96 | }; 97 | assert.throws(() => 98 | createEnv( 99 | { 100 | TEST_NUMBER: { type: 'number' } 101 | }, 102 | { env } 103 | ) 104 | ); 105 | }); 106 | 107 | it('should throw with invalid number', () => { 108 | const env = { 109 | TEST_NUMBER: '123a' 110 | }; 111 | assert.throws(() => 112 | createEnv( 113 | { 114 | TEST_NUMBER: { type: 'number' } 115 | }, 116 | { env } 117 | ) 118 | ); 119 | }); 120 | 121 | it('should work with a custom parser', () => { 122 | const env = { 123 | TEST_STRING: 'test' 124 | }; 125 | 126 | const result = createEnv( 127 | { 128 | TEST_STRING: { 129 | parser(value) { 130 | return value.toUpperCase(); 131 | } 132 | } 133 | }, 134 | { env } 135 | ); 136 | 137 | assert.deepEqual(result, { 138 | TEST_STRING: 'TEST' 139 | }); 140 | }); 141 | 142 | it('should work with process.env', () => { 143 | process.env.TEST_STRING = 'test'; 144 | 145 | const result = createEnv({ 146 | TEST_STRING: { type: 'string' } 147 | }); 148 | 149 | assert.deepEqual(result, { 150 | TEST_STRING: 'test' 151 | }); 152 | }); 153 | }); 154 | -------------------------------------------------------------------------------- /test/types.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, it } from 'node:test'; 2 | import { createEnv } from '../src/index.ts'; 3 | 4 | const assertType = (value: T) => value; 5 | 6 | describe('env types', () => { 7 | it('should infer the choice types', () => { 8 | const env = { 9 | TEST_ENV: 'production' 10 | }; 11 | 12 | const result = createEnv( 13 | { 14 | TEST_ENV: { type: 'string', choices: ['production', 'development'] } 15 | }, 16 | { env } 17 | ); 18 | 19 | assertType<'production' | 'development'>(result.TEST_ENV); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "lib": ["esnext"], 5 | 6 | "module": "esnext", 7 | "moduleResolution": "bundler", 8 | 9 | "outDir": "dist", 10 | "esModuleInterop": true, 11 | "verbatimModuleSyntax": true, 12 | 13 | "allowImportingTsExtensions": true, 14 | "resolveJsonModule": true, 15 | "noEmit": true, 16 | "skipLibCheck": true, 17 | "sourceMap": true, 18 | "declaration": true, 19 | 20 | "strict": true 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /tsdown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'tsdown'; 2 | 3 | export default defineConfig({ 4 | clean: true, 5 | dts: true, 6 | entry: ['src/index.ts'], 7 | format: ['esm', 'cjs'], 8 | platform: 'node', 9 | sourcemap: true, 10 | target: 'es2022' 11 | }); 12 | --------------------------------------------------------------------------------