@mrinal1224/safe-env 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +204 -0
- package/dist/index.cjs +187 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +158 -0
- package/dist/index.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mrinal Bhattacharya
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# 🛡️ Safe Env
|
|
2
|
+
|
|
3
|
+
> Type-safe environment variable validation for Node.js & TypeScript.
|
|
4
|
+
|
|
5
|
+
Stop discovering missing or invalid `.env` variables after your application has already started. **Safe Env** validates and parses environment configuration at startup while giving you inferred TypeScript types.
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/@mrinal/safe-env"><img src="https://img.shields.io/npm/v/@mrinal/safe-env?style=for-the-badge&label=npm" alt="npm version" /></a>
|
|
9
|
+
<a href="https://github.com/mrinal1224/Safe-Env/stargazers"><img src="https://img.shields.io/github/stars/mrinal1224/Safe-Env?style=for-the-badge" alt="GitHub stars" /></a>
|
|
10
|
+
<a href="https://github.com/mrinal1224/Safe-Env/blob/main/LICENSE"><img src="https://img.shields.io/github/license/mrinal1224/Safe-Env?style=for-the-badge" alt="License" /></a>
|
|
11
|
+
<a href="https://github.com/mrinal1224/Safe-Env/actions"><img src="https://img.shields.io/github/actions/workflow/status/mrinal1224/Safe-Env/ci.yml?branch=main&style=for-the-badge&label=CI" alt="CI status" /></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🚨 The Problem
|
|
17
|
+
|
|
18
|
+
Most Node.js applications eventually contain code like this:
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
const port = process.env.PORT;
|
|
22
|
+
const jwtSecret = process.env.JWT_SECRET;
|
|
23
|
+
const databaseUrl = process.env.DATABASE_URL;
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Environment variables are exposed as strings and can be missing, malformed, or inconsistent with what your application expects.
|
|
27
|
+
|
|
28
|
+
Safe Env gives you one schema for both runtime validation and TypeScript inference.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## ✨ Quick Start
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm install @mrinal/safe-env
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { createEnv, z } from "@mrinal/safe-env";
|
|
40
|
+
|
|
41
|
+
export const env = createEnv({
|
|
42
|
+
PORT: z.number().default(3000),
|
|
43
|
+
JWT_SECRET: z.string().min(32),
|
|
44
|
+
NODE_ENV: z.enum(["development", "production"] as const),
|
|
45
|
+
DEBUG: z.boolean().optional(),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The result is inferred automatically:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
env.PORT // number
|
|
53
|
+
env.JWT_SECRET // string
|
|
54
|
+
env.NODE_ENV // "development" | "production"
|
|
55
|
+
env.DEBUG // boolean | undefined
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 🧠 How It Works
|
|
61
|
+
|
|
62
|
+
```text
|
|
63
|
+
process.env
|
|
64
|
+
│
|
|
65
|
+
▼
|
|
66
|
+
Schema Definition
|
|
67
|
+
│
|
|
68
|
+
▼
|
|
69
|
+
Parse + Validate
|
|
70
|
+
│
|
|
71
|
+
├── ✅ Valid → Typed Config
|
|
72
|
+
│
|
|
73
|
+
└── ❌ Invalid → SafeEnvError
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Create the configuration object once and use `env` throughout the rest of your application instead of reading `process.env` everywhere.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 🔧 Validators
|
|
81
|
+
|
|
82
|
+
| Validator | Example |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| String | `z.string()` |
|
|
85
|
+
| Number | `z.number()` |
|
|
86
|
+
| Boolean | `z.boolean()` |
|
|
87
|
+
| Enum | `z.enum(["development", "production"] as const)` |
|
|
88
|
+
| Optional | `z.string().optional()` |
|
|
89
|
+
| Default | `z.number().default(3000)` |
|
|
90
|
+
| Minimum length | `z.string().min(32)` |
|
|
91
|
+
| Maximum length | `z.string().max(100)` |
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## ❌ Validation Errors
|
|
96
|
+
|
|
97
|
+
Safe Env validates the complete schema and reports configuration problems together, so you can fix several variables in one run.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
|
|
101
|
+
```text
|
|
102
|
+
[safe-env] environment: Invalid environment configuration:
|
|
103
|
+
• [safe-env] PORT: must be a valid finite number, received "abc"
|
|
104
|
+
• [safe-env] JWT_SECRET: Missing required environment variable: JWT_SECRET
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
> Do not include secret values in custom error messages or logs in production.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 📦 Package Support
|
|
112
|
+
|
|
113
|
+
Safe Env is shipped as a small TypeScript library with:
|
|
114
|
+
|
|
115
|
+
- ESM support
|
|
116
|
+
- CommonJS support
|
|
117
|
+
- Generated declaration files
|
|
118
|
+
- Node.js `>=20`
|
|
119
|
+
- No runtime dependencies
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 🧪 Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
git clone https://github.com/mrinal1224/Safe-Env.git
|
|
127
|
+
cd Safe-Env
|
|
128
|
+
npm install
|
|
129
|
+
npm run typecheck
|
|
130
|
+
npm test
|
|
131
|
+
npm run build
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
For coverage:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npm run coverage
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
GitHub Actions runs typechecking, tests and the package build for pushes to `main` and pull requests.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🗺️ Roadmap
|
|
145
|
+
|
|
146
|
+
### v0.1
|
|
147
|
+
|
|
148
|
+
- [x] Runtime parsing
|
|
149
|
+
- [x] String / number / boolean validators
|
|
150
|
+
- [x] Enum validator
|
|
151
|
+
- [x] Optional values
|
|
152
|
+
- [x] Default values
|
|
153
|
+
- [x] String length constraints
|
|
154
|
+
- [x] Aggregated validation errors
|
|
155
|
+
- [x] Type inference
|
|
156
|
+
- [x] ESM + CommonJS package exports
|
|
157
|
+
|
|
158
|
+
### v0.2
|
|
159
|
+
|
|
160
|
+
- [ ] URL validator
|
|
161
|
+
- [ ] Email validator
|
|
162
|
+
- [ ] Regex validator
|
|
163
|
+
- [ ] Custom validators
|
|
164
|
+
- [ ] Transformations
|
|
165
|
+
- [ ] Nested configuration
|
|
166
|
+
- [ ] Better error metadata
|
|
167
|
+
|
|
168
|
+
### v1.0
|
|
169
|
+
|
|
170
|
+
- [ ] Stable API
|
|
171
|
+
- [ ] Comprehensive integration tests
|
|
172
|
+
- [ ] Documentation site
|
|
173
|
+
- [ ] Automated semantic releases
|
|
174
|
+
- [ ] NestJS integration
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🤝 Contributing
|
|
179
|
+
|
|
180
|
+
Contributions are welcome. Please keep the public API small and predictable.
|
|
181
|
+
|
|
182
|
+
Before opening a pull request:
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
npm run typecheck
|
|
186
|
+
npm test
|
|
187
|
+
npm run build
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 👨💻 Creator
|
|
193
|
+
|
|
194
|
+
**Mrinal Bhattacharya**
|
|
195
|
+
|
|
196
|
+
Software Engineer · Educator · Open Source Builder
|
|
197
|
+
|
|
198
|
+
GitHub: [@mrinal1224](https://github.com/mrinal1224)
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 📄 License
|
|
203
|
+
|
|
204
|
+
MIT © 2026 Mrinal Bhattacharya
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
SafeEnvError: () => SafeEnvError,
|
|
24
|
+
createEnv: () => createEnv,
|
|
25
|
+
z: () => z
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(index_exports);
|
|
28
|
+
|
|
29
|
+
// src/errors.ts
|
|
30
|
+
var SafeEnvError = class extends Error {
|
|
31
|
+
key;
|
|
32
|
+
constructor(key, message) {
|
|
33
|
+
super(`[safe-env] ${key}: ${message}`);
|
|
34
|
+
this.name = "SafeEnvError";
|
|
35
|
+
this.key = key;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/createEnv.ts
|
|
40
|
+
function createEnv(schema, source = process.env) {
|
|
41
|
+
const result = {};
|
|
42
|
+
const errors = [];
|
|
43
|
+
for (const [key, validator] of Object.entries(schema)) {
|
|
44
|
+
try {
|
|
45
|
+
result[key] = validator.parse(source[key], key);
|
|
46
|
+
} catch (error) {
|
|
47
|
+
if (error instanceof SafeEnvError) {
|
|
48
|
+
errors.push(error);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
if (error instanceof Error) {
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
throw new Error(String(error));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (errors.length > 0) {
|
|
58
|
+
const message = errors.map((error) => ` \u2022 ${error.message}`).join("\n");
|
|
59
|
+
throw new SafeEnvError("environment", `Invalid environment configuration:
|
|
60
|
+
${message}`);
|
|
61
|
+
}
|
|
62
|
+
return result;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/validators/base.ts
|
|
66
|
+
var BaseValidator = class {
|
|
67
|
+
isOptional = false;
|
|
68
|
+
hasDefault = false;
|
|
69
|
+
defaultValue;
|
|
70
|
+
__safeEnvOutput;
|
|
71
|
+
__safeEnvOptional;
|
|
72
|
+
optional() {
|
|
73
|
+
this.isOptional = true;
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
default(value) {
|
|
77
|
+
this.defaultValue = value;
|
|
78
|
+
this.hasDefault = true;
|
|
79
|
+
return this;
|
|
80
|
+
}
|
|
81
|
+
resolveUndefined(key) {
|
|
82
|
+
if (this.hasDefault) {
|
|
83
|
+
return this.defaultValue;
|
|
84
|
+
}
|
|
85
|
+
if (this.isOptional) {
|
|
86
|
+
return void 0;
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`[safe-env] Missing required environment variable: ${key}`);
|
|
89
|
+
}
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// src/validators/boolean.ts
|
|
93
|
+
var BooleanValidator = class extends BaseValidator {
|
|
94
|
+
parse(value, key) {
|
|
95
|
+
if (value === void 0) {
|
|
96
|
+
return this.resolveUndefined(key);
|
|
97
|
+
}
|
|
98
|
+
if (value === "true") return true;
|
|
99
|
+
if (value === "false") return false;
|
|
100
|
+
throw new SafeEnvError(
|
|
101
|
+
key,
|
|
102
|
+
`must be "true" or "false", received ${JSON.stringify(value)}`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
// src/validators/enum.ts
|
|
108
|
+
var EnumValidator = class extends BaseValidator {
|
|
109
|
+
constructor(values) {
|
|
110
|
+
super();
|
|
111
|
+
this.values = values;
|
|
112
|
+
}
|
|
113
|
+
values;
|
|
114
|
+
parse(value, key) {
|
|
115
|
+
if (value === void 0) {
|
|
116
|
+
return this.resolveUndefined(key);
|
|
117
|
+
}
|
|
118
|
+
if (this.values.includes(value)) {
|
|
119
|
+
return value;
|
|
120
|
+
}
|
|
121
|
+
throw new SafeEnvError(
|
|
122
|
+
key,
|
|
123
|
+
`must be one of ${this.values.map((item) => JSON.stringify(item)).join(", ")}, received ${JSON.stringify(value)}`
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// src/validators/number.ts
|
|
129
|
+
var NumberValidator = class extends BaseValidator {
|
|
130
|
+
parse(value, key) {
|
|
131
|
+
if (value === void 0) {
|
|
132
|
+
return this.resolveUndefined(key);
|
|
133
|
+
}
|
|
134
|
+
const parsed = Number(value);
|
|
135
|
+
if (!Number.isFinite(parsed)) {
|
|
136
|
+
throw new SafeEnvError(key, `must be a valid finite number, received ${JSON.stringify(value)}`);
|
|
137
|
+
}
|
|
138
|
+
return parsed;
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
// src/validators/string.ts
|
|
143
|
+
var StringValidator = class extends BaseValidator {
|
|
144
|
+
minLength;
|
|
145
|
+
maxLength;
|
|
146
|
+
min(length) {
|
|
147
|
+
this.minLength = length;
|
|
148
|
+
return this;
|
|
149
|
+
}
|
|
150
|
+
max(length) {
|
|
151
|
+
this.maxLength = length;
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
parse(value, key) {
|
|
155
|
+
if (value === void 0) {
|
|
156
|
+
return this.resolveUndefined(key);
|
|
157
|
+
}
|
|
158
|
+
if (this.minLength !== void 0 && value.length < this.minLength) {
|
|
159
|
+
throw new SafeEnvError(
|
|
160
|
+
key,
|
|
161
|
+
`must be at least ${this.minLength} characters long`
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
if (this.maxLength !== void 0 && value.length > this.maxLength) {
|
|
165
|
+
throw new SafeEnvError(
|
|
166
|
+
key,
|
|
167
|
+
`must be at most ${this.maxLength} characters long`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
return value;
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
// src/index.ts
|
|
175
|
+
var z = {
|
|
176
|
+
string: () => new StringValidator(),
|
|
177
|
+
number: () => new NumberValidator(),
|
|
178
|
+
boolean: () => new BooleanValidator(),
|
|
179
|
+
enum: (values) => new EnumValidator(values)
|
|
180
|
+
};
|
|
181
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
182
|
+
0 && (module.exports = {
|
|
183
|
+
SafeEnvError,
|
|
184
|
+
createEnv,
|
|
185
|
+
z
|
|
186
|
+
});
|
|
187
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/errors.ts","../src/createEnv.ts","../src/validators/base.ts","../src/validators/boolean.ts","../src/validators/enum.ts","../src/validators/number.ts","../src/validators/string.ts"],"sourcesContent":["import { createEnv } from \"./createEnv\";\nimport { BooleanValidator } from \"./validators/boolean\";\nimport { EnumValidator } from \"./validators/enum\";\nimport { NumberValidator } from \"./validators/number\";\nimport { StringValidator } from \"./validators/string\";\nimport type { AnyValidator, InferEnv, InferValidator } from \"./types\";\n\nexport { createEnv } from \"./createEnv\";\nexport { SafeEnvError } from \"./errors\";\nexport type { AnyValidator, InferEnv, InferValidator } from \"./types\";\n\nexport const z = {\n string: () => new StringValidator(),\n number: () => new NumberValidator(),\n boolean: () => new BooleanValidator(),\n enum: <const T extends readonly string[]>(values: T) => new EnumValidator(values),\n};\n\nexport type EnvSchema = Record<string, AnyValidator>;\nexport type InferSchema<T extends EnvSchema> = InferEnv<T>;\n\nvoid createEnv;\nvoid (null as unknown as InferValidator<AnyValidator>);\n","export class SafeEnvError extends Error {\n public readonly key: string;\n\n constructor(key: string, message: string) {\n super(`[safe-env] ${key}: ${message}`);\n this.name = \"SafeEnvError\";\n this.key = key;\n }\n}\n","import { SafeEnvError } from \"./errors\";\nimport type { AnyValidator, InferEnv } from \"./types\";\n\nexport function createEnv<T extends Record<string, AnyValidator>>(\n schema: T,\n source: NodeJS.ProcessEnv = process.env,\n): InferEnv<T> {\n const result: Record<string, unknown> = {};\n const errors: SafeEnvError[] = [];\n\n for (const [key, validator] of Object.entries(schema)) {\n try {\n result[key] = validator.parse(source[key], key);\n } catch (error) {\n if (error instanceof SafeEnvError) {\n errors.push(error);\n continue;\n }\n\n if (error instanceof Error) {\n throw error;\n }\n\n throw new Error(String(error));\n }\n }\n\n if (errors.length > 0) {\n const message = errors.map((error) => ` • ${error.message}`).join(\"\\n\");\n throw new SafeEnvError(\"environment\", `Invalid environment configuration:\\n${message}`);\n }\n\n return result as InferEnv<T>;\n}\n","export abstract class BaseValidator<T> {\n protected isOptional = false;\n protected hasDefault = false;\n protected defaultValue!: T;\n\n readonly __safeEnvOutput!: T;\n readonly __safeEnvOptional!: false;\n\n optional(): OptionalValidator<T, this> {\n this.isOptional = true;\n return this as OptionalValidator<T, this>;\n }\n\n default(value: T): DefaultValidator<T, this> {\n this.defaultValue = value;\n this.hasDefault = true;\n return this as DefaultValidator<T, this>;\n }\n\n protected resolveUndefined(key: string): T | undefined {\n if (this.hasDefault) {\n return this.defaultValue;\n }\n\n if (this.isOptional) {\n return undefined;\n }\n\n throw new Error(`[safe-env] Missing required environment variable: ${key}`);\n }\n\n abstract parse(value: string | undefined, key: string): T | undefined;\n}\n\nexport type OptionalValidator<T, V extends BaseValidator<T>> = V & {\n readonly __safeEnvOptional: true;\n};\n\nexport type DefaultValidator<T, V extends BaseValidator<T>> = V & {\n readonly __safeEnvDefault: true;\n};\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class BooleanValidator extends BaseValidator<boolean> {\n parse(value: string | undefined, key: string): boolean | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n throw new SafeEnvError(\n key,\n `must be \"true\" or \"false\", received ${JSON.stringify(value)}`,\n );\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class EnumValidator<T extends readonly string[]> extends BaseValidator<T[number]> {\n constructor(private readonly values: T) {\n super();\n }\n\n parse(value: string | undefined, key: string): T[number] | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (this.values.includes(value)) {\n return value as T[number];\n }\n\n throw new SafeEnvError(\n key,\n `must be one of ${this.values.map((item) => JSON.stringify(item)).join(\", \")}, received ${JSON.stringify(value)}`,\n );\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class NumberValidator extends BaseValidator<number> {\n parse(value: string | undefined, key: string): number | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n const parsed = Number(value);\n\n if (!Number.isFinite(parsed)) {\n throw new SafeEnvError(key, `must be a valid finite number, received ${JSON.stringify(value)}`);\n }\n\n return parsed;\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class StringValidator extends BaseValidator<string> {\n private minLength?: number;\n private maxLength?: number;\n\n min(length: number): this {\n this.minLength = length;\n return this;\n }\n\n max(length: number): this {\n this.maxLength = length;\n return this;\n }\n\n parse(value: string | undefined, key: string): string | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (this.minLength !== undefined && value.length < this.minLength) {\n throw new SafeEnvError(\n key,\n `must be at least ${this.minLength} characters long`,\n );\n }\n\n if (this.maxLength !== undefined && value.length > this.maxLength) {\n throw new SafeEnvError(\n key,\n `must be at most ${this.maxLength} characters long`,\n );\n }\n\n return value;\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare abstract class BaseValidator<T> {
|
|
2
|
+
protected isOptional: boolean;
|
|
3
|
+
protected hasDefault: boolean;
|
|
4
|
+
protected defaultValue: T;
|
|
5
|
+
readonly __safeEnvOutput: T;
|
|
6
|
+
readonly __safeEnvOptional: false;
|
|
7
|
+
optional(): OptionalValidator<T, this>;
|
|
8
|
+
default(value: T): DefaultValidator<T, this>;
|
|
9
|
+
protected resolveUndefined(key: string): T | undefined;
|
|
10
|
+
abstract parse(value: string | undefined, key: string): T | undefined;
|
|
11
|
+
}
|
|
12
|
+
type OptionalValidator<T, V extends BaseValidator<T>> = V & {
|
|
13
|
+
readonly __safeEnvOptional: true;
|
|
14
|
+
};
|
|
15
|
+
type DefaultValidator<T, V extends BaseValidator<T>> = V & {
|
|
16
|
+
readonly __safeEnvDefault: true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare class BooleanValidator extends BaseValidator<boolean> {
|
|
20
|
+
parse(value: string | undefined, key: string): boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare class EnumValidator<T extends readonly string[]> extends BaseValidator<T[number]> {
|
|
24
|
+
private readonly values;
|
|
25
|
+
constructor(values: T);
|
|
26
|
+
parse(value: string | undefined, key: string): T[number] | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class NumberValidator extends BaseValidator<number> {
|
|
30
|
+
parse(value: string | undefined, key: string): number | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare class StringValidator extends BaseValidator<string> {
|
|
34
|
+
private minLength?;
|
|
35
|
+
private maxLength?;
|
|
36
|
+
min(length: number): this;
|
|
37
|
+
max(length: number): this;
|
|
38
|
+
parse(value: string | undefined, key: string): string | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type AnyValidator = BaseValidator<unknown>;
|
|
42
|
+
type InferValidator<T> = T extends BaseValidator<infer Output> ? T extends {
|
|
43
|
+
readonly __safeEnvOptional: true;
|
|
44
|
+
} ? Output | undefined : Output : never;
|
|
45
|
+
type InferEnv<T extends Record<string, AnyValidator>> = {
|
|
46
|
+
[K in keyof T]: InferValidator<T[K]>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare function createEnv<T extends Record<string, AnyValidator>>(schema: T, source?: NodeJS.ProcessEnv): InferEnv<T>;
|
|
50
|
+
|
|
51
|
+
declare class SafeEnvError extends Error {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
constructor(key: string, message: string);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare const z: {
|
|
57
|
+
string: () => StringValidator;
|
|
58
|
+
number: () => NumberValidator;
|
|
59
|
+
boolean: () => BooleanValidator;
|
|
60
|
+
enum: <const T extends readonly string[]>(values: T) => EnumValidator<T>;
|
|
61
|
+
};
|
|
62
|
+
type EnvSchema = Record<string, AnyValidator>;
|
|
63
|
+
type InferSchema<T extends EnvSchema> = InferEnv<T>;
|
|
64
|
+
|
|
65
|
+
export { type AnyValidator, type EnvSchema, type InferEnv, type InferSchema, type InferValidator, SafeEnvError, createEnv, z };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
declare abstract class BaseValidator<T> {
|
|
2
|
+
protected isOptional: boolean;
|
|
3
|
+
protected hasDefault: boolean;
|
|
4
|
+
protected defaultValue: T;
|
|
5
|
+
readonly __safeEnvOutput: T;
|
|
6
|
+
readonly __safeEnvOptional: false;
|
|
7
|
+
optional(): OptionalValidator<T, this>;
|
|
8
|
+
default(value: T): DefaultValidator<T, this>;
|
|
9
|
+
protected resolveUndefined(key: string): T | undefined;
|
|
10
|
+
abstract parse(value: string | undefined, key: string): T | undefined;
|
|
11
|
+
}
|
|
12
|
+
type OptionalValidator<T, V extends BaseValidator<T>> = V & {
|
|
13
|
+
readonly __safeEnvOptional: true;
|
|
14
|
+
};
|
|
15
|
+
type DefaultValidator<T, V extends BaseValidator<T>> = V & {
|
|
16
|
+
readonly __safeEnvDefault: true;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
declare class BooleanValidator extends BaseValidator<boolean> {
|
|
20
|
+
parse(value: string | undefined, key: string): boolean | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
declare class EnumValidator<T extends readonly string[]> extends BaseValidator<T[number]> {
|
|
24
|
+
private readonly values;
|
|
25
|
+
constructor(values: T);
|
|
26
|
+
parse(value: string | undefined, key: string): T[number] | undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
declare class NumberValidator extends BaseValidator<number> {
|
|
30
|
+
parse(value: string | undefined, key: string): number | undefined;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
declare class StringValidator extends BaseValidator<string> {
|
|
34
|
+
private minLength?;
|
|
35
|
+
private maxLength?;
|
|
36
|
+
min(length: number): this;
|
|
37
|
+
max(length: number): this;
|
|
38
|
+
parse(value: string | undefined, key: string): string | undefined;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type AnyValidator = BaseValidator<unknown>;
|
|
42
|
+
type InferValidator<T> = T extends BaseValidator<infer Output> ? T extends {
|
|
43
|
+
readonly __safeEnvOptional: true;
|
|
44
|
+
} ? Output | undefined : Output : never;
|
|
45
|
+
type InferEnv<T extends Record<string, AnyValidator>> = {
|
|
46
|
+
[K in keyof T]: InferValidator<T[K]>;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
declare function createEnv<T extends Record<string, AnyValidator>>(schema: T, source?: NodeJS.ProcessEnv): InferEnv<T>;
|
|
50
|
+
|
|
51
|
+
declare class SafeEnvError extends Error {
|
|
52
|
+
readonly key: string;
|
|
53
|
+
constructor(key: string, message: string);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
declare const z: {
|
|
57
|
+
string: () => StringValidator;
|
|
58
|
+
number: () => NumberValidator;
|
|
59
|
+
boolean: () => BooleanValidator;
|
|
60
|
+
enum: <const T extends readonly string[]>(values: T) => EnumValidator<T>;
|
|
61
|
+
};
|
|
62
|
+
type EnvSchema = Record<string, AnyValidator>;
|
|
63
|
+
type InferSchema<T extends EnvSchema> = InferEnv<T>;
|
|
64
|
+
|
|
65
|
+
export { type AnyValidator, type EnvSchema, type InferEnv, type InferSchema, type InferValidator, SafeEnvError, createEnv, z };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// src/errors.ts
|
|
2
|
+
var SafeEnvError = class extends Error {
|
|
3
|
+
key;
|
|
4
|
+
constructor(key, message) {
|
|
5
|
+
super(`[safe-env] ${key}: ${message}`);
|
|
6
|
+
this.name = "SafeEnvError";
|
|
7
|
+
this.key = key;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/createEnv.ts
|
|
12
|
+
function createEnv(schema, source = process.env) {
|
|
13
|
+
const result = {};
|
|
14
|
+
const errors = [];
|
|
15
|
+
for (const [key, validator] of Object.entries(schema)) {
|
|
16
|
+
try {
|
|
17
|
+
result[key] = validator.parse(source[key], key);
|
|
18
|
+
} catch (error) {
|
|
19
|
+
if (error instanceof SafeEnvError) {
|
|
20
|
+
errors.push(error);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (error instanceof Error) {
|
|
24
|
+
throw error;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(String(error));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
if (errors.length > 0) {
|
|
30
|
+
const message = errors.map((error) => ` \u2022 ${error.message}`).join("\n");
|
|
31
|
+
throw new SafeEnvError("environment", `Invalid environment configuration:
|
|
32
|
+
${message}`);
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// src/validators/base.ts
|
|
38
|
+
var BaseValidator = class {
|
|
39
|
+
isOptional = false;
|
|
40
|
+
hasDefault = false;
|
|
41
|
+
defaultValue;
|
|
42
|
+
__safeEnvOutput;
|
|
43
|
+
__safeEnvOptional;
|
|
44
|
+
optional() {
|
|
45
|
+
this.isOptional = true;
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
default(value) {
|
|
49
|
+
this.defaultValue = value;
|
|
50
|
+
this.hasDefault = true;
|
|
51
|
+
return this;
|
|
52
|
+
}
|
|
53
|
+
resolveUndefined(key) {
|
|
54
|
+
if (this.hasDefault) {
|
|
55
|
+
return this.defaultValue;
|
|
56
|
+
}
|
|
57
|
+
if (this.isOptional) {
|
|
58
|
+
return void 0;
|
|
59
|
+
}
|
|
60
|
+
throw new Error(`[safe-env] Missing required environment variable: ${key}`);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// src/validators/boolean.ts
|
|
65
|
+
var BooleanValidator = class extends BaseValidator {
|
|
66
|
+
parse(value, key) {
|
|
67
|
+
if (value === void 0) {
|
|
68
|
+
return this.resolveUndefined(key);
|
|
69
|
+
}
|
|
70
|
+
if (value === "true") return true;
|
|
71
|
+
if (value === "false") return false;
|
|
72
|
+
throw new SafeEnvError(
|
|
73
|
+
key,
|
|
74
|
+
`must be "true" or "false", received ${JSON.stringify(value)}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// src/validators/enum.ts
|
|
80
|
+
var EnumValidator = class extends BaseValidator {
|
|
81
|
+
constructor(values) {
|
|
82
|
+
super();
|
|
83
|
+
this.values = values;
|
|
84
|
+
}
|
|
85
|
+
values;
|
|
86
|
+
parse(value, key) {
|
|
87
|
+
if (value === void 0) {
|
|
88
|
+
return this.resolveUndefined(key);
|
|
89
|
+
}
|
|
90
|
+
if (this.values.includes(value)) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
throw new SafeEnvError(
|
|
94
|
+
key,
|
|
95
|
+
`must be one of ${this.values.map((item) => JSON.stringify(item)).join(", ")}, received ${JSON.stringify(value)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// src/validators/number.ts
|
|
101
|
+
var NumberValidator = class extends BaseValidator {
|
|
102
|
+
parse(value, key) {
|
|
103
|
+
if (value === void 0) {
|
|
104
|
+
return this.resolveUndefined(key);
|
|
105
|
+
}
|
|
106
|
+
const parsed = Number(value);
|
|
107
|
+
if (!Number.isFinite(parsed)) {
|
|
108
|
+
throw new SafeEnvError(key, `must be a valid finite number, received ${JSON.stringify(value)}`);
|
|
109
|
+
}
|
|
110
|
+
return parsed;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// src/validators/string.ts
|
|
115
|
+
var StringValidator = class extends BaseValidator {
|
|
116
|
+
minLength;
|
|
117
|
+
maxLength;
|
|
118
|
+
min(length) {
|
|
119
|
+
this.minLength = length;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
max(length) {
|
|
123
|
+
this.maxLength = length;
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
parse(value, key) {
|
|
127
|
+
if (value === void 0) {
|
|
128
|
+
return this.resolveUndefined(key);
|
|
129
|
+
}
|
|
130
|
+
if (this.minLength !== void 0 && value.length < this.minLength) {
|
|
131
|
+
throw new SafeEnvError(
|
|
132
|
+
key,
|
|
133
|
+
`must be at least ${this.minLength} characters long`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (this.maxLength !== void 0 && value.length > this.maxLength) {
|
|
137
|
+
throw new SafeEnvError(
|
|
138
|
+
key,
|
|
139
|
+
`must be at most ${this.maxLength} characters long`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
return value;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// src/index.ts
|
|
147
|
+
var z = {
|
|
148
|
+
string: () => new StringValidator(),
|
|
149
|
+
number: () => new NumberValidator(),
|
|
150
|
+
boolean: () => new BooleanValidator(),
|
|
151
|
+
enum: (values) => new EnumValidator(values)
|
|
152
|
+
};
|
|
153
|
+
export {
|
|
154
|
+
SafeEnvError,
|
|
155
|
+
createEnv,
|
|
156
|
+
z
|
|
157
|
+
};
|
|
158
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/createEnv.ts","../src/validators/base.ts","../src/validators/boolean.ts","../src/validators/enum.ts","../src/validators/number.ts","../src/validators/string.ts","../src/index.ts"],"sourcesContent":["export class SafeEnvError extends Error {\n public readonly key: string;\n\n constructor(key: string, message: string) {\n super(`[safe-env] ${key}: ${message}`);\n this.name = \"SafeEnvError\";\n this.key = key;\n }\n}\n","import { SafeEnvError } from \"./errors\";\nimport type { AnyValidator, InferEnv } from \"./types\";\n\nexport function createEnv<T extends Record<string, AnyValidator>>(\n schema: T,\n source: NodeJS.ProcessEnv = process.env,\n): InferEnv<T> {\n const result: Record<string, unknown> = {};\n const errors: SafeEnvError[] = [];\n\n for (const [key, validator] of Object.entries(schema)) {\n try {\n result[key] = validator.parse(source[key], key);\n } catch (error) {\n if (error instanceof SafeEnvError) {\n errors.push(error);\n continue;\n }\n\n if (error instanceof Error) {\n throw error;\n }\n\n throw new Error(String(error));\n }\n }\n\n if (errors.length > 0) {\n const message = errors.map((error) => ` • ${error.message}`).join(\"\\n\");\n throw new SafeEnvError(\"environment\", `Invalid environment configuration:\\n${message}`);\n }\n\n return result as InferEnv<T>;\n}\n","export abstract class BaseValidator<T> {\n protected isOptional = false;\n protected hasDefault = false;\n protected defaultValue!: T;\n\n readonly __safeEnvOutput!: T;\n readonly __safeEnvOptional!: false;\n\n optional(): OptionalValidator<T, this> {\n this.isOptional = true;\n return this as OptionalValidator<T, this>;\n }\n\n default(value: T): DefaultValidator<T, this> {\n this.defaultValue = value;\n this.hasDefault = true;\n return this as DefaultValidator<T, this>;\n }\n\n protected resolveUndefined(key: string): T | undefined {\n if (this.hasDefault) {\n return this.defaultValue;\n }\n\n if (this.isOptional) {\n return undefined;\n }\n\n throw new Error(`[safe-env] Missing required environment variable: ${key}`);\n }\n\n abstract parse(value: string | undefined, key: string): T | undefined;\n}\n\nexport type OptionalValidator<T, V extends BaseValidator<T>> = V & {\n readonly __safeEnvOptional: true;\n};\n\nexport type DefaultValidator<T, V extends BaseValidator<T>> = V & {\n readonly __safeEnvDefault: true;\n};\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class BooleanValidator extends BaseValidator<boolean> {\n parse(value: string | undefined, key: string): boolean | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (value === \"true\") return true;\n if (value === \"false\") return false;\n\n throw new SafeEnvError(\n key,\n `must be \"true\" or \"false\", received ${JSON.stringify(value)}`,\n );\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class EnumValidator<T extends readonly string[]> extends BaseValidator<T[number]> {\n constructor(private readonly values: T) {\n super();\n }\n\n parse(value: string | undefined, key: string): T[number] | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (this.values.includes(value)) {\n return value as T[number];\n }\n\n throw new SafeEnvError(\n key,\n `must be one of ${this.values.map((item) => JSON.stringify(item)).join(\", \")}, received ${JSON.stringify(value)}`,\n );\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class NumberValidator extends BaseValidator<number> {\n parse(value: string | undefined, key: string): number | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n const parsed = Number(value);\n\n if (!Number.isFinite(parsed)) {\n throw new SafeEnvError(key, `must be a valid finite number, received ${JSON.stringify(value)}`);\n }\n\n return parsed;\n }\n}\n","import { BaseValidator } from \"./base\";\nimport { SafeEnvError } from \"../errors\";\n\nexport class StringValidator extends BaseValidator<string> {\n private minLength?: number;\n private maxLength?: number;\n\n min(length: number): this {\n this.minLength = length;\n return this;\n }\n\n max(length: number): this {\n this.maxLength = length;\n return this;\n }\n\n parse(value: string | undefined, key: string): string | undefined {\n if (value === undefined) {\n return this.resolveUndefined(key);\n }\n\n if (this.minLength !== undefined && value.length < this.minLength) {\n throw new SafeEnvError(\n key,\n `must be at least ${this.minLength} characters long`,\n );\n }\n\n if (this.maxLength !== undefined && value.length > this.maxLength) {\n throw new SafeEnvError(\n key,\n `must be at most ${this.maxLength} characters long`,\n );\n }\n\n return value;\n }\n}\n","import { createEnv } from \"./createEnv\";\nimport { BooleanValidator } from \"./validators/boolean\";\nimport { EnumValidator } from \"./validators/enum\";\nimport { NumberValidator } from \"./validators/number\";\nimport { StringValidator } from \"./validators/string\";\nimport type { AnyValidator, InferEnv, InferValidator } from \"./types\";\n\nexport { createEnv } from \"./createEnv\";\nexport { SafeEnvError } from \"./errors\";\nexport type { AnyValidator, InferEnv, InferValidator } from \"./types\";\n\nexport const z = {\n string: () => new StringValidator(),\n number: () => new NumberValidator(),\n boolean: () => new BooleanValidator(),\n enum: <const T extends readonly string[]>(values: T) => new EnumValidator(values),\n};\n\nexport type EnvSchema = Record<string, AnyValidator>;\nexport type InferSchema<T extends EnvSchema> = InferEnv<T>;\n\nvoid createEnv;\nvoid (null as unknown as InferValidator<AnyValidator>);\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mrinal1224/safe-env",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Type-safe environment variable validation for Node.js and TypeScript.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"sideEffects": false,
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsup",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"coverage": "vitest run --coverage",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"lint": "eslint ."
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"env",
|
|
32
|
+
"environment-variables",
|
|
33
|
+
"environment",
|
|
34
|
+
"typescript",
|
|
35
|
+
"validation",
|
|
36
|
+
"configuration",
|
|
37
|
+
"nodejs",
|
|
38
|
+
"config"
|
|
39
|
+
],
|
|
40
|
+
"author": "Mrinal Bhattacharya",
|
|
41
|
+
"license": "MIT",
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "git+https://github.com/mrinal1224/Safe-Env.git"
|
|
45
|
+
},
|
|
46
|
+
"homepage": "https://github.com/mrinal1224/Safe-Env#readme",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/mrinal1224/Safe-Env/issues"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=20"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"access": "public"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.0.0",
|
|
58
|
+
"@vitest/coverage-v8": "^2.1.0",
|
|
59
|
+
"eslint": "^9.0.0",
|
|
60
|
+
"tsup": "^8.0.0",
|
|
61
|
+
"typescript": "^5.0.0",
|
|
62
|
+
"vitest": "^2.1.0"
|
|
63
|
+
}
|
|
64
|
+
}
|