@zh-moody/safe-env 0.2.0 → 0.3.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/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
- - **链式校验**:内置 `min`, `max`, `validate`, `enum` 等校验逻辑。
24
- - **自动前缀**:支持 `VITE_` 或 `REACT_APP_` 自动补全,代码里用 `PORT`,配置里用 `VITE_PORT`。
25
- - **跨端兼容**:自动识别 Node/浏览器环境,支持双端“防御性退出”。
26
- - **极致轻量**:Minified 体积约 **2.6 KB**,Gzip 后仅 **1.3 KB**,零运行时依赖。
20
+ - **极致轻量**:Minified 体积约 **2KB**,零运行时依赖。
27
21
 
28
22
  ---
29
23
 
30
24
  ### 🚀 快速上手
31
25
 
32
- #### 🔹 [Vite / React / Vue] 端使用
33
- 在前端,环境变量由构建工具(如 Vite)注入到 `import.meta.env` 中。
26
+ #### 🔹 [Vite / React / Vue] 使用
27
+ 在前端,建议配合 Vite 插件实现**构建时校验**。
34
28
 
35
- **1. 准备 `.env` 文件:**
36
- ```bash
37
- VITE_API_URL=https://api.com
38
- VITE_PORT=3000
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. 定义配置 (`src/env.ts`):**
41
+ **2. 定义配置并导出 (`src/env.ts`):**
42
42
  ```typescript
43
43
  import { safeEnv, s } from '@zh-moody/safe-env';
44
44
 
45
- export const config = safeEnv({
46
- apiUrl: s.string().from('VITE_API_URL'), // 别名映射
47
- port: s.number(3000).from('VITE_PORT'),
48
- }, {
49
- source: import.meta.env // ⚠️ 浏览器端必须手动传入数据源
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. 准备 `.env` 文件:**
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 dbConfig = safeEnv({
69
- DB_HOST: s.string('localhost'),
70
- DB_PORT: s.number(5432)
71
- }); // ⚠️ Node 端无需传 source,会自动读取文件
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 dbConfig;
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
- 每个通过 `s` 定义的字段都可以调用以下方法进行增强:
85
+ #### 2. 校验与增强 (链式调用)
86
+
87
+ 每个字段都可以通过链式调用进行深度定制:
102
88
 
103
- - **`.transform(fn)`**: **[New]** 自定义数据转换,支持链式调用。
89
+ - **`.url()`**: 校验是否为合法 URL 格式。
90
+ ```typescript
91
+ API_URL: s.string().url()
92
+ ```
93
+ - **`.email()`**: 校验是否为合法邮箱格式。
104
94
  ```typescript
105
- // 示例 1: 将字符串转大写
106
- KEY: s.string().transform(v => v.toUpperCase())
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
- // 即使变量叫 VITE_PATH,代码里也可以叫 port
115
- port: s.number().from('VITE_PATH')
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
- // 端口必须在 1-65535 之间
121
- PORT: s.number().min(1).max(65535)
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
- API: s.string().validate(v => v.startsWith('https'), 'Must be HTTPS')
125
+ // 必须是特定的内部域名
126
+ INTERNAL_URL: s.string().validate(v => v.endsWith('.internal.com'), 'Must be an internal URL')
128
127
  ```
129
128
 
130
- #### 3. 加载选项 (`SafeEnvOptions`)
131
-
132
- | 选项 | 适用环境 | 说明 |
133
- | :--- | :--- | :--- |
134
- | `source` | **[Vite/浏览器]** | **必填**。传入 `import.meta.env` 或 `process.env`。 |
135
- | `prefix` | **通用** | **可选**。自动补全前缀,如 `prefix: 'VITE_'`。 |
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 a(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 = a;
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;
@@ -1 +1 @@
1
- import o from"fs";import d 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 a(i=".env"){try{let r=d.resolve(process.cwd(),i);if(o.existsSync(r))return s(o.readFileSync(r,"utf-8"))}catch{}return{}}export{s as a,a as b};
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};
@@ -0,0 +1,5 @@
1
+ import{b as v}from"./chunk-POVTEBYN.js";function $(f){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}`),f.forEach(t=>{let d=t.value===void 0?"undefined":`"${t.value}"`;console.error(`${e.y}${t.key}${e.res} ${e.r}${t.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(f,e={}){let{loadProcessEnv:t=!0,source:d,prefix:p=""}=e,a;if(d!==void 0)a=d||{};else{let n=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),r=[".env",`.env.${n}`,".env.local",`.env.${n}.local`],i={};for(let s of r)i={...i,...v(s)};a={...i,...t&&typeof process<"u"?process.env:{}}}let E={},c=[];for(let n in f){let r=f[n],i=r.sourceKey||(a[p+n]!==void 0?p+n:n),s=a[i];try{let o;if(s===void 0||s===""&&r.default!==void 0){if(r.required&&s===void 0)throw new Error("Required field missing");o=r.default}else o=r.parse(s);if(o!==void 0&&r.metadata){let{min:l,max:u,validate:m}=r.metadata;if(typeof o=="number"){if(l!==void 0&&o<l)throw new Error(`Below min ${l}`);if(u!==void 0&&o>u)throw new Error(`Above max ${u}`)}if(m&&!m.fn(o))throw new Error(m.message)}E[n]=o}catch(o){c.push({key:i,error:o.message,value:s})}}if(c.length>0){$(c);let n=typeof process<"u"&&!!process.exit,r=typeof process<"u"&&process.env.NODE_ENV==="test";if(n&&!d&&!r)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.")}return E}export{$ as a,h as b};
@@ -0,0 +1,5 @@
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunkFH2DFX4Jcjs = require('./chunk-FH2DFX4J.cjs');function $(f){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}`),f.forEach(t=>{let d=t.value===void 0?"undefined":`"${t.value}"`;console.error(`${e.y}${t.key}${e.res} ${e.r}${t.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(f,e={}){let{loadProcessEnv:t=!0,source:d,prefix:p=""}=e,a;if(d!==void 0)a=d||{};else{let n=e.mode||(typeof process<"u"?process.env.NODE_ENV:"development"),r=[".env",`.env.${n}`,".env.local",`.env.${n}.local`],i={};for(let s of r)i={...i,..._chunkFH2DFX4Jcjs.b.call(void 0, s)};a={...i,...t&&typeof process<"u"?process.env:{}}}let E={},c=[];for(let n in f){let r=f[n],i=r.sourceKey||(a[p+n]!==void 0?p+n:n),s=a[i];try{let o;if(s===void 0||s===""&&r.default!==void 0){if(r.required&&s===void 0)throw new Error("Required field missing");o=r.default}else o=r.parse(s);if(o!==void 0&&r.metadata){let{min:l,max:u,validate:m}=r.metadata;if(typeof o=="number"){if(l!==void 0&&o<l)throw new Error(`Below min ${l}`);if(u!==void 0&&o>u)throw new Error(`Above max ${u}`)}if(m&&!m.fn(o))throw new Error(m.message)}E[n]=o}catch(o){c.push({key:i,error:o.message,value:s})}}if(c.length>0){$(c);let n=typeof process<"u"&&!!process.exit,r=typeof process<"u"&&process.env.NODE_ENV==="test";if(n&&!d&&!r)process.exit(1);else throw new Error("SafeEnv: Configuration validation failed.")}return E}exports.a = $; exports.b = h;
package/dist/fs-node.cjs CHANGED
@@ -1 +1 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true});var _chunk5ZP5OWCLcjs = require('./chunk-5ZP5OWCL.cjs');exports.loadDotEnv = _chunk5ZP5OWCLcjs.b;
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-BEFKPDEA.js";export{a as loadDotEnv};
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 _chunk5ZP5OWCLcjs = require('./chunk-5ZP5OWCL.cjs');function m(n,e,r,i=[]){return{type:n,default:e,required:e===void 0,parse:r,metadata:i.length?{options:i}:{},from(o){return this.sourceKey=o,this},validate(o,f="Custom validation failed"){return this.metadata={...this.metadata,validate:{fn:o,message:f}},this},min(o){return this.metadata={...this.metadata,min:o},this},max(o){return this.metadata={...this.metadata,max:o},this},transform(o){let f=this.parse;return this.parse=u=>o(f(u)),this}}}var g={string:n=>m("string",n,e=>String(e)),number:n=>m("number",n,e=>{let r=Number(e);if(isNaN(r))throw new Error(`Invalid number: ${e}`);return r}),boolean:n=>m("boolean",n,e=>{if(typeof e=="boolean")return e;let r=String(e).toLowerCase();return r==="true"||r==="1"||r==="yes"||r==="on"}),enum:(n,e)=>m("enum",e,r=>{if(!n.includes(r))throw new Error(`Value "${r}" is not one of: ${n.join(", ")}`);return r},n),array:(n,e=",")=>m("array",n,r=>Array.isArray(r)?r:typeof r!="string"?[]:r.split(e).map(i=>i.trim()).filter(Boolean))};function $(n){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}`),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 _chunkZMFWDJAZcjs = require('./chunk-ZMFWDJAZ.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 = _chunkZMFWDJAZcjs.a; exports.s = f; exports.safeEnv = _chunkZMFWDJAZcjs.b;
package/dist/index.d.cts CHANGED
@@ -1,36 +1,5 @@
1
- export { loadDotEnv } from './fs-node.cjs';
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 { type BaseType, type EnvError, type FieldDefinition, type InferSchema, type Schema, parseDotEnv, reportErrors, s, safeEnv };
27
+ export { EnvError, FieldDefinition, InferSchema, Schema, parseDotEnv, reportErrors, s, safeEnv };
package/dist/index.d.ts CHANGED
@@ -1,36 +1,5 @@
1
- export { loadDotEnv } from './fs-node.js';
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 { type BaseType, type EnvError, type FieldDefinition, type InferSchema, type Schema, parseDotEnv, reportErrors, s, safeEnv };
27
+ export { EnvError, FieldDefinition, InferSchema, Schema, parseDotEnv, reportErrors, s, safeEnv };
package/dist/index.js CHANGED
@@ -1,5 +1 @@
1
- import{a as b,b as h}from"./chunk-BEFKPDEA.js";function m(n,e,r,i=[]){return{type:n,default:e,required:e===void 0,parse:r,metadata:i.length?{options:i}:{},from(o){return this.sourceKey=o,this},validate(o,f="Custom validation failed"){return this.metadata={...this.metadata,validate:{fn:o,message:f}},this},min(o){return this.metadata={...this.metadata,min:o},this},max(o){return this.metadata={...this.metadata,max:o},this},transform(o){let f=this.parse;return this.parse=u=>o(f(u)),this}}}var g={string:n=>m("string",n,e=>String(e)),number:n=>m("number",n,e=>{let r=Number(e);if(isNaN(r))throw new Error(`Invalid number: ${e}`);return r}),boolean:n=>m("boolean",n,e=>{if(typeof e=="boolean")return e;let r=String(e).toLowerCase();return r==="true"||r==="1"||r==="yes"||r==="on"}),enum:(n,e)=>m("enum",e,r=>{if(!n.includes(r))throw new Error(`Value "${r}" is not one of: ${n.join(", ")}`);return r},n),array:(n,e=",")=>m("array",n,r=>Array.isArray(r)?r:typeof r!="string"?[]:r.split(e).map(i=>i.trim()).filter(Boolean))};function $(n){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}`),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-YZVFZGPW.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 _chunkZMFWDJAZcjs = require('./chunk-ZMFWDJAZ.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{_chunkZMFWDJAZcjs.b.call(void 0, t,{source:v,loadProcessEnv:!1})}catch (e2){process.exit(1)}}}}exports.viteSafeEnv = m;
@@ -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-YZVFZGPW.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.2.0",
3
+ "version": "0.3.0",
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,14 @@
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",
54
62
  "tsup": "^8.5.1",
55
63
  "typescript": "^5.9.3",
64
+ "vite": "^6.2.0",
56
65
  "vitest": "^4.1.2"
57
66
  }
58
67
  }