From 5bbc504a8e5f659f2b54e124aed6f924643a161f Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 01:59:00 +0500 Subject: [PATCH 1/9] feat: enhance Vite configuration with tsconfig paths and update dependencies - Updated the Vite configuration to include the vite-tsconfig-paths plugin for improved path resolution. - Added tsconfig paths in the tsconfig.json file to facilitate module resolution. - Updated package.json to include vite-tsconfig-paths as a dependency. - Modified arkenv.code-workspace to support wildcard tsconfig file associations. - Updated pnpm-lock.yaml to reflect the addition of vite-tsconfig-paths and its dependencies. --- apps/playgrounds/vite/package.json | 3 ++- apps/playgrounds/vite/tsconfig.json | 14 ++++++++++-- apps/playgrounds/vite/vite.config.ts | 8 +++---- arkenv.code-workspace | 2 +- packages/vite-plugin/src/index.ts | 33 ++++++++++++++++++++++++++++ pnpm-lock.yaml | 14 ++++++++++++ 6 files changed, 65 insertions(+), 9 deletions(-) diff --git a/apps/playgrounds/vite/package.json b/apps/playgrounds/vite/package.json index 00c348af8..0c20a3526 100644 --- a/apps/playgrounds/vite/package.json +++ b/apps/playgrounds/vite/package.json @@ -25,6 +25,7 @@ "@vitejs/plugin-react": "5.1.1", "globals": "16.5.0", "typescript": "5.9.3", - "vite": "npm:rolldown-vite@latest" + "vite": "npm:rolldown-vite@latest", + "vite-tsconfig-paths": "5.1.4" } } diff --git a/apps/playgrounds/vite/tsconfig.json b/apps/playgrounds/vite/tsconfig.json index fb1241881..031f621ea 100644 --- a/apps/playgrounds/vite/tsconfig.json +++ b/apps/playgrounds/vite/tsconfig.json @@ -1,7 +1,17 @@ { "files": [], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, "references": [ - { "path": "./tsconfig.app.json" }, - { "path": "./tsconfig.node.json" } + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } ] } diff --git a/apps/playgrounds/vite/vite.config.ts b/apps/playgrounds/vite/vite.config.ts index 190fe50a7..7b8869d47 100644 --- a/apps/playgrounds/vite/vite.config.ts +++ b/apps/playgrounds/vite/vite.config.ts @@ -2,12 +2,12 @@ import arkenvVitePlugin from "@arkenv/vite-plugin"; import reactPlugin from "@vitejs/plugin-react"; import arkenv, { type } from "arkenv"; import { defineConfig, loadEnv } from "vite"; +import tsconfigPaths from "vite-tsconfig-paths"; // Define the schema once, outside of defineConfig using type() // This schema is used for both: // 1. Validating unprefixed config variables (PORT) via loadEnv // 2. Validating VITE_* variables via the plugin -// The plugin automatically filters to only expose variables matching the Vite prefix (defaults to VITE_) const Env = type({ PORT: "number.port", VITE_MY_VAR: "string", @@ -17,14 +17,12 @@ const Env = type({ // https://vite.dev/config/ export default defineConfig(({ mode }) => { - // Validate unprefixed config variables (e.g., PORT) using loadEnv - // These are server-only and not exposed to client code - // The schema defined with type() can be passed directly to arkenv() const env = arkenv(Env, loadEnv(mode, process.cwd(), "")); - console.log(env.VITE_MY_NUMBER + " " + typeof env.VITE_MY_NUMBER); + console.log(`${env.VITE_MY_NUMBER} ${typeof env.VITE_MY_NUMBER}`); return { plugins: [ + tsconfigPaths(), reactPlugin(), // The plugin validates VITE_* variables and automatically filters to only expose // variables matching the Vite prefix (defaults to VITE_). Server-only variables diff --git a/arkenv.code-workspace b/arkenv.code-workspace index 0c1fdc1ed..2fb1b4536 100644 --- a/arkenv.code-workspace +++ b/arkenv.code-workspace @@ -2,7 +2,7 @@ "settings": { "files.associations": { "turbo.json": "jsonc", - "tsconfig.json": "jsonc", + "tsconfig*.json": "jsonc", ".github/renovate.json": "jsonc", "renovate.json": "jsonc" } diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts index 15ba46826..ed1181be2 100644 --- a/packages/vite-plugin/src/index.ts +++ b/packages/vite-plugin/src/index.ts @@ -8,6 +8,39 @@ import { loadEnv, type Plugin } from "vite"; * and be as close as possible to the type accepted by ArkType's `type`. */ +/** + * Vite plugin to validate environment variables using ArkEnv and expose them to client code. + * + * The plugin validates environment variables using ArkEnv's schema validation and + * automatically filters them based on Vite's `envPrefix` configuration (defaults to `"VITE_"`). + * Only environment variables matching the prefix are exposed to client code via `import.meta.env.*`. + * + * @param options - The environment variable schema definition. Can be an `EnvSchema` object + * for type-safe validation or an ArkType `type.Any` for dynamic schemas. + * @returns A Vite plugin that validates environment variables and exposes them to the client. + * + * @example + * ```ts + * // vite.config.ts + * import { defineConfig } from 'vite'; + * import arkenv from '@arkenv/vite-plugin'; + * + * export default defineConfig({ + * plugins: [ + * arkenv({ + * VITE_API_URL: 'string', + * VITE_API_KEY: 'string', + * }), + * ], + * }); + * ``` + * + * @example + * ```ts + * // In your client code + * console.log(import.meta.env.VITE_API_URL); // Type-safe access + * ``` + */ export default function arkenv>( options: EnvSchema, ): Plugin; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dbcb1a05f..21fdbb8df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: vite: specifier: npm:rolldown-vite@latest version: rolldown-vite@7.2.7(@types/node@24.10.1)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6) + vite-tsconfig-paths: + specifier: 5.1.4 + version: 5.1.4(rolldown-vite@7.2.7(@types/node@24.10.1)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6))(typescript@5.9.3) apps/www: dependencies: @@ -12404,6 +12407,17 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 + vite-tsconfig-paths@5.1.4(rolldown-vite@7.2.7(@types/node@24.10.1)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6))(typescript@5.9.3): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@5.9.3) + optionalDependencies: + vite: rolldown-vite@7.2.7(@types/node@24.10.1)(esbuild@0.25.12)(jiti@2.6.1)(terser@5.44.1)(tsx@4.20.6) + transitivePeerDependencies: + - supports-color + - typescript + vite-tsconfig-paths@5.1.4(typescript@5.9.3)(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)): dependencies: debug: 4.4.3 From 023cc7eb3d0f8448cab1562fa08c95a83d95f78e Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:02:07 +0500 Subject: [PATCH 2/9] feat: enhance TypeScript type safety in Vite environment - Added TypeScript definitions for enhanced type safety in the Vite environment. - Introduced `ImportMetaEnvAugmented` and `ViteTypeOptions` interfaces to improve type checking for environment variables. - Updated the linter configuration to disable the `noUnusedVariables` rule for TypeScript declaration files. --- apps/playgrounds/vite/src/vite-env.d.ts | 15 +++++++++++++++ biome.jsonc | 10 ++++++++++ 2 files changed, 25 insertions(+) diff --git a/apps/playgrounds/vite/src/vite-env.d.ts b/apps/playgrounds/vite/src/vite-env.d.ts index 11f02fe2a..e2c40e714 100644 --- a/apps/playgrounds/vite/src/vite-env.d.ts +++ b/apps/playgrounds/vite/src/vite-env.d.ts @@ -1 +1,16 @@ /// + +type ImportMetaEnvAugmented = + import("@julr/vite-plugin-validate-env").ImportMetaEnvAugmented< + typeof import("../env").default + >; + +interface ViteTypeOptions { + // Avoid adding an index type to `ImportMetaDev` so + // there's an error when accessing unknown properties. + // ⚠️ This option requires Vite 6.3.x or higher + strictImportMetaEnv: unknown; +} + +// Now import.meta.env is totally typesafe and based on your `Env` schema definition +interface ImportMetaEnv extends ImportMetaEnvAugmented {} diff --git a/biome.jsonc b/biome.jsonc index f423f3f71..31a64018b 100644 --- a/biome.jsonc +++ b/biome.jsonc @@ -114,6 +114,16 @@ } } } + }, + { + "includes": ["**/*.d.ts"], + "linter": { + "rules": { + "correctness": { + "noUnusedVariables": "off" + } + } + } } ] } From 6d5e4ffdeae04330db452140d48acc0bdb1ae88b Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:12:20 +0500 Subject: [PATCH 3/9] feat: export Env schema for improved type safety in Vite configuration - Exported the `Env` schema from `vite.config.ts` to enhance type safety for environment variables. - Updated type definitions in `vite-env.d.ts` to utilize the exported `Env` schema, ensuring only VITE_* prefixed variables are included. - Added export for `ImportMetaEnvAugmented` in the Vite plugin to facilitate better type integration. --- apps/playgrounds/vite/src/vite-env.d.ts | 13 +++-- apps/playgrounds/vite/vite.config.ts | 2 +- packages/vite-plugin/src/index.ts | 2 + packages/vite-plugin/src/types.ts | 70 +++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 packages/vite-plugin/src/types.ts diff --git a/apps/playgrounds/vite/src/vite-env.d.ts b/apps/playgrounds/vite/src/vite-env.d.ts index e2c40e714..8d5a79b20 100644 --- a/apps/playgrounds/vite/src/vite-env.d.ts +++ b/apps/playgrounds/vite/src/vite-env.d.ts @@ -1,9 +1,11 @@ /// -type ImportMetaEnvAugmented = - import("@julr/vite-plugin-validate-env").ImportMetaEnvAugmented< - typeof import("../env").default - >; +import type { ImportMetaEnvAugmented } from "@arkenv/vite-plugin"; + +// Import the Env schema type from vite.config.ts +// Note: In a real project, you'd typically export Env from a separate env.ts file +// and import it like: import type { Env } from "./env"; +type Env = typeof import("../vite.config").Env; interface ViteTypeOptions { // Avoid adding an index type to `ImportMetaDev` so @@ -13,4 +15,5 @@ interface ViteTypeOptions { } // Now import.meta.env is totally typesafe and based on your `Env` schema definition -interface ImportMetaEnv extends ImportMetaEnvAugmented {} +// Only VITE_* prefixed variables will be included (PORT is excluded) +interface ImportMetaEnv extends ImportMetaEnvAugmented {} diff --git a/apps/playgrounds/vite/vite.config.ts b/apps/playgrounds/vite/vite.config.ts index 7b8869d47..4c96f6aa2 100644 --- a/apps/playgrounds/vite/vite.config.ts +++ b/apps/playgrounds/vite/vite.config.ts @@ -8,7 +8,7 @@ import tsconfigPaths from "vite-tsconfig-paths"; // This schema is used for both: // 1. Validating unprefixed config variables (PORT) via loadEnv // 2. Validating VITE_* variables via the plugin -const Env = type({ +export const Env = type({ PORT: "number.port", VITE_MY_VAR: "string", VITE_MY_NUMBER: type("string").pipe((str) => Number.parseInt(str, 10)), diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts index ed1181be2..bfdbd12e6 100644 --- a/packages/vite-plugin/src/index.ts +++ b/packages/vite-plugin/src/index.ts @@ -3,6 +3,8 @@ import { createEnv } from "arkenv"; import type { type } from "arktype"; import { loadEnv, type Plugin } from "vite"; +export type { ImportMetaEnvAugmented } from "./types"; + /** * TODO: If possible, find a better type than "const T extends Record", * and be as close as possible to the type accepted by ArkType's `type`. diff --git a/packages/vite-plugin/src/types.ts b/packages/vite-plugin/src/types.ts new file mode 100644 index 000000000..ca243bd02 --- /dev/null +++ b/packages/vite-plugin/src/types.ts @@ -0,0 +1,70 @@ +import type { type } from "arktype"; + +/** + * Extract the inferred type from an ArkType type definition. + * When a type definition is called, it returns either the validated value or type.errors. + */ +type InferType = T extends ( + value: Record, +) => infer R + ? R extends type.errors + ? never + : R + : T extends type.Any + ? U + : never; + +/** + * Filter environment variables to only include those that start with the given prefix. + * This ensures only client-exposed variables (e.g., VITE_*) are included in import.meta.env. + */ +type FilterByPrefix< + T extends Record, + Prefix extends string = "VITE_", +> = { + [K in keyof T as K extends `${Prefix}${string}` ? K : never]: T[K]; +}; + +/** + * Augment the `import.meta.env` object with type-safe environment variables + * based on the schema validator. + * + * This type extracts the inferred type from the schema (result of `type()` from arkenv), + * filters it to only include variables matching the Vite prefix (defaults to "VITE_"), + * and makes them available on `import.meta.env`. + * + * @template TSchema - The environment variable schema (result of `type()` from arkenv) + * @template Prefix - The prefix to filter by (defaults to "VITE_") + * + * @example + * ```ts + * // vite.config.ts + * import arkenv from '@arkenv/vite-plugin'; + * import { type } from 'arkenv'; + * + * export const Env = type({ + * VITE_API_URL: 'string', + * VITE_API_KEY: 'string', + * PORT: 'number.port', // Server-only, won't be in ImportMetaEnvAugmented + * }); + * + * export default defineConfig({ + * plugins: [arkenv(Env)], + * }); + * ``` + * + * @example + * ```ts + * // src/vite-env.d.ts + * /// + * + * import type { ImportMetaEnvAugmented } from '@arkenv/vite-plugin'; + * import type { Env } from './env'; // or from vite.config.ts + * + * interface ImportMetaEnv extends ImportMetaEnvAugmented {} + * ``` + */ +export type ImportMetaEnvAugmented< + TSchema extends type.Any, + Prefix extends string = "VITE_", +> = FilterByPrefix, Prefix>; From 4f52dc396d19cd12d64e3474f6194d06f35e71a4 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:14:02 +0500 Subject: [PATCH 4/9] docs: add reference link to original ImportMetaEnv implementation - Included a reference link in the `ImportMetaEnvAugmented` type documentation to the original implementation by Julien-R44, enhancing clarity and accessibility for users. --- packages/vite-plugin/src/types.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/vite-plugin/src/types.ts b/packages/vite-plugin/src/types.ts index ca243bd02..b0a81f332 100644 --- a/packages/vite-plugin/src/types.ts +++ b/packages/vite-plugin/src/types.ts @@ -63,6 +63,8 @@ type FilterByPrefix< * * interface ImportMetaEnv extends ImportMetaEnvAugmented {} * ``` + * + * @see {@link https://github.com/Julien-R44/vite-plugin-validate-env#typing-importmetaenv | Original implementation by Julien-R44} */ export type ImportMetaEnvAugmented< TSchema extends type.Any, From 0d0d625be00fcce910acea9cb8b4a051325df454 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:21:17 +0500 Subject: [PATCH 5/9] refactor: update ImportMetaEnv type definition for improved type safety - Refactored the `ImportMetaEnv` interface to utilize the `ImportMetaEnvAugmented` type without directly referencing the `Env` schema, enhancing type safety and clarity in the Vite environment type definitions. --- apps/playgrounds/vite/src/vite-env.d.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/apps/playgrounds/vite/src/vite-env.d.ts b/apps/playgrounds/vite/src/vite-env.d.ts index 8d5a79b20..8d970197d 100644 --- a/apps/playgrounds/vite/src/vite-env.d.ts +++ b/apps/playgrounds/vite/src/vite-env.d.ts @@ -1,11 +1,12 @@ /// -import type { ImportMetaEnvAugmented } from "@arkenv/vite-plugin"; - // Import the Env schema type from vite.config.ts // Note: In a real project, you'd typically export Env from a separate env.ts file // and import it like: import type { Env } from "./env"; -type Env = typeof import("../vite.config").Env; +type ImportMetaEnvAugmented = + import("@arkenv/vite-plugin").ImportMetaEnvAugmented< + typeof import("../vite.config").Env + >; interface ViteTypeOptions { // Avoid adding an index type to `ImportMetaDev` so @@ -16,4 +17,4 @@ interface ViteTypeOptions { // Now import.meta.env is totally typesafe and based on your `Env` schema definition // Only VITE_* prefixed variables will be included (PORT is excluded) -interface ImportMetaEnv extends ImportMetaEnvAugmented {} +interface ImportMetaEnv extends ImportMetaEnvAugmented {} From 0703709be5b1fe844b91d9ffdd1bf3cb6a424511 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:24:46 +0500 Subject: [PATCH 6/9] docs: refine comment on Env export suggestion in vite-env.d.ts - Updated the comment in vite-env.d.ts to clarify that exporting the Env schema from a separate env.ts file is a suggestion rather than a requirement, enhancing documentation clarity. --- apps/playgrounds/vite/src/vite-env.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/playgrounds/vite/src/vite-env.d.ts b/apps/playgrounds/vite/src/vite-env.d.ts index 8d970197d..1054b418e 100644 --- a/apps/playgrounds/vite/src/vite-env.d.ts +++ b/apps/playgrounds/vite/src/vite-env.d.ts @@ -1,7 +1,7 @@ /// // Import the Env schema type from vite.config.ts -// Note: In a real project, you'd typically export Env from a separate env.ts file +// Note: In a real project, you might want to export Env from a separate env.ts file // and import it like: import type { Env } from "./env"; type ImportMetaEnvAugmented = import("@arkenv/vite-plugin").ImportMetaEnvAugmented< From 081545d5f4caa640c4edbf25cc016de14348d944 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:53:40 +0500 Subject: [PATCH 7/9] docs: update Vite plugin documentation and add new page for type-safe import.meta.env - Revised the Vite plugin documentation to clarify the filtering of environment variables and added a note on using non-prefixed variables. - Introduced a new section on making import.meta.env type-safe, with a link to the relevant setup guide. - Updated meta.json to include the new documentation page for typing import.meta.env. --- .changeset/free-oranges-bow.md | 7 ++ apps/www/content/docs/vite-plugin/index.mdx | 10 +- apps/www/content/docs/vite-plugin/meta.json | 2 +- .../vite-plugin/typing-import-meta-env.mdx | 101 ++++++++++++++++++ 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 .changeset/free-oranges-bow.md create mode 100644 apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx diff --git a/.changeset/free-oranges-bow.md b/.changeset/free-oranges-bow.md new file mode 100644 index 000000000..4398124c3 --- /dev/null +++ b/.changeset/free-oranges-bow.md @@ -0,0 +1,7 @@ +--- +"@arkenv/vite-plugin": patch +--- + +#### Title + +Content. diff --git a/apps/www/content/docs/vite-plugin/index.mdx b/apps/www/content/docs/vite-plugin/index.mdx index afbc55da6..17af26474 100644 --- a/apps/www/content/docs/vite-plugin/index.mdx +++ b/apps/www/content/docs/vite-plugin/index.mdx @@ -4,9 +4,6 @@ title: What is the Vite plugin? This is the Vite plugin for ArkEnv. It validates environment variables at build-time with ArkEnv. -> [!IMPORTANT] -> The plugin automatically filters environment variables to only expose those matching Vite's configured prefix (defaults to `VITE_`). Server-only variables without the prefix are automatically excluded from the client bundle, preventing accidental exposure of sensitive configuration. - ```ts title="vite.config.ts" import arkenv from "@arkenv/vite-plugin"; import { defineConfig } from "vite"; @@ -21,3 +18,10 @@ export default defineConfig({ ], }); ``` + +> [!NOTE] +> The plugin automatically filters environment variables to only expose those matching Vite's configured prefix (defaults to `VITE_`). Server-only variables without the prefix are automatically excluded from the client bundle. If you _do_ need to use non-prefixed variables, see [Using ArkEnv in Vite config](/docs/vite-plugin/arkenv-in-viteconfig). + +## Type-safe import.meta.env + +We strongly recommend making `import.meta.env` fully typesafe by augmenting it with your schema. See [Typing import.meta.env](/docs/vite-plugin/typing-import-meta-env) for quick, one-time setup. diff --git a/apps/www/content/docs/vite-plugin/meta.json b/apps/www/content/docs/vite-plugin/meta.json index ba8afca52..2b0d95fbd 100644 --- a/apps/www/content/docs/vite-plugin/meta.json +++ b/apps/www/content/docs/vite-plugin/meta.json @@ -3,5 +3,5 @@ "description": "The Vite plugin", "root": true, "icon": "Vite", - "pages": ["index", "arkenv-in-viteconfig"] + "pages": ["index", "arkenv-in-viteconfig", "typing-import-meta-env"] } diff --git a/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx new file mode 100644 index 000000000..c4c281762 --- /dev/null +++ b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx @@ -0,0 +1,101 @@ +--- +title: Typing import.meta.env +--- + +For most use cases, you'll want to access your environment variables in your client code. [In a Vite project, this is done via `import.meta.env`](https://vite.dev/guide/env-and-mode). + +With ArkEnv, this can be typesafe - just by making a one-time addition to your `vite.config.ts` file. + +> [!NOTE] +> After you make this change, each time you add/remove environment variables from your schema, your `import.meta.env` will be always be typesafe - no codegen needed. + +## Setup + +Add this to a `vite-env.d.ts` file in your `src` directory: + +```ts title="src/vite-env.d.ts" +/// + +type ImportMetaEnvAugmented = + import("@arkenv/vite-plugin").ImportMetaEnvAugmented< + typeof import("../vite.config").Env + >; + +interface ViteTypeOptions { + // Avoid adding an index type to `ImportMetaDev` so + // there's an error when accessing unknown properties. + // ⚠️ This option requires Vite 6.3.x or higher + strictImportMetaEnv: unknown; +} + +// Augment import.meta.env with your schema +// Only `VITE_*` prefixed variables will be included +interface ImportMetaEnv extends ImportMetaEnvAugmented {} +``` + +## Usage + +Once set up, `import.meta.env` is fully type-safe: + +```ts title="src/App.tsx" +// TypeScript knows about your VITE_* variables +const apiUrl = import.meta.env.VITE_API_URL; // ✅ Type-safe +const port = import.meta.env.PORT; // ❌ Error: PORT is not in ImportMetaEnv + +// Autocomplete works too! +import.meta.env.VITE_ // Shows all your VITE_* variables +``` + +## How it works + +The `ImportMetaEnvAugmented` type: + +1. Extracts the inferred type from your schema (the result of `type()` from arkenv) +2. Filters to only include variables matching the Vite prefix (defaults to `"VITE_"`) +3. Makes them available on `import.meta.env` with full type safety + +Server-only variables (like `PORT`) are automatically excluded from the client bundle and won't appear in `import.meta.env`. + +## Example with separate env file + +As your project grows, you might want to define your schema in a dedicated file for better organization: + +```ts title="src/env.ts" +import { type } from "arkenv"; + +export const Env = type({ + PORT: "number.port", + VITE_API_URL: "string", + VITE_API_KEY: "string", + VITE_MY_NUMBER: type("string").pipe((str) => Number.parseInt(str, 10)), + VITE_MY_BOOLEAN: type("string").pipe((str) => str === "true"), +}); + +export type Env = typeof Env.infer; +``` + +```ts title="vite.config.ts" +import arkenvVitePlugin from "@arkenv/vite-plugin"; +import { defineConfig } from "vite"; +import { Env } from "./src/env"; + +export default defineConfig({ + plugins: [arkenvVitePlugin(Env)], +}); +``` + +```ts title="src/vite-env.d.ts" +/// + +import type { ImportMetaEnvAugmented } from "@arkenv/vite-plugin"; +import type { Env } from "./env"; + +interface ViteTypeOptions { + strictImportMetaEnv: unknown; +} + +interface ImportMetaEnv extends ImportMetaEnvAugmented {} +``` + +## Credit +This implementation is inspired by [vite-plugin-validate-env by Julien-R44](https://github.com/Julien-R44/vite-plugin-validate-env#typing-importmetaenv). From 1eda22f5d2050e8ddbbf0f26abf8a8f339e5b394 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 02:56:40 +0500 Subject: [PATCH 8/9] feat: add `ImportMetaEnvAugmented` type for typesafe `import.meta.env` - Introduced the `ImportMetaEnvAugmented` type helper to enhance type safety for `import.meta.env` by augmenting it with a defined environment variable schema. - Updated documentation to reflect the new type and its usage, ensuring full type safety and autocomplete for `VITE_*` environment variables in client code. - Minor adjustments made to existing documentation for consistency in terminology regarding typesafety. --- .changeset/free-oranges-bow.md | 6 ++++-- .../docs/vite-plugin/typing-import-meta-env.mdx | 2 +- .../specs/vite-config-usage/spec.md | 10 +++++----- .../archive/2025-11-20-add-arkenv-vite-config/tasks.md | 2 +- openspec/specs/vite-config-usage/spec.md | 10 +++++----- packages/vite-plugin/src/index.ts | 2 +- packages/vite-plugin/src/types.ts | 2 +- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/.changeset/free-oranges-bow.md b/.changeset/free-oranges-bow.md index 4398124c3..b4ce77244 100644 --- a/.changeset/free-oranges-bow.md +++ b/.changeset/free-oranges-bow.md @@ -2,6 +2,8 @@ "@arkenv/vite-plugin": patch --- -#### Title +#### Add `ImportMetaEnvAugmented` type helper for typesafe `import.meta.env`. -Content. +Add a new `ImportMetaEnvAugmented` type that augments `import.meta.env` with your environment variable schema. This provides full type safety and autocomplete for all your `VITE_*` environment variables in client code. + +Implementation inspired by [Julien-R44](https://github.com/Julien-R44)'s [vite-plugin-validate-env](https://github.com/Julien-R44/vite-plugin-validate-env#typing-importmetaenv). diff --git a/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx index c4c281762..d05b56629 100644 --- a/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx +++ b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx @@ -35,7 +35,7 @@ interface ImportMetaEnv extends ImportMetaEnvAugmented {} ## Usage -Once set up, `import.meta.env` is fully type-safe: +Once set up, `import.meta.env` is fully typesafe: ```ts title="src/App.tsx" // TypeScript knows about your VITE_* variables diff --git a/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/specs/vite-config-usage/spec.md b/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/specs/vite-config-usage/spec.md index e6979b008..3b2df17d3 100644 --- a/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/specs/vite-config-usage/spec.md +++ b/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/specs/vite-config-usage/spec.md @@ -10,7 +10,7 @@ Users SHALL be able to use ArkEnv directly in `vite.config.ts` files to validate - **AND** they define a schema for unprefixed environment variables needed by the config (e.g., `PORT`, `DATABASE_URL`) - **THEN** the environment variables are validated at build-time - **AND** invalid or missing variables cause the build to fail with clear error messages -- **AND** the validated variables are type-safe and can be used in the Vite config +- **AND** the validated variables are typesafe and can be used in the Vite config #### Scenario: Type-safe Vite config with environment variables - **WHEN** a user uses ArkEnv to validate environment variables in vite.config.ts @@ -48,17 +48,17 @@ The project SHALL support using ArkEnv with Vite's `loadEnv` function. Since `cr - **WHEN** a user defines a schema using `type()` outside of `defineConfig` - **AND** they call `loadEnv()` to load environment variables - **AND** they pass both the type definition and `loadEnv()` result to `createEnv()` or `arkenv()` -- **THEN** the environment variables are validated and returned as type-safe +- **THEN** the environment variables are validated and returned as typesafe - **AND** the same type definition can be passed to the Vite plugin for validating `VITE_*` variables - **AND** no separate wrapper utility is needed ### Requirement: Type Safety Constraint -The environment object returned from `loadEnv` or any wrapper SHALL be type-safe. Unsafe patterns that bypass validation or type checking are FORBIDDEN. +The environment object returned from `loadEnv` or any wrapper SHALL be typesafe. Unsafe patterns that bypass validation or type checking are FORBIDDEN. #### Constraint: Environment object must be wrapped or typed - **FORBIDDEN**: Directly using `loadEnv()` result without validation or proper typing -- **FORBIDDEN**: Using `as const` assertion on environment objects (not type-safe, bypasses validation) +- **FORBIDDEN**: Using `as const` assertion on environment objects (not typesafe, bypasses validation) - **FORBIDDEN**: Defining schemas as raw objects instead of using `type()` function - **REQUIRED**: The schema MUST be defined using `type()` function from ArkType - **REQUIRED**: The environment object MUST be wrapped in a function like `arkenv()` or `createEnv()` with the type definition @@ -67,7 +67,7 @@ The environment object returned from `loadEnv` or any wrapper SHALL be type-safe - **WHEN** a user attempts to use `loadEnv()` directly without validation - **OR** a user attempts to use `as const` assertion on an environment object - **THEN** the pattern is documented as forbidden -- **AND** examples demonstrate only type-safe patterns +- **AND** examples demonstrate only typesafe patterns - **AND** documentation clearly explains why unsafe patterns (including `as const`) are not allowed and do not provide type safety ### Requirement: Documentation for Vite Config Usage diff --git a/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/tasks.md b/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/tasks.md index 6ede952f4..a88c48210 100644 --- a/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/tasks.md +++ b/openspec/changes/archive/2025-11-20-add-arkenv-vite-config/tasks.md @@ -11,7 +11,7 @@ - [x] Modified Vite plugin to accept both raw schema objects and type definitions - [x] Updated vite.config.ts example to use `type()` to define schema once, outside `defineConfig` - [x] Verified type inference works correctly - `env` is properly typed (not `unknown`) -- [x] 1.3 If implementing a `loadEnv` wrapper, ensure it provides type-safe return values +- [x] 1.3 If implementing a `loadEnv` wrapper, ensure it provides typesafe return values - Not needed - `createEnv` directly accepts type definitions, no wrapper required - [x] 1.4 Ensure the solution allows schema to be shared between `loadEnv` call and plugin definition - Schema defined with `type()` outside `defineConfig` can be reused in both places diff --git a/openspec/specs/vite-config-usage/spec.md b/openspec/specs/vite-config-usage/spec.md index 718d377e5..03eea54c5 100644 --- a/openspec/specs/vite-config-usage/spec.md +++ b/openspec/specs/vite-config-usage/spec.md @@ -13,7 +13,7 @@ Users SHALL be able to use ArkEnv directly in `vite.config.ts` files to validate - **AND** they define a schema for unprefixed environment variables needed by the config (e.g., `PORT`, `DATABASE_URL`) - **THEN** the environment variables are validated at build-time - **AND** invalid or missing variables cause the build to fail with clear error messages -- **AND** the validated variables are type-safe and can be used in the Vite config +- **AND** the validated variables are typesafe and can be used in the Vite config #### Scenario: Type-safe Vite config with environment variables - **WHEN** a user uses ArkEnv to validate environment variables in vite.config.ts @@ -51,17 +51,17 @@ The project SHALL support using ArkEnv with Vite's `loadEnv` function. Since `cr - **WHEN** a user defines a schema using `type()` outside of `defineConfig` - **AND** they call `loadEnv()` to load environment variables - **AND** they pass both the type definition and `loadEnv()` result to `createEnv()` or `arkenv()` -- **THEN** the environment variables are validated and returned as type-safe +- **THEN** the environment variables are validated and returned as typesafe - **AND** the same type definition can be passed to the Vite plugin for validating `VITE_*` variables - **AND** no separate wrapper utility is needed ### Requirement: Type Safety Constraint -The environment object returned from `loadEnv` or any wrapper SHALL be type-safe. Unsafe patterns that bypass validation or type checking are FORBIDDEN. +The environment object returned from `loadEnv` or any wrapper SHALL be typesafe. Unsafe patterns that bypass validation or type checking are FORBIDDEN. #### Constraint: Environment object must be wrapped or typed - **FORBIDDEN**: Directly using `loadEnv()` result without validation or proper typing -- **FORBIDDEN**: Using `as const` assertion on environment objects (not type-safe, bypasses validation) +- **FORBIDDEN**: Using `as const` assertion on environment objects (not typesafe, bypasses validation) - **FORBIDDEN**: Defining schemas as raw objects instead of using `type()` function - **REQUIRED**: The schema MUST be defined using `type()` function from ArkType - **REQUIRED**: The environment object MUST be wrapped in a function like `arkenv()` or `createEnv()` with the type definition @@ -70,7 +70,7 @@ The environment object returned from `loadEnv` or any wrapper SHALL be type-safe - **WHEN** a user attempts to use `loadEnv()` directly without validation - **OR** a user attempts to use `as const` assertion on an environment object - **THEN** the pattern is documented as forbidden -- **AND** examples demonstrate only type-safe patterns +- **AND** examples demonstrate only typesafe patterns - **AND** documentation clearly explains why unsafe patterns (including `as const`) are not allowed and do not provide type safety ### Requirement: Documentation for Vite Config Usage diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts index bfdbd12e6..d24aaca25 100644 --- a/packages/vite-plugin/src/index.ts +++ b/packages/vite-plugin/src/index.ts @@ -18,7 +18,7 @@ export type { ImportMetaEnvAugmented } from "./types"; * Only environment variables matching the prefix are exposed to client code via `import.meta.env.*`. * * @param options - The environment variable schema definition. Can be an `EnvSchema` object - * for type-safe validation or an ArkType `type.Any` for dynamic schemas. + * for typesafe validation or an ArkType `type.Any` for dynamic schemas. * @returns A Vite plugin that validates environment variables and exposes them to the client. * * @example diff --git a/packages/vite-plugin/src/types.ts b/packages/vite-plugin/src/types.ts index b0a81f332..2b2a7ac5d 100644 --- a/packages/vite-plugin/src/types.ts +++ b/packages/vite-plugin/src/types.ts @@ -26,7 +26,7 @@ type FilterByPrefix< }; /** - * Augment the `import.meta.env` object with type-safe environment variables + * Augment the `import.meta.env` object with typesafe environment variables * based on the schema validator. * * This type extracts the inferred type from the schema (result of `type()` from arkenv), From 40d779509896658f6467868b66ee933a8f2c3b3e Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Sun, 23 Nov 2025 21:11:49 +0500 Subject: [PATCH 9/9] docs: clarify comments in vite-env.d.ts and update documentation for type-safe import.meta.env - Enhanced comments in vite-env.d.ts to explain the strictImportMetaEnv option and its impact on type safety for import.meta.env. - Updated documentation to reflect the streamlined setup process for achieving type safety with ArkEnv, ensuring clarity for users. --- apps/playgrounds/vite/src/vite-env.d.ts | 5 +++-- .../content/docs/vite-plugin/typing-import-meta-env.mdx | 9 +++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/apps/playgrounds/vite/src/vite-env.d.ts b/apps/playgrounds/vite/src/vite-env.d.ts index 1054b418e..05a012746 100644 --- a/apps/playgrounds/vite/src/vite-env.d.ts +++ b/apps/playgrounds/vite/src/vite-env.d.ts @@ -9,8 +9,9 @@ type ImportMetaEnvAugmented = >; interface ViteTypeOptions { - // Avoid adding an index type to `ImportMetaDev` so - // there's an error when accessing unknown properties. + // By adding this line, you can make the type of ImportMetaEnv strict + // to disallow unknown keys. + // See: https://vite.dev/guide/env-and-mode#intellisense-for-typescript // ⚠️ This option requires Vite 6.3.x or higher strictImportMetaEnv: unknown; } diff --git a/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx index d05b56629..65db2240b 100644 --- a/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx +++ b/apps/www/content/docs/vite-plugin/typing-import-meta-env.mdx @@ -4,10 +4,10 @@ title: Typing import.meta.env For most use cases, you'll want to access your environment variables in your client code. [In a Vite project, this is done via `import.meta.env`](https://vite.dev/guide/env-and-mode). -With ArkEnv, this can be typesafe - just by making a one-time addition to your `vite.config.ts` file. +With ArkEnv, this can be typesafe with a one-time project setup: add a small file in your `src` directory and ensure your `Env` schema is defined in `vite.config.ts` (or exported from a separate file). > [!NOTE] -> After you make this change, each time you add/remove environment variables from your schema, your `import.meta.env` will be always be typesafe - no codegen needed. +> After you complete this setup, each time you add/remove environment variables from your schema, your `import.meta.env` will always be typesafe - no codegen needed. ## Setup @@ -22,8 +22,9 @@ type ImportMetaEnvAugmented = >; interface ViteTypeOptions { - // Avoid adding an index type to `ImportMetaDev` so - // there's an error when accessing unknown properties. + // By adding this line, you can make the type of ImportMetaEnv strict + // to disallow unknown keys. + // See: https://vite.dev/guide/env-and-mode#intellisense-for-typescript // ⚠️ This option requires Vite 6.3.x or higher strictImportMetaEnv: unknown; }