From 5d90cafb63e1aa9e7b94143729d9ff53a3ca42ce Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Mon, 8 Dec 2025 22:39:23 +0500 Subject: [PATCH 01/14] fix: improve type stability and address deep instantiation errors by introducing `SchemaShape` and `EnvSchemaWithType` across packages. --- .changeset/smooth-lamps-go.md | 8 +++++++ .changeset/twelve-tigers-sell.md | 7 +++++++ packages/arkenv/src/create-env.test.ts | 16 ++++++++------ packages/arkenv/src/create-env.ts | 29 +++++++++++++------------- packages/bun-plugin/src/index.test.ts | 2 +- packages/bun-plugin/src/index.ts | 29 +++++++++++++++----------- packages/internal/types/README.md | 4 ++-- packages/internal/types/package.json | 5 +++-- packages/internal/types/src/index.ts | 8 +++++-- packages/internal/types/src/schema.ts | 5 +++++ packages/vite-plugin/src/index.ts | 29 ++++++++++++-------------- 11 files changed, 87 insertions(+), 55 deletions(-) create mode 100644 .changeset/smooth-lamps-go.md create mode 100644 .changeset/twelve-tigers-sell.md create mode 100644 packages/internal/types/src/schema.ts diff --git a/.changeset/smooth-lamps-go.md b/.changeset/smooth-lamps-go.md new file mode 100644 index 000000000..bdbc2d7d8 --- /dev/null +++ b/.changeset/smooth-lamps-go.md @@ -0,0 +1,8 @@ +--- +"@arkenv/vite-plugin": patch +"@arkenv/bun-plugin": patch +--- + +#### Improve internal types + +Fixed "Type instantiation is excessively deep and possibly infinite" errors when using the plugins with complex ArkType schemas. This change improves type stability during the build process by optimizing how generics are passed to the validation logic. diff --git a/.changeset/twelve-tigers-sell.md b/.changeset/twelve-tigers-sell.md new file mode 100644 index 000000000..22a3f9064 --- /dev/null +++ b/.changeset/twelve-tigers-sell.md @@ -0,0 +1,7 @@ +--- +"@repo/types": patch +--- + +#### Add Schema types + +Add Schema types to internal types package, including `EnvSchemaWithType` and `SchemaShape`. diff --git a/packages/arkenv/src/create-env.test.ts b/packages/arkenv/src/create-env.test.ts index 43cd3ab89..5dbb37646 100644 --- a/packages/arkenv/src/create-env.test.ts +++ b/packages/arkenv/src/create-env.test.ts @@ -119,7 +119,7 @@ describe("env", () => { TEST_PORT: "number.port", }); - const env = createEnv(envSchema); + const env = createEnv(envSchema as never) as any; expect(env.TEST_STRING).toBe("hello"); expect(env.TEST_PORT).toBe(3000); @@ -134,7 +134,7 @@ describe("env", () => { TEST_PORT: "number.port", }); - const env = createEnv(envSchema); + const env = createEnv(envSchema as never) as any; // TypeScript should infer these correctly const str: string = env.TEST_STRING; @@ -152,8 +152,12 @@ describe("env", () => { }); // Use the same schema multiple times - const env1 = createEnv(envSchema, { TEST_STRING: "first" }); - const env2 = createEnv(envSchema, { TEST_STRING: "second" }); + const env1 = createEnv(envSchema as never, { + TEST_STRING: "first", + }) as any; + const env2 = createEnv(envSchema as never, { + TEST_STRING: "second", + }) as any; expect(env1.TEST_STRING).toBe("first"); expect(env2.TEST_STRING).toBe("second"); @@ -166,7 +170,7 @@ describe("env", () => { INVALID_PORT: "number.port", }); - expect(() => createEnv(envSchema)).toThrow(/INVALID_PORT/); + expect(() => createEnv(envSchema as never)).toThrow(/INVALID_PORT/); }); it("should work with custom environment and type definitions", () => { @@ -180,7 +184,7 @@ describe("env", () => { PORT: "8080", }; - const env = createEnv(envSchema, customEnv); + const env = createEnv(envSchema as never, customEnv) as any; expect(env.HOST).toBe("localhost"); expect(env.PORT).toBe(8080); diff --git a/packages/arkenv/src/create-env.ts b/packages/arkenv/src/create-env.ts index 18f7ebb36..ac2d9d50f 100644 --- a/packages/arkenv/src/create-env.ts +++ b/packages/arkenv/src/create-env.ts @@ -1,14 +1,15 @@ -import type { InferType } from "@repo/types"; -import { type distill, type } from "arktype"; +import type { EnvSchemaWithType, InferType, SchemaShape } from "@repo/types"; +import type { type as at, distill } from "arktype"; import { ArkEnvError } from "./errors"; import { $ } from "./scope"; +import { type } from "./type"; +export type EnvSchema = at.validate; type RuntimeEnvironment = Record; -export type EnvSchema = type.validate; - /** - * TODO: If possible, find a better type than "const T extends Record", + * TODO: `SchemaShape` is basically `Record`. + * 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`. */ @@ -19,22 +20,22 @@ export type EnvSchema = type.validate; * @returns The validated environment variable schema * @throws An {@link ArkEnvError | error} if the environment variables are invalid. */ -export function createEnv>( +export function createEnv( def: EnvSchema, env?: RuntimeEnvironment, -): distill.Out>; -export function createEnv( +): distill.Out>; +export function createEnv( def: T, env?: RuntimeEnvironment, ): InferType; -export function createEnv>( - def: EnvSchema | type.Any, +export function createEnv( + def: EnvSchema | EnvSchemaWithType, env?: RuntimeEnvironment, -): distill.Out> | InferType; -export function createEnv>( - def: EnvSchema | type.Any, +): distill.Out> | InferType; +export function createEnv( + def: EnvSchema | EnvSchemaWithType, env: RuntimeEnvironment = process.env, -): distill.Out> | InferType { +): distill.Out> | InferType { // If def is a type definition (has assert method), use it directly // Otherwise, use raw() to convert the schema definition const schema = diff --git a/packages/bun-plugin/src/index.test.ts b/packages/bun-plugin/src/index.test.ts index 96a576a63..e4b9ee329 100644 --- a/packages/bun-plugin/src/index.test.ts +++ b/packages/bun-plugin/src/index.test.ts @@ -54,7 +54,7 @@ describe("Bun Plugin", () => { BUN_PUBLIC_API_URL: "string", PORT: "number.port", DATABASE_URL: "string", - }); + } as const); // Check that prefixed variables are present expect(envMap.has("BUN_PUBLIC_API_URL")).toBe(true); diff --git a/packages/bun-plugin/src/index.ts b/packages/bun-plugin/src/index.ts index 3153ae79c..70232e4d5 100644 --- a/packages/bun-plugin/src/index.ts +++ b/packages/bun-plugin/src/index.ts @@ -1,7 +1,7 @@ import { join } from "node:path"; +import type { EnvSchemaWithType, SchemaShape } from "@repo/types"; import type { EnvSchema } from "arkenv"; import { createEnv } from "arkenv"; -import type { type } from "arktype"; import type { BunPlugin, Loader, PluginBuilder } from "bun"; export type { ProcessEnvAugmented } from "./types"; @@ -9,18 +9,23 @@ export type { ProcessEnvAugmented } from "./types"; /** * Helper to process env schema and return envMap */ -export function processEnvSchema(options: EnvSchema | type.Any) { +export function processEnvSchema( + options: EnvSchema | EnvSchemaWithType, +): Map { // Validate environment variables - const env = createEnv(options, process.env); + + // TODO: We're using type assertions and explicitly pass in the type arguments here to avoid + // "Type instantiation is excessively deep and possibly infinite" errors. + // Ideally, we should find a way to avoid these assertions while maintaining type safety. + + const env = createEnv(options, process.env); // Get Bun's prefix for client-exposed environment variables const prefix = "BUN_PUBLIC_"; // Filter to only include environment variables matching the prefix const filteredEnv = Object.fromEntries( - Object.entries(>env).filter(([key]) => - key.startsWith(prefix), - ), + Object.entries(env).filter(([key]) => key.startsWith(prefix)), ); // Create a map of variable names to their JSON-stringified values @@ -81,7 +86,7 @@ function registerLoader(build: PluginBuilder, envMap: Map) { loader, contents: transformed, }; - } catch (error) { + } catch { // If file can't be read, return undefined to let Bun handle it return undefined; } @@ -119,14 +124,14 @@ function registerLoader(build: PluginBuilder, envMap: Map) { * }) * ``` */ -export function arkenv>( +export function arkenv( options: EnvSchema, ): BunPlugin; -export function arkenv(options: type.Any): BunPlugin; -export function arkenv>( - options: EnvSchema | type.Any, +export function arkenv(options: EnvSchemaWithType): BunPlugin; +export function arkenv( + options: EnvSchema | EnvSchemaWithType, ): BunPlugin { - const envMap = processEnvSchema(options); + const envMap = processEnvSchema(options); return { name: "@arkenv/bun-plugin", diff --git a/packages/internal/types/README.md b/packages/internal/types/README.md index e0c39407a..fa64c4f4f 100644 --- a/packages/internal/types/README.md +++ b/packages/internal/types/README.md @@ -1,10 +1,10 @@ # @repo/types -Internal TypeScript types shared across ArkEnv packages. +Internal TypeScript/ArkType types shared across ArkEnv packages. ## Purpose -This package provides common TypeScript types used internally by multiple packages in the ArkEnv monorepo. It eliminates code duplication and provides a single source of truth for shared type definitions. +This package provides common TypeScript/ArkType types used internally by multiple packages in the ArkEnv monorepo. It eliminates code duplication and provides a single source of truth for shared type definitions. ## Usage diff --git a/packages/internal/types/package.json b/packages/internal/types/package.json index 5d7207840..d050e5b08 100644 --- a/packages/internal/types/package.json +++ b/packages/internal/types/package.json @@ -13,13 +13,14 @@ }, "scripts": { "typecheck": "tsc --noEmit", - "build": "tsc" + "build": "tsc", + "fix": "pnpm -w run fix" }, "devDependencies": { "arktype": "catalog:", "typescript": "catalog:" }, "peerDependencies": { - "arktype": "^2.1.22" + "arktype": "workspace:*" } } diff --git a/packages/internal/types/src/index.ts b/packages/internal/types/src/index.ts index 8bc203371..b210e61e9 100644 --- a/packages/internal/types/src/index.ts +++ b/packages/internal/types/src/index.ts @@ -1,8 +1,12 @@ /** - * Internal TypeScript types shared across ArkEnv packages. + * Internal TypeScript/ArkType types shared across ArkEnv packages. * * This package is not published to npm and is intended for internal use only * within the monorepo. */ -export type { InferType } from "./infer-type"; +// Only TypeScript types +export type * from "./infer-type"; + +// Also includes ArkType types +export * from "./schema"; diff --git a/packages/internal/types/src/schema.ts b/packages/internal/types/src/schema.ts new file mode 100644 index 000000000..5888418fc --- /dev/null +++ b/packages/internal/types/src/schema.ts @@ -0,0 +1,5 @@ +import { type } from "arktype"; + +export const SchemaShape = type({ "[string]": "unknown" }); +export type SchemaShape = typeof SchemaShape.infer; +export type EnvSchemaWithType = typeof SchemaShape; diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts index 06eae7cff..e947825d3 100644 --- a/packages/vite-plugin/src/index.ts +++ b/packages/vite-plugin/src/index.ts @@ -1,12 +1,9 @@ -import type { EnvSchema } from "arkenv"; -import { createEnv } from "arkenv"; -import type { type } from "arktype"; +import type { EnvSchemaWithType, SchemaShape } from "@repo/types"; +import { createEnv, type EnvSchema } from "arkenv"; import { loadEnv, type Plugin } from "vite"; -export type { ImportMetaEnvAugmented } from "./types"; - /** - * TODO: If possible, find a better type than "const T extends Record", + * TODO: If possible, find a better type than "const T extends SchemaShape", * and be as close as possible to the type accepted by ArkType's `type`. */ @@ -18,7 +15,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 typesafe validation or an ArkType `type.Any` for dynamic schemas. + * for typesafe validation or an ArkType `EnvSchemaWithType` for dynamic schemas. * @returns A Vite plugin that validates environment variables and exposes them to the client. * * @example @@ -43,12 +40,12 @@ export type { ImportMetaEnvAugmented } from "./types"; * console.log(import.meta.env.VITE_API_URL); // Typesafe access * ``` */ -export default function arkenv>( +export default function arkenv( options: EnvSchema, ): Plugin; -export default function arkenv(options: type.Any): Plugin; -export default function arkenv>( - options: EnvSchema | type.Any, +export default function arkenv(options: EnvSchemaWithType): Plugin; +export default function arkenv( + options: EnvSchema | EnvSchemaWithType, ): Plugin { return { name: "@arkenv/vite-plugin", @@ -59,15 +56,15 @@ export default function arkenv>( const envPrefix = config.envPrefix ?? "VITE_"; const prefixes = Array.isArray(envPrefix) ? envPrefix : [envPrefix]; - // createEnv accepts both EnvSchema and type.Any at runtime - // We use overloads above to provide external type precision - // TODO: Improve the `EnvSchema` type rather than using `as never`. See #501. - const env = createEnv(options as never, loadEnv(mode, process.cwd(), "")); + // TODO: We're using type assertions and explicitly pass in the type arguments here to avoid + // "Type instantiation is excessively deep and possibly infinite" errors. + // Ideally, we should find a way to avoid these assertions while maintaining type safety. + const env = createEnv(options, loadEnv(mode, process.cwd(), "")); // Filter to only include environment variables matching the prefix // This prevents server-only variables from being exposed to client code const filteredEnv = Object.fromEntries( - Object.entries(>env).filter(([key]) => + Object.entries(env).filter(([key]) => prefixes.some((prefix) => key.startsWith(prefix)), ), ); From 6ddab5d7d31cba8c86e8f63cf5238c2741de54c7 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Mon, 8 Dec 2025 23:52:56 +0500 Subject: [PATCH 02/14] feat: introduce `@repo/scope` and `@repo/keywords` internal packages, and restructure `arkenv` for publishing. --- .gitignore | 3 + arkenv.code-workspace | 8 ++ packages/arkenv/.gitignore | 2 - packages/arkenv/package.json | 2 + packages/arkenv/package/LICENSE | 21 +++ packages/arkenv/package/README.md | 133 ++++++++++++++++++ packages/arkenv/package/package.json | 68 +++++++++ packages/arkenv/src/create-env.ts | 10 +- packages/arkenv/src/type.ts | 2 +- packages/arkenv/tsconfig.json | 6 +- packages/arkenv/tsdown.config.ts | 1 + packages/bun-plugin/src/index.ts | 2 +- packages/internal/keywords/README.md | 3 + packages/internal/keywords/package.json | 44 ++++++ .../keywords/src/index.test.ts} | 2 +- .../keywords/src/index.ts} | 0 packages/internal/keywords/tsconfig.json | 19 +++ packages/internal/keywords/tsdown.config.ts | 10 ++ packages/internal/keywords/turbo.json | 10 ++ packages/internal/keywords/vitest.config.ts | 7 + packages/internal/scope/README.md | 3 + packages/internal/scope/package.json | 47 +++++++ .../scope/src/index.test.ts} | 2 +- .../scope.ts => internal/scope/src/index.ts} | 2 +- packages/internal/scope/tsconfig.json | 19 +++ packages/internal/scope/tsdown.config.ts | 10 ++ packages/internal/scope/turbo.json | 10 ++ packages/internal/scope/vitest.config.ts | 7 + packages/internal/types/src/schema.ts | 4 +- packages/vite-plugin/src/index.ts | 19 ++- pnpm-lock.yaml | 34 +++++ pnpm-workspace.yaml | 28 ++-- 32 files changed, 508 insertions(+), 30 deletions(-) delete mode 100644 packages/arkenv/.gitignore create mode 100644 packages/arkenv/package/LICENSE create mode 100644 packages/arkenv/package/README.md create mode 100644 packages/arkenv/package/package.json create mode 100644 packages/internal/keywords/README.md create mode 100644 packages/internal/keywords/package.json rename packages/{arkenv/src/types.test.ts => internal/keywords/src/index.test.ts} (96%) rename packages/{arkenv/src/types.ts => internal/keywords/src/index.ts} (100%) create mode 100644 packages/internal/keywords/tsconfig.json create mode 100644 packages/internal/keywords/tsdown.config.ts create mode 100644 packages/internal/keywords/turbo.json create mode 100644 packages/internal/keywords/vitest.config.ts create mode 100644 packages/internal/scope/README.md create mode 100644 packages/internal/scope/package.json rename packages/{arkenv/src/scope.test.ts => internal/scope/src/index.test.ts} (98%) rename packages/{arkenv/src/scope.ts => internal/scope/src/index.ts} (90%) create mode 100644 packages/internal/scope/tsconfig.json create mode 100644 packages/internal/scope/tsdown.config.ts create mode 100644 packages/internal/scope/turbo.json create mode 100644 packages/internal/scope/vitest.config.ts diff --git a/.gitignore b/.gitignore index 51e575514..b1efd3e8a 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ pnpm-debug.log* # coverage coverage/ + +# size-limit +esbuild-why*.html \ No newline at end of file diff --git a/arkenv.code-workspace b/arkenv.code-workspace index 8bbdff392..9abb53fbd 100644 --- a/arkenv.code-workspace +++ b/arkenv.code-workspace @@ -40,6 +40,14 @@ "path": "packages/internal/types", "name": " @repo/types" }, + { + "path": "packages/internal/scope", + "name": " @repo/scope" + }, + { + "path": "packages/internal/keywords", + "name": " @repo/keywords" + }, { "path": "tooling", "name": "tooling" diff --git a/packages/arkenv/.gitignore b/packages/arkenv/.gitignore deleted file mode 100644 index 1a512237f..000000000 --- a/packages/arkenv/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -# size-limit -esbuild-why*.html \ No newline at end of file diff --git a/packages/arkenv/package.json b/packages/arkenv/package.json index eccd5ef8f..6cdb96201 100644 --- a/packages/arkenv/package.json +++ b/packages/arkenv/package.json @@ -40,6 +40,8 @@ "bugs": "https://github.com/yamcodes/arkenv/labels/arkenv", "author": "Yam Borodetsky ", "devDependencies": { + "@repo/keywords": "workspace:*", + "@repo/scope": "workspace:*", "@repo/types": "workspace:*", "@size-limit/esbuild-why": "catalog:", "@size-limit/preset-small-lib": "catalog:", diff --git a/packages/arkenv/package/LICENSE b/packages/arkenv/package/LICENSE new file mode 100644 index 000000000..7ce78d74d --- /dev/null +++ b/packages/arkenv/package/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Yam Borodetsky + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/arkenv/package/README.md b/packages/arkenv/package/README.md new file mode 100644 index 000000000..30a008f3b --- /dev/null +++ b/packages/arkenv/package/README.md @@ -0,0 +1,133 @@ +

+ ArkEnv Logo +

ArkEnv

+
+

Typesafe environment variables
+ powered by ArkType

+ Test Status + npm bundle size + TypeScript + Powered By ArkType + Node.js + Bun + Vite + Chat on Discord +
+

+

Proud member of the ArkType ecosystem

+ +

+ ArkEnv Demo +

+ +
+
+
+ +### [Read the docs →](https://arkenv.js.org/docs/arkenv) + +
+
+ +## Introduction + +> [!TIP] +> 📖 **Reading this on GitHub?** Check out [this page in our docs](https://arkenv.js.org/docs/arkenv) to hover over code blocks and get type hints! + +ArkEnv is an environment variable parser powered by [ArkType](https://arktype.io/), TypeScript's 1:1 validator. ArkEnv lets you use familiar TypeScript-like syntax to create a ready to use, typesafe environment variable object: + +```ts twoslash +import arkenv from 'arkenv'; + +const env = arkenv({ + HOST: "string.host", // valid IP address or localhost + PORT: "number.port", // valid port number (0-65535) + NODE_ENV: "'development' | 'production' | 'test'", +}); + +// Hover to see ✨exact✨ types +const host = env.HOST; +const port = env.PORT; +const nodeEnv = env.NODE_ENV; +``` + +With ArkEnv, your environment variables are **guaranteed to match your schema**. If any variable is incorrect or missing, the app won't start and a clear error will be thrown: + +```bash title="Terminal" +ArkEnvError: Errors found while validating environment variables + HOST must be a string or "localhost" (was missing) + PORT must be an integer between 0 and 65535 (was "hello") +``` + +## Features + +- Zero external dependencies +- Works in Node.js, Bun, and Vite +- Tiny: <1kB gzipped +- Build-time and runtime validation +- Single import, zero config for most projects +- Validated, defaultable, typesafe environment variables +- Powered by ArkType, TypeScript's 1:1 validator +- Optimized from editor to runtime + +## Installation + +
+npm + +```sh +npm install arkenv arktype +``` +
+ +
+pnpm + +```sh +pnpm add arkenv arktype +``` +
+ +
+Yarn + +```sh +yarn add arkenv arktype +``` +
+ +
+Bun + +```sh +bun add arkenv arktype +``` +
+ +:rocket: **Let's get started!** Read the [2-minute setup guide](https://arkenv.js.org/docs/quickstart) or [start with an example](https://arkenv.js.org/docs/examples). + +> [!TIP] +> Improve your DX with *syntax highlighting* in [VS Code & Cursor](https://arkenv.js.org/docs/integrations/vscode) or [JetBrains IDEs](https://arkenv.js.org/docs/integrations/jetbrains). + +## Requirements + +- **TypeScript >= 5.1** and [anything else required by ArkType](https://arktype.io/docs/intro/setup#installation) +- [**Modern TypeScript module resolution**](https://www.typescriptlang.org/tsconfig/#moduleResolution). One of the following is required in your `tsconfig.json`: + - `"moduleResolution": "bundler"` - Recommended for modern bundlers (Vite, Next.js, etc.). Supplied by default when using `"module": "Preserve"`. + - `"moduleResolution": "node16"` or `"nodenext"` - For Node.js projects. Supplied by default when using a matching `"module"` value. +- Tested on [**Node.js LTS** and **Current**](https://github.com/yamcodes/arkenv/tree/main/examples/basic), [**Bun 1.3.2**](https://github.com/yamcodes/arkenv/tree/main/examples/with-bun), and [**Vite** from **2.9.18** to **7.x**](https://github.com/yamcodes/arkenv/tree/main/examples/with-vite-react-ts). Older versions may work but are not officially supported + +## Plugins + +Beyond [the core package](https://arkenv.js.org/docs/arkenv), we also provide plugins for frameworks that require a specific implementation to adhere to best practices. + +- [@arkenv/vite-plugin](https://arkenv.js.org/docs/vite-plugin) +- [@arkenv/bun-plugin](https://arkenv.js.org/docs/bun-plugin) + +## Supporting ArkEnv + +If you love ArkEnv, you can support the project by **starring it on GitHub**! + +You are also welcome to directly [contribute to the project's development](https://github.com/yamcodes/arkenv/blob/main/CONTRIBUTING.md). + +## [Thanks / Inspiration](https://github.com/yamcodes/arkenv/blob/main/THANKS.md) diff --git a/packages/arkenv/package/package.json b/packages/arkenv/package/package.json new file mode 100644 index 000000000..0d7c0ba25 --- /dev/null +++ b/packages/arkenv/package/package.json @@ -0,0 +1,68 @@ +{ + "name": "arkenv", + "type": "module", + "version": "0.7.6", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "description": "Typesafe environment variables parsing and validation with ArkType", + "exports": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "files": [ + "dist" + ], + "keywords": [ + "pnpm", + "arktype", + "environment", + "variables", + "typesafe", + "validation" + ], + "license": "MIT", + "homepage": "https://arkenv.js.org", + "repository": { + "type": "git", + "url": "git+https://github.com/yamcodes/arkenv.git" + }, + "bugs": "https://github.com/yamcodes/arkenv/labels/arkenv", + "author": "Yam Borodetsky ", + "devDependencies": { + "@size-limit/esbuild-why": "11.2.0", + "@size-limit/preset-small-lib": "11.2.0", + "@types/node": "24.10.1", + "arktype": "2.1.28", + "size-limit": "11.2.0", + "tsdown": "0.16.8", + "typescript": "5.9.3", + "vitest": "4.0.15", + "@repo/scope": "0.0.0", + "@repo/types": "0.0.0", + "@repo/keywords": "0.0.0" + }, + "peerDependencies": { + "arktype": "^2.1.22" + }, + "size-limit": [ + { + "path": "dist/index.js", + "limit": "2 kB", + "import": "*", + "ignore": [ + "arktype" + ] + } + ], + "scripts": { + "build": "tsdown", + "size": "size-limit", + "test:once": "pnpm test", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist node_modules", + "test": "vitest", + "fix": "pnpm -w run fix" + } +} \ No newline at end of file diff --git a/packages/arkenv/src/create-env.ts b/packages/arkenv/src/create-env.ts index ac2d9d50f..1961c0a8f 100644 --- a/packages/arkenv/src/create-env.ts +++ b/packages/arkenv/src/create-env.ts @@ -1,7 +1,7 @@ +import { $ } from "@repo/scope"; import type { EnvSchemaWithType, InferType, SchemaShape } from "@repo/types"; import type { type as at, distill } from "arktype"; import { ArkEnvError } from "./errors"; -import { $ } from "./scope"; import { type } from "./type"; export type EnvSchema = at.validate; @@ -20,14 +20,14 @@ type RuntimeEnvironment = Record; * @returns The validated environment variable schema * @throws An {@link ArkEnvError | error} if the environment variables are invalid. */ -export function createEnv( - def: EnvSchema, - env?: RuntimeEnvironment, -): distill.Out>; export function createEnv( def: T, env?: RuntimeEnvironment, ): InferType; +export function createEnv( + def: EnvSchema, + env?: RuntimeEnvironment, +): distill.Out>; export function createEnv( def: EnvSchema | EnvSchemaWithType, env?: RuntimeEnvironment, diff --git a/packages/arkenv/src/type.ts b/packages/arkenv/src/type.ts index 535d884a5..2410781c8 100644 --- a/packages/arkenv/src/type.ts +++ b/packages/arkenv/src/type.ts @@ -1,3 +1,3 @@ -import { $ } from "./scope"; +import { $ } from "@repo/scope"; export const type = $.type; diff --git a/packages/arkenv/tsconfig.json b/packages/arkenv/tsconfig.json index 56793c550..358688946 100644 --- a/packages/arkenv/tsconfig.json +++ b/packages/arkenv/tsconfig.json @@ -15,5 +15,7 @@ "declaration": true, "declarationMap": true }, - "include": ["src"] -} + "include": [ + "src" + ] +} \ No newline at end of file diff --git a/packages/arkenv/tsdown.config.ts b/packages/arkenv/tsdown.config.ts index 8fc08e8db..eddd6ae49 100644 --- a/packages/arkenv/tsdown.config.ts +++ b/packages/arkenv/tsdown.config.ts @@ -7,4 +7,5 @@ export default defineConfig({ dts: { resolve: ["@repo/types"], }, + sourcemap: false, }); diff --git a/packages/bun-plugin/src/index.ts b/packages/bun-plugin/src/index.ts index 70232e4d5..a274a4428 100644 --- a/packages/bun-plugin/src/index.ts +++ b/packages/bun-plugin/src/index.ts @@ -124,10 +124,10 @@ function registerLoader(build: PluginBuilder, envMap: Map) { * }) * ``` */ +export function arkenv(options: EnvSchemaWithType): BunPlugin; export function arkenv( options: EnvSchema, ): BunPlugin; -export function arkenv(options: EnvSchemaWithType): BunPlugin; export function arkenv( options: EnvSchema | EnvSchemaWithType, ): BunPlugin { diff --git a/packages/internal/keywords/README.md b/packages/internal/keywords/README.md new file mode 100644 index 000000000..9500dfd47 --- /dev/null +++ b/packages/internal/keywords/README.md @@ -0,0 +1,3 @@ +# `@repo/keywords` + +Internal keywords for ArkEnv. diff --git a/packages/internal/keywords/package.json b/packages/internal/keywords/package.json new file mode 100644 index 000000000..f98305fe0 --- /dev/null +++ b/packages/internal/keywords/package.json @@ -0,0 +1,44 @@ +{ + "name": "@repo/keywords", + "private": true, + "type": "module", + "version": "0.0.0", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "description": "ArkEnv keywords (internal usage)", + "exports": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "scripts": { + "build": "tsdown", + "size": "size-limit", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist node_modules", + "test": "vitest", + "fix": "pnpm -w run fix" + }, + "files": [ + "dist" + ], + "keywords": [ + "keywords" + ], + "license": "MIT", + "homepage": "https://arkenv.js.org", + "repository": { + "type": "git", + "url": "git+https://github.com/yamcodes/arkenv.git" + }, + "author": "Yam Borodetsky ", + "devDependencies": { + "arktype": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "peerDependencies": { + "arktype": "workspace:*" + } +} diff --git a/packages/arkenv/src/types.test.ts b/packages/internal/keywords/src/index.test.ts similarity index 96% rename from packages/arkenv/src/types.test.ts rename to packages/internal/keywords/src/index.test.ts index dc562460a..c535d157e 100644 --- a/packages/arkenv/src/types.test.ts +++ b/packages/internal/keywords/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { host, port } from "./types"; +import { host, port } from "./index"; describe("port", () => { it("should validate a valid port number", () => { diff --git a/packages/arkenv/src/types.ts b/packages/internal/keywords/src/index.ts similarity index 100% rename from packages/arkenv/src/types.ts rename to packages/internal/keywords/src/index.ts diff --git a/packages/internal/keywords/tsconfig.json b/packages/internal/keywords/tsconfig.json new file mode 100644 index 000000000..56793c550 --- /dev/null +++ b/packages/internal/keywords/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "preserve", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "exactOptionalPropertyTypes": true, + "noImplicitAny": true, + "noEmit": true, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": "src", + "declaration": true, + "declarationMap": true + }, + "include": ["src"] +} diff --git a/packages/internal/keywords/tsdown.config.ts b/packages/internal/keywords/tsdown.config.ts new file mode 100644 index 000000000..8fc08e8db --- /dev/null +++ b/packages/internal/keywords/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + format: ["esm", "cjs"], + minify: true, + fixedExtension: false, + dts: { + resolve: ["@repo/types"], + }, +}); diff --git a/packages/internal/keywords/turbo.json b/packages/internal/keywords/turbo.json new file mode 100644 index 000000000..3e34e8a2f --- /dev/null +++ b/packages/internal/keywords/turbo.json @@ -0,0 +1,10 @@ +{ + "extends": ["//"], + "tasks": { + // Defined here and not in the root turbo.json to avoid building all packages when running `turbo run size`. + "size": { + "dependsOn": ["build"], + "cache": false + } + } +} diff --git a/packages/internal/keywords/vitest.config.ts b/packages/internal/keywords/vitest.config.ts new file mode 100644 index 000000000..d3f85876b --- /dev/null +++ b/packages/internal/keywords/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + name: "@repo/keywords", + }, +}); diff --git a/packages/internal/scope/README.md b/packages/internal/scope/README.md new file mode 100644 index 000000000..102a74a7b --- /dev/null +++ b/packages/internal/scope/README.md @@ -0,0 +1,3 @@ +# `@repo/scope` + +Internal scope for ArkEnv. diff --git a/packages/internal/scope/package.json b/packages/internal/scope/package.json new file mode 100644 index 000000000..0404306e1 --- /dev/null +++ b/packages/internal/scope/package.json @@ -0,0 +1,47 @@ +{ + "name": "@repo/scope", + "private": true, + "type": "module", + "version": "0.0.0", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "description": "ArkEnv scope (internal usage)", + "exports": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "scripts": { + "build": "tsdown", + "size": "size-limit", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist node_modules", + "test": "vitest", + "fix": "pnpm -w run fix" + }, + "files": [ + "dist" + ], + "keywords": [ + "scope" + ], + "license": "MIT", + "homepage": "https://arkenv.js.org", + "repository": { + "type": "git", + "url": "git+https://github.com/yamcodes/arkenv.git" + }, + "author": "Yam Borodetsky ", + "dependencies": { + "@repo/keywords": "workspace:*" + }, + "devDependencies": { + "arktype": "catalog:", + "tsdown": "catalog:", + "typescript": "catalog:" + }, + "peerDependencies": { + "arktype": "workspace:*" + } +} diff --git a/packages/arkenv/src/scope.test.ts b/packages/internal/scope/src/index.test.ts similarity index 98% rename from packages/arkenv/src/scope.test.ts rename to packages/internal/scope/src/index.test.ts index 9fdbd2711..35bcf1246 100644 --- a/packages/arkenv/src/scope.test.ts +++ b/packages/internal/scope/src/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { $ } from "./scope"; +import { $ } from "./index"; describe("scope", () => { it("should validate string.host", () => { diff --git a/packages/arkenv/src/scope.ts b/packages/internal/scope/src/index.ts similarity index 90% rename from packages/arkenv/src/scope.ts rename to packages/internal/scope/src/index.ts index ce2d1e4a4..16d209b7c 100644 --- a/packages/arkenv/src/scope.ts +++ b/packages/internal/scope/src/index.ts @@ -1,5 +1,5 @@ +import { boolean, host, port } from "@repo/keywords"; import { scope, type } from "arktype"; -import { boolean, host, port } from "./types"; // For an explanation of the `$` variable naming convention, see: https://discord.com/channels/957797212103016458/1414659167008063588/1414670282756587581 diff --git a/packages/internal/scope/tsconfig.json b/packages/internal/scope/tsconfig.json new file mode 100644 index 000000000..56793c550 --- /dev/null +++ b/packages/internal/scope/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "preserve", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "exactOptionalPropertyTypes": true, + "noImplicitAny": true, + "noEmit": true, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": "src", + "declaration": true, + "declarationMap": true + }, + "include": ["src"] +} diff --git a/packages/internal/scope/tsdown.config.ts b/packages/internal/scope/tsdown.config.ts new file mode 100644 index 000000000..8fc08e8db --- /dev/null +++ b/packages/internal/scope/tsdown.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + format: ["esm", "cjs"], + minify: true, + fixedExtension: false, + dts: { + resolve: ["@repo/types"], + }, +}); diff --git a/packages/internal/scope/turbo.json b/packages/internal/scope/turbo.json new file mode 100644 index 000000000..3e34e8a2f --- /dev/null +++ b/packages/internal/scope/turbo.json @@ -0,0 +1,10 @@ +{ + "extends": ["//"], + "tasks": { + // Defined here and not in the root turbo.json to avoid building all packages when running `turbo run size`. + "size": { + "dependsOn": ["build"], + "cache": false + } + } +} diff --git a/packages/internal/scope/vitest.config.ts b/packages/internal/scope/vitest.config.ts new file mode 100644 index 000000000..0b201f245 --- /dev/null +++ b/packages/internal/scope/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineProject } from "vitest/config"; + +export default defineProject({ + test: { + name: "@repo/scope", + }, +}); diff --git a/packages/internal/types/src/schema.ts b/packages/internal/types/src/schema.ts index 5888418fc..af52b115e 100644 --- a/packages/internal/types/src/schema.ts +++ b/packages/internal/types/src/schema.ts @@ -1,5 +1,5 @@ -import { type } from "arktype"; +import { type Type, type } from "arktype"; export const SchemaShape = type({ "[string]": "unknown" }); export type SchemaShape = typeof SchemaShape.infer; -export type EnvSchemaWithType = typeof SchemaShape; +export type EnvSchemaWithType = Type; diff --git a/packages/vite-plugin/src/index.ts b/packages/vite-plugin/src/index.ts index e947825d3..b83bb44e8 100644 --- a/packages/vite-plugin/src/index.ts +++ b/packages/vite-plugin/src/index.ts @@ -1,7 +1,24 @@ import type { EnvSchemaWithType, SchemaShape } from "@repo/types"; import { createEnv, type EnvSchema } from "arkenv"; +import type { type } from "arktype"; import { loadEnv, type Plugin } from "vite"; +/** + * Extract the inferred type from an ArkType type definition by checking its call signature. + * 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; + +export type ImportMetaEnvAugmented = InferType; + /** * TODO: If possible, find a better type than "const T extends SchemaShape", * and be as close as possible to the type accepted by ArkType's `type`. @@ -40,10 +57,10 @@ import { loadEnv, type Plugin } from "vite"; * console.log(import.meta.env.VITE_API_URL); // Typesafe access * ``` */ +export default function arkenv(options: EnvSchemaWithType): Plugin; export default function arkenv( options: EnvSchema, ): Plugin; -export default function arkenv(options: EnvSchemaWithType): Plugin; export default function arkenv( options: EnvSchema | EnvSchemaWithType, ): Plugin { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9053ae73d..2d545a3ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -408,6 +408,12 @@ importers: packages/arkenv: devDependencies: + '@repo/keywords': + specifier: workspace:* + version: link:../internal/keywords + '@repo/scope': + specifier: workspace:* + version: link:../internal/scope '@repo/types': specifier: workspace:* version: link:../internal/types @@ -473,6 +479,34 @@ importers: specifier: 'catalog:' version: 4.0.15(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/ui@4.0.15)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + packages/internal/keywords: + devDependencies: + arktype: + specifier: 'catalog:' + version: 2.1.28 + tsdown: + specifier: 'catalog:' + version: 0.16.8(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + + packages/internal/scope: + dependencies: + '@repo/keywords': + specifier: workspace:* + version: link:../keywords + devDependencies: + arktype: + specifier: 'catalog:' + version: 2.1.28 + tsdown: + specifier: 'catalog:' + version: 0.16.8(typescript@5.9.3) + typescript: + specifier: 'catalog:' + version: 5.9.3 + packages/internal/types: devDependencies: arktype: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3c10c2136..3895704ae 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,20 +4,17 @@ packages: - apps/* - apps/playgrounds/* - tooling/* -overrides: - "@playwright/test": 1.56.1 - import-in-the-middle: 2.0.0 - require-in-the-middle: 8.0.1 + catalog: - "@axe-core/playwright": 4.11.0 - "@size-limit/esbuild-why": 11.2.0 - "@size-limit/preset-small-lib": 11.2.0 - "@types/bun": 1.3.3 - "@types/node": 24.10.1 - "@types/react": 19.2.7 - "@types/react-dom": 19.2.3 - "@vitejs/plugin-react": 5.1.1 - "@vitest/ui": 4.0.15 + '@axe-core/playwright': 4.11.0 + '@size-limit/esbuild-why': 11.2.0 + '@size-limit/preset-small-lib': 11.2.0 + '@types/bun': 1.3.3 + '@types/node': 24.10.1 + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3 + '@vitejs/plugin-react': 5.1.1 + '@vitest/ui': 4.0.15 arktype: 2.1.28 bun: 1.3.3 concurrently: 9.2.1 @@ -31,3 +28,8 @@ catalog: typescript: 5.9.3 vite-tsconfig-paths: 5.1.4 vitest: 4.0.15 + +overrides: + '@playwright/test': 1.56.1 + import-in-the-middle: 2.0.0 + require-in-the-middle: 8.0.1 From 4993d8b88150f62c3aa9b1e20649ed6f176c0d1d Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 18:53:44 +0000 Subject: [PATCH 03/14] [autofix.ci] apply automated fixes --- packages/arkenv/package/package.json | 134 +++++++++++++-------------- packages/arkenv/tsconfig.json | 6 +- 2 files changed, 69 insertions(+), 71 deletions(-) diff --git a/packages/arkenv/package/package.json b/packages/arkenv/package/package.json index 0d7c0ba25..383c6e325 100644 --- a/packages/arkenv/package/package.json +++ b/packages/arkenv/package/package.json @@ -1,68 +1,68 @@ { - "name": "arkenv", - "type": "module", - "version": "0.7.6", - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "description": "Typesafe environment variables parsing and validation with ArkType", - "exports": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "files": [ - "dist" - ], - "keywords": [ - "pnpm", - "arktype", - "environment", - "variables", - "typesafe", - "validation" - ], - "license": "MIT", - "homepage": "https://arkenv.js.org", - "repository": { - "type": "git", - "url": "git+https://github.com/yamcodes/arkenv.git" - }, - "bugs": "https://github.com/yamcodes/arkenv/labels/arkenv", - "author": "Yam Borodetsky ", - "devDependencies": { - "@size-limit/esbuild-why": "11.2.0", - "@size-limit/preset-small-lib": "11.2.0", - "@types/node": "24.10.1", - "arktype": "2.1.28", - "size-limit": "11.2.0", - "tsdown": "0.16.8", - "typescript": "5.9.3", - "vitest": "4.0.15", - "@repo/scope": "0.0.0", - "@repo/types": "0.0.0", - "@repo/keywords": "0.0.0" - }, - "peerDependencies": { - "arktype": "^2.1.22" - }, - "size-limit": [ - { - "path": "dist/index.js", - "limit": "2 kB", - "import": "*", - "ignore": [ - "arktype" - ] - } - ], - "scripts": { - "build": "tsdown", - "size": "size-limit", - "test:once": "pnpm test", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist node_modules", - "test": "vitest", - "fix": "pnpm -w run fix" - } -} \ No newline at end of file + "name": "arkenv", + "type": "module", + "version": "0.7.6", + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "description": "Typesafe environment variables parsing and validation with ArkType", + "exports": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "files": [ + "dist" + ], + "keywords": [ + "pnpm", + "arktype", + "environment", + "variables", + "typesafe", + "validation" + ], + "license": "MIT", + "homepage": "https://arkenv.js.org", + "repository": { + "type": "git", + "url": "git+https://github.com/yamcodes/arkenv.git" + }, + "bugs": "https://github.com/yamcodes/arkenv/labels/arkenv", + "author": "Yam Borodetsky ", + "devDependencies": { + "@size-limit/esbuild-why": "11.2.0", + "@size-limit/preset-small-lib": "11.2.0", + "@types/node": "24.10.1", + "arktype": "2.1.28", + "size-limit": "11.2.0", + "tsdown": "0.16.8", + "typescript": "5.9.3", + "vitest": "4.0.15", + "@repo/scope": "0.0.0", + "@repo/types": "0.0.0", + "@repo/keywords": "0.0.0" + }, + "peerDependencies": { + "arktype": "^2.1.22" + }, + "size-limit": [ + { + "path": "dist/index.js", + "limit": "2 kB", + "import": "*", + "ignore": [ + "arktype" + ] + } + ], + "scripts": { + "build": "tsdown", + "size": "size-limit", + "test:once": "pnpm test", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist node_modules", + "test": "vitest", + "fix": "pnpm -w run fix" + } +} diff --git a/packages/arkenv/tsconfig.json b/packages/arkenv/tsconfig.json index 358688946..56793c550 100644 --- a/packages/arkenv/tsconfig.json +++ b/packages/arkenv/tsconfig.json @@ -15,7 +15,5 @@ "declaration": true, "declarationMap": true }, - "include": [ - "src" - ] -} \ No newline at end of file + "include": ["src"] +} From 87c020f68cc6b9cff90cbc46ce5e9f10e0a18c01 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Mon, 8 Dec 2025 23:56:40 +0500 Subject: [PATCH 04/14] chore: Update arktype peer dependency to catalog reference --- packages/internal/types/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/internal/types/package.json b/packages/internal/types/package.json index d050e5b08..c22e6e8b4 100644 --- a/packages/internal/types/package.json +++ b/packages/internal/types/package.json @@ -21,6 +21,6 @@ "typescript": "catalog:" }, "peerDependencies": { - "arktype": "workspace:*" + "arktype": "catalog:" } } From 0ec00a44ab6ddcbeeab0fde71e2be8f705ac7917 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Mon, 8 Dec 2025 23:57:49 +0500 Subject: [PATCH 05/14] refactor: remove redundant `as never` and `as any` type assertions in `createEnv` tests --- packages/arkenv/src/create-env.test.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/arkenv/src/create-env.test.ts b/packages/arkenv/src/create-env.test.ts index 5dbb37646..ba2d29d47 100644 --- a/packages/arkenv/src/create-env.test.ts +++ b/packages/arkenv/src/create-env.test.ts @@ -119,7 +119,7 @@ describe("env", () => { TEST_PORT: "number.port", }); - const env = createEnv(envSchema as never) as any; + const env = createEnv(envSchema); expect(env.TEST_STRING).toBe("hello"); expect(env.TEST_PORT).toBe(3000); @@ -134,7 +134,7 @@ describe("env", () => { TEST_PORT: "number.port", }); - const env = createEnv(envSchema as never) as any; + const env = createEnv(envSchema); // TypeScript should infer these correctly const str: string = env.TEST_STRING; @@ -152,12 +152,12 @@ describe("env", () => { }); // Use the same schema multiple times - const env1 = createEnv(envSchema as never, { + const env1 = createEnv(envSchema, { TEST_STRING: "first", - }) as any; - const env2 = createEnv(envSchema as never, { + }); + const env2 = createEnv(envSchema, { TEST_STRING: "second", - }) as any; + }); expect(env1.TEST_STRING).toBe("first"); expect(env2.TEST_STRING).toBe("second"); @@ -184,7 +184,7 @@ describe("env", () => { PORT: "8080", }; - const env = createEnv(envSchema as never, customEnv) as any; + const env = createEnv(envSchema, customEnv); expect(env.HOST).toBe("localhost"); expect(env.PORT).toBe(8080); From c990b8427ad7d7a2e088816ba29d8df343613942 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Mon, 8 Dec 2025 23:58:56 +0500 Subject: [PATCH 06/14] refactor: Rename `envSchema` to `Env` and remove redundant type annotations in tests. --- packages/arkenv/src/create-env.test.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/arkenv/src/create-env.test.ts b/packages/arkenv/src/create-env.test.ts index ba2d29d47..16ea81fb4 100644 --- a/packages/arkenv/src/create-env.test.ts +++ b/packages/arkenv/src/create-env.test.ts @@ -114,12 +114,12 @@ describe("env", () => { process.env.TEST_STRING = "hello"; process.env.TEST_PORT = "3000"; - const envSchema = type({ + const Env = type({ TEST_STRING: "string", TEST_PORT: "number.port", }); - const env = createEnv(envSchema); + const env = createEnv(Env); expect(env.TEST_STRING).toBe("hello"); expect(env.TEST_PORT).toBe(3000); @@ -129,16 +129,16 @@ describe("env", () => { process.env.TEST_STRING = "hello"; process.env.TEST_PORT = "3000"; - const envSchema = type({ + const Env = type({ TEST_STRING: "string", TEST_PORT: "number.port", }); - const env = createEnv(envSchema); + const env = createEnv(Env); // TypeScript should infer these correctly - const str: string = env.TEST_STRING; - const port: number = env.TEST_PORT; + const str = env.TEST_STRING; + const port = env.TEST_PORT; expect(str).toBe("hello"); expect(port).toBe(3000); @@ -147,15 +147,15 @@ describe("env", () => { it("should allow reusing the same type definition multiple times", () => { process.env.TEST_STRING = "hello"; - const envSchema = type({ + const Env = type({ TEST_STRING: "string", }); // Use the same schema multiple times - const env1 = createEnv(envSchema, { + const env1 = createEnv(Env, { TEST_STRING: "first", }); - const env2 = createEnv(envSchema, { + const env2 = createEnv(Env, { TEST_STRING: "second", }); @@ -166,15 +166,15 @@ describe("env", () => { it("should throw when type definition validation fails", () => { process.env.INVALID_PORT = "not-a-port"; - const envSchema = type({ + const Env = type({ INVALID_PORT: "number.port", }); - expect(() => createEnv(envSchema as never)).toThrow(/INVALID_PORT/); + expect(() => createEnv(Env as never)).toThrow(/INVALID_PORT/); }); it("should work with custom environment and type definitions", () => { - const envSchema = type({ + const Env = type({ HOST: "string.host", PORT: "number.port", }); @@ -184,7 +184,7 @@ describe("env", () => { PORT: "8080", }; - const env = createEnv(envSchema, customEnv); + const env = createEnv(Env, customEnv); expect(env.HOST).toBe("localhost"); expect(env.PORT).toBe(8080); From 70193a448b1175b0b68074aad2e8d24f0b1bf323 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 00:03:00 +0500 Subject: [PATCH 07/14] feat: Add `@repo/scope` dependency and integrate its type into `EnvSchemaWithType`. --- packages/internal/types/package.json | 2 ++ packages/internal/types/src/schema.ts | 3 ++- pnpm-lock.yaml | 3 +++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/internal/types/package.json b/packages/internal/types/package.json index c22e6e8b4..09234a19b 100644 --- a/packages/internal/types/package.json +++ b/packages/internal/types/package.json @@ -17,10 +17,12 @@ "fix": "pnpm -w run fix" }, "devDependencies": { + "@repo/scope": "workspace:*", "arktype": "catalog:", "typescript": "catalog:" }, "peerDependencies": { + "@repo/scope": "workspace:*", "arktype": "catalog:" } } diff --git a/packages/internal/types/src/schema.ts b/packages/internal/types/src/schema.ts index af52b115e..35fb602f4 100644 --- a/packages/internal/types/src/schema.ts +++ b/packages/internal/types/src/schema.ts @@ -1,5 +1,6 @@ +import type { $ } from "@repo/scope"; import { type Type, type } from "arktype"; export const SchemaShape = type({ "[string]": "unknown" }); export type SchemaShape = typeof SchemaShape.infer; -export type EnvSchemaWithType = Type; +export type EnvSchemaWithType = Type; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2d545a3ce..78868cafc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -509,6 +509,9 @@ importers: packages/internal/types: devDependencies: + '@repo/scope': + specifier: workspace:* + version: link:../scope arktype: specifier: 'catalog:' version: 2.1.28 From e185e6c0361ca1c2c318de86f568f203a67237b7 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 00:07:00 +0500 Subject: [PATCH 08/14] chore: remove explicit sourcemap disabling from tsdown config --- packages/arkenv/tsdown.config.ts | 1 - packages/internal/keywords/package.json | 2 +- packages/internal/scope/package.json | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/arkenv/tsdown.config.ts b/packages/arkenv/tsdown.config.ts index eddd6ae49..8fc08e8db 100644 --- a/packages/arkenv/tsdown.config.ts +++ b/packages/arkenv/tsdown.config.ts @@ -7,5 +7,4 @@ export default defineConfig({ dts: { resolve: ["@repo/types"], }, - sourcemap: false, }); diff --git a/packages/internal/keywords/package.json b/packages/internal/keywords/package.json index f98305fe0..765172aef 100644 --- a/packages/internal/keywords/package.json +++ b/packages/internal/keywords/package.json @@ -39,6 +39,6 @@ "typescript": "catalog:" }, "peerDependencies": { - "arktype": "workspace:*" + "arktype": "catalog:" } } diff --git a/packages/internal/scope/package.json b/packages/internal/scope/package.json index 0404306e1..ab9171d64 100644 --- a/packages/internal/scope/package.json +++ b/packages/internal/scope/package.json @@ -42,6 +42,6 @@ "typescript": "catalog:" }, "peerDependencies": { - "arktype": "workspace:*" + "arktype": "catalog:" } } From 71dea7eea945e9a50fa55f5e433b5e013ae4b4e2 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 00:15:29 +0500 Subject: [PATCH 09/14] test: remove unnecessary `as never` type assertion from createEnv error test. --- packages/arkenv/src/create-env.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arkenv/src/create-env.test.ts b/packages/arkenv/src/create-env.test.ts index 16ea81fb4..758831138 100644 --- a/packages/arkenv/src/create-env.test.ts +++ b/packages/arkenv/src/create-env.test.ts @@ -170,7 +170,7 @@ describe("env", () => { INVALID_PORT: "number.port", }); - expect(() => createEnv(Env as never)).toThrow(/INVALID_PORT/); + expect(() => createEnv(Env)).toThrow(/INVALID_PORT/); }); it("should work with custom environment and type definitions", () => { From 69c592a282ad0eb9841867573208ec109f8f1a28 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 00:49:07 +0500 Subject: [PATCH 10/14] feat: Add `arkenv#test` task configuration with build and transit dependencies. --- packages/arkenv/package/LICENSE | 21 ----- packages/arkenv/package/README.md | 133 --------------------------- packages/arkenv/package/package.json | 68 -------------- packages/arkenv/turbo.json | 23 ++++- 4 files changed, 20 insertions(+), 225 deletions(-) delete mode 100644 packages/arkenv/package/LICENSE delete mode 100644 packages/arkenv/package/README.md delete mode 100644 packages/arkenv/package/package.json diff --git a/packages/arkenv/package/LICENSE b/packages/arkenv/package/LICENSE deleted file mode 100644 index 7ce78d74d..000000000 --- a/packages/arkenv/package/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 Yam Borodetsky - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/packages/arkenv/package/README.md b/packages/arkenv/package/README.md deleted file mode 100644 index 30a008f3b..000000000 --- a/packages/arkenv/package/README.md +++ /dev/null @@ -1,133 +0,0 @@ -

- ArkEnv Logo -

ArkEnv

-
-

Typesafe environment variables
- powered by ArkType

- Test Status - npm bundle size - TypeScript - Powered By ArkType - Node.js - Bun - Vite - Chat on Discord -
-

-

Proud member of the ArkType ecosystem

- -

- ArkEnv Demo -

- -
-
-
- -### [Read the docs →](https://arkenv.js.org/docs/arkenv) - -
-
- -## Introduction - -> [!TIP] -> 📖 **Reading this on GitHub?** Check out [this page in our docs](https://arkenv.js.org/docs/arkenv) to hover over code blocks and get type hints! - -ArkEnv is an environment variable parser powered by [ArkType](https://arktype.io/), TypeScript's 1:1 validator. ArkEnv lets you use familiar TypeScript-like syntax to create a ready to use, typesafe environment variable object: - -```ts twoslash -import arkenv from 'arkenv'; - -const env = arkenv({ - HOST: "string.host", // valid IP address or localhost - PORT: "number.port", // valid port number (0-65535) - NODE_ENV: "'development' | 'production' | 'test'", -}); - -// Hover to see ✨exact✨ types -const host = env.HOST; -const port = env.PORT; -const nodeEnv = env.NODE_ENV; -``` - -With ArkEnv, your environment variables are **guaranteed to match your schema**. If any variable is incorrect or missing, the app won't start and a clear error will be thrown: - -```bash title="Terminal" -ArkEnvError: Errors found while validating environment variables - HOST must be a string or "localhost" (was missing) - PORT must be an integer between 0 and 65535 (was "hello") -``` - -## Features - -- Zero external dependencies -- Works in Node.js, Bun, and Vite -- Tiny: <1kB gzipped -- Build-time and runtime validation -- Single import, zero config for most projects -- Validated, defaultable, typesafe environment variables -- Powered by ArkType, TypeScript's 1:1 validator -- Optimized from editor to runtime - -## Installation - -
-npm - -```sh -npm install arkenv arktype -``` -
- -
-pnpm - -```sh -pnpm add arkenv arktype -``` -
- -
-Yarn - -```sh -yarn add arkenv arktype -``` -
- -
-Bun - -```sh -bun add arkenv arktype -``` -
- -:rocket: **Let's get started!** Read the [2-minute setup guide](https://arkenv.js.org/docs/quickstart) or [start with an example](https://arkenv.js.org/docs/examples). - -> [!TIP] -> Improve your DX with *syntax highlighting* in [VS Code & Cursor](https://arkenv.js.org/docs/integrations/vscode) or [JetBrains IDEs](https://arkenv.js.org/docs/integrations/jetbrains). - -## Requirements - -- **TypeScript >= 5.1** and [anything else required by ArkType](https://arktype.io/docs/intro/setup#installation) -- [**Modern TypeScript module resolution**](https://www.typescriptlang.org/tsconfig/#moduleResolution). One of the following is required in your `tsconfig.json`: - - `"moduleResolution": "bundler"` - Recommended for modern bundlers (Vite, Next.js, etc.). Supplied by default when using `"module": "Preserve"`. - - `"moduleResolution": "node16"` or `"nodenext"` - For Node.js projects. Supplied by default when using a matching `"module"` value. -- Tested on [**Node.js LTS** and **Current**](https://github.com/yamcodes/arkenv/tree/main/examples/basic), [**Bun 1.3.2**](https://github.com/yamcodes/arkenv/tree/main/examples/with-bun), and [**Vite** from **2.9.18** to **7.x**](https://github.com/yamcodes/arkenv/tree/main/examples/with-vite-react-ts). Older versions may work but are not officially supported - -## Plugins - -Beyond [the core package](https://arkenv.js.org/docs/arkenv), we also provide plugins for frameworks that require a specific implementation to adhere to best practices. - -- [@arkenv/vite-plugin](https://arkenv.js.org/docs/vite-plugin) -- [@arkenv/bun-plugin](https://arkenv.js.org/docs/bun-plugin) - -## Supporting ArkEnv - -If you love ArkEnv, you can support the project by **starring it on GitHub**! - -You are also welcome to directly [contribute to the project's development](https://github.com/yamcodes/arkenv/blob/main/CONTRIBUTING.md). - -## [Thanks / Inspiration](https://github.com/yamcodes/arkenv/blob/main/THANKS.md) diff --git a/packages/arkenv/package/package.json b/packages/arkenv/package/package.json deleted file mode 100644 index 383c6e325..000000000 --- a/packages/arkenv/package/package.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "name": "arkenv", - "type": "module", - "version": "0.7.6", - "main": "./dist/index.cjs", - "module": "./dist/index.js", - "types": "./dist/index.d.ts", - "description": "Typesafe environment variables parsing and validation with ArkType", - "exports": { - "types": "./dist/index.d.ts", - "import": "./dist/index.js", - "require": "./dist/index.cjs" - }, - "files": [ - "dist" - ], - "keywords": [ - "pnpm", - "arktype", - "environment", - "variables", - "typesafe", - "validation" - ], - "license": "MIT", - "homepage": "https://arkenv.js.org", - "repository": { - "type": "git", - "url": "git+https://github.com/yamcodes/arkenv.git" - }, - "bugs": "https://github.com/yamcodes/arkenv/labels/arkenv", - "author": "Yam Borodetsky ", - "devDependencies": { - "@size-limit/esbuild-why": "11.2.0", - "@size-limit/preset-small-lib": "11.2.0", - "@types/node": "24.10.1", - "arktype": "2.1.28", - "size-limit": "11.2.0", - "tsdown": "0.16.8", - "typescript": "5.9.3", - "vitest": "4.0.15", - "@repo/scope": "0.0.0", - "@repo/types": "0.0.0", - "@repo/keywords": "0.0.0" - }, - "peerDependencies": { - "arktype": "^2.1.22" - }, - "size-limit": [ - { - "path": "dist/index.js", - "limit": "2 kB", - "import": "*", - "ignore": [ - "arktype" - ] - } - ], - "scripts": { - "build": "tsdown", - "size": "size-limit", - "test:once": "pnpm test", - "typecheck": "tsc --noEmit", - "clean": "rimraf dist node_modules", - "test": "vitest", - "fix": "pnpm -w run fix" - } -} diff --git a/packages/arkenv/turbo.json b/packages/arkenv/turbo.json index 3e34e8a2f..d52b0ff49 100644 --- a/packages/arkenv/turbo.json +++ b/packages/arkenv/turbo.json @@ -1,10 +1,27 @@ { - "extends": ["//"], + "extends": [ + "//" + ], "tasks": { // Defined here and not in the root turbo.json to avoid building all packages when running `turbo run size`. "size": { - "dependsOn": ["build"], + "dependsOn": [ + "build" + ], "cache": false + }, + // Vitest + // See: https://turborepo.com/docs/guides/tools/vitest + "arkenv#test": { + "inputs": [ + "$TURBO_DEFAULT$", + "$TURBO_ROOT$/vitest.config.ts" + ], + "dependsOn": [ + "@repo/scope#build", + "@repo/keywords#build", + "transit" + ] } } -} +} \ No newline at end of file From 8f70b5627ae8f7dae348757788c02acc4f374f0e Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Mon, 8 Dec 2025 19:49:50 +0000 Subject: [PATCH 11/14] [autofix.ci] apply automated fixes --- packages/arkenv/turbo.json | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/packages/arkenv/turbo.json b/packages/arkenv/turbo.json index d52b0ff49..c46d0fc1f 100644 --- a/packages/arkenv/turbo.json +++ b/packages/arkenv/turbo.json @@ -1,27 +1,16 @@ { - "extends": [ - "//" - ], + "extends": ["//"], "tasks": { // Defined here and not in the root turbo.json to avoid building all packages when running `turbo run size`. "size": { - "dependsOn": [ - "build" - ], + "dependsOn": ["build"], "cache": false }, // Vitest // See: https://turborepo.com/docs/guides/tools/vitest "arkenv#test": { - "inputs": [ - "$TURBO_DEFAULT$", - "$TURBO_ROOT$/vitest.config.ts" - ], - "dependsOn": [ - "@repo/scope#build", - "@repo/keywords#build", - "transit" - ] + "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/vitest.config.ts"], + "dependsOn": ["@repo/scope#build", "@repo/keywords#build", "transit"] } } -} \ No newline at end of file +} From abf3516514baf413e5ffd96aca9bf3cce62d49f6 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 00:52:55 +0500 Subject: [PATCH 12/14] refactor: Rename `arkenv#test` task to `test` in turbo.json. --- packages/arkenv/turbo.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/arkenv/turbo.json b/packages/arkenv/turbo.json index c46d0fc1f..7f2c79df5 100644 --- a/packages/arkenv/turbo.json +++ b/packages/arkenv/turbo.json @@ -8,7 +8,7 @@ }, // Vitest // See: https://turborepo.com/docs/guides/tools/vitest - "arkenv#test": { + "test": { "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/vitest.config.ts"], "dependsOn": ["@repo/scope#build", "@repo/keywords#build", "transit"] } From e48aceaa8baba9e8982f91cf94c2fbac33ae6399 Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 01:05:58 +0500 Subject: [PATCH 13/14] chore: Consolidate and update test pipeline dependencies in turbo.json configurations. --- packages/arkenv/turbo.json | 6 ------ turbo.json | 2 +- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/arkenv/turbo.json b/packages/arkenv/turbo.json index 7f2c79df5..3e34e8a2f 100644 --- a/packages/arkenv/turbo.json +++ b/packages/arkenv/turbo.json @@ -5,12 +5,6 @@ "size": { "dependsOn": ["build"], "cache": false - }, - // Vitest - // See: https://turborepo.com/docs/guides/tools/vitest - "test": { - "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/vitest.config.ts"], - "dependsOn": ["@repo/scope#build", "@repo/keywords#build", "transit"] } } } diff --git a/turbo.json b/turbo.json index bd82d7108..7d5f70e6f 100644 --- a/turbo.json +++ b/turbo.json @@ -39,7 +39,7 @@ // See: https://turborepo.com/docs/guides/tools/vitest "test": { "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/vitest.config.ts"], - "dependsOn": ["transit"] + "dependsOn": ["transit", "@repo/scope#build", "@repo/keywords#build"] }, // Transit Nodes // See: From 644e1f416c69289317efef7cde23460eb8da713b Mon Sep 17 00:00:00 2001 From: Yam C Borodetsky Date: Tue, 9 Dec 2025 01:08:29 +0500 Subject: [PATCH 14/14] chore: Add build step to CI test workflow and simplify turbo test dependencies. --- .github/workflows/test.yml | 2 ++ turbo.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e49c4a54d..2918e81a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -33,6 +33,8 @@ jobs: cache: pnpm - name: Install dependencies run: pnpm install + - name: Build packages + run: pnpm run build --filter=./packages/* - run: pnpm run test test-typesafety: diff --git a/turbo.json b/turbo.json index 7d5f70e6f..bd82d7108 100644 --- a/turbo.json +++ b/turbo.json @@ -39,7 +39,7 @@ // See: https://turborepo.com/docs/guides/tools/vitest "test": { "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/vitest.config.ts"], - "dependsOn": ["transit", "@repo/scope#build", "@repo/keywords#build"] + "dependsOn": ["transit"] }, // Transit Nodes // See: