@zh-moody/safe-env 0.2.0 → 0.3.1
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/README.md +73 -84
- package/dist/{chunk-5ZP5OWCL.cjs → chunk-FH2DFX4J.cjs} +1 -1
- package/dist/chunk-GCSZ35F2.cjs +5 -0
- package/dist/chunk-NRXIL5NF.js +5 -0
- package/dist/{chunk-BEFKPDEA.js → chunk-POVTEBYN.js} +1 -1
- package/dist/fs-node.cjs +1 -1
- package/dist/fs-node.js +1 -1
- package/dist/index.cjs +1 -5
- package/dist/index.d.cts +3 -34
- package/dist/index.d.ts +3 -34
- package/dist/index.js +1 -5
- package/dist/types-B59cqDDx.d.cts +40 -0
- package/dist/types-B59cqDDx.d.ts +40 -0
- package/dist/vite.cjs +1 -0
- package/dist/vite.d.cts +19 -0
- package/dist/vite.d.ts +19 -0
- package/dist/vite.js +1 -0
- package/package.json +13 -3
package/README.md
CHANGED
|
@@ -10,82 +10,69 @@
|
|
|
10
10
|
|
|
11
11
|
---
|
|
12
12
|
|
|
13
|
-
### 🔗 链接 (Links)
|
|
14
|
-
|
|
15
|
-
- **GitHub Repository**: [https://github.com/zhMoody/safe-env](https://github.com/zhMoody/safe-env)
|
|
16
|
-
- **NPM Package**: [https://www.npmjs.com/package/@zh-moody/safe-env](https://www.npmjs.com/package/@zh-moody/safe-env)
|
|
17
|
-
|
|
18
|
-
---
|
|
19
|
-
|
|
20
13
|
### 🚀 核心特性
|
|
21
14
|
|
|
15
|
+
- **构建时预校验**:提供 Vite 插件,在 `npm run dev` 或 `build` 的瞬间拦截错误,无需打开浏览器。
|
|
16
|
+
- **IDE 增强**:支持 `.description()` 字段,直接在代码中通过悬停查看变量用途。
|
|
17
|
+
- **更强的数据转换**:内置 `s.array()`, `s.boolean()` 增强转换,支持 `.transform()` 链式 Pipe 处理。
|
|
18
|
+
- **内置常用规则**:提供 `.url()`, `.email()`, `.regex()` 等高频校验,无需依赖 zod。
|
|
22
19
|
- **类型安全**:自动推断配置对象类型,拥有完美的 IDE 补全。
|
|
23
|
-
-
|
|
24
|
-
- **自动前缀**:支持 `VITE_` 或 `REACT_APP_` 自动补全,代码里用 `PORT`,配置里用 `VITE_PORT`。
|
|
25
|
-
- **跨端兼容**:自动识别 Node/浏览器环境,支持双端“防御性退出”。
|
|
26
|
-
- **极致轻量**:Minified 体积约 **2.6 KB**,Gzip 后仅 **1.3 KB**,零运行时依赖。
|
|
20
|
+
- **极致轻量**:Gzip 压缩后仅 **1.9 KB**,零运行时依赖。
|
|
27
21
|
|
|
28
22
|
---
|
|
29
23
|
|
|
30
24
|
### 🚀 快速上手
|
|
31
25
|
|
|
32
|
-
#### 🔹 [Vite / React / Vue]
|
|
33
|
-
|
|
26
|
+
#### 🔹 [Vite / React / Vue] 使用
|
|
27
|
+
在前端,建议配合 Vite 插件实现**构建时校验**。
|
|
34
28
|
|
|
35
|
-
**1.
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
|
|
29
|
+
**1. 配置 Vite 插件 (`vite.config.ts`):**
|
|
30
|
+
```typescript
|
|
31
|
+
import { viteSafeEnv } from '@zh-moody/safe-env/vite';
|
|
32
|
+
import { schema } from './src/env'; // 建议将 schema 提取到独立文件
|
|
33
|
+
|
|
34
|
+
export default {
|
|
35
|
+
plugins: [
|
|
36
|
+
viteSafeEnv(schema) // 构建时校验,配置错误直接停止构建
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
**2.
|
|
41
|
+
**2. 定义配置并导出 (`src/env.ts`):**
|
|
42
42
|
```typescript
|
|
43
43
|
import { safeEnv, s } from '@zh-moody/safe-env';
|
|
44
44
|
|
|
45
|
-
export const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
export const schema = {
|
|
46
|
+
VITE_API_URL: s.string().url().description("后端 API 地址"),
|
|
47
|
+
VITE_PORT: s.number(3000).description("服务端口"),
|
|
48
|
+
VITE_FEATURES: s.array().description("启用的特性列表")
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export const env = safeEnv(schema, {
|
|
52
|
+
source: import.meta.env
|
|
50
53
|
});
|
|
51
54
|
```
|
|
52
55
|
|
|
53
56
|
---
|
|
54
57
|
|
|
55
|
-
#### 🔸 [Node.js / 服务端]
|
|
58
|
+
#### 🔸 [Node.js / 服务端] 使用
|
|
56
59
|
在后端,库会自动寻找并解析磁盘上的 `.env` 文件。
|
|
57
60
|
|
|
58
|
-
**1.
|
|
59
|
-
```bash
|
|
60
|
-
DB_HOST=localhost
|
|
61
|
-
DB_PORT=5432
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**2. 定义配置 (`src/db.ts`):**
|
|
61
|
+
**1. 定义配置 (`src/config.ts`):**
|
|
65
62
|
```typescript
|
|
66
63
|
import { safeEnv, s } from '@zh-moody/safe-env';
|
|
67
64
|
|
|
68
|
-
const
|
|
69
|
-
DB_HOST: s.string('localhost'),
|
|
70
|
-
DB_PORT: s.number(5432)
|
|
71
|
-
|
|
65
|
+
const config = safeEnv({
|
|
66
|
+
DB_HOST: s.string('localhost').description("数据库主机"),
|
|
67
|
+
DB_PORT: s.number(5432).min(1).max(65535),
|
|
68
|
+
ADMIN_EMAIL: s.string().email()
|
|
69
|
+
});
|
|
72
70
|
|
|
73
|
-
export default
|
|
71
|
+
export default config;
|
|
74
72
|
```
|
|
75
73
|
|
|
76
74
|
---
|
|
77
75
|
|
|
78
|
-
### 📂 核心对比:Schema 与 .env 对应关系
|
|
79
|
-
|
|
80
|
-
| Schema 定义 | .env 中的写法 | 结果 |
|
|
81
|
-
| :--- | :--- | :--- |
|
|
82
|
-
| `s.string()` | `KEY=val` | ✅ 通过 |
|
|
83
|
-
| `s.string()` | *(未填写)* | ❌ **报错并拦截启动** |
|
|
84
|
-
| `s.string('App')` | `KEY=` (留空) | ✅ 自动降级为 `'App'` |
|
|
85
|
-
| `s.number()` | `KEY=abc` | ❌ **报错:Invalid number** |
|
|
86
|
-
|
|
87
|
-
---
|
|
88
|
-
|
|
89
76
|
### 🛠️ API 详解
|
|
90
77
|
|
|
91
78
|
#### 1. 定义字段 (`s.xxx`)
|
|
@@ -93,63 +80,65 @@ export default dbConfig;
|
|
|
93
80
|
- `s.number(default?)`: 数字。自动将字符串转为 `number`。
|
|
94
81
|
- `s.boolean(default?)`: 布尔型。支持将 `"true"`, `"1"`, `"yes"`, `"on"` 解析为 `true`(大小写无关)。
|
|
95
82
|
- `s.array(default?, separator?)`: 数组型。支持将字符串按分隔符(默认 `,`)拆分为数组。
|
|
96
|
-
- 示例:`s.array([], '|')`
|
|
97
83
|
- `s.enum(options, default?)`: 枚举。值必须在数组中。
|
|
98
|
-
- 示例:`s.enum(['dev', 'prod'], 'dev')`
|
|
99
84
|
|
|
100
|
-
#### 2.
|
|
101
|
-
|
|
85
|
+
#### 2. 校验与增强 (链式调用)
|
|
86
|
+
|
|
87
|
+
每个字段都可以通过链式调用进行深度定制:
|
|
102
88
|
|
|
103
|
-
- **`.
|
|
89
|
+
- **`.url()`**: 校验是否为合法 URL 格式。
|
|
90
|
+
```typescript
|
|
91
|
+
API_URL: s.string().url()
|
|
92
|
+
```
|
|
93
|
+
- **`.email()`**: 校验是否为合法邮箱格式。
|
|
104
94
|
```typescript
|
|
105
|
-
|
|
106
|
-
|
|
95
|
+
CONTACT: s.string().email()
|
|
96
|
+
```
|
|
97
|
+
- **`.regex(pattern, msg?)`**: 自定义正则校验。
|
|
98
|
+
```typescript
|
|
99
|
+
VERSION: s.string().regex(/^v\d+\.\d+\.\d+$/, "版本号必须以 v 开头")
|
|
100
|
+
```
|
|
101
|
+
- **`.description(text)`**: 添加变量描述。该描述会映射到 IDE 的悬停提示中。
|
|
102
|
+
```typescript
|
|
103
|
+
PORT: s.number(3000).description("本地服务器端口")
|
|
104
|
+
```
|
|
105
|
+
- **`.transform(fn)`**: 自定义数据转换,支持多级链式 Pipe。
|
|
106
|
+
```typescript
|
|
107
|
+
// 示例 1: 拿到字符串 -> 去空格 -> 转大写
|
|
108
|
+
NAME: s.string().transform(v => v.trim()).transform(v => v.toUpperCase())
|
|
107
109
|
|
|
108
110
|
// 示例 2: 将逗号分隔的数字字符串转为数字数组
|
|
109
111
|
SCORES: s.array().transform(arr => arr.map(Number))
|
|
110
112
|
```
|
|
111
|
-
|
|
112
|
-
- **`.from(key)`**: 指定环境变量名。
|
|
113
|
+
- **`.from(key)`**: 映射环境变量名(别名)。
|
|
113
114
|
```typescript
|
|
114
|
-
//
|
|
115
|
-
port: s.number().from('
|
|
115
|
+
// 即使环境变量叫 VITE_SERVER_PORT,代码里也可以叫 port
|
|
116
|
+
port: s.number().from('VITE_SERVER_PORT')
|
|
116
117
|
```
|
|
117
|
-
|
|
118
118
|
- **`.min(n)` / `.max(n)`**: 限制数字取值范围。
|
|
119
119
|
```typescript
|
|
120
|
-
// 端口必须在
|
|
121
|
-
PORT: s.number().min(
|
|
120
|
+
// 端口必须在 1024-65535 之间
|
|
121
|
+
PORT: s.number().min(1024).max(65535)
|
|
122
122
|
```
|
|
123
|
-
|
|
124
|
-
- **`.validate(fn, msg?)`**: 自定义校验(如:邮箱、URL 格式)。
|
|
123
|
+
- **`.validate(fn, msg?)`**: 完全自定义的校验逻辑。
|
|
125
124
|
```typescript
|
|
126
|
-
//
|
|
127
|
-
|
|
125
|
+
// 必须是特定的内部域名
|
|
126
|
+
INTERNAL_URL: s.string().validate(v => v.endsWith('.internal.com'), 'Must be an internal URL')
|
|
128
127
|
```
|
|
129
128
|
|
|
130
|
-
#### 3.
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
| `mode` | **[Node.js]** | **可选**。指定模式(dev/prod)以加载对应的 `.env.prod` 等文件。 |
|
|
137
|
-
| `loadProcessEnv` | **[Node.js]** | **可选**。是否加载系统环境变量,默认 `true`。 |
|
|
129
|
+
#### 3. 加载规则 (Env Priority)
|
|
130
|
+
`safe-env` 遵循标准的优先级顺序(从低到高):
|
|
131
|
+
1. `.env` (基础配置)
|
|
132
|
+
2. `.env.[mode]` (环境配置,如 `.env.development`)
|
|
133
|
+
3. `.env.local` (本地覆盖)
|
|
134
|
+
4. `.env.[mode].local` (环境特定的本地覆盖)
|
|
138
135
|
|
|
139
136
|
---
|
|
140
137
|
|
|
141
|
-
### 🎨
|
|
142
|
-
|
|
143
|
-
当校验失败时,`safe-env` 会在控制台打印一个显眼的表格,告诉你哪个字段错了、原因是什么以及当前的值。在浏览器中这会抛出一个 Error 阻止应用启动,在 Node.js 中会直接 `process.exit(1)`。
|
|
144
|
-
|
|
145
|
-
---
|
|
146
|
-
|
|
147
|
-
### 📦 跨平台支持
|
|
148
|
-
- **前端**: Vite (Vue/React/Vanilla)。
|
|
149
|
-
- **后端**: Node.js (ESM/CJS)。
|
|
138
|
+
### 🎨 错误报告
|
|
139
|
+
当校验失败时,`safe-env` 会在控制台打印精美的表格,清晰展示:**Key / 错误原因 / 当前值**。
|
|
150
140
|
|
|
151
141
|
---
|
|
152
142
|
|
|
153
143
|
### 📄 开源协议 (License)
|
|
154
|
-
|
|
155
|
-
[MIT License](./LICENSE) - Copyright (c) 2025 Moody.
|
|
144
|
+
[MIT License](./LICENSE) - Copyright (c) 2026 Moody.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);var _path = require('path'); var _path2 = _interopRequireDefault(_path);function s(i){let r={},c=i.split(/\r?\n/);for(let f of c){let n=f.trim();if(!n||n.startsWith("#"))continue;let e=n.indexOf("=");if(e==-1)continue;let l=n.slice(0,e).trim(),t=n.slice(e+1).trim();(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),r[l]=t}return r}function
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }var _fs = require('fs'); var _fs2 = _interopRequireDefault(_fs);var _path = require('path'); var _path2 = _interopRequireDefault(_path);function s(i){let r={},c=i.split(/\r?\n/);for(let f of c){let n=f.trim();if(!n||n.startsWith("#"))continue;let e=n.indexOf("=");if(e==-1)continue;let l=n.slice(0,e).trim(),t=n.slice(e+1).trim();(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),r[l]=t}return r}function h(i=".env"){try{let r=_path2.default.resolve(process.cwd(),i);if(_fs2.default.existsSync(r))return s(_fs2.default.readFileSync(r,"utf-8"))}catch (e2){}return{}}exports.a = s; exports.b = h;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkFH2DFX4Jcjs = require('./chunk-FH2DFX4J.cjs');function $(a){let e={r:"\x1B[31m",g:"\x1B[32m",y:"\x1B[33m",b:"\x1B[1m",res:"\x1B[0m",d:"\x1B[2m"};console.error(`
|
|
2
|
+
${e.r}${e.b}\u274C SafeEnv Error${e.res}
|
|
3
|
+
${e.d}${"".padEnd(40,"-")}${e.res}`),a.forEach(i=>{let d=i.value===void 0?"undefined":`"${i.value}"`;console.error(`${e.y}${i.key}${e.res} ${e.r}${i.error}${e.res} ${e.d}${d}${e.res}`)}),console.error(`${e.d}${"".padEnd(40,"-")}${e.res}
|
|
4
|
+
${e.g}\u{1F4A1} Check your .env files.${e.res}
|
|
5
|
+
`)}function h(a,e={}){let{loadProcessEnv:i=!0,source:d,prefix:p=""}=e,f;if(d!==void 0)f=d||{};else{let n=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),o=[".env",`.env.${n}`,".env.local",`.env.${n}.local`],s={};for(let t of o)s={...s,..._chunkFH2DFX4Jcjs.b.call(void 0, t)};f={...s,...i&&typeof process<"u"?process.env:{}}}let E={},c=[];for(let n in a){let o=a[n],s=o.sourceKey;if(!s){let r=p+n;f[r]!==void 0?s=r:f[n]!==void 0?s=n:s=p?r:n}let t=f[s];try{let r;if(t===void 0||t===""&&o.default!==void 0){if(o.required&&t===void 0)throw new Error("Required field missing");r=o.default}else r=o.parse(t);if(r!==void 0&&o.metadata){let{min:l,max:u,validate:m}=o.metadata;if(typeof r=="number"){if(l!==void 0&&r<l)throw new Error(`Below min ${l}`);if(u!==void 0&&r>u)throw new Error(`Above max ${u}`)}if(m&&!m.fn(r))throw new Error(m.message)}E[n]=r}catch(r){c.push({key:s,error:r.message,value:t})}}if(c.length>0){$(c);let n=typeof process<"u"&&!!process.exit,o=typeof process<"u"&&process.env.NODE_ENV==="test";if(n&&!d&&!o)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.")}return E}exports.a = $; exports.b = h;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{b as v}from"./chunk-POVTEBYN.js";function $(a){let e={r:"\x1B[31m",g:"\x1B[32m",y:"\x1B[33m",b:"\x1B[1m",res:"\x1B[0m",d:"\x1B[2m"};console.error(`
|
|
2
|
+
${e.r}${e.b}\u274C SafeEnv Error${e.res}
|
|
3
|
+
${e.d}${"".padEnd(40,"-")}${e.res}`),a.forEach(i=>{let d=i.value===void 0?"undefined":`"${i.value}"`;console.error(`${e.y}${i.key}${e.res} ${e.r}${i.error}${e.res} ${e.d}${d}${e.res}`)}),console.error(`${e.d}${"".padEnd(40,"-")}${e.res}
|
|
4
|
+
${e.g}\u{1F4A1} Check your .env files.${e.res}
|
|
5
|
+
`)}function h(a,e={}){let{loadProcessEnv:i=!0,source:d,prefix:p=""}=e,f;if(d!==void 0)f=d||{};else{let n=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),o=[".env",`.env.${n}`,".env.local",`.env.${n}.local`],s={};for(let t of o)s={...s,...v(t)};f={...s,...i&&typeof process<"u"?process.env:{}}}let E={},c=[];for(let n in a){let o=a[n],s=o.sourceKey;if(!s){let r=p+n;f[r]!==void 0?s=r:f[n]!==void 0?s=n:s=p?r:n}let t=f[s];try{let r;if(t===void 0||t===""&&o.default!==void 0){if(o.required&&t===void 0)throw new Error("Required field missing");r=o.default}else r=o.parse(t);if(r!==void 0&&o.metadata){let{min:l,max:u,validate:m}=o.metadata;if(typeof r=="number"){if(l!==void 0&&r<l)throw new Error(`Below min ${l}`);if(u!==void 0&&r>u)throw new Error(`Above max ${u}`)}if(m&&!m.fn(r))throw new Error(m.message)}E[n]=r}catch(r){c.push({key:s,error:r.message,value:t})}}if(c.length>0){$(c);let n=typeof process<"u"&&!!process.exit,o=typeof process<"u"&&process.env.NODE_ENV==="test";if(n&&!d&&!o)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.")}return E}export{$ as a,h as b};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import o from"fs";import
|
|
1
|
+
import o from"fs";import a from"path";function s(i){let r={},c=i.split(/\r?\n/);for(let f of c){let n=f.trim();if(!n||n.startsWith("#"))continue;let e=n.indexOf("=");if(e==-1)continue;let l=n.slice(0,e).trim(),t=n.slice(e+1).trim();(t.startsWith('"')&&t.endsWith('"')||t.startsWith("'")&&t.endsWith("'"))&&(t=t.slice(1,-1)),r[l]=t}return r}function h(i=".env"){try{let r=a.resolve(process.cwd(),i);if(o.existsSync(r))return s(o.readFileSync(r,"utf-8"))}catch{}return{}}export{s as a,h as b};
|
package/dist/fs-node.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkFH2DFX4Jcjs = require('./chunk-FH2DFX4J.cjs');exports.loadDotEnv = _chunkFH2DFX4Jcjs.b;
|
package/dist/fs-node.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{b as a}from"./chunk-
|
|
1
|
+
import{b as a}from"./chunk-POVTEBYN.js";export{a as loadDotEnv};
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var
|
|
2
|
-
${e.r}${e.b}\u274C SafeEnv Error${e.res}
|
|
3
|
-
${e.d}${"".padEnd(40,"-")}${e.res}`),n.forEach(r=>{let i=r.value===void 0?"undefined":`"${r.value}"`;console.error(`${e.y}${r.key}${e.res} ${e.r}${r.error}${e.res} ${e.d}${i}${e.res}`)}),console.error(`${e.d}${"".padEnd(40,"-")}${e.res}
|
|
4
|
-
${e.g}\u{1F4A1} Check your .env files.${e.res}
|
|
5
|
-
`)}function D(n,e={}){let{loadProcessEnv:r=!0,source:i,prefix:c=""}=e,o;if(i)o=i;else{let a=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),s=[".env",".env.local",`.env.${a}`,`.env.${a}.local`],l={};for(let d of s)l={...l,..._chunk5ZP5OWCLcjs.b.call(void 0, d)};o={...l,...r&&typeof process<"u"?process.env:{}}}let f={},u=[];for(let a in n){let s=n[a],l=s.sourceKey||(o[c+a]!==void 0?c+a:a),d=o[l];try{let t;if(d===void 0||d===""&&s.default!==void 0){if(s.required&&d===void 0)throw new Error("Required field missing");t=s.default}else t=s.parse(d);if(t!==void 0&&s.metadata){let{min:p,max:y,validate:E}=s.metadata;if(typeof t=="number"){if(p!==void 0&&t<p)throw new Error(`Below min ${p}`);if(y!==void 0&&t>y)throw new Error(`Above max ${y}`)}if(E&&!E.fn(t))throw new Error(E.message)}f[a]=t}catch(t){u.push({key:l,error:t.message,value:d})}}if(u.length>0)if($(u),typeof process<"u"&&!!process.exit&&!i)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.");return f}exports.loadDotEnv = _chunk5ZP5OWCLcjs.b; exports.parseDotEnv = _chunk5ZP5OWCLcjs.a; exports.reportErrors = $; exports.s = g; exports.safeEnv = D;
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkGCSZ35F2cjs = require('./chunk-GCSZ35F2.cjs');var _chunkFH2DFX4Jcjs = require('./chunk-FH2DFX4J.cjs');function a(n,i,e,o=[]){return{type:n,default:i,required:i===void 0,parse:e,metadata:o.length?{options:o}:{},from(t){return this.sourceKey=t,this},validate(t,r="Custom validation failed"){return this.metadata={...this.metadata,validate:{fn:t,message:r}},this},min(t){return this.metadata={...this.metadata,min:t},this},max(t){return this.metadata={...this.metadata,max:t},this},transform(t){let r=this.parse;return this.parse=s=>t(r(s)),this},url(){return this.validate(t=>{try{return new URL(String(t)),!0}catch (e2){return!1}},"Invalid URL format")},email(){let t=/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;return this.validate(r=>t.test(String(r)),"Invalid email format")},regex(t,r="Value does not match pattern"){return this.validate(s=>t.test(String(s)),r)},description(t){return this.metadata={...this.metadata,description:t},this}}}var f={string:n=>a("string",n,i=>String(i)),number:n=>a("number",n,i=>{let e=Number(i);if(isNaN(e))throw new Error(`Invalid number: ${i}`);return e}),boolean:n=>a("boolean",n,i=>{if(typeof i=="boolean")return i;let e=String(i).toLowerCase();return e==="true"||e==="1"||e==="yes"||e==="on"}),enum:(n,i)=>a("enum",i,e=>{if(!n.includes(e))throw new Error(`Value "${e}" is not one of: ${n.join(", ")}`);return e},n),array:(n,i=",")=>a("array",n,e=>Array.isArray(e)?e:typeof e!="string"?[]:e.split(i).map(o=>o.trim()).filter(Boolean))};exports.parseDotEnv = _chunkFH2DFX4Jcjs.a; exports.reportErrors = _chunkGCSZ35F2cjs.a; exports.s = f; exports.safeEnv = _chunkGCSZ35F2cjs.b;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,36 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
type BaseType = "string" | "number" | "boolean" | "enum" | "array";
|
|
4
|
-
interface FieldDefinition<T = any> {
|
|
5
|
-
type: BaseType;
|
|
6
|
-
default?: T;
|
|
7
|
-
required: boolean;
|
|
8
|
-
sourceKey?: string;
|
|
9
|
-
metadata?: {
|
|
10
|
-
min?: number;
|
|
11
|
-
max?: number;
|
|
12
|
-
options?: T[];
|
|
13
|
-
validate?: {
|
|
14
|
-
fn: (val: T) => boolean;
|
|
15
|
-
message: string;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
parse: (val: any) => T;
|
|
19
|
-
from: (key: string) => FieldDefinition<T>;
|
|
20
|
-
validate: (fn: (val: T) => boolean, message?: string) => FieldDefinition<T>;
|
|
21
|
-
min: (val: number) => FieldDefinition<T>;
|
|
22
|
-
max: (val: number) => FieldDefinition<T>;
|
|
23
|
-
transform: <U>(fn: (val: T) => U) => FieldDefinition<U>;
|
|
24
|
-
}
|
|
25
|
-
type Schema = Record<string, FieldDefinition>;
|
|
26
|
-
interface EnvError {
|
|
27
|
-
key: string;
|
|
28
|
-
error: string;
|
|
29
|
-
value: any;
|
|
30
|
-
}
|
|
31
|
-
type InferSchema<T> = {
|
|
32
|
-
[K in keyof T]: T[K] extends FieldDefinition<infer U> ? U : never;
|
|
33
|
-
};
|
|
1
|
+
import { F as FieldDefinition, S as Schema, I as InferSchema, E as EnvError } from './types-B59cqDDx.cjs';
|
|
2
|
+
export { B as BaseType } from './types-B59cqDDx.cjs';
|
|
34
3
|
|
|
35
4
|
declare const s: {
|
|
36
5
|
string: (defaultValue?: string) => FieldDefinition<string>;
|
|
@@ -55,4 +24,4 @@ declare function parseDotEnv(content: string): Record<string, string>;
|
|
|
55
24
|
|
|
56
25
|
declare function reportErrors(errors: EnvError[]): void;
|
|
57
26
|
|
|
58
|
-
export {
|
|
27
|
+
export { EnvError, FieldDefinition, InferSchema, Schema, parseDotEnv, reportErrors, s, safeEnv };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,36 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
type BaseType = "string" | "number" | "boolean" | "enum" | "array";
|
|
4
|
-
interface FieldDefinition<T = any> {
|
|
5
|
-
type: BaseType;
|
|
6
|
-
default?: T;
|
|
7
|
-
required: boolean;
|
|
8
|
-
sourceKey?: string;
|
|
9
|
-
metadata?: {
|
|
10
|
-
min?: number;
|
|
11
|
-
max?: number;
|
|
12
|
-
options?: T[];
|
|
13
|
-
validate?: {
|
|
14
|
-
fn: (val: T) => boolean;
|
|
15
|
-
message: string;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
parse: (val: any) => T;
|
|
19
|
-
from: (key: string) => FieldDefinition<T>;
|
|
20
|
-
validate: (fn: (val: T) => boolean, message?: string) => FieldDefinition<T>;
|
|
21
|
-
min: (val: number) => FieldDefinition<T>;
|
|
22
|
-
max: (val: number) => FieldDefinition<T>;
|
|
23
|
-
transform: <U>(fn: (val: T) => U) => FieldDefinition<U>;
|
|
24
|
-
}
|
|
25
|
-
type Schema = Record<string, FieldDefinition>;
|
|
26
|
-
interface EnvError {
|
|
27
|
-
key: string;
|
|
28
|
-
error: string;
|
|
29
|
-
value: any;
|
|
30
|
-
}
|
|
31
|
-
type InferSchema<T> = {
|
|
32
|
-
[K in keyof T]: T[K] extends FieldDefinition<infer U> ? U : never;
|
|
33
|
-
};
|
|
1
|
+
import { F as FieldDefinition, S as Schema, I as InferSchema, E as EnvError } from './types-B59cqDDx.js';
|
|
2
|
+
export { B as BaseType } from './types-B59cqDDx.js';
|
|
34
3
|
|
|
35
4
|
declare const s: {
|
|
36
5
|
string: (defaultValue?: string) => FieldDefinition<string>;
|
|
@@ -55,4 +24,4 @@ declare function parseDotEnv(content: string): Record<string, string>;
|
|
|
55
24
|
|
|
56
25
|
declare function reportErrors(errors: EnvError[]): void;
|
|
57
26
|
|
|
58
|
-
export {
|
|
27
|
+
export { EnvError, FieldDefinition, InferSchema, Schema, parseDotEnv, reportErrors, s, safeEnv };
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1 @@
|
|
|
1
|
-
import{a as
|
|
2
|
-
${e.r}${e.b}\u274C SafeEnv Error${e.res}
|
|
3
|
-
${e.d}${"".padEnd(40,"-")}${e.res}`),n.forEach(r=>{let i=r.value===void 0?"undefined":`"${r.value}"`;console.error(`${e.y}${r.key}${e.res} ${e.r}${r.error}${e.res} ${e.d}${i}${e.res}`)}),console.error(`${e.d}${"".padEnd(40,"-")}${e.res}
|
|
4
|
-
${e.g}\u{1F4A1} Check your .env files.${e.res}
|
|
5
|
-
`)}function D(n,e={}){let{loadProcessEnv:r=!0,source:i,prefix:c=""}=e,o;if(i)o=i;else{let a=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),s=[".env",".env.local",`.env.${a}`,`.env.${a}.local`],l={};for(let d of s)l={...l,...h(d)};o={...l,...r&&typeof process<"u"?process.env:{}}}let f={},u=[];for(let a in n){let s=n[a],l=s.sourceKey||(o[c+a]!==void 0?c+a:a),d=o[l];try{let t;if(d===void 0||d===""&&s.default!==void 0){if(s.required&&d===void 0)throw new Error("Required field missing");t=s.default}else t=s.parse(d);if(t!==void 0&&s.metadata){let{min:p,max:y,validate:E}=s.metadata;if(typeof t=="number"){if(p!==void 0&&t<p)throw new Error(`Below min ${p}`);if(y!==void 0&&t>y)throw new Error(`Above max ${y}`)}if(E&&!E.fn(t))throw new Error(E.message)}f[a]=t}catch(t){u.push({key:l,error:t.message,value:d})}}if(u.length>0)if($(u),typeof process<"u"&&!!process.exit&&!i)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.");return f}export{h as loadDotEnv,b as parseDotEnv,$ as reportErrors,g as s,D as safeEnv};
|
|
1
|
+
import{a as u,b as m}from"./chunk-NRXIL5NF.js";import{a as l}from"./chunk-POVTEBYN.js";function a(n,i,e,o=[]){return{type:n,default:i,required:i===void 0,parse:e,metadata:o.length?{options:o}:{},from(t){return this.sourceKey=t,this},validate(t,r="Custom validation failed"){return this.metadata={...this.metadata,validate:{fn:t,message:r}},this},min(t){return this.metadata={...this.metadata,min:t},this},max(t){return this.metadata={...this.metadata,max:t},this},transform(t){let r=this.parse;return this.parse=s=>t(r(s)),this},url(){return this.validate(t=>{try{return new URL(String(t)),!0}catch{return!1}},"Invalid URL format")},email(){let t=/^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;return this.validate(r=>t.test(String(r)),"Invalid email format")},regex(t,r="Value does not match pattern"){return this.validate(s=>t.test(String(s)),r)},description(t){return this.metadata={...this.metadata,description:t},this}}}var f={string:n=>a("string",n,i=>String(i)),number:n=>a("number",n,i=>{let e=Number(i);if(isNaN(e))throw new Error(`Invalid number: ${i}`);return e}),boolean:n=>a("boolean",n,i=>{if(typeof i=="boolean")return i;let e=String(i).toLowerCase();return e==="true"||e==="1"||e==="yes"||e==="on"}),enum:(n,i)=>a("enum",i,e=>{if(!n.includes(e))throw new Error(`Value "${e}" is not one of: ${n.join(", ")}`);return e},n),array:(n,i=",")=>a("array",n,e=>Array.isArray(e)?e:typeof e!="string"?[]:e.split(i).map(o=>o.trim()).filter(Boolean))};export{l as parseDotEnv,u as reportErrors,f as s,m as safeEnv};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type BaseType = "string" | "number" | "boolean" | "enum" | "array";
|
|
2
|
+
interface FieldDefinition<T = any, D extends string = string> {
|
|
3
|
+
type: BaseType;
|
|
4
|
+
default?: T;
|
|
5
|
+
required: boolean;
|
|
6
|
+
sourceKey?: string;
|
|
7
|
+
metadata?: {
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
options?: T[];
|
|
11
|
+
description?: string;
|
|
12
|
+
validate?: {
|
|
13
|
+
fn: (val: T) => boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
parse: (val: any) => T;
|
|
18
|
+
from: (key: string) => FieldDefinition<T, D>;
|
|
19
|
+
validate: (fn: (val: T) => boolean, message?: string) => FieldDefinition<T, D>;
|
|
20
|
+
min: (val: number) => FieldDefinition<T, D>;
|
|
21
|
+
max: (val: number) => FieldDefinition<T, D>;
|
|
22
|
+
transform: <U>(fn: (val: T) => U) => FieldDefinition<U, D>;
|
|
23
|
+
url: () => FieldDefinition<T, D>;
|
|
24
|
+
email: () => FieldDefinition<T, D>;
|
|
25
|
+
regex: (pattern: RegExp, message?: string) => FieldDefinition<T, D>;
|
|
26
|
+
description: <NewD extends string>(text: NewD) => FieldDefinition<T, NewD>;
|
|
27
|
+
}
|
|
28
|
+
type Schema = Record<string, FieldDefinition<any, any>>;
|
|
29
|
+
interface EnvError {
|
|
30
|
+
key: string;
|
|
31
|
+
error: string;
|
|
32
|
+
value: any;
|
|
33
|
+
}
|
|
34
|
+
type InferSchema<T> = {
|
|
35
|
+
[K in keyof T]: T[K] extends FieldDefinition<infer U, infer D> ? string extends D ? U : U & {
|
|
36
|
+
readonly __description?: D;
|
|
37
|
+
} : never;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type { BaseType as B, EnvError as E, FieldDefinition as F, InferSchema as I, Schema as S };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
type BaseType = "string" | "number" | "boolean" | "enum" | "array";
|
|
2
|
+
interface FieldDefinition<T = any, D extends string = string> {
|
|
3
|
+
type: BaseType;
|
|
4
|
+
default?: T;
|
|
5
|
+
required: boolean;
|
|
6
|
+
sourceKey?: string;
|
|
7
|
+
metadata?: {
|
|
8
|
+
min?: number;
|
|
9
|
+
max?: number;
|
|
10
|
+
options?: T[];
|
|
11
|
+
description?: string;
|
|
12
|
+
validate?: {
|
|
13
|
+
fn: (val: T) => boolean;
|
|
14
|
+
message: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
parse: (val: any) => T;
|
|
18
|
+
from: (key: string) => FieldDefinition<T, D>;
|
|
19
|
+
validate: (fn: (val: T) => boolean, message?: string) => FieldDefinition<T, D>;
|
|
20
|
+
min: (val: number) => FieldDefinition<T, D>;
|
|
21
|
+
max: (val: number) => FieldDefinition<T, D>;
|
|
22
|
+
transform: <U>(fn: (val: T) => U) => FieldDefinition<U, D>;
|
|
23
|
+
url: () => FieldDefinition<T, D>;
|
|
24
|
+
email: () => FieldDefinition<T, D>;
|
|
25
|
+
regex: (pattern: RegExp, message?: string) => FieldDefinition<T, D>;
|
|
26
|
+
description: <NewD extends string>(text: NewD) => FieldDefinition<T, NewD>;
|
|
27
|
+
}
|
|
28
|
+
type Schema = Record<string, FieldDefinition<any, any>>;
|
|
29
|
+
interface EnvError {
|
|
30
|
+
key: string;
|
|
31
|
+
error: string;
|
|
32
|
+
value: any;
|
|
33
|
+
}
|
|
34
|
+
type InferSchema<T> = {
|
|
35
|
+
[K in keyof T]: T[K] extends FieldDefinition<infer U, infer D> ? string extends D ? U : U & {
|
|
36
|
+
readonly __description?: D;
|
|
37
|
+
} : never;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type { BaseType as B, EnvError as E, FieldDefinition as F, InferSchema as I, Schema as S };
|
package/dist/vite.cjs
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkGCSZ35F2cjs = require('./chunk-GCSZ35F2.cjs');require('./chunk-FH2DFX4J.cjs');var _vite = require('vite');function m(t,r={}){return{name:"vite-plugin-safe-env",configResolved(e){let{envDir:i=e.root,prefix:o=e.envPrefix||"VITE_"}=r,s=e.mode,v=_vite.loadEnv.call(void 0, s,i,o);try{_chunkGCSZ35F2cjs.b.call(void 0, t,{source:v,loadProcessEnv:!1})}catch (e2){process.exit(1)}}}}exports.viteSafeEnv = m;
|
package/dist/vite.d.cts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { S as Schema } from './types-B59cqDDx.cjs';
|
|
3
|
+
|
|
4
|
+
interface ViteSafeEnvOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 环境变量目录,默认为项目根目录
|
|
7
|
+
*/
|
|
8
|
+
envDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 环境变量前缀,默认是 VITE_
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string | string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Vite 插件:在构建或开发启动时校验环境变量
|
|
16
|
+
*/
|
|
17
|
+
declare function viteSafeEnv(schema: Schema, options?: ViteSafeEnvOptions): Plugin;
|
|
18
|
+
|
|
19
|
+
export { viteSafeEnv };
|
package/dist/vite.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Plugin } from 'vite';
|
|
2
|
+
import { S as Schema } from './types-B59cqDDx.js';
|
|
3
|
+
|
|
4
|
+
interface ViteSafeEnvOptions {
|
|
5
|
+
/**
|
|
6
|
+
* 环境变量目录,默认为项目根目录
|
|
7
|
+
*/
|
|
8
|
+
envDir?: string;
|
|
9
|
+
/**
|
|
10
|
+
* 环境变量前缀,默认是 VITE_
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string | string[];
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Vite 插件:在构建或开发启动时校验环境变量
|
|
16
|
+
*/
|
|
17
|
+
declare function viteSafeEnv(schema: Schema, options?: ViteSafeEnvOptions): Plugin;
|
|
18
|
+
|
|
19
|
+
export { viteSafeEnv };
|
package/dist/vite.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{b as n}from"./chunk-NRXIL5NF.js";import"./chunk-POVTEBYN.js";import{loadEnv as a}from"vite";function m(t,r={}){return{name:"vite-plugin-safe-env",configResolved(e){let{envDir:i=e.root,prefix:o=e.envPrefix||"VITE_"}=r,s=e.mode,v=a(s,i,o);try{n(t,{source:v,loadProcessEnv:!1})}catch{process.exit(1)}}}}export{m as viteSafeEnv};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zh-moody/safe-env",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Type-safe environment variables for Node.js and Browser with schema validation.",
|
|
5
5
|
"author": "Moody",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"./dist/fs-node.cjs": "./dist/fs-browser.js"
|
|
27
27
|
},
|
|
28
28
|
"scripts": {
|
|
29
|
-
"dev": "tsup src/index.ts src/fs-node.ts src/fs-browser.ts --watch",
|
|
30
|
-
"build": "tsup src/index.ts src/fs-node.ts src/fs-browser.ts --format cjs,esm --dts --minify --clean --splitting",
|
|
29
|
+
"dev": "tsup src/index.ts src/vite.ts src/fs-node.ts src/fs-browser.ts --watch",
|
|
30
|
+
"build": "tsup src/index.ts src/vite.ts src/fs-node.ts src/fs-browser.ts --format cjs,esm --dts --minify --clean --splitting",
|
|
31
31
|
"test": "vitest run"
|
|
32
32
|
},
|
|
33
33
|
"exports": {
|
|
@@ -35,6 +35,11 @@
|
|
|
35
35
|
"types": "./dist/index.d.ts",
|
|
36
36
|
"import": "./dist/index.js",
|
|
37
37
|
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./vite": {
|
|
40
|
+
"types": "./dist/vite.d.ts",
|
|
41
|
+
"import": "./dist/vite.js",
|
|
42
|
+
"require": "./dist/vite.cjs"
|
|
38
43
|
}
|
|
39
44
|
},
|
|
40
45
|
"keywords": [
|
|
@@ -49,10 +54,15 @@
|
|
|
49
54
|
"files": [
|
|
50
55
|
"dist"
|
|
51
56
|
],
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"vite": ">=3.0.0"
|
|
59
|
+
},
|
|
52
60
|
"devDependencies": {
|
|
53
61
|
"@types/node": "^20.0.0",
|
|
62
|
+
"terser": "^5.46.1",
|
|
54
63
|
"tsup": "^8.5.1",
|
|
55
64
|
"typescript": "^5.9.3",
|
|
65
|
+
"vite": "^6.2.0",
|
|
56
66
|
"vitest": "^4.1.2"
|
|
57
67
|
}
|
|
58
68
|
}
|