@vite-env/core 0.1.0 → 0.2.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 ADDED
@@ -0,0 +1,62 @@
1
+ # @vite-env/core
2
+
3
+ The `env.ts` layer for Vite — define once, validate everywhere, import with types.
4
+
5
+ - Typed virtual modules (`virtual:env/client`, `virtual:env/server`)
6
+ - Server/client split with build-time leak detection
7
+ - Auto-coercion via Zod v4 (`z.stringbool()`, `z.coerce.number()`)
8
+ - Auto `.d.ts` generation
9
+ - Vite 8 / Rolldown native
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pnpm add @vite-env/core zod
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ **1. Define your schema** — `env.ts`
20
+
21
+ ```ts
22
+ import { defineEnv, z } from '@vite-env/core'
23
+
24
+ export default defineEnv({
25
+ server: {
26
+ DATABASE_URL: z.url(),
27
+ JWT_SECRET: z.string().min(32),
28
+ },
29
+ client: {
30
+ VITE_API_URL: z.url(),
31
+ VITE_DARK_MODE: z.stringbool().default(false),
32
+ VITE_NODE_ENV: z.enum(['development', 'test', 'production']).default('development'),
33
+ },
34
+ })
35
+ ```
36
+
37
+ **2. Add the plugin** — `vite.config.ts`
38
+
39
+ ```ts
40
+ import ViteEnv from '@vite-env/core/plugin'
41
+ import { defineConfig } from 'vite'
42
+
43
+ export default defineConfig({
44
+ plugins: [ViteEnv()],
45
+ })
46
+ ```
47
+
48
+ **3. Import typed env**
49
+
50
+ ```ts
51
+ import { env } from 'virtual:env/client'
52
+
53
+ env.VITE_API_URL // string
54
+ env.VITE_DARK_MODE // boolean
55
+ env.VITE_NODE_ENV // 'development' | 'test' | 'production'
56
+ ```
57
+
58
+ See the [full documentation](https://github.com/pyyupsk/vite-env) for server/client split details, CLI tools, and more.
59
+
60
+ ## License
61
+
62
+ [MIT](https://github.com/pyyupsk/vite-env/blob/main/LICENSE)
package/dist/config.cjs CHANGED
@@ -1,10 +1,12 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./dts-DT7HjKrz.cjs");
2
+ require("./dts-DF71HNdJ.cjs");
3
3
  let jiti = require("jiti");
4
4
  //#region src/config.ts
5
5
  async function loadEnvConfig(configPath) {
6
6
  const mod = await (0, jiti.createJiti)(configPath).import(configPath);
7
- return mod.default ?? mod;
7
+ const def = mod.default ?? mod;
8
+ if (!def || typeof def !== "object") throw new Error(`[vite-env] env config at ${configPath} must export an object (got ${typeof def}).\n Use: export default defineEnv({ ... })`);
9
+ return def;
8
10
  }
9
11
  //#endregion
10
12
  exports.loadEnvConfig = loadEnvConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\nimport { createJiti } from 'jiti'\n\nexport async function loadEnvConfig(configPath: string): Promise<EnvDefinition> {\n const jiti = createJiti(configPath)\n const mod = await jiti.import(configPath) as any\n return (mod.default ?? mod) as EnvDefinition\n}\n"],"mappings":";;;;AAGA,eAAsB,cAAc,YAA4C;CAE9E,MAAM,MAAM,OAAA,GAAA,KAAA,YADY,WAAW,CACZ,OAAO,WAAW;AACzC,QAAQ,IAAI,WAAW"}
1
+ {"version":3,"file":"config.cjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\nimport { createJiti } from 'jiti'\n\nexport async function loadEnvConfig(configPath: string): Promise<EnvDefinition> {\n const jiti = createJiti(configPath)\n const mod: Record<string, unknown> = await jiti.import(configPath) as any\n const def: unknown = mod.default ?? mod\n\n if (!def || typeof def !== 'object') {\n throw new Error(\n `[vite-env] env config at ${configPath} must export an object (got ${typeof def}).\\n`\n + ` Use: export default defineEnv({ ... })`,\n )\n }\n\n return def as EnvDefinition\n}\n"],"mappings":";;;;AAGA,eAAsB,cAAc,YAA4C;CAE9E,MAAM,MAA+B,OAAA,GAAA,KAAA,YADb,WAAW,CACa,OAAO,WAAW;CAClE,MAAM,MAAe,IAAI,WAAW;AAEpC,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,OAAM,IAAI,MACR,4BAA4B,WAAW,8BAA8B,OAAO,IAAI,8CAEjF;AAGH,QAAO"}
package/dist/config.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-BmEBJZdy.cjs";
1
+ import { t as EnvDefinition } from "./types-DqTMuWwc.cjs";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  declare function loadEnvConfig(configPath: string): Promise<EnvDefinition>;
package/dist/config.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-CLfG9amO.mjs";
1
+ import { t as EnvDefinition } from "./types-CluiDKAQ.mjs";
2
2
 
3
3
  //#region src/config.d.ts
4
4
  declare function loadEnvConfig(configPath: string): Promise<EnvDefinition>;
package/dist/config.mjs CHANGED
@@ -2,7 +2,9 @@ import { createJiti } from "jiti";
2
2
  //#region src/config.ts
3
3
  async function loadEnvConfig(configPath) {
4
4
  const mod = await createJiti(configPath).import(configPath);
5
- return mod.default ?? mod;
5
+ const def = mod.default ?? mod;
6
+ if (!def || typeof def !== "object") throw new Error(`[vite-env] env config at ${configPath} must export an object (got ${typeof def}).\n Use: export default defineEnv({ ... })`);
7
+ return def;
6
8
  }
7
9
  //#endregion
8
10
  export { loadEnvConfig };
@@ -1 +1 @@
1
- {"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\nimport { createJiti } from 'jiti'\n\nexport async function loadEnvConfig(configPath: string): Promise<EnvDefinition> {\n const jiti = createJiti(configPath)\n const mod = await jiti.import(configPath) as any\n return (mod.default ?? mod) as EnvDefinition\n}\n"],"mappings":";;AAGA,eAAsB,cAAc,YAA4C;CAE9E,MAAM,MAAM,MADC,WAAW,WAAW,CACZ,OAAO,WAAW;AACzC,QAAQ,IAAI,WAAW"}
1
+ {"version":3,"file":"config.mjs","names":[],"sources":["../src/config.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\nimport { createJiti } from 'jiti'\n\nexport async function loadEnvConfig(configPath: string): Promise<EnvDefinition> {\n const jiti = createJiti(configPath)\n const mod: Record<string, unknown> = await jiti.import(configPath) as any\n const def: unknown = mod.default ?? mod\n\n if (!def || typeof def !== 'object') {\n throw new Error(\n `[vite-env] env config at ${configPath} must export an object (got ${typeof def}).\\n`\n + ` Use: export default defineEnv({ ... })`,\n )\n }\n\n return def as EnvDefinition\n}\n"],"mappings":";;AAGA,eAAsB,cAAc,YAA4C;CAE9E,MAAM,MAA+B,MADxB,WAAW,WAAW,CACa,OAAO,WAAW;CAClE,MAAM,MAAe,IAAI,WAAW;AAEpC,KAAI,CAAC,OAAO,OAAO,QAAQ,SACzB,OAAM,IAAI,MACR,4BAA4B,WAAW,8BAA8B,OAAO,IAAI,8CAEjF;AAGH,QAAO"}
@@ -32,14 +32,10 @@ node_fs_promises = __toESM(node_fs_promises);
32
32
  * Users never need to manually augment ImportMetaEnv again.
33
33
  */
34
34
  async function generateDts(def, root) {
35
- const clientKeys = {
36
- ...def.client,
37
- ...def.shared
38
- };
35
+ const clientKeys = { ...def.client };
39
36
  const serverKeys = {
40
37
  ...def.server,
41
- ...def.client,
42
- ...def.shared
38
+ ...def.client
43
39
  };
44
40
  const dts = `// Auto-generated by @vite-env/core
45
41
  // Do not edit manually — re-generated on every dev server start and build
@@ -60,7 +56,12 @@ ${zodShapeToTsFields(serverKeys)}
60
56
  export default env
61
57
  }
62
58
  `;
63
- await node_fs_promises.default.writeFile(node_path.default.join(root, "vite-env.d.ts"), dts, "utf-8");
59
+ const filePath = node_path.default.join(root, "vite-env.d.ts");
60
+ try {
61
+ await node_fs_promises.default.writeFile(filePath, dts, "utf-8");
62
+ } catch (e) {
63
+ throw new Error(`[vite-env] Failed to write vite-env.d.ts to ${root}. Check file permissions.`, { cause: e });
64
+ }
64
65
  }
65
66
  function zodShapeToTsFields(shape) {
66
67
  return Object.entries(shape).map(([key, schema]) => {
@@ -80,7 +81,7 @@ function zodToTs(schema) {
80
81
  return "string";
81
82
  }
82
83
  function isOptional(schema) {
83
- return schema instanceof zod.z.ZodOptional || schema instanceof zod.z.ZodDefault;
84
+ return schema instanceof zod.z.ZodOptional;
84
85
  }
85
86
  //#endregion
86
87
  Object.defineProperty(exports, "__toESM", {
@@ -96,4 +97,4 @@ Object.defineProperty(exports, "generateDts", {
96
97
  }
97
98
  });
98
99
 
99
- //# sourceMappingURL=dts-DT7HjKrz.cjs.map
100
+ //# sourceMappingURL=dts-DF71HNdJ.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dts-DF71HNdJ.cjs","names":["path","fs","z"],"sources":["../src/dts.ts"],"sourcesContent":["// @env node\nimport type { EnvDefinition } from './types'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\n/**\n * Writes vite-env.d.ts to project root.\n * Declares virtual:env/client and virtual:env/server module types.\n * Users never need to manually augment ImportMetaEnv again.\n */\nexport async function generateDts(\n def: EnvDefinition,\n root: string,\n): Promise<void> {\n const clientKeys = {\n ...def.client,\n }\n const serverKeys = {\n ...def.server,\n ...def.client,\n }\n\n const clientFields = zodShapeToTsFields(clientKeys)\n const serverFields = zodShapeToTsFields(serverKeys)\n\n const dts = `// Auto-generated by @vite-env/core\n// Do not edit manually — re-generated on every dev server start and build\n\ndeclare module 'virtual:env/client' {\n const env: {\n${clientFields}\n }\n export { env }\n export default env\n}\n\ndeclare module 'virtual:env/server' {\n const env: {\n${serverFields}\n }\n export { env }\n export default env\n}\n`\n\n const filePath = path.join(root, 'vite-env.d.ts')\n try {\n await fs.writeFile(filePath, dts, 'utf-8')\n }\n catch (e) {\n throw new Error(\n `[vite-env] Failed to write vite-env.d.ts to ${root}. Check file permissions.`,\n { cause: e },\n )\n }\n}\n\nfunction zodShapeToTsFields(shape: z.ZodRawShape): string {\n return Object.entries(shape)\n .map(([key, schema]) => {\n // Zod v4: ZodRawShape values are $ZodType, cast to ZodTypeAny for instanceof checks\n const s = schema as unknown as z.ZodTypeAny\n const tsType = zodToTs(s)\n const optional = isOptional(s)\n return ` readonly ${key}${optional ? '?' : ''}: ${tsType}`\n })\n .join('\\n')\n}\n\nfunction zodToTs(schema: z.ZodTypeAny): string {\n if (schema instanceof z.ZodOptional)\n return zodToTs(schema.unwrap() as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodDefault)\n return zodToTs(schema.def.innerType as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodString)\n return 'string'\n if (schema instanceof z.ZodNumber)\n return 'number'\n if (schema instanceof z.ZodBoolean)\n return 'boolean'\n if (schema instanceof z.ZodEnum)\n return (schema.options as string[]).map(o => `'${o}'`).join(' | ')\n if (schema instanceof z.ZodPipe)\n return zodToTs(schema.def.out as unknown as z.ZodTypeAny)\n return 'string'\n}\n\nfunction isOptional(schema: z.ZodTypeAny): boolean {\n return schema instanceof z.ZodOptional\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,eAAsB,YACpB,KACA,MACe;CACf,MAAM,aAAa,EACjB,GAAG,IAAI,QACR;CACD,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAKD,MAAM,MAAM;;;;;EAHS,mBAAmB,WAAW,CAQtC;;;;;;;;EAPQ,mBAAmB,WAAW,CAetC;;;;;;CAOb,MAAM,WAAWA,UAAAA,QAAK,KAAK,MAAM,gBAAgB;AACjD,KAAI;AACF,QAAMC,iBAAAA,QAAG,UAAU,UAAU,KAAK,QAAQ;UAErC,GAAG;AACR,QAAM,IAAI,MACR,+CAA+C,KAAK,4BACpD,EAAE,OAAO,GAAG,CACb;;;AAIL,SAAS,mBAAmB,OAA8B;AACxD,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,YAAY;EAEtB,MAAM,IAAI;EACV,MAAM,SAAS,QAAQ,EAAE;AAEzB,SAAO,gBAAgB,MADN,WAAW,EAAE,GACU,MAAM,GAAG,IAAI;GACrD,CACD,KAAK,KAAK;;AAGf,SAAS,QAAQ,QAA8B;AAC7C,KAAI,kBAAkBC,IAAAA,EAAE,YACtB,QAAO,QAAQ,OAAO,QAAQ,CAA4B;AAC5D,KAAI,kBAAkBA,IAAAA,EAAE,WACtB,QAAO,QAAQ,OAAO,IAAI,UAAqC;AACjE,KAAI,kBAAkBA,IAAAA,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,WACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,QACtB,QAAQ,OAAO,QAAqB,KAAI,MAAK,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM;AACpE,KAAI,kBAAkBA,IAAAA,EAAE,QACtB,QAAO,QAAQ,OAAO,IAAI,IAA+B;AAC3D,QAAO;;AAGT,SAAS,WAAW,QAA+B;AACjD,QAAO,kBAAkBA,IAAAA,EAAE"}
package/dist/dts.cjs CHANGED
@@ -1,3 +1,3 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_dts = require("./dts-DT7HjKrz.cjs");
2
+ const require_dts = require("./dts-DF71HNdJ.cjs");
3
3
  exports.generateDts = require_dts.generateDts;
package/dist/dts.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-BmEBJZdy.cjs";
1
+ import { t as EnvDefinition } from "./types-DqTMuWwc.cjs";
2
2
 
3
3
  //#region src/dts.d.ts
4
4
  /**
package/dist/dts.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-CLfG9amO.mjs";
1
+ import { t as EnvDefinition } from "./types-CluiDKAQ.mjs";
2
2
 
3
3
  //#region src/dts.d.ts
4
4
  /**
package/dist/dts.mjs CHANGED
@@ -8,14 +8,10 @@ import fs from "node:fs/promises";
8
8
  * Users never need to manually augment ImportMetaEnv again.
9
9
  */
10
10
  async function generateDts(def, root) {
11
- const clientKeys = {
12
- ...def.client,
13
- ...def.shared
14
- };
11
+ const clientKeys = { ...def.client };
15
12
  const serverKeys = {
16
13
  ...def.server,
17
- ...def.client,
18
- ...def.shared
14
+ ...def.client
19
15
  };
20
16
  const dts = `// Auto-generated by @vite-env/core
21
17
  // Do not edit manually — re-generated on every dev server start and build
@@ -36,7 +32,12 @@ ${zodShapeToTsFields(serverKeys)}
36
32
  export default env
37
33
  }
38
34
  `;
39
- await fs.writeFile(path.join(root, "vite-env.d.ts"), dts, "utf-8");
35
+ const filePath = path.join(root, "vite-env.d.ts");
36
+ try {
37
+ await fs.writeFile(filePath, dts, "utf-8");
38
+ } catch (e) {
39
+ throw new Error(`[vite-env] Failed to write vite-env.d.ts to ${root}. Check file permissions.`, { cause: e });
40
+ }
40
41
  }
41
42
  function zodShapeToTsFields(shape) {
42
43
  return Object.entries(shape).map(([key, schema]) => {
@@ -56,7 +57,7 @@ function zodToTs(schema) {
56
57
  return "string";
57
58
  }
58
59
  function isOptional(schema) {
59
- return schema instanceof z.ZodOptional || schema instanceof z.ZodDefault;
60
+ return schema instanceof z.ZodOptional;
60
61
  }
61
62
  //#endregion
62
63
  export { generateDts };
package/dist/dts.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dts.mjs","names":[],"sources":["../src/dts.ts"],"sourcesContent":["// @env node\nimport type { EnvDefinition } from './types'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\n/**\n * Writes vite-env.d.ts to project root.\n * Declares virtual:env/client and virtual:env/server module types.\n * Users never need to manually augment ImportMetaEnv again.\n */\nexport async function generateDts(\n def: EnvDefinition,\n root: string,\n): Promise<void> {\n const clientKeys = {\n ...def.client,\n ...def.shared,\n }\n const serverKeys = {\n ...def.server,\n ...def.client,\n ...def.shared,\n }\n\n const clientFields = zodShapeToTsFields(clientKeys)\n const serverFields = zodShapeToTsFields(serverKeys)\n\n const dts = `// Auto-generated by @vite-env/core\n// Do not edit manually — re-generated on every dev server start and build\n\ndeclare module 'virtual:env/client' {\n const env: {\n${clientFields}\n }\n export { env }\n export default env\n}\n\ndeclare module 'virtual:env/server' {\n const env: {\n${serverFields}\n }\n export { env }\n export default env\n}\n`\n\n await fs.writeFile(path.join(root, 'vite-env.d.ts'), dts, 'utf-8')\n}\n\nfunction zodShapeToTsFields(shape: z.ZodRawShape): string {\n return Object.entries(shape)\n .map(([key, schema]) => {\n // Zod v4: ZodRawShape values are $ZodType, cast to ZodTypeAny for instanceof checks\n const s = schema as unknown as z.ZodTypeAny\n const tsType = zodToTs(s)\n const optional = isOptional(s)\n return ` readonly ${key}${optional ? '?' : ''}: ${tsType}`\n })\n .join('\\n')\n}\n\nfunction zodToTs(schema: z.ZodTypeAny): string {\n if (schema instanceof z.ZodOptional)\n return zodToTs(schema.unwrap() as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodDefault)\n return zodToTs(schema.def.innerType as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodString)\n return 'string'\n if (schema instanceof z.ZodNumber)\n return 'number'\n if (schema instanceof z.ZodBoolean)\n return 'boolean'\n if (schema instanceof z.ZodEnum)\n return (schema.options as string[]).map(o => `'${o}'`).join(' | ')\n if (schema instanceof z.ZodPipe)\n return zodToTs(schema.def.out as unknown as z.ZodTypeAny)\n return 'string'\n}\n\nfunction isOptional(schema: z.ZodTypeAny): boolean {\n return schema instanceof z.ZodOptional || schema instanceof z.ZodDefault\n}\n"],"mappings":";;;;;;;;;AAWA,eAAsB,YACpB,KACA,MACe;CACf,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CACD,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAKD,MAAM,MAAM;;;;;EAHS,mBAAmB,WAAW,CAQtC;;;;;;;;EAPQ,mBAAmB,WAAW,CAetC;;;;;;AAOb,OAAM,GAAG,UAAU,KAAK,KAAK,MAAM,gBAAgB,EAAE,KAAK,QAAQ;;AAGpE,SAAS,mBAAmB,OAA8B;AACxD,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,YAAY;EAEtB,MAAM,IAAI;EACV,MAAM,SAAS,QAAQ,EAAE;AAEzB,SAAO,gBAAgB,MADN,WAAW,EAAE,GACU,MAAM,GAAG,IAAI;GACrD,CACD,KAAK,KAAK;;AAGf,SAAS,QAAQ,QAA8B;AAC7C,KAAI,kBAAkB,EAAE,YACtB,QAAO,QAAQ,OAAO,QAAQ,CAA4B;AAC5D,KAAI,kBAAkB,EAAE,WACtB,QAAO,QAAQ,OAAO,IAAI,UAAqC;AACjE,KAAI,kBAAkB,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,WACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,QACtB,QAAQ,OAAO,QAAqB,KAAI,MAAK,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM;AACpE,KAAI,kBAAkB,EAAE,QACtB,QAAO,QAAQ,OAAO,IAAI,IAA+B;AAC3D,QAAO;;AAGT,SAAS,WAAW,QAA+B;AACjD,QAAO,kBAAkB,EAAE,eAAe,kBAAkB,EAAE"}
1
+ {"version":3,"file":"dts.mjs","names":[],"sources":["../src/dts.ts"],"sourcesContent":["// @env node\nimport type { EnvDefinition } from './types'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\n/**\n * Writes vite-env.d.ts to project root.\n * Declares virtual:env/client and virtual:env/server module types.\n * Users never need to manually augment ImportMetaEnv again.\n */\nexport async function generateDts(\n def: EnvDefinition,\n root: string,\n): Promise<void> {\n const clientKeys = {\n ...def.client,\n }\n const serverKeys = {\n ...def.server,\n ...def.client,\n }\n\n const clientFields = zodShapeToTsFields(clientKeys)\n const serverFields = zodShapeToTsFields(serverKeys)\n\n const dts = `// Auto-generated by @vite-env/core\n// Do not edit manually — re-generated on every dev server start and build\n\ndeclare module 'virtual:env/client' {\n const env: {\n${clientFields}\n }\n export { env }\n export default env\n}\n\ndeclare module 'virtual:env/server' {\n const env: {\n${serverFields}\n }\n export { env }\n export default env\n}\n`\n\n const filePath = path.join(root, 'vite-env.d.ts')\n try {\n await fs.writeFile(filePath, dts, 'utf-8')\n }\n catch (e) {\n throw new Error(\n `[vite-env] Failed to write vite-env.d.ts to ${root}. Check file permissions.`,\n { cause: e },\n )\n }\n}\n\nfunction zodShapeToTsFields(shape: z.ZodRawShape): string {\n return Object.entries(shape)\n .map(([key, schema]) => {\n // Zod v4: ZodRawShape values are $ZodType, cast to ZodTypeAny for instanceof checks\n const s = schema as unknown as z.ZodTypeAny\n const tsType = zodToTs(s)\n const optional = isOptional(s)\n return ` readonly ${key}${optional ? '?' : ''}: ${tsType}`\n })\n .join('\\n')\n}\n\nfunction zodToTs(schema: z.ZodTypeAny): string {\n if (schema instanceof z.ZodOptional)\n return zodToTs(schema.unwrap() as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodDefault)\n return zodToTs(schema.def.innerType as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodString)\n return 'string'\n if (schema instanceof z.ZodNumber)\n return 'number'\n if (schema instanceof z.ZodBoolean)\n return 'boolean'\n if (schema instanceof z.ZodEnum)\n return (schema.options as string[]).map(o => `'${o}'`).join(' | ')\n if (schema instanceof z.ZodPipe)\n return zodToTs(schema.def.out as unknown as z.ZodTypeAny)\n return 'string'\n}\n\nfunction isOptional(schema: z.ZodTypeAny): boolean {\n return schema instanceof z.ZodOptional\n}\n"],"mappings":";;;;;;;;;AAWA,eAAsB,YACpB,KACA,MACe;CACf,MAAM,aAAa,EACjB,GAAG,IAAI,QACR;CACD,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAKD,MAAM,MAAM;;;;;EAHS,mBAAmB,WAAW,CAQtC;;;;;;;;EAPQ,mBAAmB,WAAW,CAetC;;;;;;CAOb,MAAM,WAAW,KAAK,KAAK,MAAM,gBAAgB;AACjD,KAAI;AACF,QAAM,GAAG,UAAU,UAAU,KAAK,QAAQ;UAErC,GAAG;AACR,QAAM,IAAI,MACR,+CAA+C,KAAK,4BACpD,EAAE,OAAO,GAAG,CACb;;;AAIL,SAAS,mBAAmB,OAA8B;AACxD,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,YAAY;EAEtB,MAAM,IAAI;EACV,MAAM,SAAS,QAAQ,EAAE;AAEzB,SAAO,gBAAgB,MADN,WAAW,EAAE,GACU,MAAM,GAAG,IAAI;GACrD,CACD,KAAK,KAAK;;AAGf,SAAS,QAAQ,QAA8B;AAC7C,KAAI,kBAAkB,EAAE,YACtB,QAAO,QAAQ,OAAO,QAAQ,CAA4B;AAC5D,KAAI,kBAAkB,EAAE,WACtB,QAAO,QAAQ,OAAO,IAAI,UAAqC;AACjE,KAAI,kBAAkB,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,WACtB,QAAO;AACT,KAAI,kBAAkB,EAAE,QACtB,QAAQ,OAAO,QAAqB,KAAI,MAAK,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM;AACpE,KAAI,kBAAkB,EAAE,QACtB,QAAO,QAAQ,OAAO,IAAI,IAA+B;AAC3D,QAAO;;AAGT,SAAS,WAAW,QAA+B;AACjD,QAAO,kBAAkB,EAAE"}
package/dist/index.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./dts-DT7HjKrz.cjs");
2
+ require("./dts-DF71HNdJ.cjs");
3
3
  const require_schema = require("./schema.cjs");
4
4
  let zod = require("zod");
5
5
  exports.defineEnv = require_schema.defineEnv;
package/dist/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as InferClientEnv, r as InferServerEnv, t as EnvDefinition } from "./types-BmEBJZdy.cjs";
1
+ import { n as InferClientEnv, r as InferServerEnv, t as EnvDefinition } from "./types-DqTMuWwc.cjs";
2
2
  import { defineEnv } from "./schema.cjs";
3
3
  import { z } from "zod";
4
4
  export { type EnvDefinition, type InferClientEnv, type InferServerEnv, defineEnv, z };
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as InferClientEnv, r as InferServerEnv, t as EnvDefinition } from "./types-CLfG9amO.mjs";
1
+ import { n as InferClientEnv, r as InferServerEnv, t as EnvDefinition } from "./types-CluiDKAQ.mjs";
2
2
  import { defineEnv } from "./schema.mjs";
3
3
  import { z } from "zod";
4
4
  export { type EnvDefinition, type InferClientEnv, type InferServerEnv, defineEnv, z };
package/dist/leak.cjs CHANGED
@@ -8,8 +8,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
8
8
  * appears as a literal string in any output chunk's source code.
9
9
  * Short/common values (< 8 chars) are skipped to avoid false positives.
10
10
  */
11
- function detectServerLeak(def, data, bundle) {
11
+ function detectServerLeak(def, data, bundle, onSkipped) {
12
12
  const serverKeys = new Set(Object.keys(def.server ?? {}));
13
+ const shortSecrets = Object.entries(data).filter((entry) => serverKeys.has(entry[0]) && typeof entry[1] === "string" && entry[1].length < 8);
14
+ if (shortSecrets.length > 0 && onSkipped) onSkipped(shortSecrets.map(([k]) => k));
13
15
  const serverSecrets = Object.entries(data).filter((entry) => serverKeys.has(entry[0]) && typeof entry[1] === "string" && entry[1].length >= 8);
14
16
  const chunks = Object.entries(bundle).filter(([, chunk]) => chunk.type === "chunk" && !!chunk.code);
15
17
  const leaks = [];
package/dist/leak.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"leak.cjs","names":[],"sources":["../src/leak.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\n\ninterface LeakReport {\n key: string\n chunk: string\n}\n\n/**\n * Scans all client-destined chunks for literal values of server-only vars.\n * Fires in generateBundle() — Rolldown sequential hook, safe.\n *\n * Strategy: for each server-only key, check if its actual runtime value\n * appears as a literal string in any output chunk's source code.\n * Short/common values (< 8 chars) are skipped to avoid false positives.\n */\nexport function detectServerLeak(\n def: EnvDefinition,\n data: Record<string, unknown>,\n bundle: Record<string, { type: string, code?: string }>,\n): LeakReport[] {\n const serverKeys = new Set(Object.keys(def.server ?? {}))\n\n const serverSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length >= 8,\n )\n\n const chunks = Object.entries(bundle).filter(\n ([, chunk]) => chunk.type === 'chunk' && !!chunk.code,\n )\n\n const leaks: LeakReport[] = []\n for (const [key, value] of serverSecrets) {\n for (const [chunkName, chunk] of chunks) {\n if (chunk.code!.includes(value)) {\n leaks.push({ key, chunk: chunkName })\n }\n }\n }\n\n return leaks\n}\n"],"mappings":";;;;;;;;;;AAeA,SAAgB,iBACd,KACA,MACA,QACc;CACd,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU,EAAE,CAAC,CAAC;CAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,CAAC,QACxC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,UAAU,EACzB;CAED,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC,QACnC,GAAG,WAAW,MAAM,SAAS,WAAW,CAAC,CAAC,MAAM,KAClD;CAED,MAAM,QAAsB,EAAE;AAC9B,MAAK,MAAM,CAAC,KAAK,UAAU,cACzB,MAAK,MAAM,CAAC,WAAW,UAAU,OAC/B,KAAI,MAAM,KAAM,SAAS,MAAM,CAC7B,OAAM,KAAK;EAAE;EAAK,OAAO;EAAW,CAAC;AAK3C,QAAO"}
1
+ {"version":3,"file":"leak.cjs","names":[],"sources":["../src/leak.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\n\ninterface LeakReport {\n key: string\n chunk: string\n}\n\n/**\n * Scans all client-destined chunks for literal values of server-only vars.\n * Fires in generateBundle() — Rolldown sequential hook, safe.\n *\n * Strategy: for each server-only key, check if its actual runtime value\n * appears as a literal string in any output chunk's source code.\n * Short/common values (< 8 chars) are skipped to avoid false positives.\n */\nexport function detectServerLeak(\n def: EnvDefinition,\n data: Record<string, unknown>,\n bundle: Record<string, { type: string, code?: string }>,\n onSkipped?: (keys: string[]) => void,\n): LeakReport[] {\n const serverKeys = new Set(Object.keys(def.server ?? {}))\n\n const shortSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length < 8,\n )\n\n if (shortSecrets.length > 0 && onSkipped) {\n onSkipped(shortSecrets.map(([k]) => k))\n }\n\n const serverSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length >= 8,\n )\n\n const chunks = Object.entries(bundle).filter(\n ([, chunk]) => chunk.type === 'chunk' && !!chunk.code,\n )\n\n const leaks: LeakReport[] = []\n for (const [key, value] of serverSecrets) {\n for (const [chunkName, chunk] of chunks) {\n if (chunk.code!.includes(value)) {\n leaks.push({ key, chunk: chunkName })\n }\n }\n }\n\n return leaks\n}\n"],"mappings":";;;;;;;;;;AAeA,SAAgB,iBACd,KACA,MACA,QACA,WACc;CACd,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU,EAAE,CAAC,CAAC;CAEzD,MAAM,eAAe,OAAO,QAAQ,KAAK,CAAC,QACvC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,SAAS,EACxB;AAED,KAAI,aAAa,SAAS,KAAK,UAC7B,WAAU,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC;CAGzC,MAAM,gBAAgB,OAAO,QAAQ,KAAK,CAAC,QACxC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,UAAU,EACzB;CAED,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC,QACnC,GAAG,WAAW,MAAM,SAAS,WAAW,CAAC,CAAC,MAAM,KAClD;CAED,MAAM,QAAsB,EAAE;AAC9B,MAAK,MAAM,CAAC,KAAK,UAAU,cACzB,MAAK,MAAM,CAAC,WAAW,UAAU,OAC/B,KAAI,MAAM,KAAM,SAAS,MAAM,CAC7B,OAAM,KAAK;EAAE;EAAK,OAAO;EAAW,CAAC;AAK3C,QAAO"}
package/dist/leak.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-BmEBJZdy.cjs";
1
+ import { t as EnvDefinition } from "./types-DqTMuWwc.cjs";
2
2
 
3
3
  //#region src/leak.d.ts
4
4
  interface LeakReport {
@@ -16,7 +16,7 @@ interface LeakReport {
16
16
  declare function detectServerLeak(def: EnvDefinition, data: Record<string, unknown>, bundle: Record<string, {
17
17
  type: string;
18
18
  code?: string;
19
- }>): LeakReport[];
19
+ }>, onSkipped?: (keys: string[]) => void): LeakReport[];
20
20
  //#endregion
21
21
  export { detectServerLeak };
22
22
  //# sourceMappingURL=leak.d.cts.map
package/dist/leak.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as EnvDefinition } from "./types-CLfG9amO.mjs";
1
+ import { t as EnvDefinition } from "./types-CluiDKAQ.mjs";
2
2
 
3
3
  //#region src/leak.d.ts
4
4
  interface LeakReport {
@@ -16,7 +16,7 @@ interface LeakReport {
16
16
  declare function detectServerLeak(def: EnvDefinition, data: Record<string, unknown>, bundle: Record<string, {
17
17
  type: string;
18
18
  code?: string;
19
- }>): LeakReport[];
19
+ }>, onSkipped?: (keys: string[]) => void): LeakReport[];
20
20
  //#endregion
21
21
  export { detectServerLeak };
22
22
  //# sourceMappingURL=leak.d.mts.map
package/dist/leak.mjs CHANGED
@@ -7,8 +7,10 @@
7
7
  * appears as a literal string in any output chunk's source code.
8
8
  * Short/common values (< 8 chars) are skipped to avoid false positives.
9
9
  */
10
- function detectServerLeak(def, data, bundle) {
10
+ function detectServerLeak(def, data, bundle, onSkipped) {
11
11
  const serverKeys = new Set(Object.keys(def.server ?? {}));
12
+ const shortSecrets = Object.entries(data).filter((entry) => serverKeys.has(entry[0]) && typeof entry[1] === "string" && entry[1].length < 8);
13
+ if (shortSecrets.length > 0 && onSkipped) onSkipped(shortSecrets.map(([k]) => k));
12
14
  const serverSecrets = Object.entries(data).filter((entry) => serverKeys.has(entry[0]) && typeof entry[1] === "string" && entry[1].length >= 8);
13
15
  const chunks = Object.entries(bundle).filter(([, chunk]) => chunk.type === "chunk" && !!chunk.code);
14
16
  const leaks = [];
package/dist/leak.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"leak.mjs","names":[],"sources":["../src/leak.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\n\ninterface LeakReport {\n key: string\n chunk: string\n}\n\n/**\n * Scans all client-destined chunks for literal values of server-only vars.\n * Fires in generateBundle() — Rolldown sequential hook, safe.\n *\n * Strategy: for each server-only key, check if its actual runtime value\n * appears as a literal string in any output chunk's source code.\n * Short/common values (< 8 chars) are skipped to avoid false positives.\n */\nexport function detectServerLeak(\n def: EnvDefinition,\n data: Record<string, unknown>,\n bundle: Record<string, { type: string, code?: string }>,\n): LeakReport[] {\n const serverKeys = new Set(Object.keys(def.server ?? {}))\n\n const serverSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length >= 8,\n )\n\n const chunks = Object.entries(bundle).filter(\n ([, chunk]) => chunk.type === 'chunk' && !!chunk.code,\n )\n\n const leaks: LeakReport[] = []\n for (const [key, value] of serverSecrets) {\n for (const [chunkName, chunk] of chunks) {\n if (chunk.code!.includes(value)) {\n leaks.push({ key, chunk: chunkName })\n }\n }\n }\n\n return leaks\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,iBACd,KACA,MACA,QACc;CACd,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU,EAAE,CAAC,CAAC;CAEzD,MAAM,gBAAgB,OAAO,QAAQ,KAAK,CAAC,QACxC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,UAAU,EACzB;CAED,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC,QACnC,GAAG,WAAW,MAAM,SAAS,WAAW,CAAC,CAAC,MAAM,KAClD;CAED,MAAM,QAAsB,EAAE;AAC9B,MAAK,MAAM,CAAC,KAAK,UAAU,cACzB,MAAK,MAAM,CAAC,WAAW,UAAU,OAC/B,KAAI,MAAM,KAAM,SAAS,MAAM,CAC7B,OAAM,KAAK;EAAE;EAAK,OAAO;EAAW,CAAC;AAK3C,QAAO"}
1
+ {"version":3,"file":"leak.mjs","names":[],"sources":["../src/leak.ts"],"sourcesContent":["import type { EnvDefinition } from './types'\n\ninterface LeakReport {\n key: string\n chunk: string\n}\n\n/**\n * Scans all client-destined chunks for literal values of server-only vars.\n * Fires in generateBundle() — Rolldown sequential hook, safe.\n *\n * Strategy: for each server-only key, check if its actual runtime value\n * appears as a literal string in any output chunk's source code.\n * Short/common values (< 8 chars) are skipped to avoid false positives.\n */\nexport function detectServerLeak(\n def: EnvDefinition,\n data: Record<string, unknown>,\n bundle: Record<string, { type: string, code?: string }>,\n onSkipped?: (keys: string[]) => void,\n): LeakReport[] {\n const serverKeys = new Set(Object.keys(def.server ?? {}))\n\n const shortSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length < 8,\n )\n\n if (shortSecrets.length > 0 && onSkipped) {\n onSkipped(shortSecrets.map(([k]) => k))\n }\n\n const serverSecrets = Object.entries(data).filter(\n (entry): entry is [string, string] =>\n serverKeys.has(entry[0])\n && typeof entry[1] === 'string'\n && entry[1].length >= 8,\n )\n\n const chunks = Object.entries(bundle).filter(\n ([, chunk]) => chunk.type === 'chunk' && !!chunk.code,\n )\n\n const leaks: LeakReport[] = []\n for (const [key, value] of serverSecrets) {\n for (const [chunkName, chunk] of chunks) {\n if (chunk.code!.includes(value)) {\n leaks.push({ key, chunk: chunkName })\n }\n }\n }\n\n return leaks\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,iBACd,KACA,MACA,QACA,WACc;CACd,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU,EAAE,CAAC,CAAC;CAEzD,MAAM,eAAe,OAAO,QAAQ,KAAK,CAAC,QACvC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,SAAS,EACxB;AAED,KAAI,aAAa,SAAS,KAAK,UAC7B,WAAU,aAAa,KAAK,CAAC,OAAO,EAAE,CAAC;CAGzC,MAAM,gBAAgB,OAAO,QAAQ,KAAK,CAAC,QACxC,UACC,WAAW,IAAI,MAAM,GAAG,IACrB,OAAO,MAAM,OAAO,YACpB,MAAM,GAAG,UAAU,EACzB;CAED,MAAM,SAAS,OAAO,QAAQ,OAAO,CAAC,QACnC,GAAG,WAAW,MAAM,SAAS,WAAW,CAAC,CAAC,MAAM,KAClD;CAED,MAAM,QAAsB,EAAE;AAC9B,MAAK,MAAM,CAAC,KAAK,UAAU,cACzB,MAAK,MAAM,CAAC,WAAW,UAAU,OAC/B,KAAI,MAAM,KAAM,SAAS,MAAM,CAC7B,OAAM,KAAK;EAAE;EAAK,OAAO;EAAW,CAAC;AAK3C,QAAO"}
package/dist/plugin.cjs CHANGED
@@ -1,4 +1,4 @@
1
- const require_dts = require("./dts-DT7HjKrz.cjs");
1
+ const require_dts = require("./dts-DF71HNdJ.cjs");
2
2
  const require_schema = require("./schema.cjs");
3
3
  const require_config = require("./config.cjs");
4
4
  const require_format = require("./format.cjs");
@@ -31,7 +31,7 @@ function filterStrings(env) {
31
31
  //#endregion
32
32
  //#region src/virtual.ts
33
33
  function buildClientModule(def, data) {
34
- const clientKeys = new Set([...Object.keys(def.client ?? {}), ...Object.keys(def.shared ?? {})]);
34
+ const clientKeys = new Set(Object.keys(def.client ?? {}));
35
35
  const clientData = Object.fromEntries(Object.entries(data).filter(([k]) => clientKeys.has(k)));
36
36
  return {
37
37
  moduleType: "js",
@@ -75,7 +75,7 @@ function ViteEnv(options = {}) {
75
75
  }
76
76
  lastValidated = result.data;
77
77
  await require_dts.generateDts(envDefinition, resolvedConfig.root);
78
- const count = Object.keys(result.data ?? {}).length;
78
+ const count = Object.keys(result.data).length;
79
79
  resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m ${count} variables validated`);
80
80
  },
81
81
  resolveId(id) {
@@ -86,15 +86,11 @@ function ViteEnv(options = {}) {
86
86
  if (id === "\0virtual:env/client") return buildClientModule(envDefinition, lastValidated);
87
87
  if (id === "\0virtual:env/server") return buildServerModule(envDefinition, lastValidated);
88
88
  },
89
- async generateBundle(_options, bundle) {
89
+ generateBundle(_options, bundle) {
90
90
  if (resolvedConfig.build.ssr) return;
91
- const rawEnv = await loadEnvSources(resolvedConfig);
92
- const result = require_schema.validateEnv(envDefinition, rawEnv);
93
- if (!result.success) {
94
- const formatted = require_format.formatZodError(result.errors);
95
- throw new Error(`[vite-env] Env validation failed at bundle emit:\n\n${formatted}`);
96
- }
97
- const leaks = require_leak.detectServerLeak(envDefinition, result.data || {}, bundle);
91
+ const leaks = require_leak.detectServerLeak(envDefinition, lastValidated, bundle, (keys) => {
92
+ resolvedConfig.logger.warn(` \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(", ")}`);
93
+ });
98
94
  if (leaks.length > 0) throw new Error(`[vite-env] Server environment variables detected in client bundle!\n\n${leaks.map((l) => ` ✗ ${l.key} found in ${l.chunk}`).join("\n")}\n\n These variables are marked as server-only and must never reach the browser.`);
99
95
  },
100
96
  configureServer(server) {
@@ -105,19 +101,25 @@ function ViteEnv(options = {}) {
105
101
  if (!node_path.default.basename(file).startsWith(".env")) return;
106
102
  clearTimeout(debounceTimer);
107
103
  debounceTimer = setTimeout(async () => {
108
- const rawEnv = await loadEnvSources(resolvedConfig);
109
- const result = require_schema.validateEnv(envDefinition, rawEnv);
110
- if (!result.success) {
111
- const formatted = require_format.formatZodError(result.errors);
112
- resolvedConfig.logger.warn(`\n \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidation failed:\n${formatted}`);
113
- return;
114
- }
115
- lastValidated = result.data;
116
- const clientMod = server.moduleGraph.getModuleById("\0virtual:env/client");
117
- if (clientMod) {
118
- server.moduleGraph.invalidateModule(clientMod);
119
- server.hot.send({ type: "full-reload" });
120
- resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidated`);
104
+ try {
105
+ const rawEnv = await loadEnvSources(resolvedConfig);
106
+ const result = require_schema.validateEnv(envDefinition, rawEnv);
107
+ if (!result.success) {
108
+ const formatted = require_format.formatZodError(result.errors);
109
+ resolvedConfig.logger.warn(`\n \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidation failed:\n${formatted}`);
110
+ return;
111
+ }
112
+ lastValidated = result.data;
113
+ const clientMod = server.moduleGraph.getModuleById("\0virtual:env/client");
114
+ const serverMod = server.moduleGraph.getModuleById("\0virtual:env/server");
115
+ if (clientMod) server.moduleGraph.invalidateModule(clientMod);
116
+ if (serverMod) server.moduleGraph.invalidateModule(serverMod);
117
+ if (clientMod || serverMod) {
118
+ server.hot.send({ type: "full-reload" });
119
+ resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidated`);
120
+ }
121
+ } catch (e) {
122
+ resolvedConfig.logger.error(`\n \x1B[31m✗\x1B[0m \x1B[36m[vite-env]\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`);
121
123
  }
122
124
  }, 150);
123
125
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.cjs","names":["process","path","loadEnvConfig","validateEnv","formatZodError","generateDts","detectServerLeak"],"sources":["../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { EnvDefinition } from './types'\n\nexport function buildClientModule(\n def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set([\n ...Object.keys(def.client ?? {}),\n ...Object.keys(def.shared ?? {}),\n ])\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { EnvDefinition } from './types'\nimport path from 'node:path'\nimport { loadEnvConfig } from './config'\nimport { generateDts } from './dts'\nimport { formatZodError } from './format'\nimport { detectServerLeak } from './leak'\nimport { validateEnv } from './schema'\nimport { loadEnvSources } from './sources'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: EnvDefinition\n let lastValidated: Record<string, unknown> = {}\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${formatted}`,\n )\n }\n\n lastValidated = result.data!\n\n await generateDts(envDefinition, resolvedConfig.root)\n\n const count = Object.keys(result.data ?? {}).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(id) {\n if (id === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (id === 'virtual:env/server')\n return '\\0virtual:env/server'\n },\n\n load(id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server')\n return buildServerModule(envDefinition, lastValidated)\n },\n\n async generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Env validation failed at bundle emit:\\n\\n${formatted}`,\n )\n }\n\n const leaks = detectServerLeak(\n envDefinition,\n result.data || {},\n bundle as Record<string, { type: string, code?: string }>,\n )\n\n if (leaks.length > 0) {\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n }\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${formatted}`,\n )\n return\n }\n\n lastValidated = result.data!\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n if (clientMod) {\n server.moduleGraph.invalidateModule(clientMod)\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }, 150) // 150ms debounce\n })\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.cjs","names":["process","path","loadEnvConfig","validateEnv","formatZodError","generateDts","detectServerLeak"],"sources":["../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { EnvDefinition } from './types'\n\nexport function buildClientModule(\n def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set(Object.keys(def.client ?? {}))\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { EnvDefinition } from './types'\nimport path from 'node:path'\nimport { loadEnvConfig } from './config'\nimport { generateDts } from './dts'\nimport { formatZodError } from './format'\nimport { detectServerLeak } from './leak'\nimport { validateEnv } from './schema'\nimport { loadEnvSources } from './sources'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: EnvDefinition\n let lastValidated: Record<string, unknown> = {}\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${formatted}`,\n )\n }\n\n lastValidated = result.data\n\n await generateDts(envDefinition, resolvedConfig.root)\n\n const count = Object.keys(result.data).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(id) {\n if (id === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (id === 'virtual:env/server')\n return '\\0virtual:env/server'\n },\n\n load(id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server')\n return buildServerModule(envDefinition, lastValidated)\n },\n\n generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const leaks = detectServerLeak(\n envDefinition,\n lastValidated,\n bundle as Record<string, { type: string, code?: string }>,\n (keys) => {\n resolvedConfig.logger.warn(\n ` \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(', ')}`,\n )\n },\n )\n\n if (leaks.length > 0) {\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n }\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n try {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${formatted}`,\n )\n return\n }\n\n lastValidated = result.data\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n const serverMod = server.moduleGraph.getModuleById('\\0virtual:env/server')\n if (clientMod)\n server.moduleGraph.invalidateModule(clientMod)\n if (serverMod)\n server.moduleGraph.invalidateModule(serverMod)\n if (clientMod || serverMod) {\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }\n catch (e) {\n resolvedConfig.logger.error(\n `\\n \\x1B[31m✗\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n }, 150) // 150ms debounce\n })\n },\n }\n}\n"],"mappings":"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"}
package/dist/plugin.mjs CHANGED
@@ -29,7 +29,7 @@ function filterStrings(env) {
29
29
  //#endregion
30
30
  //#region src/virtual.ts
31
31
  function buildClientModule(def, data) {
32
- const clientKeys = new Set([...Object.keys(def.client ?? {}), ...Object.keys(def.shared ?? {})]);
32
+ const clientKeys = new Set(Object.keys(def.client ?? {}));
33
33
  const clientData = Object.fromEntries(Object.entries(data).filter(([k]) => clientKeys.has(k)));
34
34
  return {
35
35
  moduleType: "js",
@@ -73,7 +73,7 @@ function ViteEnv(options = {}) {
73
73
  }
74
74
  lastValidated = result.data;
75
75
  await generateDts(envDefinition, resolvedConfig.root);
76
- const count = Object.keys(result.data ?? {}).length;
76
+ const count = Object.keys(result.data).length;
77
77
  resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m ${count} variables validated`);
78
78
  },
79
79
  resolveId(id) {
@@ -84,15 +84,11 @@ function ViteEnv(options = {}) {
84
84
  if (id === "\0virtual:env/client") return buildClientModule(envDefinition, lastValidated);
85
85
  if (id === "\0virtual:env/server") return buildServerModule(envDefinition, lastValidated);
86
86
  },
87
- async generateBundle(_options, bundle) {
87
+ generateBundle(_options, bundle) {
88
88
  if (resolvedConfig.build.ssr) return;
89
- const rawEnv = await loadEnvSources(resolvedConfig);
90
- const result = validateEnv(envDefinition, rawEnv);
91
- if (!result.success) {
92
- const formatted = formatZodError(result.errors);
93
- throw new Error(`[vite-env] Env validation failed at bundle emit:\n\n${formatted}`);
94
- }
95
- const leaks = detectServerLeak(envDefinition, result.data || {}, bundle);
89
+ const leaks = detectServerLeak(envDefinition, lastValidated, bundle, (keys) => {
90
+ resolvedConfig.logger.warn(` \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(", ")}`);
91
+ });
96
92
  if (leaks.length > 0) throw new Error(`[vite-env] Server environment variables detected in client bundle!\n\n${leaks.map((l) => ` ✗ ${l.key} found in ${l.chunk}`).join("\n")}\n\n These variables are marked as server-only and must never reach the browser.`);
97
93
  },
98
94
  configureServer(server) {
@@ -103,19 +99,25 @@ function ViteEnv(options = {}) {
103
99
  if (!path.basename(file).startsWith(".env")) return;
104
100
  clearTimeout(debounceTimer);
105
101
  debounceTimer = setTimeout(async () => {
106
- const rawEnv = await loadEnvSources(resolvedConfig);
107
- const result = validateEnv(envDefinition, rawEnv);
108
- if (!result.success) {
109
- const formatted = formatZodError(result.errors);
110
- resolvedConfig.logger.warn(`\n \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidation failed:\n${formatted}`);
111
- return;
112
- }
113
- lastValidated = result.data;
114
- const clientMod = server.moduleGraph.getModuleById("\0virtual:env/client");
115
- if (clientMod) {
116
- server.moduleGraph.invalidateModule(clientMod);
117
- server.hot.send({ type: "full-reload" });
118
- resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidated`);
102
+ try {
103
+ const rawEnv = await loadEnvSources(resolvedConfig);
104
+ const result = validateEnv(envDefinition, rawEnv);
105
+ if (!result.success) {
106
+ const formatted = formatZodError(result.errors);
107
+ resolvedConfig.logger.warn(`\n \x1B[33m⚠\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidation failed:\n${formatted}`);
108
+ return;
109
+ }
110
+ lastValidated = result.data;
111
+ const clientMod = server.moduleGraph.getModuleById("\0virtual:env/client");
112
+ const serverMod = server.moduleGraph.getModuleById("\0virtual:env/server");
113
+ if (clientMod) server.moduleGraph.invalidateModule(clientMod);
114
+ if (serverMod) server.moduleGraph.invalidateModule(serverMod);
115
+ if (clientMod || serverMod) {
116
+ server.hot.send({ type: "full-reload" });
117
+ resolvedConfig.logger.info(` \x1B[32m✓\x1B[0m \x1B[36m[vite-env]\x1B[0m Env revalidated`);
118
+ }
119
+ } catch (e) {
120
+ resolvedConfig.logger.error(`\n \x1B[31m✗\x1B[0m \x1B[36m[vite-env]\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`);
119
121
  }
120
122
  }, 150);
121
123
  });
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { EnvDefinition } from './types'\n\nexport function buildClientModule(\n def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set([\n ...Object.keys(def.client ?? {}),\n ...Object.keys(def.shared ?? {}),\n ])\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { EnvDefinition } from './types'\nimport path from 'node:path'\nimport { loadEnvConfig } from './config'\nimport { generateDts } from './dts'\nimport { formatZodError } from './format'\nimport { detectServerLeak } from './leak'\nimport { validateEnv } from './schema'\nimport { loadEnvSources } from './sources'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: EnvDefinition\n let lastValidated: Record<string, unknown> = {}\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${formatted}`,\n )\n }\n\n lastValidated = result.data!\n\n await generateDts(envDefinition, resolvedConfig.root)\n\n const count = Object.keys(result.data ?? {}).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(id) {\n if (id === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (id === 'virtual:env/server')\n return '\\0virtual:env/server'\n },\n\n load(id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server')\n return buildServerModule(envDefinition, lastValidated)\n },\n\n async generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Env validation failed at bundle emit:\\n\\n${formatted}`,\n )\n }\n\n const leaks = detectServerLeak(\n envDefinition,\n result.data || {},\n bundle as Record<string, { type: string, code?: string }>,\n )\n\n if (leaks.length > 0) {\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n }\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${formatted}`,\n )\n return\n }\n\n lastValidated = result.data!\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n if (clientMod) {\n server.moduleGraph.invalidateModule(clientMod)\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }, 150) // 150ms debounce\n })\n },\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"plugin.mjs","names":[],"sources":["../src/sources.ts","../src/virtual.ts","../src/plugin.ts"],"sourcesContent":["// @env node\nimport type { ResolvedConfig } from 'vite'\nimport process from 'node:process'\nimport { loadEnv } from 'vite'\n\n/**\n * Merge priority (highest → lowest):\n * 1. process.env (CI pipeline secrets win)\n * 2. .env.[mode].local\n * 3. .env.[mode]\n * 4. .env.local\n * 5. .env\n *\n * Prefix '' = load everything, schema decides what's valid.\n */\nexport async function loadEnvSources(\n config: ResolvedConfig,\n): Promise<Record<string, string>> {\n const fileEnv = loadEnv(\n config.mode,\n config.envDir || config.root,\n '', // no prefix filter — schema is the filter\n )\n\n return {\n ...fileEnv,\n ...filterStrings(process.env),\n }\n}\n\nfunction filterStrings(env: NodeJS.ProcessEnv): Record<string, string> {\n return Object.fromEntries(\n Object.entries(env).filter(\n (entry): entry is [string, string] => typeof entry[1] === 'string',\n ),\n )\n}\n","import type { EnvDefinition } from './types'\n\nexport function buildClientModule(\n def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n const clientKeys = new Set(Object.keys(def.client ?? {}))\n\n const clientData = Object.fromEntries(\n Object.entries(data).filter(([k]) => clientKeys.has(k)),\n )\n\n return {\n moduleType: 'js', // Required: Vite 8 / Rolldown explicit moduleType\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(clientData, null, 2)});\nexport default env;`,\n }\n}\n\nexport function buildServerModule(\n _def: EnvDefinition,\n data: Record<string, unknown>,\n): { code: string, moduleType: 'js' } {\n return {\n moduleType: 'js',\n code: `// Auto-generated by @vite-env/core — do not edit\nexport const env = Object.freeze(${JSON.stringify(data, null, 2)});\nexport default env;`,\n }\n}\n","// @env node\nimport type { Plugin, ResolvedConfig } from 'vite'\nimport type { EnvDefinition } from './types'\nimport path from 'node:path'\nimport { loadEnvConfig } from './config'\nimport { generateDts } from './dts'\nimport { formatZodError } from './format'\nimport { detectServerLeak } from './leak'\nimport { validateEnv } from './schema'\nimport { loadEnvSources } from './sources'\nimport { buildClientModule, buildServerModule } from './virtual'\n\nexport interface ViteEnvOptions {\n /**\n * Path to env definition file.\n * @default './env.ts' (resolved from project root)\n */\n configFile?: string\n}\n\nexport default function ViteEnv(options: ViteEnvOptions = {}): Plugin {\n let resolvedConfig: ResolvedConfig\n let envDefinition: EnvDefinition\n let lastValidated: Record<string, unknown> = {}\n\n return {\n name: 'vite-env',\n enforce: 'pre',\n\n async configResolved(config) {\n resolvedConfig = config\n\n const configPath = path.resolve(\n config.root,\n options.configFile ?? 'env.ts',\n )\n\n try {\n envDefinition = await loadEnvConfig(configPath)\n }\n catch (e) {\n throw new Error(\n `[vite-env] Could not load env definition file at: ${configPath}\\n`\n + ` Create an env.ts file and export default defineEnv({ ... })`,\n { cause: e },\n )\n }\n },\n\n async buildStart() {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n throw new Error(\n `[vite-env] Environment validation failed:\\n\\n${formatted}`,\n )\n }\n\n lastValidated = result.data\n\n await generateDts(envDefinition, resolvedConfig.root)\n\n const count = Object.keys(result.data).length\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m ${count} variables validated`,\n )\n },\n\n resolveId(id) {\n if (id === 'virtual:env/client')\n return '\\0virtual:env/client'\n if (id === 'virtual:env/server')\n return '\\0virtual:env/server'\n },\n\n load(id) {\n if (id === '\\0virtual:env/client')\n return buildClientModule(envDefinition, lastValidated)\n if (id === '\\0virtual:env/server')\n return buildServerModule(envDefinition, lastValidated)\n },\n\n generateBundle(_options, bundle) {\n if (resolvedConfig.build.ssr)\n return\n\n const leaks = detectServerLeak(\n envDefinition,\n lastValidated,\n bundle as Record<string, { type: string, code?: string }>,\n (keys) => {\n resolvedConfig.logger.warn(\n ` \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Leak detection skipped ${keys.length} server variable(s) with values shorter than 8 chars: ${keys.join(', ')}`,\n )\n },\n )\n\n if (leaks.length > 0) {\n throw new Error(\n `[vite-env] Server environment variables detected in client bundle!\\n\\n${leaks.map(l => ` ✗ ${l.key} found in ${l.chunk}`).join('\\n')\n }\\n\\n These variables are marked as server-only and must never reach the browser.`,\n )\n }\n },\n\n configureServer(server) {\n const envDir = resolvedConfig.envDir || resolvedConfig.root\n server.watcher.add(path.join(envDir, '.env*'))\n\n let debounceTimer: ReturnType<typeof setTimeout>\n\n server.watcher.on('change', async (file) => {\n if (!path.basename(file).startsWith('.env'))\n return\n\n clearTimeout(debounceTimer)\n debounceTimer = setTimeout(async () => {\n try {\n const rawEnv = await loadEnvSources(resolvedConfig)\n const result = validateEnv(envDefinition, rawEnv)\n\n if (!result.success) {\n const formatted = formatZodError(result.errors)\n resolvedConfig.logger.warn(\n `\\n \\x1B[33m⚠\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidation failed:\\n${formatted}`,\n )\n return\n }\n\n lastValidated = result.data\n\n const clientMod = server.moduleGraph.getModuleById('\\0virtual:env/client')\n const serverMod = server.moduleGraph.getModuleById('\\0virtual:env/server')\n if (clientMod)\n server.moduleGraph.invalidateModule(clientMod)\n if (serverMod)\n server.moduleGraph.invalidateModule(serverMod)\n if (clientMod || serverMod) {\n server.hot.send({ type: 'full-reload' })\n resolvedConfig.logger.info(\n ` \\x1B[32m✓\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Env revalidated`,\n )\n }\n }\n catch (e) {\n resolvedConfig.logger.error(\n `\\n \\x1B[31m✗\\x1B[0m \\x1B[36m[vite-env]\\x1B[0m Failed to reload env files: ${e instanceof Error ? e.message : String(e)}`,\n )\n }\n }, 150) // 150ms debounce\n })\n },\n }\n}\n"],"mappings":"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"}
package/dist/schema.cjs CHANGED
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- require("./dts-DT7HjKrz.cjs");
2
+ require("./dts-DF71HNdJ.cjs");
3
3
  let zod = require("zod");
4
4
  //#region src/schema.ts
5
5
  function defineEnv(definition) {
@@ -11,8 +11,7 @@ function defineEnv(definition) {
11
11
  function validateEnv(def, rawEnv) {
12
12
  const combinedShape = {
13
13
  ...def.server,
14
- ...def.client,
15
- ...def.shared
14
+ ...def.client
16
15
  };
17
16
  const result = zod.z.object(combinedShape).safeParse(rawEnv);
18
17
  if (result.success) return {
@@ -1 +1 @@
1
- {"version":3,"file":"schema.cjs","names":["z"],"sources":["../src/schema.ts"],"sourcesContent":["import type { EnvDefinition, ValidationResult } from './types'\nimport { z } from 'zod'\n\nexport function defineEnv(definition: EnvDefinition): EnvDefinition {\n if (definition.client) {\n for (const key of Object.keys(definition.client)) {\n if (!key.startsWith('VITE_')) {\n throw new Error(\n `[vite-env] Client env var \"${key}\" must be prefixed with VITE_.\\n`\n + ` Rename it to \"VITE_${key}\" or move it to \"server\" if it's secret.`,\n )\n }\n }\n }\n return definition\n}\n\nexport function validateEnv(\n def: EnvDefinition,\n rawEnv: Record<string, string>,\n): ValidationResult {\n const combinedShape = {\n ...def.server,\n ...def.client,\n ...def.shared,\n }\n\n const schema = z.object(combinedShape)\n const result = schema.safeParse(rawEnv)\n\n if (result.success) {\n return { success: true, data: result.data, errors: [] }\n }\n\n return {\n success: false,\n data: null,\n errors: result.error.issues,\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,UAAU,YAA0C;AAClE,KAAI,WAAW;OACR,MAAM,OAAO,OAAO,KAAK,WAAW,OAAO,CAC9C,KAAI,CAAC,IAAI,WAAW,QAAQ,CAC1B,OAAM,IAAI,MACR,8BAA8B,IAAI,uDACR,IAAI,0CAC/B;;AAIP,QAAO;;AAGT,SAAgB,YACd,KACA,QACkB;CAClB,MAAM,gBAAgB;EACpB,GAAG,IAAI;EACP,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAGD,MAAM,SADSA,IAAAA,EAAE,OAAO,cAAc,CAChB,UAAU,OAAO;AAEvC,KAAI,OAAO,QACT,QAAO;EAAE,SAAS;EAAM,MAAM,OAAO;EAAM,QAAQ,EAAE;EAAE;AAGzD,QAAO;EACL,SAAS;EACT,MAAM;EACN,QAAQ,OAAO,MAAM;EACtB"}
1
+ {"version":3,"file":"schema.cjs","names":["z"],"sources":["../src/schema.ts"],"sourcesContent":["import type { EnvDefinition, ValidationResult } from './types'\nimport { z } from 'zod'\n\nexport function defineEnv<T extends EnvDefinition>(definition: T): T {\n if (definition.client) {\n for (const key of Object.keys(definition.client)) {\n if (!key.startsWith('VITE_')) {\n throw new Error(\n `[vite-env] Client env var \"${key}\" must be prefixed with VITE_.\\n`\n + ` Rename it to \"VITE_${key}\" or move it to \"server\" if it's secret.`,\n )\n }\n }\n }\n\n return definition\n}\n\nexport function validateEnv(\n def: EnvDefinition,\n rawEnv: Record<string, string>,\n): ValidationResult {\n const combinedShape = {\n ...def.server,\n ...def.client,\n }\n\n const schema = z.object(combinedShape)\n const result = schema.safeParse(rawEnv)\n\n if (result.success) {\n return { success: true, data: result.data, errors: [] as const }\n }\n\n return {\n success: false,\n data: null,\n errors: result.error.issues,\n }\n}\n"],"mappings":";;;;AAGA,SAAgB,UAAmC,YAAkB;AACnE,KAAI,WAAW;OACR,MAAM,OAAO,OAAO,KAAK,WAAW,OAAO,CAC9C,KAAI,CAAC,IAAI,WAAW,QAAQ,CAC1B,OAAM,IAAI,MACR,8BAA8B,IAAI,uDACR,IAAI,0CAC/B;;AAKP,QAAO;;AAGT,SAAgB,YACd,KACA,QACkB;CAClB,MAAM,gBAAgB;EACpB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAGD,MAAM,SADSA,IAAAA,EAAE,OAAO,cAAc,CAChB,UAAU,OAAO;AAEvC,KAAI,OAAO,QACT,QAAO;EAAE,SAAS;EAAM,MAAM,OAAO;EAAM,QAAQ,EAAE;EAAW;AAGlE,QAAO;EACL,SAAS;EACT,MAAM;EACN,QAAQ,OAAO,MAAM;EACtB"}
package/dist/schema.d.cts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as ValidationResult, t as EnvDefinition } from "./types-BmEBJZdy.cjs";
1
+ import { i as ValidationResult, t as EnvDefinition } from "./types-DqTMuWwc.cjs";
2
2
 
3
3
  //#region src/schema.d.ts
4
- declare function defineEnv(definition: EnvDefinition): EnvDefinition;
4
+ declare function defineEnv<T extends EnvDefinition>(definition: T): T;
5
5
  declare function validateEnv(def: EnvDefinition, rawEnv: Record<string, string>): ValidationResult;
6
6
  //#endregion
7
7
  export { defineEnv, validateEnv };
package/dist/schema.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as ValidationResult, t as EnvDefinition } from "./types-CLfG9amO.mjs";
1
+ import { i as ValidationResult, t as EnvDefinition } from "./types-CluiDKAQ.mjs";
2
2
 
3
3
  //#region src/schema.d.ts
4
- declare function defineEnv(definition: EnvDefinition): EnvDefinition;
4
+ declare function defineEnv<T extends EnvDefinition>(definition: T): T;
5
5
  declare function validateEnv(def: EnvDefinition, rawEnv: Record<string, string>): ValidationResult;
6
6
  //#endregion
7
7
  export { defineEnv, validateEnv };
package/dist/schema.mjs CHANGED
@@ -9,8 +9,7 @@ function defineEnv(definition) {
9
9
  function validateEnv(def, rawEnv) {
10
10
  const combinedShape = {
11
11
  ...def.server,
12
- ...def.client,
13
- ...def.shared
12
+ ...def.client
14
13
  };
15
14
  const result = z.object(combinedShape).safeParse(rawEnv);
16
15
  if (result.success) return {
@@ -1 +1 @@
1
- {"version":3,"file":"schema.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["import type { EnvDefinition, ValidationResult } from './types'\nimport { z } from 'zod'\n\nexport function defineEnv(definition: EnvDefinition): EnvDefinition {\n if (definition.client) {\n for (const key of Object.keys(definition.client)) {\n if (!key.startsWith('VITE_')) {\n throw new Error(\n `[vite-env] Client env var \"${key}\" must be prefixed with VITE_.\\n`\n + ` Rename it to \"VITE_${key}\" or move it to \"server\" if it's secret.`,\n )\n }\n }\n }\n return definition\n}\n\nexport function validateEnv(\n def: EnvDefinition,\n rawEnv: Record<string, string>,\n): ValidationResult {\n const combinedShape = {\n ...def.server,\n ...def.client,\n ...def.shared,\n }\n\n const schema = z.object(combinedShape)\n const result = schema.safeParse(rawEnv)\n\n if (result.success) {\n return { success: true, data: result.data, errors: [] }\n }\n\n return {\n success: false,\n data: null,\n errors: result.error.issues,\n }\n}\n"],"mappings":";;AAGA,SAAgB,UAAU,YAA0C;AAClE,KAAI,WAAW;OACR,MAAM,OAAO,OAAO,KAAK,WAAW,OAAO,CAC9C,KAAI,CAAC,IAAI,WAAW,QAAQ,CAC1B,OAAM,IAAI,MACR,8BAA8B,IAAI,uDACR,IAAI,0CAC/B;;AAIP,QAAO;;AAGT,SAAgB,YACd,KACA,QACkB;CAClB,MAAM,gBAAgB;EACpB,GAAG,IAAI;EACP,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAGD,MAAM,SADS,EAAE,OAAO,cAAc,CAChB,UAAU,OAAO;AAEvC,KAAI,OAAO,QACT,QAAO;EAAE,SAAS;EAAM,MAAM,OAAO;EAAM,QAAQ,EAAE;EAAE;AAGzD,QAAO;EACL,SAAS;EACT,MAAM;EACN,QAAQ,OAAO,MAAM;EACtB"}
1
+ {"version":3,"file":"schema.mjs","names":[],"sources":["../src/schema.ts"],"sourcesContent":["import type { EnvDefinition, ValidationResult } from './types'\nimport { z } from 'zod'\n\nexport function defineEnv<T extends EnvDefinition>(definition: T): T {\n if (definition.client) {\n for (const key of Object.keys(definition.client)) {\n if (!key.startsWith('VITE_')) {\n throw new Error(\n `[vite-env] Client env var \"${key}\" must be prefixed with VITE_.\\n`\n + ` Rename it to \"VITE_${key}\" or move it to \"server\" if it's secret.`,\n )\n }\n }\n }\n\n return definition\n}\n\nexport function validateEnv(\n def: EnvDefinition,\n rawEnv: Record<string, string>,\n): ValidationResult {\n const combinedShape = {\n ...def.server,\n ...def.client,\n }\n\n const schema = z.object(combinedShape)\n const result = schema.safeParse(rawEnv)\n\n if (result.success) {\n return { success: true, data: result.data, errors: [] as const }\n }\n\n return {\n success: false,\n data: null,\n errors: result.error.issues,\n }\n}\n"],"mappings":";;AAGA,SAAgB,UAAmC,YAAkB;AACnE,KAAI,WAAW;OACR,MAAM,OAAO,OAAO,KAAK,WAAW,OAAO,CAC9C,KAAI,CAAC,IAAI,WAAW,QAAQ,CAC1B,OAAM,IAAI,MACR,8BAA8B,IAAI,uDACR,IAAI,0CAC/B;;AAKP,QAAO;;AAGT,SAAgB,YACd,KACA,QACkB;CAClB,MAAM,gBAAgB;EACpB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAGD,MAAM,SADS,EAAE,OAAO,cAAc,CAChB,UAAU,OAAO;AAEvC,KAAI,OAAO,QACT,QAAO;EAAE,SAAS;EAAM,MAAM,OAAO;EAAM,QAAQ,EAAE;EAAW;AAGlE,QAAO;EACL,SAAS;EACT,MAAM;EACN,QAAQ,OAAO,MAAM;EACtB"}
@@ -4,15 +4,19 @@ import { z } from "zod";
4
4
  interface EnvDefinition {
5
5
  server?: z.ZodRawShape;
6
6
  client?: z.ZodRawShape;
7
- shared?: z.ZodRawShape;
8
7
  }
9
- interface ValidationResult {
10
- success: boolean;
11
- data: Record<string, unknown> | null;
8
+ type ValidationResult = {
9
+ success: true;
10
+ data: Record<string, unknown>;
11
+ errors: [];
12
+ } | {
13
+ success: false;
14
+ data: null;
12
15
  errors: z.core.$ZodIssue[];
13
- }
14
- type InferClientEnv<T extends EnvDefinition> = z.infer<z.ZodObject<NonNullable<T['client']> & NonNullable<T['shared']>>>;
15
- type InferServerEnv<T extends EnvDefinition> = z.infer<z.ZodObject<NonNullable<T['server']> & NonNullable<T['client']> & NonNullable<T['shared']>>>;
16
+ };
17
+ type OrEmptyShape<T> = T extends z.ZodRawShape ? T : Record<string, never>;
18
+ type InferClientEnv<T extends EnvDefinition> = z.infer<z.ZodObject<OrEmptyShape<T['client']>>>;
19
+ type InferServerEnv<T extends EnvDefinition> = z.infer<z.ZodObject<OrEmptyShape<T['server']> & OrEmptyShape<T['client']>>>;
16
20
  //#endregion
17
21
  export { ValidationResult as i, InferClientEnv as n, InferServerEnv as r, EnvDefinition as t };
18
- //# sourceMappingURL=types-BmEBJZdy.d.cts.map
22
+ //# sourceMappingURL=types-CluiDKAQ.d.mts.map
@@ -4,15 +4,19 @@ import { z } from "zod";
4
4
  interface EnvDefinition {
5
5
  server?: z.ZodRawShape;
6
6
  client?: z.ZodRawShape;
7
- shared?: z.ZodRawShape;
8
7
  }
9
- interface ValidationResult {
10
- success: boolean;
11
- data: Record<string, unknown> | null;
8
+ type ValidationResult = {
9
+ success: true;
10
+ data: Record<string, unknown>;
11
+ errors: [];
12
+ } | {
13
+ success: false;
14
+ data: null;
12
15
  errors: z.core.$ZodIssue[];
13
- }
14
- type InferClientEnv<T extends EnvDefinition> = z.infer<z.ZodObject<NonNullable<T['client']> & NonNullable<T['shared']>>>;
15
- type InferServerEnv<T extends EnvDefinition> = z.infer<z.ZodObject<NonNullable<T['server']> & NonNullable<T['client']> & NonNullable<T['shared']>>>;
16
+ };
17
+ type OrEmptyShape<T> = T extends z.ZodRawShape ? T : Record<string, never>;
18
+ type InferClientEnv<T extends EnvDefinition> = z.infer<z.ZodObject<OrEmptyShape<T['client']>>>;
19
+ type InferServerEnv<T extends EnvDefinition> = z.infer<z.ZodObject<OrEmptyShape<T['server']> & OrEmptyShape<T['client']>>>;
16
20
  //#endregion
17
21
  export { ValidationResult as i, InferClientEnv as n, InferServerEnv as r, EnvDefinition as t };
18
- //# sourceMappingURL=types-CLfG9amO.d.mts.map
22
+ //# sourceMappingURL=types-DqTMuWwc.d.cts.map
package/package.json CHANGED
@@ -1,9 +1,26 @@
1
1
  {
2
2
  "name": "@vite-env/core",
3
3
  "type": "module",
4
- "version": "0.1.0",
4
+ "version": "0.2.0",
5
5
  "description": "The env.ts layer for Vite — define once, validate everywhere, import with types",
6
6
  "license": "MIT",
7
+ "homepage": "https://github.com/pyyupsk/vite-env#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/pyyupsk/vite-env.git",
11
+ "directory": "packages/core"
12
+ },
13
+ "keywords": [
14
+ "vite",
15
+ "vite-plugin",
16
+ "environment-variables",
17
+ "env",
18
+ "typescript",
19
+ "zod",
20
+ "validation",
21
+ "rolldown",
22
+ "dotenv"
23
+ ],
7
24
  "exports": {
8
25
  ".": {
9
26
  "import": {
@@ -81,6 +98,7 @@
81
98
  "module": "./dist/index.mjs",
82
99
  "types": "./dist/index.d.cts",
83
100
  "files": [
101
+ "README.md",
84
102
  "dist"
85
103
  ],
86
104
  "engines": {
@@ -1 +0,0 @@
1
- {"version":3,"file":"dts-DT7HjKrz.cjs","names":["fs","path","z"],"sources":["../src/dts.ts"],"sourcesContent":["// @env node\nimport type { EnvDefinition } from './types'\nimport fs from 'node:fs/promises'\nimport path from 'node:path'\nimport { z } from 'zod'\n\n/**\n * Writes vite-env.d.ts to project root.\n * Declares virtual:env/client and virtual:env/server module types.\n * Users never need to manually augment ImportMetaEnv again.\n */\nexport async function generateDts(\n def: EnvDefinition,\n root: string,\n): Promise<void> {\n const clientKeys = {\n ...def.client,\n ...def.shared,\n }\n const serverKeys = {\n ...def.server,\n ...def.client,\n ...def.shared,\n }\n\n const clientFields = zodShapeToTsFields(clientKeys)\n const serverFields = zodShapeToTsFields(serverKeys)\n\n const dts = `// Auto-generated by @vite-env/core\n// Do not edit manually — re-generated on every dev server start and build\n\ndeclare module 'virtual:env/client' {\n const env: {\n${clientFields}\n }\n export { env }\n export default env\n}\n\ndeclare module 'virtual:env/server' {\n const env: {\n${serverFields}\n }\n export { env }\n export default env\n}\n`\n\n await fs.writeFile(path.join(root, 'vite-env.d.ts'), dts, 'utf-8')\n}\n\nfunction zodShapeToTsFields(shape: z.ZodRawShape): string {\n return Object.entries(shape)\n .map(([key, schema]) => {\n // Zod v4: ZodRawShape values are $ZodType, cast to ZodTypeAny for instanceof checks\n const s = schema as unknown as z.ZodTypeAny\n const tsType = zodToTs(s)\n const optional = isOptional(s)\n return ` readonly ${key}${optional ? '?' : ''}: ${tsType}`\n })\n .join('\\n')\n}\n\nfunction zodToTs(schema: z.ZodTypeAny): string {\n if (schema instanceof z.ZodOptional)\n return zodToTs(schema.unwrap() as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodDefault)\n return zodToTs(schema.def.innerType as unknown as z.ZodTypeAny)\n if (schema instanceof z.ZodString)\n return 'string'\n if (schema instanceof z.ZodNumber)\n return 'number'\n if (schema instanceof z.ZodBoolean)\n return 'boolean'\n if (schema instanceof z.ZodEnum)\n return (schema.options as string[]).map(o => `'${o}'`).join(' | ')\n if (schema instanceof z.ZodPipe)\n return zodToTs(schema.def.out as unknown as z.ZodTypeAny)\n return 'string'\n}\n\nfunction isOptional(schema: z.ZodTypeAny): boolean {\n return schema instanceof z.ZodOptional || schema instanceof z.ZodDefault\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAWA,eAAsB,YACpB,KACA,MACe;CACf,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CACD,MAAM,aAAa;EACjB,GAAG,IAAI;EACP,GAAG,IAAI;EACP,GAAG,IAAI;EACR;CAKD,MAAM,MAAM;;;;;EAHS,mBAAmB,WAAW,CAQtC;;;;;;;;EAPQ,mBAAmB,WAAW,CAetC;;;;;;AAOb,OAAMA,iBAAAA,QAAG,UAAUC,UAAAA,QAAK,KAAK,MAAM,gBAAgB,EAAE,KAAK,QAAQ;;AAGpE,SAAS,mBAAmB,OAA8B;AACxD,QAAO,OAAO,QAAQ,MAAM,CACzB,KAAK,CAAC,KAAK,YAAY;EAEtB,MAAM,IAAI;EACV,MAAM,SAAS,QAAQ,EAAE;AAEzB,SAAO,gBAAgB,MADN,WAAW,EAAE,GACU,MAAM,GAAG,IAAI;GACrD,CACD,KAAK,KAAK;;AAGf,SAAS,QAAQ,QAA8B;AAC7C,KAAI,kBAAkBC,IAAAA,EAAE,YACtB,QAAO,QAAQ,OAAO,QAAQ,CAA4B;AAC5D,KAAI,kBAAkBA,IAAAA,EAAE,WACtB,QAAO,QAAQ,OAAO,IAAI,UAAqC;AACjE,KAAI,kBAAkBA,IAAAA,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,UACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,WACtB,QAAO;AACT,KAAI,kBAAkBA,IAAAA,EAAE,QACtB,QAAQ,OAAO,QAAqB,KAAI,MAAK,IAAI,EAAE,GAAG,CAAC,KAAK,MAAM;AACpE,KAAI,kBAAkBA,IAAAA,EAAE,QACtB,QAAO,QAAQ,OAAO,IAAI,IAA+B;AAC3D,QAAO;;AAGT,SAAS,WAAW,QAA+B;AACjD,QAAO,kBAAkBA,IAAAA,EAAE,eAAe,kBAAkBA,IAAAA,EAAE"}