diff --git a/.changeset/selectconfig-sync-config.md b/.changeset/selectconfig-sync-config.md
new file mode 100644
index 0000000..be4dd5d
--- /dev/null
+++ b/.changeset/selectconfig-sync-config.md
@@ -0,0 +1,20 @@
+---
+"@vlandoss/env": minor
+---
+
+Add `selectConfig(configs)` — a synchronous, runtime-agnostic counterpart to `loadConfig` for config files that can't use top-level `await`.
+
+Some tooling loads its config file via `require()` or by bundling it to CJS, where `await loadConfig(...)` is rejected (`ERR_REQUIRE_ASYNC_MODULE` / "top-level await is not supported with the cjs output format"). `selectConfig` pairs with static `import`s and picks the entry matching `envName()` — no dynamic `import()`, no `await`:
+
+```ts
+import { defineEnv, selectConfig } from "@vlandoss/env";
+import development from "./config/development.ts";
+import production from "./config/production.ts";
+
+export const env = defineEnv({
+ schema: Env,
+ config: selectConfig({ development, production }),
+});
+```
+
+It throws when the current env has no entry. See the new `config-cjs` example and the `loadConfig` guide for details.
diff --git a/docsite/content/docs/api-reference/core.mdx b/docsite/content/docs/api-reference/core.mdx
index 7100a91..6c09472 100644
--- a/docsite/content/docs/api-reference/core.mdx
+++ b/docsite/content/docs/api-reference/core.mdx
@@ -12,6 +12,7 @@ The `@vlandoss/env` entrypoint is the only one you always import — it contains
| ------------ | -------- | ------------------------------------------------------------------------------------ |
| `schema` | Function | Declare the contract — branches and Standard Schema leaves. |
| `defineEnv` | Function | Merge config + environment variables, validate, and return a typed `env` object. |
+| `selectConfig` | Function | Synchronously pick the current env's config from a map of statically-imported configs. The no-`await` counterpart to `loadConfig` for config files that tooling loads via `require()` or bundles to CJS. |
| `envName` | Function | Detect the current environment name across runtimes — `development`, `production`, … |
| `readEnv` | Function | Read raw values from the active source (`process.env` on the server, `window.__env` in the browser). Returns `{}` on runtimes without `process` (Workers) — pass `runtimeEnv` to `defineEnv` instead. |
| `Config` | Type | The input shape of per-environment config files for a given schema. |
diff --git a/docsite/content/docs/guides/fs-loadconfig.mdx b/docsite/content/docs/guides/fs-loadconfig.mdx
index 1f9eb90..7a27745 100644
--- a/docsite/content/docs/guides/fs-loadconfig.mdx
+++ b/docsite/content/docs/guides/fs-loadconfig.mdx
@@ -87,8 +87,30 @@ The pattern **must** contain `{env}`, and it throws if the resolved file doesn't
`loadConfig` always reads `envName()`. To load a non-current env, set `ENV=…` in the process env before calling — there's no `env` override on the function itself.
+## Config files that can't use top-level `await` (`selectConfig`)
+
+`loadConfig` is async — it uses dynamic `import()` under the hood, which is irreducibly asynchronous. That's fine in app code, but it breaks in **config files that tooling loads synchronously** — files pulled in via `require()` or bundled to CJS, where a top-level `await` is rejected (`ERR_REQUIRE_ASYNC_MODULE`, or a build-time _"top-level await is not supported with the cjs output format"_). Some database/ORM tooling configs fall in this bucket.
+
+There's no runtime-agnostic way to make `loadConfig` synchronous — a sync module load means `require()`, which only exists on CJS-capable runtimes and can't load `.ts` without a transpile hook. So for these files, use **`selectConfig`** instead: the synchronous, runtime-agnostic counterpart. Pair it with **static `import`s** so the runtime/bundler resolves and transpiles each config at parse time — no dynamic import, no `await`:
+
+```ts title="src/env/index.ts (imported by the tool's config file)"
+import { defineEnv, selectConfig } from "@vlandoss/env";
+import development from "./config/development.ts";
+import production from "./config/production.ts";
+import { Env } from "./env/schema.ts";
+
+export const env = defineEnv({
+ schema: Env,
+ config: selectConfig({ development, production }),
+});
+```
+
+`selectConfig` picks the entry matching `envName()` and **throws** if there's none (the map is explicit, so a miss is almost always a typo). Like `loadConfig`, it has no `env` override — set `ENV=…` first to select a non-current env.
+
+See the [`config-cjs` example](https://github.com/variableland/env/tree/main/examples/config-cjs) for a runnable reproduction (it asserts the `loadConfig` + top-level await variant throws under `require()`, and the `selectConfig` variant loads).
+
## Tradeoffs
- Requires a runtime with a filesystem — works on Node, Bun, and Deno; not on Workers/Edge. Use the [Vite plugin](/docs/api-reference/vite) for those instead.
- Loading `.ts` / `.mts` config files depends on the host runtime's TypeScript support: native in Bun and Deno, behind `--experimental-strip-types` in Node ≥22.6 (stable in 23.6). `.js` / `.mjs` / `.json` work everywhere.
-- The startup is async because `loadConfig` is async. If you can't use top-level `await`, hoist the boot into an `async function main()` and call it from your entrypoint.
+- The startup is async because `loadConfig` is async. If you can't use top-level `await`, hoist the boot into an `async function main()` and call it from your entrypoint — or, for config files loaded synchronously by tooling, use [`selectConfig`](#config-files-that-cant-use-top-level-await-selectconfig) with static imports.
diff --git a/examples/README.md b/examples/README.md
index b8176dd..b17b90f 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,6 +1,6 @@
# `@vlandoss/env` examples
-Real-world usage examples for [`@vlandoss/env`](../package). **Each example is runtime-isolated**: it declares its own runtime in a local `mise.toml`, brings its own package manager and lockfile, and consumes `@vlandoss/env` from a packed tarball — exactly as an external consumer would. End-to-end tests use Playwright and exercise real `env` failure modes (missing required vars, wrong types, per-mode config isolation, SSR↔client hydration drift).
+Real-world usage examples for [`@vlandoss/env`](../package). **Each example is runtime-isolated**: it declares its own runtime in a local `mise.toml`, brings its own package manager and lockfile, and consumes `@vlandoss/env` from a packed tarball — exactly as an external consumer would. End-to-end tests use Playwright (and `node:test` for the `config-cjs` loader case) and exercise real `env` failure modes (missing required vars, wrong types, per-mode config isolation, SSR↔client hydration drift, config files that can't use top-level await).
| Example | Runtime | Package manager | `env` entries exercised |
| -------------------------------------------- | ---------------------------------------- | --------------- | -------------------------------------------------------------------------------- |
@@ -13,8 +13,9 @@ Real-world usage examples for [`@vlandoss/env`](../package). **Each example is r
| [`spa-vite-dynamic`](./spa-vite-dynamic) | Node.js (Vite) | pnpm | `@vlandoss/env` (dynamic `import()`) + `@vlandoss/env/vite` (for `__ENV_NAME__`) |
| [`ssr-react-router`](./ssr-react-router) | Node.js (React Router 7) | pnpm | `@vlandoss/env` + `@vlandoss/env/fs` + `@vlandoss/env/react` (``) |
| [`ssr-tanstack-start`](./ssr-tanstack-start) | Node.js (TanStack Start) | pnpm | `@vlandoss/env` + `@vlandoss/env/vite` + `@vlandoss/env/react` |
+| [`config-cjs`](./config-cjs) | Node.js 22.22.3 | pnpm | `@vlandoss/env` (`selectConfig`) + `@vlandoss/env/fs` (`loadConfig`) + `@vlandoss/env/zod` |
-The `backend-*`, `worker-*`, and `edge-nextjs` examples drive Playwright in HTTP-only mode (the `request` fixture); the 4 SPA / SSR examples drive a real chromium browser.
+The `backend-*`, `worker-*`, and `edge-nextjs` examples drive Playwright in HTTP-only mode (the `request` fixture); the 4 SPA / SSR examples drive a real chromium browser. `config-cjs` is the odd one out — it runs `node:test` to assert config-file loading semantics under `require()` (no browser, no server).
## How `@vlandoss/env` is consumed
@@ -33,7 +34,7 @@ The tarball is generated by `mise run env:pack` (which calls `pnpm pack` inside
```sh
mise install # node + pnpm; bun/deno installed per-example as needed
mise run setup # bootstraps everything: tools, root deps, env tarball, all examples, Playwright browsers
-mise run test:e2e # runs all 9 e2e suites
+mise run test:e2e # runs all 10 e2e suites
```
## Run a single example
diff --git a/examples/config-cjs/.gitignore b/examples/config-cjs/.gitignore
new file mode 100644
index 0000000..3c3629e
--- /dev/null
+++ b/examples/config-cjs/.gitignore
@@ -0,0 +1 @@
+node_modules
diff --git a/examples/config-cjs/README.md b/examples/config-cjs/README.md
new file mode 100644
index 0000000..6e40f29
--- /dev/null
+++ b/examples/config-cjs/README.md
@@ -0,0 +1,54 @@
+# config-cjs
+
+Loading `@vlandoss/env` from a **config file that can't use top-level `await`** —
+the kind of config file that tooling pulls in via `require()` or bundles to CJS
+(e.g. some ORM / database migration tooling).
+
+## The problem
+
+```ts title="db.config.broken.mts"
+const config = await loadConfig(Env); // ⛔ top-level await
+```
+
+`loadConfig` is async (it uses dynamic `import()` under the hood, which is
+irreducibly asynchronous). The top-level `await` makes the module an **async ES
+module**. When a tool loads the config synchronously, that's rejected:
+
+- `require()` → `ERR_REQUIRE_ASYNC_MODULE` (Node ≥22.12, where `require(esm)` is on)
+- esbuild/CJS bundle → _"Top-level await is currently not supported with the cjs output format"_
+
+## The fix: `selectConfig`
+
+`selectConfig` is the synchronous, runtime-agnostic counterpart to `loadConfig`.
+Pair it with **static `import`s** so the runtime/bundler resolves and transpiles
+each config file at parse time — no dynamic import, no `await`:
+
+```ts title="src/env/index.ts"
+import { defineEnv, selectConfig } from "@vlandoss/env";
+import development from "../../config/development.ts";
+import production from "../../config/production.ts";
+import { Env } from "./schema.ts";
+
+export const env = defineEnv({
+ schema: Env,
+ config: selectConfig({ development, production }),
+});
+```
+
+`db.config.mts` imports that `env` and exports a plain config object — no
+top-level await anywhere — so `require()` loads it cleanly.
+
+## Run it
+
+```sh
+mise run //examples/config-cjs:test:e2e
+```
+
+[`test/loader.test.ts`](./test/loader.test.ts) `require()`s both configs and
+asserts: the `selectConfig` one loads, the `loadConfig` + top-level await one
+throws `ERR_REQUIRE_ASYNC_MODULE`.
+
+> Pinned to Node 22.22.3 — `require(esm)` is default from 22.12 and native TS
+> stripping from 22.18, so this is the lowest LTS line where the reproduction
+> (and the fix) behaves as described. Node 20 would throw `ERR_REQUIRE_ESM`
+> instead, which is a different failure.
diff --git a/examples/config-cjs/biome.json b/examples/config-cjs/biome.json
new file mode 100644
index 0000000..5b190a0
--- /dev/null
+++ b/examples/config-cjs/biome.json
@@ -0,0 +1,4 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
+ "extends": ["@rrlab/biome-config"]
+}
diff --git a/examples/config-cjs/config/development.ts b/examples/config-cjs/config/development.ts
new file mode 100644
index 0000000..1087937
--- /dev/null
+++ b/examples/config-cjs/config/development.ts
@@ -0,0 +1,6 @@
+import type { EnvConfig } from "../src/env/schema.ts";
+
+export default {
+ server: { PORT: 3001, HOST: "127.0.0.1" },
+ db: { URL: "postgres://localhost/dev", LOGGING: true },
+} satisfies EnvConfig;
diff --git a/examples/config-cjs/config/production.ts b/examples/config-cjs/config/production.ts
new file mode 100644
index 0000000..36f7d90
--- /dev/null
+++ b/examples/config-cjs/config/production.ts
@@ -0,0 +1,6 @@
+import type { EnvConfig } from "../src/env/schema.ts";
+
+export default {
+ server: { PORT: 3001, HOST: "0.0.0.0" },
+ db: { LOGGING: false },
+} satisfies EnvConfig;
diff --git a/examples/config-cjs/db.config.broken.mts b/examples/config-cjs/db.config.broken.mts
new file mode 100644
index 0000000..0c56642
--- /dev/null
+++ b/examples/config-cjs/db.config.broken.mts
@@ -0,0 +1,19 @@
+import { defineEnv } from "@vlandoss/env";
+import { loadConfig } from "@vlandoss/env/fs";
+import { Env } from "./src/env/schema.ts";
+
+// ❌ The pattern that BREAKS in a config file. `loadConfig` is async, so this
+// top-level `await` turns the module into an async ES module. Tooling that
+// loads the config via `require()` throws `ERR_REQUIRE_ASYNC_MODULE`, and
+// bundlers targeting CJS reject the top-level await at build time.
+//
+// Kept here only to demonstrate the failure — see `db.config.mts` for the
+// `selectConfig` fix and `test/loader.test.ts` for the assertion.
+const config = await loadConfig(Env);
+
+const env = defineEnv({ schema: Env, config });
+
+export default {
+ dialect: "postgresql",
+ dbCredentials: { url: env.db.URL },
+} as const;
diff --git a/examples/config-cjs/db.config.mts b/examples/config-cjs/db.config.mts
new file mode 100644
index 0000000..086c000
--- /dev/null
+++ b/examples/config-cjs/db.config.mts
@@ -0,0 +1,14 @@
+import { env } from "./src/env/index.ts";
+
+// Stand-in for an ORM / database tooling config: a file that tooling loads via
+// `require()` or bundles to CJS. Because `env` is built with `selectConfig`
+// (synchronous), importing it here adds no top-level `await`, so this module
+// loads cleanly in a CJS / no-TLA context.
+//
+// Shape the default export however your tool expects — this mirrors a typical
+// database migration config.
+export default {
+ dialect: "postgresql",
+ dbCredentials: { url: env.db.URL },
+ verbose: env.db.LOGGING,
+} as const;
diff --git a/examples/config-cjs/mise.toml b/examples/config-cjs/mise.toml
new file mode 100644
index 0000000..09d0f4c
--- /dev/null
+++ b/examples/config-cjs/mise.toml
@@ -0,0 +1,37 @@
+# Pinned to 22.22.3 on purpose: this example reproduces a `require()`-time
+# failure that only surfaces with `require(esm)` (default since 22.12) AND native
+# TypeScript stripping (default since 22.18). An older 22.x would either resolve
+# the .mts as plain JS (syntax error) or report a different error code.
+[tools]
+node = "22.22.3"
+pnpm = "10.30.3"
+
+[env]
+_.path = ["{{config_root}}/node_modules/.bin"]
+
+[tasks.setup]
+description = "Pack env + install deps (pnpm)"
+depends = ["//:env:pack"]
+sources = [
+ "package.json",
+ "pnpm-lock.yaml",
+ "{{config_root}}/../../package/.local/vlandoss-env.tgz",
+]
+outputs = ["node_modules/.modules.yaml"]
+run = "pnpm install --ignore-workspace --no-frozen-lockfile"
+
+[tasks.reinstall]
+description = "Force-reinstall the env tarball"
+depends = ["//:env:pack"]
+run = "pnpm install --ignore-workspace --no-frozen-lockfile --force"
+
+[tasks."test:e2e"]
+description = "Assert the config loads under require() (selectConfig) and that the loadConfig+TLA variant is rejected"
+depends = ["setup"]
+env = { ENV = "development" }
+run = "node --test test/loader.test.ts"
+
+[tasks.check]
+description = "JS & TS check"
+depends = ["setup"]
+run = "rr check"
diff --git a/examples/config-cjs/package.json b/examples/config-cjs/package.json
new file mode 100644
index 0000000..e0f2561
--- /dev/null
+++ b/examples/config-cjs/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "example-config-cjs",
+ "version": "0.0.0",
+ "private": true,
+ "type": "module",
+ "dependencies": {
+ "@vlandoss/env": "file:../../package/.local/vlandoss-env.tgz",
+ "zod": "4.3.6"
+ },
+ "devDependencies": {
+ "@biomejs/biome": "^2.0.0",
+ "@rrlab/biome-config": "^0.0.2",
+ "@rrlab/biome-plugin": "^0.1.1",
+ "@rrlab/cli": "0.0.3",
+ "@rrlab/ts-config": "^0.0.2",
+ "@rrlab/ts-plugin": "^0.1.1",
+ "@types/node": "24.12.4",
+ "typescript": "^6.0.0"
+ }
+}
diff --git a/examples/config-cjs/pnpm-lock.yaml b/examples/config-cjs/pnpm-lock.yaml
new file mode 100644
index 0000000..92ce6c3
--- /dev/null
+++ b/examples/config-cjs/pnpm-lock.yaml
@@ -0,0 +1,990 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@vlandoss/env':
+ specifier: file:../../package/.local/vlandoss-env.tgz
+ version: file:../../package/.local/vlandoss-env.tgz(zod@4.3.6)
+ zod:
+ specifier: 4.3.6
+ version: 4.3.6
+ devDependencies:
+ '@biomejs/biome':
+ specifier: ^2.0.0
+ version: 2.4.15
+ '@rrlab/biome-config':
+ specifier: ^0.0.2
+ version: 0.0.2(@biomejs/biome@2.4.15)
+ '@rrlab/biome-plugin':
+ specifier: ^0.1.1
+ version: 0.1.1(@biomejs/biome@2.4.15)(@pnpm/logger@1001.0.1)(@rrlab/cli@0.0.3(@pnpm/logger@1001.0.1))
+ '@rrlab/cli':
+ specifier: 0.0.3
+ version: 0.0.3(@pnpm/logger@1001.0.1)
+ '@rrlab/ts-config':
+ specifier: ^0.0.2
+ version: 0.0.2(@types/node@24.12.4)(typescript@6.0.3)
+ '@rrlab/ts-plugin':
+ specifier: ^0.1.1
+ version: 0.1.1(@pnpm/logger@1001.0.1)(@rrlab/cli@0.0.3(@pnpm/logger@1001.0.1))(typescript@6.0.3)
+ '@types/node':
+ specifier: 24.12.4
+ version: 24.12.4
+ typescript:
+ specifier: ^6.0.0
+ version: 6.0.3
+
+packages:
+
+ '@babel/code-frame@7.29.7':
+ resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-string-parser@7.29.7':
+ resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-validator-identifier@7.29.7':
+ resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/parser@7.29.7':
+ resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==}
+ engines: {node: '>=6.0.0'}
+ hasBin: true
+
+ '@babel/types@7.29.7':
+ resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==}
+ engines: {node: '>=6.9.0'}
+
+ '@bgotink/kdl@0.4.0':
+ resolution: {integrity: sha512-F0uJCjo5FQvFdcGF5QbYVNfcGiRWlocuzyIdQxottZF2+gu6L2xjMGEu9PIpse2hifAca/19vIospgaETCKxIg==}
+
+ '@biomejs/biome@2.4.15':
+ resolution: {integrity: sha512-j5VH3a/h/HXTKBM50MDMxRCzkeLv9S2XJcW2WgnZT1+xyisi+0bISrXR82gCX+8S9lvK0skEvHJRN+3Ktr2hlw==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@2.4.15':
+ resolution: {integrity: sha512-rF3PPqLq1yoST79zaQbDjVJwsuIeci/O+9bgNmC5QpgOqz6aqYuzA4abyAGx+mgyiDXn4A049xAN8gijbuR1Qg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@2.4.15':
+ resolution: {integrity: sha512-/5KHXYMfSJs1fNXiX30xFtI8JcCFV6zaVVLxOa0M2sfqBKHkpQhRTv94yxQWxeTY2lzo2OuTlNvPC+hDQt2wcQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@2.4.15':
+ resolution: {integrity: sha512-ZPcxznxm0pogHBLZhYntyR3sR+MrZjqJIKEr7ZqVen0Rl+P/4upVmfYXjftizi9RoqZntg33fv/1fbdhbYXpEQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [musl]
+
+ '@biomejs/cli-linux-arm64@2.4.15':
+ resolution: {integrity: sha512-owaAMZD/T4LrD0ELNCk0Km3qrRHuM0X6EAyVE1FSqGY0rbLoiDLrO4Us2tllm6cAeB2Ioa9C2C08NZPdr8+0Ug==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+ libc: [glibc]
+
+ '@biomejs/cli-linux-x64-musl@2.4.15':
+ resolution: {integrity: sha512-CNq/9W38SYSH023lfcQ4KKU8K0YX8T//FZUhcgtMMRABDojx5XsMV7jlweAvGSl389wJQB29Qo6Zb/a+jdvt+w==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+ libc: [musl]
+
+ '@biomejs/cli-linux-x64@2.4.15':
+ resolution: {integrity: sha512-0jj7THz12GbUOLmMibktK6DZjqz2zV64KFxyBtcFTKPiiOIY0a7vns1elpO1dERvxpsZ5ik0oFfz0oGwFde1+g==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+ libc: [glibc]
+
+ '@biomejs/cli-win32-arm64@2.4.15':
+ resolution: {integrity: sha512-ouhkYdlhp/1GghEJPdWwD/Vi3gQ1nFxuSpMolWsbq3Lsq3QUR4jl6UdhhscdCugKU5vOEuMiJhvKj66O0OCq+w==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@2.4.15':
+ resolution: {integrity: sha512-zBrGq5mx5wwpnow4+2BxUvleDM+GNd4sLbPaMapsSLQLD0NGRCquqPBTgN+7XkUteHvj7M+BstuI8tmnV7+HgQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
+ '@clack/core@0.5.0':
+ resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==}
+
+ '@clack/prompts@0.11.0':
+ resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==}
+
+ '@gwhitney/detect-indent@7.0.1':
+ resolution: {integrity: sha512-7bQW+gkKa2kKZPeJf6+c6gFK9ARxQfn+FKy9ScTBppyKRWH2KzsmweXUoklqeEiHiNVWaeP5csIdsNq6w7QhzA==}
+ engines: {node: '>=12.20'}
+
+ '@pnpm/constants@1001.3.1':
+ resolution: {integrity: sha512-2hf0s4pVrVEH8RvdJJ7YRKjQdiG8m0iAT26TTqXnCbK30kKwJW69VLmP5tED5zstmDRXcOeH5eRcrpkdwczQ9g==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/core-loggers@1001.0.9':
+ resolution: {integrity: sha512-pW58m3ssrwVjwhlmTXDW1dh1sv2y6R2Gl5YvQInjM2d01/5mre/sYAY4MK3XfgEShZJQxv6wVXDUvyHHJ0oizg==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': '>=1001.0.0 <1002.0.0'
+
+ '@pnpm/error@1000.1.0':
+ resolution: {integrity: sha512-Dqc2IJJPjUatwc9Letw+vG29rnaMrDGi5g6WCx1HiZYm0obXbTmLygeRafMbgf+sLKXrWE1shOeiayQuczBdoA==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/fs.find-packages@1000.0.24':
+ resolution: {integrity: sha512-6r2lpvoljgTvQ+CiJYz3jCunzO1PM6g1Cqc3xon49he8sgg8BatMsNxcGnuZWK//du80+ylS/uBXKxwuHMuHUw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/graceful-fs@1000.1.0':
+ resolution: {integrity: sha512-EsMX4slK0qJN2AR0/AYohY5m0HQNYGMNe+jhN74O994zp22/WbX+PbkIKyw3UQn39yQm2+z6SgwklDxbeapsmQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/logger@1001.0.1':
+ resolution: {integrity: sha512-gdwlAMXC4Wc0s7Dmg/4wNybMEd/4lSd9LsXQxeg/piWY0PPXjgz1IXJWnVScx6dZRaaodWP3c1ornrw8mZdFZw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/manifest-utils@1002.0.5':
+ resolution: {integrity: sha512-2DSwQ6pP73IuJS5mCCtPd5fibJwuAdufXKuSL/Oq1n6AggCqy8616Xea1X3RH3z5dL4mn7Z4EZ+vnX8jX3Wrfw==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': ^1001.0.1
+
+ '@pnpm/read-project-manifest@1001.2.6':
+ resolution: {integrity: sha512-BcNO50lAkE4m9JaJ0WmG3m/DH/qLSvMgZywtmb/dfyyLVu5nDZfDqmOd8U+f1NhLcLMbBK6AnS3hyUqZYvw9Vg==}
+ engines: {node: '>=18.12'}
+ peerDependencies:
+ '@pnpm/logger': ^1001.0.1
+
+ '@pnpm/semver.peer-range@1000.0.0':
+ resolution: {integrity: sha512-r6VzkrdH7ZKjPmAogTNvxuV/UyS/xwHNme+ZuEFiG0UthZgqudDftYtKmG20fcfrjG1lgJbbWICA8KvZy7mmbw==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/text.comments-parser@1000.0.0':
+ resolution: {integrity: sha512-ivv/esrETOq9uMiKOC0ddVZ1BktEGsfsMQ9RWmrDpwPiqFSqWsIspnquxTBmm5GflC5N06fbqjGOpulZVYo3vQ==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/types@1001.3.0':
+ resolution: {integrity: sha512-NLTXheat/u7OEGg5M5vF6Z85zx8uKUZE0+whtX/sbFV2XL48RdnOWGPTKYuVVkv8M+launaLUTgGEXNs/ess2w==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/util.lex-comparator@3.0.2':
+ resolution: {integrity: sha512-blFO4Ws97tWv/SNE6N39ZdGmZBrocXnBOfVp0ln4kELmns4pGPZizqyRtR8EjfOLMLstbmNCTReBoDvLz1isVg==}
+ engines: {node: '>=18.12'}
+
+ '@pnpm/write-project-manifest@1000.0.16':
+ resolution: {integrity: sha512-zG68fk03ryot7TWUl9S/ShQ91uHWzIL9sVr2aQCuNHJo8G9kjsG6S0p58Zj/voahdDQeakZYYBSJ0mjNZeiJnw==}
+ engines: {node: '>=18.12'}
+
+ '@rrlab/biome-config@0.0.2':
+ resolution: {integrity: sha512-b54jSWnYejnTSemC/arKz8glA/5W/iLhyEmd/aznYZEXT18pHtRtYz76bMJHZfElmcU3woGC2adHtXBI3/Sing==}
+ peerDependencies:
+ '@biomejs/biome': '>=2.0.0'
+
+ '@rrlab/biome-plugin@0.1.1':
+ resolution: {integrity: sha512-wtnJ4BQrjTk5EDpf9zCkWXUEGVHIJeFb1butcJ0YxAOUpUE9HMxe6xBnWlpkzg0mI4sHnAg5e1owjdxRzi1J+Q==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@biomejs/biome': '>=2.0.0'
+ '@rrlab/cli': 0.0.3
+
+ '@rrlab/cli@0.0.3':
+ resolution: {integrity: sha512-0+dwwG9C/FgOyKWNlyuEaiekQuovyVWqIKdvEuTVqqNSbaVZIcyI0lfkb8GIhh3b3Lc5IS6OY6S/FAlYEx6TVA==}
+ engines: {node: '>=20.0.0'}
+ hasBin: true
+
+ '@rrlab/ts-config@0.0.2':
+ resolution: {integrity: sha512-Tt+XP7TE21Ev17kvpmhjrnmHWx/LI7iJ3cz7sTaYgTWkiFTicr9h9NCwcHaVDswCXo52jKyaQZOHfBkACgXB7Q==}
+ peerDependencies:
+ '@types/node': '>=20'
+ typescript: '>=5.0.0'
+ peerDependenciesMeta:
+ '@types/node':
+ optional: true
+
+ '@rrlab/ts-plugin@0.1.1':
+ resolution: {integrity: sha512-HKYVyeKogn1nps3JzrH+0T5K1CxBkz38HnlXgnCAqjeCBQY09K/eF1l/JMoNb7VoylZ63iSGoVu3Qy5hdr/Pag==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@rrlab/cli': 0.0.3
+ typescript: '>=5.0.0'
+
+ '@standard-schema/spec@1.1.0':
+ resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
+
+ '@total-typescript/tsconfig@1.0.4':
+ resolution: {integrity: sha512-fO4ctMPGz1kOFOQ4RCPBRBfMy3gDn+pegUfrGyUFRMv/Rd0ZM3/SHH3hFCYG4u6bPLG8OlmOGcBLDexvyr3A5w==}
+
+ '@types/node@24.12.4':
+ resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==}
+
+ '@usage-spec/commander@1.1.0':
+ resolution: {integrity: sha512-hVv+ccKtcPaiaywLrm7Q/Nb4nGdRD319FBhfmTWQq3yUlS1SK/pmwyn0+BFQGAYN4uOMxvYDrqPH+qXpmINrkg==}
+
+ '@usage-spec/core@1.1.0':
+ resolution: {integrity: sha512-OjcN6IWdvuxN6bZYknTPIx9n/UTZODtGNaQEQe3yN7Y5DluH8/UJ8GeG+C0hNBWc/OePc0n9MmBw3rTbtoRVKg==}
+
+ '@vlandoss/clibuddy@0.6.1':
+ resolution: {integrity: sha512-beguPZIA9qvDUxb1/oTBx2JBf71MHNQ4YJAict7VHS1t5Uxl1tXBxi+/I+Dhx3QaEmcD5sNrQsW/7d6YYDsAzQ==}
+ engines: {node: '>=20.0.0'}
+
+ '@vlandoss/env@file:../../package/.local/vlandoss-env.tgz':
+ resolution: {integrity: sha512-fy/i1owvfERHP9Jk2WcuPYtSHdeZFUyaz7pWOARrrTlTPlu0opZkDKX1wdwIbqXy7e0Oc2VAougm5MpBQgz5JQ==, tarball: file:../../package/.local/vlandoss-env.tgz}
+ version: 0.2.1
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: '>=19'
+ react-dom: '>=19'
+ vite: '>=5'
+ zod: ^4
+ peerDependenciesMeta:
+ react:
+ optional: true
+ react-dom:
+ optional: true
+ vite:
+ optional: true
+ zod:
+ optional: true
+
+ '@vlandoss/loggy@0.2.1':
+ resolution: {integrity: sha512-M/Lx4FTF54+EQalmGGKpsk52LzdHVS6s38JWzObMDJqZ8Odx3Q3US2kpTGeOZIWx/alCRucne+ud+zQIuzl9DA==}
+ engines: {node: '>=20.0.0'}
+
+ ansis@4.2.0:
+ resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==}
+ engines: {node: '>=14'}
+
+ argparse@2.0.1:
+ resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
+
+ array-timsort@1.0.3:
+ resolution: {integrity: sha512-/+3GRL7dDAGEfM6TseQk/U+mi18TU2Ms9I3UlLdUMhz2hbvGNTKdj9xniwXfUqgYhHxRx0+8UnKkvlNwVU+cWQ==}
+
+ balanced-match@4.0.4:
+ resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+ engines: {node: 18 || 20 || >=22}
+
+ bole@5.0.29:
+ resolution: {integrity: sha512-eYR9i2ubLv5/4TFGyZsQ1cVH4jF9+qLJA72Aow+E7ZZQfqHqQNUZeX3w+pVWF76PQyjl5eDKf2xylyOOX76ozA==}
+
+ brace-expansion@5.0.6:
+ resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==}
+ engines: {node: 18 || 20 || >=22}
+
+ citty@0.1.6:
+ resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==}
+
+ commander@14.0.3:
+ resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==}
+ engines: {node: '>=20'}
+
+ comment-json@4.2.5:
+ resolution: {integrity: sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==}
+ engines: {node: '>= 6'}
+
+ confbox@0.2.4:
+ resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
+
+ consola@3.4.2:
+ resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
+ engines: {node: ^14.18.0 || >=16.10.0}
+
+ core-util-is@1.0.3:
+ resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
+
+ debug@4.4.3:
+ resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
+ engines: {node: '>=6.0'}
+ peerDependencies:
+ supports-color: '*'
+ peerDependenciesMeta:
+ supports-color:
+ optional: true
+
+ defu@6.1.7:
+ resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==}
+
+ error-ex@1.3.4:
+ resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
+
+ esprima@4.0.1:
+ resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
+ engines: {node: '>=4'}
+ hasBin: true
+
+ exsolve@1.0.8:
+ resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
+
+ fast-deep-equal@3.1.3:
+ resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
+
+ fast-safe-stringify@2.1.1:
+ resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
+
+ fdir@6.5.0:
+ resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
+ engines: {node: '>=12.0.0'}
+ peerDependencies:
+ picomatch: ^3 || ^4
+ peerDependenciesMeta:
+ picomatch:
+ optional: true
+
+ glob@13.0.6:
+ resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==}
+ engines: {node: 18 || 20 || >=22}
+
+ graceful-fs@4.2.11:
+ resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+
+ has-own-prop@2.0.0:
+ resolution: {integrity: sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==}
+ engines: {node: '>=8'}
+
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
+ individual@3.0.0:
+ resolution: {integrity: sha512-rUY5vtT748NMRbEMrTNiFfy29BgGZwGXUi2NFUVMWQrogSLzlJvQV9eeMWi+g1aVaQ53tpyLAQtd5x/JH0Nh1g==}
+
+ is-arrayish@0.2.1:
+ resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
+
+ is-windows@1.0.2:
+ resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==}
+ engines: {node: '>=0.10.0'}
+
+ js-tokens@4.0.0:
+ resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
+
+ js-yaml@4.1.1:
+ resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
+ hasBin: true
+
+ json-parse-even-better-errors@2.3.1:
+ resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
+
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
+ lilconfig@3.1.3:
+ resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
+ engines: {node: '>=14'}
+
+ lines-and-columns@1.2.4:
+ resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
+
+ lru-cache@11.5.0:
+ resolution: {integrity: sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==}
+ engines: {node: 20 || >=22}
+
+ magicast@0.3.5:
+ resolution: {integrity: sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==}
+
+ memoize@10.2.0:
+ resolution: {integrity: sha512-DeC6b7QBrZsRs3Y02A6A7lQyzFbsQbqgjI6UW0GigGWV+u1s25TycMr0XHZE4cJce7rY/vyw2ctMQqfDkIhUEA==}
+ engines: {node: '>=18'}
+
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
+ minimatch@10.2.5:
+ resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==}
+ engines: {node: 18 || 20 || >=22}
+
+ minipass@7.1.3:
+ resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
+ engines: {node: '>=16 || 14 >=14.17'}
+
+ ms@2.1.3:
+ resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
+
+ nypm@0.6.0:
+ resolution: {integrity: sha512-mn8wBFV9G9+UFHIrq+pZ2r2zL4aPau/by3kJb3cM7+5tQHMt6HGQB8FDIeKFYp8o0D2pnH6nVsO88N4AmUxIWg==}
+ engines: {node: ^14.16.0 || >=16.10.0}
+ hasBin: true
+
+ p-filter@2.1.0:
+ resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==}
+ engines: {node: '>=8'}
+
+ p-map@2.1.0:
+ resolution: {integrity: sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==}
+ engines: {node: '>=6'}
+
+ package-json-from-dist@1.0.1:
+ resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
+
+ parse-json@5.2.0:
+ resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
+ engines: {node: '>=8'}
+
+ path-scurry@2.0.2:
+ resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==}
+ engines: {node: 18 || 20 || >=22}
+
+ pathe@2.0.3:
+ resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+
+ picocolors@1.1.1:
+ resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
+
+ picomatch@4.0.4:
+ resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
+ engines: {node: '>=12'}
+
+ pkg-types@2.3.0:
+ resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
+
+ pkg-types@2.3.1:
+ resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==}
+
+ read-yaml-file@2.1.0:
+ resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==}
+ engines: {node: '>=10.13'}
+
+ repeat-string@1.6.1:
+ resolution: {integrity: sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==}
+ engines: {node: '>=0.10'}
+
+ rimraf@6.1.3:
+ resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==}
+ engines: {node: 20 || >=22}
+ hasBin: true
+
+ semver@7.8.1:
+ resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==}
+ engines: {node: '>=10'}
+ hasBin: true
+
+ signal-exit@4.1.0:
+ resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
+ engines: {node: '>=14'}
+
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
+ source-map-js@1.2.1:
+ resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
+ engines: {node: '>=0.10.0'}
+
+ split2@4.2.0:
+ resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
+ engines: {node: '>= 10.x'}
+
+ std-env@3.9.0:
+ resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==}
+
+ strip-bom@4.0.0:
+ resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==}
+ engines: {node: '>=8'}
+
+ strip-comments-strings@1.2.0:
+ resolution: {integrity: sha512-zwF4bmnyEjZwRhaak9jUWNxc0DoeKBJ7lwSN/LEc8dQXZcUFG6auaaTQJokQWXopLdM3iTx01nQT8E4aL29DAQ==}
+
+ tinyexec@0.3.2:
+ resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
+
+ tinyexec@1.1.2:
+ resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==}
+ engines: {node: '>=18'}
+
+ tinyglobby@0.2.16:
+ resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
+ engines: {node: '>=12.0.0'}
+
+ typescript@6.0.3:
+ resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
+ undici-types@7.16.0:
+ resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
+
+ write-file-atomic@5.0.1:
+ resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==}
+ engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+
+ write-yaml-file@5.0.0:
+ resolution: {integrity: sha512-FdNA4RyH1L43TlvGG8qOMIfcEczwA5ij+zLXUy3Z83CjxhLvcV7/Q/8pk22wnCgYw7PJhtK+7lhO+qqyT4NdvQ==}
+ engines: {node: '>=16.14'}
+
+ yaml@2.8.4:
+ resolution: {integrity: sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
+ zod@4.3.6:
+ resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==}
+
+snapshots:
+
+ '@babel/code-frame@7.29.7':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.29.7
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/helper-string-parser@7.29.7': {}
+
+ '@babel/helper-validator-identifier@7.29.7': {}
+
+ '@babel/parser@7.29.7':
+ dependencies:
+ '@babel/types': 7.29.7
+
+ '@babel/types@7.29.7':
+ dependencies:
+ '@babel/helper-string-parser': 7.29.7
+ '@babel/helper-validator-identifier': 7.29.7
+
+ '@bgotink/kdl@0.4.0': {}
+
+ '@biomejs/biome@2.4.15':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 2.4.15
+ '@biomejs/cli-darwin-x64': 2.4.15
+ '@biomejs/cli-linux-arm64': 2.4.15
+ '@biomejs/cli-linux-arm64-musl': 2.4.15
+ '@biomejs/cli-linux-x64': 2.4.15
+ '@biomejs/cli-linux-x64-musl': 2.4.15
+ '@biomejs/cli-win32-arm64': 2.4.15
+ '@biomejs/cli-win32-x64': 2.4.15
+
+ '@biomejs/cli-darwin-arm64@2.4.15':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@2.4.15':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@2.4.15':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@2.4.15':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@2.4.15':
+ optional: true
+
+ '@biomejs/cli-linux-x64@2.4.15':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@2.4.15':
+ optional: true
+
+ '@biomejs/cli-win32-x64@2.4.15':
+ optional: true
+
+ '@clack/core@0.5.0':
+ dependencies:
+ picocolors: 1.1.1
+ sisteransi: 1.0.5
+
+ '@clack/prompts@0.11.0':
+ dependencies:
+ '@clack/core': 0.5.0
+ picocolors: 1.1.1
+ sisteransi: 1.0.5
+
+ '@gwhitney/detect-indent@7.0.1': {}
+
+ '@pnpm/constants@1001.3.1': {}
+
+ '@pnpm/core-loggers@1001.0.9(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@pnpm/logger': 1001.0.1
+ '@pnpm/types': 1001.3.0
+
+ '@pnpm/error@1000.1.0':
+ dependencies:
+ '@pnpm/constants': 1001.3.1
+
+ '@pnpm/fs.find-packages@1000.0.24(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@pnpm/read-project-manifest': 1001.2.6(@pnpm/logger@1001.0.1)
+ '@pnpm/types': 1001.3.0
+ '@pnpm/util.lex-comparator': 3.0.2
+ p-filter: 2.1.0
+ tinyglobby: 0.2.16
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+
+ '@pnpm/graceful-fs@1000.1.0':
+ dependencies:
+ graceful-fs: 4.2.11
+
+ '@pnpm/logger@1001.0.1':
+ dependencies:
+ bole: 5.0.29
+ split2: 4.2.0
+
+ '@pnpm/manifest-utils@1002.0.5(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@pnpm/core-loggers': 1001.0.9(@pnpm/logger@1001.0.1)
+ '@pnpm/error': 1000.1.0
+ '@pnpm/logger': 1001.0.1
+ '@pnpm/semver.peer-range': 1000.0.0
+ '@pnpm/types': 1001.3.0
+ semver: 7.8.1
+
+ '@pnpm/read-project-manifest@1001.2.6(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@gwhitney/detect-indent': 7.0.1
+ '@pnpm/error': 1000.1.0
+ '@pnpm/graceful-fs': 1000.1.0
+ '@pnpm/logger': 1001.0.1
+ '@pnpm/manifest-utils': 1002.0.5(@pnpm/logger@1001.0.1)
+ '@pnpm/text.comments-parser': 1000.0.0
+ '@pnpm/types': 1001.3.0
+ '@pnpm/write-project-manifest': 1000.0.16
+ fast-deep-equal: 3.1.3
+ is-windows: 1.0.2
+ json5: 2.2.3
+ parse-json: 5.2.0
+ read-yaml-file: 2.1.0
+ strip-bom: 4.0.0
+
+ '@pnpm/semver.peer-range@1000.0.0':
+ dependencies:
+ semver: 7.8.1
+
+ '@pnpm/text.comments-parser@1000.0.0':
+ dependencies:
+ strip-comments-strings: 1.2.0
+
+ '@pnpm/types@1001.3.0': {}
+
+ '@pnpm/util.lex-comparator@3.0.2': {}
+
+ '@pnpm/write-project-manifest@1000.0.16':
+ dependencies:
+ '@pnpm/text.comments-parser': 1000.0.0
+ '@pnpm/types': 1001.3.0
+ json5: 2.2.3
+ write-file-atomic: 5.0.1
+ write-yaml-file: 5.0.0
+
+ '@rrlab/biome-config@0.0.2(@biomejs/biome@2.4.15)':
+ dependencies:
+ '@biomejs/biome': 2.4.15
+
+ '@rrlab/biome-plugin@0.1.1(@biomejs/biome@2.4.15)(@pnpm/logger@1001.0.1)(@rrlab/cli@0.0.3(@pnpm/logger@1001.0.1))':
+ dependencies:
+ '@biomejs/biome': 2.4.15
+ '@rrlab/cli': 0.0.3(@pnpm/logger@1001.0.1)
+ '@vlandoss/clibuddy': 0.6.1(@pnpm/logger@1001.0.1)
+ comment-json: 4.2.5
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+
+ '@rrlab/cli@0.0.3(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@clack/prompts': 0.11.0
+ '@usage-spec/commander': 1.1.0
+ '@vlandoss/clibuddy': 0.6.1(@pnpm/logger@1001.0.1)
+ '@vlandoss/loggy': 0.2.1
+ commander: 14.0.3
+ comment-json: 4.2.5
+ glob: 13.0.6
+ lilconfig: 3.1.3
+ magicast: 0.3.5
+ memoize: 10.2.0
+ nypm: 0.6.0
+ rimraf: 6.1.3
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+ - supports-color
+
+ '@rrlab/ts-config@0.0.2(@types/node@24.12.4)(typescript@6.0.3)':
+ dependencies:
+ '@total-typescript/tsconfig': 1.0.4
+ typescript: 6.0.3
+ optionalDependencies:
+ '@types/node': 24.12.4
+
+ '@rrlab/ts-plugin@0.1.1(@pnpm/logger@1001.0.1)(@rrlab/cli@0.0.3(@pnpm/logger@1001.0.1))(typescript@6.0.3)':
+ dependencies:
+ '@rrlab/cli': 0.0.3(@pnpm/logger@1001.0.1)
+ '@vlandoss/clibuddy': 0.6.1(@pnpm/logger@1001.0.1)
+ comment-json: 4.2.5
+ typescript: 6.0.3
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+
+ '@standard-schema/spec@1.1.0': {}
+
+ '@total-typescript/tsconfig@1.0.4': {}
+
+ '@types/node@24.12.4':
+ dependencies:
+ undici-types: 7.16.0
+
+ '@usage-spec/commander@1.1.0':
+ dependencies:
+ '@usage-spec/core': 1.1.0
+ commander: 14.0.3
+
+ '@usage-spec/core@1.1.0':
+ dependencies:
+ '@bgotink/kdl': 0.4.0
+
+ '@vlandoss/clibuddy@0.6.1(@pnpm/logger@1001.0.1)':
+ dependencies:
+ '@pnpm/fs.find-packages': 1000.0.24(@pnpm/logger@1001.0.1)
+ '@pnpm/types': 1001.3.0
+ ansis: 4.2.0
+ memoize: 10.2.0
+ pkg-types: 2.3.0
+ std-env: 3.9.0
+ tinyexec: 1.1.2
+ yaml: 2.8.4
+ transitivePeerDependencies:
+ - '@pnpm/logger'
+
+ '@vlandoss/env@file:../../package/.local/vlandoss-env.tgz(zod@4.3.6)':
+ dependencies:
+ '@standard-schema/spec': 1.1.0
+ defu: 6.1.7
+ optionalDependencies:
+ zod: 4.3.6
+
+ '@vlandoss/loggy@0.2.1':
+ dependencies:
+ consola: 3.4.2
+ debug: 4.4.3
+ transitivePeerDependencies:
+ - supports-color
+
+ ansis@4.2.0: {}
+
+ argparse@2.0.1: {}
+
+ array-timsort@1.0.3: {}
+
+ balanced-match@4.0.4: {}
+
+ bole@5.0.29:
+ dependencies:
+ fast-safe-stringify: 2.1.1
+ individual: 3.0.0
+
+ brace-expansion@5.0.6:
+ dependencies:
+ balanced-match: 4.0.4
+
+ citty@0.1.6:
+ dependencies:
+ consola: 3.4.2
+
+ commander@14.0.3: {}
+
+ comment-json@4.2.5:
+ dependencies:
+ array-timsort: 1.0.3
+ core-util-is: 1.0.3
+ esprima: 4.0.1
+ has-own-prop: 2.0.0
+ repeat-string: 1.6.1
+
+ confbox@0.2.4: {}
+
+ consola@3.4.2: {}
+
+ core-util-is@1.0.3: {}
+
+ debug@4.4.3:
+ dependencies:
+ ms: 2.1.3
+
+ defu@6.1.7: {}
+
+ error-ex@1.3.4:
+ dependencies:
+ is-arrayish: 0.2.1
+
+ esprima@4.0.1: {}
+
+ exsolve@1.0.8: {}
+
+ fast-deep-equal@3.1.3: {}
+
+ fast-safe-stringify@2.1.1: {}
+
+ fdir@6.5.0(picomatch@4.0.4):
+ optionalDependencies:
+ picomatch: 4.0.4
+
+ glob@13.0.6:
+ dependencies:
+ minimatch: 10.2.5
+ minipass: 7.1.3
+ path-scurry: 2.0.2
+
+ graceful-fs@4.2.11: {}
+
+ has-own-prop@2.0.0: {}
+
+ imurmurhash@0.1.4: {}
+
+ individual@3.0.0: {}
+
+ is-arrayish@0.2.1: {}
+
+ is-windows@1.0.2: {}
+
+ js-tokens@4.0.0: {}
+
+ js-yaml@4.1.1:
+ dependencies:
+ argparse: 2.0.1
+
+ json-parse-even-better-errors@2.3.1: {}
+
+ json5@2.2.3: {}
+
+ lilconfig@3.1.3: {}
+
+ lines-and-columns@1.2.4: {}
+
+ lru-cache@11.5.0: {}
+
+ magicast@0.3.5:
+ dependencies:
+ '@babel/parser': 7.29.7
+ '@babel/types': 7.29.7
+ source-map-js: 1.2.1
+
+ memoize@10.2.0:
+ dependencies:
+ mimic-function: 5.0.1
+
+ mimic-function@5.0.1: {}
+
+ minimatch@10.2.5:
+ dependencies:
+ brace-expansion: 5.0.6
+
+ minipass@7.1.3: {}
+
+ ms@2.1.3: {}
+
+ nypm@0.6.0:
+ dependencies:
+ citty: 0.1.6
+ consola: 3.4.2
+ pathe: 2.0.3
+ pkg-types: 2.3.1
+ tinyexec: 0.3.2
+
+ p-filter@2.1.0:
+ dependencies:
+ p-map: 2.1.0
+
+ p-map@2.1.0: {}
+
+ package-json-from-dist@1.0.1: {}
+
+ parse-json@5.2.0:
+ dependencies:
+ '@babel/code-frame': 7.29.7
+ error-ex: 1.3.4
+ json-parse-even-better-errors: 2.3.1
+ lines-and-columns: 1.2.4
+
+ path-scurry@2.0.2:
+ dependencies:
+ lru-cache: 11.5.0
+ minipass: 7.1.3
+
+ pathe@2.0.3: {}
+
+ picocolors@1.1.1: {}
+
+ picomatch@4.0.4: {}
+
+ pkg-types@2.3.0:
+ dependencies:
+ confbox: 0.2.4
+ exsolve: 1.0.8
+ pathe: 2.0.3
+
+ pkg-types@2.3.1:
+ dependencies:
+ confbox: 0.2.4
+ exsolve: 1.0.8
+ pathe: 2.0.3
+
+ read-yaml-file@2.1.0:
+ dependencies:
+ js-yaml: 4.1.1
+ strip-bom: 4.0.0
+
+ repeat-string@1.6.1: {}
+
+ rimraf@6.1.3:
+ dependencies:
+ glob: 13.0.6
+ package-json-from-dist: 1.0.1
+
+ semver@7.8.1: {}
+
+ signal-exit@4.1.0: {}
+
+ sisteransi@1.0.5: {}
+
+ source-map-js@1.2.1: {}
+
+ split2@4.2.0: {}
+
+ std-env@3.9.0: {}
+
+ strip-bom@4.0.0: {}
+
+ strip-comments-strings@1.2.0: {}
+
+ tinyexec@0.3.2: {}
+
+ tinyexec@1.1.2: {}
+
+ tinyglobby@0.2.16:
+ dependencies:
+ fdir: 6.5.0(picomatch@4.0.4)
+ picomatch: 4.0.4
+
+ typescript@6.0.3: {}
+
+ undici-types@7.16.0: {}
+
+ write-file-atomic@5.0.1:
+ dependencies:
+ imurmurhash: 0.1.4
+ signal-exit: 4.1.0
+
+ write-yaml-file@5.0.0:
+ dependencies:
+ js-yaml: 4.1.1
+ write-file-atomic: 5.0.1
+
+ yaml@2.8.4: {}
+
+ zod@4.3.6: {}
diff --git a/examples/config-cjs/run-run.config.mts b/examples/config-cjs/run-run.config.mts
new file mode 100644
index 0000000..c5cd9f7
--- /dev/null
+++ b/examples/config-cjs/run-run.config.mts
@@ -0,0 +1,7 @@
+import biome from "@rrlab/biome-plugin";
+import { defineConfig } from "@rrlab/cli/config";
+import ts from "@rrlab/ts-plugin";
+
+export default defineConfig({
+ plugins: [biome(), ts()],
+});
diff --git a/examples/config-cjs/src/env/index.ts b/examples/config-cjs/src/env/index.ts
new file mode 100644
index 0000000..eb636d3
--- /dev/null
+++ b/examples/config-cjs/src/env/index.ts
@@ -0,0 +1,20 @@
+import { defineEnv, selectConfig } from "@vlandoss/env";
+import development from "../../config/development.ts";
+import production from "../../config/production.ts";
+import { Env } from "./schema.ts";
+
+// Synchronous config selection — no `loadConfig`, no top-level `await`. This is
+// what lets `db.config.mts` be loaded by tooling that pulls the config in via
+// `require()` or bundles it to CJS, where a top-level await would throw
+// `ERR_REQUIRE_ASYNC_MODULE` / fail the bundle.
+//
+// The configs are static `import`s, so the runtime/bundler resolves and
+// transpiles each one at parse time; `selectConfig` just picks the one matching
+// the current `envName()`.
+export const env = defineEnv({
+ schema: Env,
+ config: selectConfig({ development, production }),
+ vars: {
+ db: { URL: "DATABASE_URL" },
+ },
+});
diff --git a/examples/config-cjs/src/env/schema.ts b/examples/config-cjs/src/env/schema.ts
new file mode 100644
index 0000000..ff7538b
--- /dev/null
+++ b/examples/config-cjs/src/env/schema.ts
@@ -0,0 +1,13 @@
+import { type Config, schema } from "@vlandoss/env";
+import * as e from "@vlandoss/env/zod";
+import * as z from "zod";
+
+export const Env = schema({
+ server: { PORT: e.port, HOST: e.host },
+ db: {
+ URL: z.url(),
+ LOGGING: e.bool.default(false),
+ },
+});
+
+export type EnvConfig = Config;
diff --git a/examples/config-cjs/test/loader.test.ts b/examples/config-cjs/test/loader.test.ts
new file mode 100644
index 0000000..dc802a1
--- /dev/null
+++ b/examples/config-cjs/test/loader.test.ts
@@ -0,0 +1,23 @@
+import assert from "node:assert/strict";
+import { createRequire } from "node:module";
+import { describe, it } from "node:test";
+
+// A real CJS `require`, the way some tooling pulls in its config file. On Node
+// ≥22.18 `require()` loads ES modules and strips TypeScript, but it still
+// refuses an ES module graph that uses top-level await.
+const require = createRequire(import.meta.url);
+
+describe("config file loading in a CJS / no-top-level-await context", () => {
+ it("selectConfig (sync) loads fine via require()", () => {
+ const mod = require("../db.config.mts") as { default: { dialect: string; dbCredentials: { url: string } } };
+ assert.equal(mod.default.dialect, "postgresql");
+ assert.equal(mod.default.dbCredentials.url, "postgres://localhost/dev");
+ });
+
+ it("loadConfig + top-level await throws ERR_REQUIRE_ASYNC_MODULE via require()", () => {
+ assert.throws(
+ () => require("../db.config.broken.mts"),
+ (err: NodeJS.ErrnoException) => err.code === "ERR_REQUIRE_ASYNC_MODULE",
+ );
+ });
+});
diff --git a/examples/config-cjs/tsconfig.json b/examples/config-cjs/tsconfig.json
new file mode 100644
index 0000000..880ca9a
--- /dev/null
+++ b/examples/config-cjs/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "@rrlab/ts-config/no-dom/app",
+ "include": ["src", "test", "config", "db.config.mts", "db.config.broken.mts"]
+}
diff --git a/mise.toml b/mise.toml
index 584a5ae..dbd53ca 100644
--- a/mise.toml
+++ b/mise.toml
@@ -20,6 +20,7 @@ config_roots = [
"examples/spa-vite-dynamic",
"examples/ssr-react-router",
"examples/ssr-tanstack-start",
+ "examples/config-cjs",
]
[tasks.setup]
diff --git a/package/src/__tests__/lib.test-d.ts b/package/src/__tests__/lib.test-d.ts
index 4439c8e..bbd3e26 100644
--- a/package/src/__tests__/lib.test-d.ts
+++ b/package/src/__tests__/lib.test-d.ts
@@ -1,7 +1,7 @@
import { assertType, describe, expectTypeOf, test } from "vitest";
import * as z from "zod";
import type { AssertEnvVarNames, Config, Defaults, Env, Vars } from "../lib/index.ts";
-import { defineEnv, schema } from "../lib/index.ts";
+import { defineEnv, schema, selectConfig } from "../lib/index.ts";
const S = schema({
server: { PORT: z.coerce.number(), HOST: z.string() },
@@ -136,3 +136,18 @@ describe("defineEnv() return type overloads", () => {
defineEnv({ schema: S, config: "src/config/*.ts" });
});
});
+
+describe("selectConfig()", () => {
+ const development: Config = { server: { PORT: 3000, HOST: "x" } };
+ const production: Config = { server: { PORT: 8080, HOST: "y" } };
+
+ test("returns the map's value type (T), never `T | undefined`", () => {
+ const config = selectConfig({ development, production });
+ expectTypeOf(config).toEqualTypeOf>();
+ });
+
+ test("result pipes into defineEnv as a sync config -> Env", () => {
+ const env = defineEnv({ schema: S, config: selectConfig({ development, production }) });
+ expectTypeOf(env).toEqualTypeOf>();
+ });
+});
diff --git a/package/src/__tests__/select-config.test.ts b/package/src/__tests__/select-config.test.ts
new file mode 100644
index 0000000..49351bb
--- /dev/null
+++ b/package/src/__tests__/select-config.test.ts
@@ -0,0 +1,56 @@
+import { afterEach, describe, expect, it } from "vitest";
+import * as z from "zod";
+import { defineEnv, schema, selectConfig } from "../lib/index.ts";
+
+const S = schema({
+ server: { PORT: z.coerce.number().int(), HOST: z.string() },
+});
+
+const development = { server: { PORT: 3000, HOST: "localhost" } };
+const production = { server: { PORT: 8080, HOST: "0.0.0.0" } };
+
+afterEach(() => {
+ delete process.env.ENV;
+});
+
+describe("selectConfig", () => {
+ it("returns the config for the current env (envName)", () => {
+ process.env.ENV = "development";
+ expect(selectConfig({ development, production })).toBe(development);
+ });
+
+ it("respects ENV when selecting", () => {
+ process.env.ENV = "production";
+ expect(selectConfig({ development, production })).toBe(production);
+ });
+
+ it("supports custom env names", () => {
+ process.env.ENV = "staging";
+ const staging = { server: { PORT: 5000, HOST: "staging.local" } };
+ expect(selectConfig({ development, staging })).toBe(staging);
+ });
+
+ it("throws listing available keys when the current env has no entry", () => {
+ process.env.ENV = "staging";
+ expect(() => selectConfig({ development, production })).toThrow(
+ 'selectConfig: no config for env "staging". Available: development, production.',
+ );
+ });
+
+ it("reports (none) when the map is empty", () => {
+ process.env.ENV = "development";
+ expect(() => selectConfig({})).toThrow('selectConfig: no config for env "development". Available: (none).');
+ });
+
+ it("pipes into defineEnv as the (sync) config source", () => {
+ process.env.ENV = "production";
+ const env = defineEnv({
+ schema: S,
+ config: selectConfig({ development, production }),
+ runtimeEnv: {},
+ });
+ // selectConfig read process.env.ENV and picked the production config
+ expect(env.server.PORT).toBe(8080);
+ expect(env.server.HOST).toBe("0.0.0.0");
+ });
+});
diff --git a/package/src/lib/index.ts b/package/src/lib/index.ts
index b0280a6..859e789 100644
--- a/package/src/lib/index.ts
+++ b/package/src/lib/index.ts
@@ -2,6 +2,7 @@ export { ENV_GLOBAL_ID, ENV_SCRIPT_ID } from "./const.ts";
export { defineEnv } from "./define-env.ts";
export { envName, readEnv } from "./runtime.ts";
export { schema } from "./schema.ts";
+export { selectConfig } from "./select-config.ts";
export type {
AssertEnvVarNames,
diff --git a/package/src/lib/select-config.ts b/package/src/lib/select-config.ts
new file mode 100644
index 0000000..e06d358
--- /dev/null
+++ b/package/src/lib/select-config.ts
@@ -0,0 +1,32 @@
+import { envName } from "./runtime.ts";
+
+/**
+ * Synchronously pick the config for the current environment from a map keyed by
+ * env name. The sync, runtime-agnostic counterpart to `loadConfig` for config
+ * files that can't use top-level `await` — files that tooling loads via
+ * `require()` or bundles to CJS, where top-level await is rejected
+ * (`ERR_REQUIRE_ASYNC_MODULE`).
+ *
+ * Pair it with static `import`s so the bundler/runtime resolves and transpiles
+ * each config file at parse time — no dynamic `import()`, no `await`:
+ *
+ * ```ts
+ * import development from "./config/development.ts";
+ * import production from "./config/production.ts";
+ *
+ * const config = selectConfig({ development, production });
+ * ```
+ *
+ * Selects by `envName()`. Throws when the current env has no entry — the map is
+ * explicit, so a miss is almost always a typo or a forgotten environment. To
+ * select a non-current env, set `ENV=…` in the process env first (same rule as
+ * `loadConfig`).
+ */
+export function selectConfig(configs: Record): T {
+ const env = envName();
+ if (!Object.hasOwn(configs, env)) {
+ const available = Object.keys(configs).join(", ") || "(none)";
+ throw new Error(`selectConfig: no config for env "${env}". Available: ${available}.`);
+ }
+ return configs[env] as T;
+}