bini-env 1.0.9 → 1.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/README.md CHANGED
@@ -1,14 +1,15 @@
1
1
  # bini-env
2
2
 
3
- ![npm](https://img.shields.io/npm/v/bini-env?color=cyan\&style=flat-square)
3
+ ![npm](https://img.shields.io/npm/v/bini-env?color=cyan&style=flat-square)
4
4
  ![npm downloads](https://img.shields.io/npm/dm/bini-env?style=flat-square)
5
5
  ![license](https://img.shields.io/npm/l/bini-env?style=flat-square)
6
- ![vite](https://img.shields.io/badge/vite-%3E%3D8.0-646CFF?style=flat-square\&logo=vite)
7
- ![typescript](https://img.shields.io/badge/TypeScript-ready-3178C6?style=flat-square\&logo=typescript)
8
- ![node](https://img.shields.io/badge/node-%3E%3D20.19-339933?style=flat-square\&logo=node.js)
6
+ ![vite](https://img.shields.io/badge/vite-%3E%3D8.0-646CFF?style=flat-square&logo=vite)
7
+ ![hono](https://img.shields.io/badge/hono-%3E%3D4.0-E36002?style=flat-square&logo=hono)
8
+ ![typescript](https://img.shields.io/badge/TypeScript-ready-3178C6?style=flat-square&logo=typescript)
9
+ ![node](https://img.shields.io/badge/node-%3E%3D20.19-339933?style=flat-square&logo=node.js)
9
10
 
10
- **Zero-config environment variable system + Vite plugin for Bini.js**
11
- Loads `.env` in development, uses host-provided variables in production, and works across Node.js, Bun, Deno, and edge runtimes — without leaking secrets or adding runtime cost.
11
+ **Zero-config environment variable system + Vite plugin for Bini.js**
12
+ Powered by [Hono](https://hono.dev) — reads env vars from the request context so variables are always resolved from the correct runtime binding, whether you're on Node.js, Bun, Deno, Vercel Edge, Netlify Edge, or Cloudflare Workers.
12
13
 
13
14
  ---
14
15
 
@@ -16,60 +17,76 @@ Loads `.env` in development, uses host-provided variables in production, and wor
16
17
 
17
18
  This library **does NOT magically make env vars safe**.
18
19
 
19
- * Anything exposed to the client (`import.meta.env`) is **public**.
20
- * Only server-side code (`getEnv`, `requireEnv`) can safely access secrets.
21
- * Misconfigured prefixes = **data leak**.
20
+ - Anything exposed to the client (`import.meta.env`) is **public**.
21
+ - Only server-side code (`getEnv`, `requireEnv`) can safely access secrets.
22
+ - Misconfigured prefixes = **data leak**.
22
23
 
23
- If you don’t understand this, stop and fix that first.
24
+ If you don't understand this, stop and fix that first.
24
25
 
25
26
  ---
26
27
 
27
28
  ## ✨ Features
28
29
 
29
- * **Universal API** — `getEnv()` / `requireEnv()` work across runtimes
30
- * **Zero-config `.env` loading** in development
31
- * **Strict production behavior** — no file reads, no dotenv
32
- * **Prefix control** — supports `BINI_`, `VITE_`, or custom
33
- * **Tree-shakeable** — no dead code in client bundles
34
- * **Edge-safe** — no static dotenv import
35
- * **Typed** — full TypeScript support
36
- * **Fast** — single load in dev, zero overhead in prod
30
+ - **Hono-native API** — `getEnv(c, key)` / `requireEnv(c, key)` read from Hono's request context
31
+ - **Universal runtime support** — CF Workers, Node, Bun, Deno, Vercel Edge, Netlify Edge
32
+ - **Zero dotenv** — no `.env` file parsing at runtime; vars come from the host
33
+ - **Build-time `process.env` fallback** — for Vite plugin hooks that run outside a request
34
+ - **Auto server restart** — dev server restarts when `.env` files are created or changed
35
+ - **Prefix control** — supports `BINI_`, `VITE_`, or custom
36
+ - **Tree-shakeable** — no dead code in client bundles
37
+ - **Typed** — full TypeScript support with exported `HonoContext` duck type
38
+ - **Fast** — zero overhead; no file reads in production
37
39
 
38
40
  ---
39
41
 
40
42
  ## 📦 Installation
41
43
 
42
44
  ```bash
43
- pnpm add bini-env
45
+ pnpm add bini-env hono
44
46
  # or
45
- npm install bini-env
47
+ npm install bini-env hono
46
48
  # or
47
- yarn add bini-env
49
+ yarn add bini-env hono
48
50
  ```
49
51
 
52
+ > `hono` is a required peer dependency.
53
+
50
54
  ---
51
55
 
52
- ## 🚀 Quick Start (Don’t Overthink It)
56
+ ## 🚀 Quick Start
53
57
 
54
58
  ```ts
55
59
  // vite.config.ts
56
- import { defineConfig } from 'vite';
57
- import { biniEnv } from 'bini-env';
60
+ import { defineConfig } from 'vite'
61
+ import { biniEnv } from 'bini-env'
58
62
 
59
63
  export default defineConfig({
60
64
  plugins: [biniEnv()]
61
- });
65
+ })
62
66
  ```
63
67
 
64
- Done.
68
+ ```ts
69
+ // src/app/api/example.ts
70
+ import { Hono } from 'hono'
71
+ import { getEnv, requireEnv } from 'bini-env'
72
+
73
+ const app = new Hono()
74
+
75
+ app.get('/hello', (c) => {
76
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
77
+ const ctx = c as any
78
+ const name = getEnv(ctx, 'APP_NAME') ?? 'World'
79
+ return c.json({ message: `Hello, ${name}!` })
80
+ })
65
81
 
66
- If this doesn’t work, your project setup is broken — not the plugin.
82
+ export default app
83
+ ```
67
84
 
68
85
  ---
69
86
 
70
87
  ## 🔐 Environment Rules (Read This Twice)
71
88
 
72
- > ✅ **Good news:** Both `BINI_` and `VITE_` prefixes work out of the box — no extra config needed.
89
+ > ✅ Both `BINI_` and `VITE_` prefixes work out of the box — no extra config needed.
73
90
 
74
91
  ### Client (PUBLIC)
75
92
 
@@ -78,16 +95,12 @@ BINI_PUBLIC_API_URL=https://api.example.com
78
95
  VITE_ANALYTICS_ID=UA-XXXX
79
96
  ```
80
97
 
81
- Accessible via:
82
-
83
98
  ```ts
84
99
  import.meta.env.BINI_PUBLIC_API_URL
85
100
  ```
86
101
 
87
102
  👉 **Never put secrets here. Ever.**
88
103
 
89
- ---
90
-
91
104
  ### Server (PRIVATE)
92
105
 
93
106
  ```env
@@ -96,25 +109,58 @@ DATABASE_URL=postgres://...
96
109
  ```
97
110
 
98
111
  ```ts
99
- import { requireEnv } from 'bini-env';
100
-
101
- const pass = requireEnv('SMTP_PASS');
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ const ctx = c as any
114
+ const pass = requireEnv(ctx, 'SMTP_PASS') // throws if missing
115
+ const db = getEnv(ctx, 'DATABASE_URL') // returns undefined if missing
102
116
  ```
103
117
 
104
- 👉 If this leaks, it’s your fault, not the library’s.
118
+ 👉 If this leaks, it's your fault, not the library's.
119
+
120
+ ---
121
+
122
+ ## 🌍 Platform Support
123
+
124
+ `getEnv` and `requireEnv` resolve vars from Hono's `env(c)` adapter, which maps to the correct source on every supported platform automatically.
125
+
126
+ | Platform | Runtime | Where vars come from | How `env(c)` reads them |
127
+ | -------------------- | ------------- | ----------------------------------- | ---------------------------- |
128
+ | Node.js | Node | System env / platform dashboard | `process.env` ✅ |
129
+ | Bun | Bun | System env / platform dashboard | `process.env` ✅ |
130
+ | Vercel Edge | V8 isolate | Project settings → Environment Vars | `process.env` ✅ |
131
+ | Netlify Edge | Deno | Site settings → Environment Vars | `Deno.env.get()` ✅ |
132
+ | Cloudflare Workers | V8 isolate | `wrangler.toml [vars]` / dashboard | CF bindings via `c.env` ✅ |
133
+ | Deno Deploy | Deno | Project settings → Environment Vars | `Deno.env.get()` ✅ |
134
+
135
+ > **Cloudflare note:** Secrets added via `wrangler secret put` are only available inside the fetch handler via `c.env` — this is a Cloudflare Workers architecture constraint, not a bini-env limitation. `getEnv` reads them correctly when called with `c` inside a route handler.
105
136
 
106
137
  ---
107
138
 
108
139
  ## 🧠 How It Actually Works
109
140
 
110
- | Mode | Behavior |
111
- | ---------------- | ---------------------------------------- |
112
- | Dev (`vite dev`) | Loads `.env` once via dynamic dotenv |
113
- | Preview | Same as dev |
114
- | Production | Uses `process.env` / host injection only |
115
- | Edge/Deno | Uses native `Deno.env` |
141
+ `getEnv(c, key)` resolves in this order:
116
142
 
117
- **No runtime branching in client bundles. No hidden magic.**
143
+ 1. **`env(c)` from `hono/adapter`** — the Hono request context. This is the primary and correct source on every runtime. Never cached — request-scoped.
144
+ 2. **Stored context** — if you called `setContext(c)` in a global middleware, `getEnv('KEY')` (no `c`) uses it.
145
+ 3. **`process.env`** — build-time only, for Vite plugin hooks that run outside any request.
146
+
147
+ `dotenv` is not used anywhere. Variables must be set in your platform's environment config.
148
+
149
+ ---
150
+
151
+ ## 🔄 Auto Server Restart
152
+
153
+ The dev server automatically restarts when any `.env*` file is created or changed in your project root.
154
+
155
+ ```
156
+ 12:00:01 [bini-env] .env.local changed — restarting server
157
+ ```
158
+
159
+ Covered events:
160
+ - Creating a new `.env`, `.env.local`, `.env.production`, etc.
161
+ - Editing any existing `.env*` file
162
+
163
+ Editor swap files (`.env.swp`, `.env~`, `.env.bak`) are ignored.
118
164
 
119
165
  ---
120
166
 
@@ -122,96 +168,144 @@ const pass = requireEnv('SMTP_PASS');
122
168
 
123
169
  ```ts
124
170
  biniEnv({
125
- enabled: true,
171
+ enabled : true,
126
172
  clearViteHeader: true,
127
- logo: 'ß',
128
- envPrefix: ['BINI_', 'VITE_']
129
- });
173
+ logo : 'ß',
174
+ envPrefix : ['BINI_', 'VITE_'],
175
+ loadInPreview : true,
176
+ })
130
177
  ```
131
178
 
132
- ### Critical Detail
133
-
134
- If you change `envPrefix`, you are changing what gets exposed to the browser.
135
-
136
- Break this → you leak secrets.
179
+ > **Critical:** If you change `envPrefix`, you are changing what gets exposed to the browser. Break this → you leak secrets.
137
180
 
138
181
  ---
139
182
 
140
183
  ## 📚 API
141
184
 
142
- ### `getEnv(key)`
185
+ ### `getEnv(c, key)`
143
186
 
144
- Returns `string | undefined`.
187
+ Returns `string | undefined`. Reads from Hono's request context.
145
188
 
146
- Safe fallback reader across:
189
+ ```ts
190
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
191
+ const ctx = c as any
192
+ const debug = getEnv(ctx, 'DEBUG_MODE')
193
+ ```
147
194
 
148
- * `Deno.env`
149
- * `process.env`
150
- * `import.meta.env`
195
+ Also works without `c` outside a request (build-time / Vite hooks):
151
196
 
152
- ---
197
+ ```ts
198
+ const debug = getEnv('DEBUG_MODE')
199
+ ```
153
200
 
154
- ### `requireEnv(key)`
201
+ ### `requireEnv(c, key)`
155
202
 
156
- Same as `getEnv` but throws:
203
+ Same as `getEnv` but throws if the variable is missing:
157
204
 
158
- ```txt
205
+ ```
159
206
  [bini-env] Missing required environment variable: "SMTP_PASS"
207
+ → In development: set it in your platform's env config.
208
+ → In production: set it in your hosting dashboard.
160
209
  ```
161
210
 
162
- Use this for anything critical.
211
+ ```ts
212
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
213
+ const ctx = c as any
214
+ const dbUrl = requireEnv(ctx, 'DATABASE_URL')
215
+ ```
163
216
 
164
- ---
217
+ ### `setContext(c)` / `clearContext()`
218
+
219
+ Store a Hono context module-level so `getEnv('KEY')` works without passing `c` at every call site. Register once in a global middleware:
220
+
221
+ ```ts
222
+ app.use('*', (c, next) => {
223
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
224
+ setContext(c as any)
225
+ return next()
226
+ })
227
+ ```
165
228
 
166
229
  ### `biniEnv(options)`
167
230
 
168
- Vite plugin.
231
+ Vite plugin. Handles terminal output and dev server watching.
232
+
233
+ ### `biniLogger`
169
234
 
170
- If your plugin order is wrong and things break, that’s on your config.
235
+ Standalone logger that uses Vite-style terminal output. Available for use in your own Bini.js plugins.
236
+
237
+ ```ts
238
+ import { biniLogger } from 'bini-env'
239
+
240
+ biniLogger.info('Server ready')
241
+ biniLogger.warn('Missing optional var')
242
+ biniLogger.error('Something broke', error)
243
+ ```
244
+
245
+ ### `HonoContext`
246
+
247
+ Exported duck type satisfied by any Hono `Context`. Use it if you need to type a wrapper:
248
+
249
+ ```ts
250
+ import type { HonoContext } from 'bini-env'
251
+
252
+ function readConfig(c: HonoContext) {
253
+ return getEnv(c, 'CONFIG_KEY')
254
+ }
255
+ ```
171
256
 
172
257
  ---
173
258
 
174
- ## 📂 Env File Resolution Order
259
+ ## 📂 Env File Resolution Order (Dev only)
260
+
261
+ The Vite plugin watches these files and triggers a server restart on change:
175
262
 
176
263
  1. `.env.local`
177
264
  2. `.env.[mode].local`
178
265
  3. `.env.[mode]`
179
266
  4. `.env`
180
267
 
181
- Loaded once. Cached. No repeated disk reads.
268
+ In production, none of these are read. Variables must come from your platform.
182
269
 
183
270
  ---
184
271
 
185
272
  ## ⚡ Performance
186
273
 
187
- | Metric | Dev | Prod |
188
- | ------------- | --------- | ----------- |
189
- | File Reads | 1–5 | 0 |
190
- | Runtime Cost | ~5ms once | 0 |
191
- | Bundle Impact | Minimal | Tree-shaken |
274
+ | Metric | Dev | Prod |
275
+ | ------------- | -------- | ----------- |
276
+ | File Reads | 0 | 0 |
277
+ | Runtime Cost | ~0ms | 0 |
278
+ | Bundle Impact | Minimal | Tree-shaken |
192
279
 
193
- If you see overhead in production, you did something wrong.
280
+ No dotenv. No disk reads. No overhead.
194
281
 
195
282
  ---
196
283
 
197
284
  ## 🔥 Common Failure Modes
198
285
 
199
- ### 1. “Env is undefined”
200
-
201
- You forgot the prefix.
286
+ ### "Env is undefined in production"
287
+ You relied on a `.env` file in production. Set vars in your hosting dashboard — bini-env does not read `.env` files at runtime.
202
288
 
203
- ### 2. “Works in dev, broken in prod”
289
+ ### "Works in dev, broken in prod"
290
+ Same as above. Production vars come from the host, not from files.
204
291
 
205
- You relied on `.env` in production.
292
+ ### "Secrets leaked"
293
+ You exposed them via `BINI_` or `VITE_` prefix. Those are public. Use unprefixed vars for secrets and only read them server-side via `getEnv(c, key)`.
206
294
 
207
- ### 3. “Secrets leaked”
295
+ ### "Cloudflare secret not found"
296
+ Secrets added via `wrangler secret put` are only available via `c.env` inside your handler — which is exactly where `getEnv(c, key)` reads from. Make sure you're passing `c`.
208
297
 
209
- You exposed them via prefix.
298
+ ### "TypeScript error: Context not assignable to HonoContext"
299
+ Hono 4.12 added a `[GET_MATCH_RESULT]` symbol to `HonoRequest` that breaks structural assignability. Cast once per handler:
210
300
 
211
- ### 4. “Types not found”
212
-
213
- Add:
301
+ ```ts
302
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
303
+ const ctx = c as any
304
+ const key = requireEnv(ctx, 'MY_KEY')
305
+ ```
214
306
 
307
+ ### "Types not found"
308
+ Add to your `tsconfig` or entry file:
215
309
  ```ts
216
310
  /// <reference types="vite/client" />
217
311
  ```
@@ -223,24 +317,22 @@ Add:
223
317
  This library is intentionally simple.
224
318
 
225
319
  If you need:
320
+ - Secret rotation
321
+ - Encrypted envs
322
+ - Runtime validation schemas
226
323
 
227
- * secret rotation
228
- * encrypted envs
229
- * runtime validation schemas
230
-
231
- That’s **your job**, not this package.
324
+ That's **your job**, not this package.
232
325
 
233
326
  ---
234
327
 
235
328
  ## 🤝 Contributing
236
329
 
237
330
  PRs welcome — but:
331
+ - No bloat
332
+ - No magic
333
+ - No runtime cost
238
334
 
239
- * No bloat
240
- * No magic
241
- * No runtime cost
242
-
243
- If it slows startup or increases bundle size, it’s getting rejected.
335
+ If it slows startup or increases bundle size, it's getting rejected.
244
336
 
245
337
  ---
246
338
 
@@ -250,4 +342,4 @@ MIT © Bini.js Team
250
342
 
251
343
  ---
252
344
 
253
- **Ship fast. Leak nothing. Blame config, not tooling.**
345
+ **Ship fast. Leak nothing. Blame config, not tooling.**
package/dist/index.d.mts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { Plugin } from 'vite';
2
2
  export { Plugin } from 'vite';
3
+ export { Context } from 'hono';
3
4
 
4
- declare function suppressDotenvLogsGlobally(): void;
5
+ /** Minimal shape accepted by getEnv / requireEnv. Satisfied by any Hono Context. */
6
+ type HonoContext = {
7
+ env: unknown;
8
+ } & Record<string, unknown>;
5
9
  interface BiniEnvPluginOptions {
6
10
  readonly enabled?: boolean;
7
11
  readonly clearViteHeader?: boolean;
8
12
  readonly logo?: string;
9
13
  readonly envPrefix?: string | string[];
10
14
  readonly loadInPreview?: boolean;
11
- /** @deprecated Suppression is always applied at module load; this option is ignored. */
12
- readonly suppressDotenvLogs?: boolean;
13
15
  }
14
16
  interface DetectedEnvFile {
15
17
  readonly name: string;
@@ -20,10 +22,11 @@ declare const biniLogger: {
20
22
  readonly warn: (msg: string) => void;
21
23
  readonly error: (msg: string, err?: unknown) => void;
22
24
  };
23
- declare function getEnv(key: string): string | undefined;
24
- declare function requireEnv(key: string): string;
25
- declare function loadEnv(projectRoot?: string): Promise<void>;
25
+ declare function setContext(c: HonoContext): void;
26
+ declare function clearContext(): void;
27
+ declare function getEnv(c: HonoContext | string, key?: string): string | undefined;
28
+ declare function requireEnv(c: HonoContext | string, key?: string): string;
26
29
  declare function detectEnvFiles(projectRoot?: string): DetectedEnvFile[];
27
30
  declare function biniEnv(options?: Readonly<BiniEnvPluginOptions>): Plugin;
28
31
 
29
- export { type BiniEnvPluginOptions, type DetectedEnvFile, biniEnv, biniLogger, detectEnvFiles, getEnv, loadEnv, requireEnv, suppressDotenvLogsGlobally };
32
+ export { type BiniEnvPluginOptions, type DetectedEnvFile, type HonoContext, biniEnv, biniLogger, clearContext, detectEnvFiles, getEnv, requireEnv, setContext };
package/dist/index.d.ts CHANGED
@@ -1,15 +1,17 @@
1
1
  import { Plugin } from 'vite';
2
2
  export { Plugin } from 'vite';
3
+ export { Context } from 'hono';
3
4
 
4
- declare function suppressDotenvLogsGlobally(): void;
5
+ /** Minimal shape accepted by getEnv / requireEnv. Satisfied by any Hono Context. */
6
+ type HonoContext = {
7
+ env: unknown;
8
+ } & Record<string, unknown>;
5
9
  interface BiniEnvPluginOptions {
6
10
  readonly enabled?: boolean;
7
11
  readonly clearViteHeader?: boolean;
8
12
  readonly logo?: string;
9
13
  readonly envPrefix?: string | string[];
10
14
  readonly loadInPreview?: boolean;
11
- /** @deprecated Suppression is always applied at module load; this option is ignored. */
12
- readonly suppressDotenvLogs?: boolean;
13
15
  }
14
16
  interface DetectedEnvFile {
15
17
  readonly name: string;
@@ -20,10 +22,11 @@ declare const biniLogger: {
20
22
  readonly warn: (msg: string) => void;
21
23
  readonly error: (msg: string, err?: unknown) => void;
22
24
  };
23
- declare function getEnv(key: string): string | undefined;
24
- declare function requireEnv(key: string): string;
25
- declare function loadEnv(projectRoot?: string): Promise<void>;
25
+ declare function setContext(c: HonoContext): void;
26
+ declare function clearContext(): void;
27
+ declare function getEnv(c: HonoContext | string, key?: string): string | undefined;
28
+ declare function requireEnv(c: HonoContext | string, key?: string): string;
26
29
  declare function detectEnvFiles(projectRoot?: string): DetectedEnvFile[];
27
30
  declare function biniEnv(options?: Readonly<BiniEnvPluginOptions>): Plugin;
28
31
 
29
- export { type BiniEnvPluginOptions, type DetectedEnvFile, biniEnv, biniLogger, detectEnvFiles, getEnv, loadEnv, requireEnv, suppressDotenvLogsGlobally };
32
+ export { type BiniEnvPluginOptions, type DetectedEnvFile, type HonoContext, biniEnv, biniLogger, clearContext, detectEnvFiles, getEnv, requireEnv, setContext };