diff --git a/.agent/workflows/openspec-apply.md b/.agent/workflows/openspec-apply.md new file mode 100644 index 000000000..b0154dd9d --- /dev/null +++ b/.agent/workflows/openspec-apply.md @@ -0,0 +1,20 @@ +--- +description: Implement an approved OpenSpec change and keep tasks in sync. +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** +Track these steps as TODOs and complete them one by one. +1. Read `changes//proposal.md`, `design.md` (if present), and `tasks.md` to confirm scope and acceptance criteria. +2. Work through tasks sequentially, keeping edits minimal and focused on the requested change. +3. Confirm completion before updating statuses—make sure every item in `tasks.md` is finished. +4. Update the checklist after all work is done so each task is marked `- [x]` and reflects reality. +5. Reference `openspec list` or `openspec show ` when additional context is required. + +**Reference** +- Use `openspec show --json --deltas-only` if you need additional context from the proposal while implementing. + diff --git a/.agent/workflows/openspec-archive.md b/.agent/workflows/openspec-archive.md new file mode 100644 index 000000000..38dcc8174 --- /dev/null +++ b/.agent/workflows/openspec-archive.md @@ -0,0 +1,24 @@ +--- +description: Archive a deployed OpenSpec change and update specs. +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. + +**Steps** +1. Determine the change ID to archive: + - If this prompt already includes a specific change ID (for example inside a `` block populated by slash-command arguments), use that value after trimming whitespace. + - If the conversation references a change loosely (for example by title or summary), run `openspec list` to surface likely IDs, share the relevant candidates, and confirm which one the user intends. + - Otherwise, review the conversation, run `openspec list`, and ask the user which change to archive; wait for a confirmed change ID before proceeding. + - If you still cannot identify a single change ID, stop and tell the user you cannot archive anything yet. +2. Validate the change ID by running `openspec list` (or `openspec show `) and stop if the change is missing, already archived, or otherwise not ready to archive. +3. Run `openspec archive --yes` so the CLI moves the change and applies spec updates without prompts (use `--skip-specs` only for tooling-only work). +4. Review the command output to confirm the target specs were updated and the change landed in `changes/archive/`. +5. Validate with `openspec validate --strict` and inspect with `openspec show ` if anything looks off. + +**Reference** +- Use `openspec list` to confirm change IDs before archiving. +- Inspect refreshed specs with `openspec list --specs` and address any validation issues before handing off. + diff --git a/.agent/workflows/openspec-proposal.md b/.agent/workflows/openspec-proposal.md new file mode 100644 index 000000000..db8a95ad0 --- /dev/null +++ b/.agent/workflows/openspec-proposal.md @@ -0,0 +1,25 @@ +--- +description: Scaffold a new OpenSpec change and validate strictly. +--- + +**Guardrails** +- Favor straightforward, minimal implementations first and add complexity only when it is requested or clearly required. +- Keep changes tightly scoped to the requested outcome. +- Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. +- Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files. +- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval. + +**Steps** +1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification. +2. Choose a unique verb-led `change-id` and scaffold `proposal.md`, `tasks.md`, and `design.md` (when needed) under `openspec/changes//`. +3. Map the change into concrete capabilities or requirements, breaking multi-scope efforts into distinct spec deltas with clear relationships and sequencing. +4. Capture architectural reasoning in `design.md` when the solution spans multiple systems, introduces new patterns, or demands trade-off discussion before committing to specs. +5. Draft spec deltas in `changes//specs//spec.md` (one folder per capability) using `## ADDED|MODIFIED|REMOVED Requirements` with at least one `#### Scenario:` per requirement and cross-reference related capabilities when relevant. +6. Draft `tasks.md` as an ordered list of small, verifiable work items that deliver user-visible progress, include validation (tests, tooling), and highlight dependencies or parallelizable work. +7. Validate with `openspec validate --strict` and resolve every issue before sharing the proposal. + +**Reference** +- Use `openspec show --json --deltas-only` or `openspec show --type spec` to inspect details when validation fails. +- Search existing requirements with `rg -n "Requirement:|Scenario:" openspec/specs` before writing new ones. +- Explore the codebase with `rg `, `ls`, or direct file reads so proposals align with current implementation realities. + diff --git a/.changeset/bun-plugin.md b/.changeset/bun-plugin.md new file mode 100644 index 000000000..f650d64d4 --- /dev/null +++ b/.changeset/bun-plugin.md @@ -0,0 +1,6 @@ +--- +"@arkenv/bun-plugin": minor +--- + +Add Bun plugin for build-time environment variable validation and type-safe access, similar to the Vite plugin. + diff --git a/.cursor/commands/openspec-proposal.md b/.cursor/commands/openspec-proposal.md index 2d7ed7e93..25f1a3f57 100644 --- a/.cursor/commands/openspec-proposal.md +++ b/.cursor/commands/openspec-proposal.md @@ -10,6 +10,7 @@ description: Scaffold a new OpenSpec change and validate strictly. - Keep changes tightly scoped to the requested outcome. - Refer to `openspec/AGENTS.md` (located inside the `openspec/` directory—run `ls openspec` or `openspec update` if you don't see it) if you need additional OpenSpec conventions or clarifications. - Identify any vague or ambiguous details and ask the necessary follow-up questions before editing files. +- Do not write any code during the proposal stage. Only create design documents (proposal.md, tasks.md, design.md, and spec deltas). Implementation happens in the apply stage after approval. **Steps** 1. Review `openspec/project.md`, run `openspec list` and `openspec list --specs`, and inspect related code or docs (e.g., via `rg`/`ls`) to ground the proposal in current behaviour; note any gaps that require clarification. diff --git a/apps/playgrounds/bun-react/.env.production b/apps/playgrounds/bun-react/.env.production new file mode 100644 index 000000000..2cce42ed5 --- /dev/null +++ b/apps/playgrounds/bun-react/.env.production @@ -0,0 +1,2 @@ +BUN_PUBLIC_API_URL=https://api.example.com +BUN_PUBLIC_DEBUG=true diff --git a/apps/playgrounds/bun-react/bin/build.ts b/apps/playgrounds/bun-react/bin/build.ts new file mode 100644 index 000000000..409918899 --- /dev/null +++ b/apps/playgrounds/bun-react/bin/build.ts @@ -0,0 +1,20 @@ +import arkenv from "@arkenv/bun-plugin"; + +const result = await Bun.build({ + entrypoints: ["./src/index.html"], + outdir: "./dist", + sourcemap: true, + target: "browser", + minify: true, + plugins: [arkenv], +}); + +if (!result.success) { + console.error("Build failed:"); + for (const log of result.logs) { + console.error(log); + } + process.exit(1); +} + +console.log(`✓ Build succeeded: ${result.outputs.length} files generated`); diff --git a/apps/playgrounds/bun-react/bun-env.d.ts b/apps/playgrounds/bun-react/bun-env.d.ts index 809de985d..6270f7509 100644 --- a/apps/playgrounds/bun-react/bun-env.d.ts +++ b/apps/playgrounds/bun-react/bun-env.d.ts @@ -1,17 +1,23 @@ -// Generated by `bun init` +/// + +// Import the Env schema type from env.ts +type ProcessEnvAugmented = import("@arkenv/bun-plugin").ProcessEnvAugmented< + typeof import("./src/env").default +>; + +declare namespace NodeJS { + interface ProcessEnv extends ProcessEnvAugmented { + BUN_PUBLIC_API_URL: string; + BUN_PUBLIC_DEBUG: boolean; + } +} declare module "*.svg" { - /** - * A path to the SVG file - */ - const path: `${string}.svg`; - export = path; + const content: string; + export default content; } declare module "*.module.css" { - /** - * A record of class names to their corresponding CSS module classes - */ const classes: { readonly [key: string]: string }; - export = classes; + export default classes; } diff --git a/apps/playgrounds/bun-react/bunfig.toml b/apps/playgrounds/bun-react/bunfig.toml index 9819bf6de..339d48643 100644 --- a/apps/playgrounds/bun-react/bunfig.toml +++ b/apps/playgrounds/bun-react/bunfig.toml @@ -1,2 +1,3 @@ [serve.static] -env = "BUN_PUBLIC_*" \ No newline at end of file +env = "BUN_PUBLIC_*" +plugins = ["@arkenv/bun-plugin"] diff --git a/apps/playgrounds/bun-react/package.json b/apps/playgrounds/bun-react/package.json index 0b0282048..4d29842f7 100644 --- a/apps/playgrounds/bun-react/package.json +++ b/apps/playgrounds/bun-react/package.json @@ -5,11 +5,15 @@ "type": "module", "scripts": { "dev": "bun --hot src/index.tsx", - "build": "bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*'", + "build": "NODE_ENV=production bun bin/build.ts", + "preview": "bun dist/index.html", "start": "NODE_ENV=production bun src/index.tsx", "clean": "rimraf dist node_modules" }, "dependencies": { + "@arkenv/bun-plugin": "workspace:*", + "arkenv": "workspace:*", + "arktype": "catalog:", "react": "catalog:", "react-dom": "catalog:" }, diff --git a/apps/playgrounds/bun-react/src/App.tsx b/apps/playgrounds/bun-react/src/App.tsx index 5c178f495..0aa16de31 100644 --- a/apps/playgrounds/bun-react/src/App.tsx +++ b/apps/playgrounds/bun-react/src/App.tsx @@ -17,6 +17,29 @@ export function App() { Edit src/App.tsx and save to test HMR

+ + + + + + + + + + + + + + + + + + + + +
VariableValueType
BUN_PUBLIC_API_URL{process.env.BUN_PUBLIC_API_URL}{typeof process.env.BUN_PUBLIC_API_URL}
BUN_PUBLIC_DEBUG{String(process.env.BUN_PUBLIC_DEBUG)}{typeof process.env.BUN_PUBLIC_DEBUG}
+ {/* Print whether we are in "build" or in the dev server */} +

Mode: {process.env.NODE_ENV}

); } diff --git a/apps/playgrounds/bun-react/src/env.ts b/apps/playgrounds/bun-react/src/env.ts new file mode 100644 index 000000000..beb7155b6 --- /dev/null +++ b/apps/playgrounds/bun-react/src/env.ts @@ -0,0 +1,6 @@ +import { type } from "arkenv"; + +export default type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); diff --git a/apps/playgrounds/bun-react/src/index.css b/apps/playgrounds/bun-react/src/index.css index 09eaa7e9c..593d4ddfd 100644 --- a/apps/playgrounds/bun-react/src/index.css +++ b/apps/playgrounds/bun-react/src/index.css @@ -185,3 +185,37 @@ code { animation: none !important; } } + +.env-table { + width: 100%; + max-width: 600px; + margin: 2rem auto; + border-collapse: separate; + border-spacing: 0; + background: #1a1a1a; + border-radius: 12px; + overflow: hidden; + border: 2px solid #fbf0df; +} + +.env-table th, +.env-table td { + padding: 1rem; + text-align: left; + border-bottom: 1px solid rgba(251, 240, 223, 0.1); +} + +.env-table th { + background: rgba(251, 240, 223, 0.1); + color: #fbf0df; + font-weight: 600; +} + +.env-table td { + color: #e0e0e0; + font-family: monospace; +} + +.env-table tr:last-child td { + border-bottom: none; +} diff --git a/apps/playgrounds/bun-react/src/index.tsx b/apps/playgrounds/bun-react/src/index.tsx index bd8df61e4..05980fe74 100644 --- a/apps/playgrounds/bun-react/src/index.tsx +++ b/apps/playgrounds/bun-react/src/index.tsx @@ -7,13 +7,13 @@ const server = serve({ "/*": index, "/api/hello": { - async GET(req) { + async GET() { return Response.json({ message: "Hello, world!", method: "GET", }); }, - async PUT(req) { + async PUT() { return Response.json({ message: "Hello, world!", method: "PUT", diff --git a/apps/www/app/layout.tsx b/apps/www/app/layout.tsx index a51daff8d..967b317d6 100644 --- a/apps/www/app/layout.tsx +++ b/apps/www/app/layout.tsx @@ -1,4 +1,4 @@ -import { Banner } from "fumadocs-ui/components/banner"; +import { Banner } from "~/components/banner"; import "./globals.css"; import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; @@ -48,18 +48,7 @@ export default function Layout({ children }: { children: ReactNode }) { enableSystem: true, }} > - - 🎉 Announcing Vite support: check out the  - - new docs - - ! - + {children} diff --git a/apps/www/components/banner.tsx b/apps/www/components/banner.tsx new file mode 100644 index 000000000..cd79057b6 --- /dev/null +++ b/apps/www/components/banner.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { Banner as FumadocsBanner } from "fumadocs-ui/components/banner"; +import { useRouter } from "next/navigation"; + +export function Banner() { + const router = useRouter(); + return ( + router.push("/docs/bun-plugin")} + className="cursor-pointer" + > + 🎉 Official Bun support is here: ArkEnv at build time, fullstack dev + server + + ); +} diff --git a/apps/www/components/docs/install-button.tsx b/apps/www/components/docs/install-button.tsx index 278d10014..60387bdd0 100644 --- a/apps/www/components/docs/install-button.tsx +++ b/apps/www/components/docs/install-button.tsx @@ -8,18 +8,24 @@ import { Button } from "~/components/ui/button"; export function InstallButton() { const pathname = usePathname(); const isVitePluginPage = pathname?.includes("/docs/vite-plugin"); + const isBunPluginPage = pathname?.includes("/docs/bun-plugin"); + + let href = "/docs/arkenv/quickstart#install"; + let label = "Install ArkEnv"; + + if (isVitePluginPage) { + href = "/docs/vite-plugin#installation"; + label = "Install ArkEnv Vite plugin"; + } else if (isBunPluginPage) { + href = "/docs/bun-plugin#installation"; + label = "Install ArkEnv Bun plugin"; + } return ( ); diff --git a/apps/www/content/docs/bun-plugin/index.mdx b/apps/www/content/docs/bun-plugin/index.mdx new file mode 100644 index 000000000..2002288b2 --- /dev/null +++ b/apps/www/content/docs/bun-plugin/index.mdx @@ -0,0 +1,78 @@ +--- +title: Introduction +icon: Album +--- + +## What is this? + +The Bun plugin for ArkEnv lets you validate environment variables at build-time and runtime with ArkEnv. + +It supports **zero-config** usage for most projects, automatically discovering your schema from `./src/env.ts` or `./env.ts`. + +## Usage + +### Zero-Config (Recommended) + +Add the plugin to your `bunfig.toml`. This works for both `bun run dev` and `bun run build`. + +```toml title="bunfig.toml" +[serve.static] +plugins = ["@arkenv/bun-plugin"] +``` + +Create your schema in `src/env.ts` (or `env.ts`): + +```ts title="src/env.ts" +import { type } from "arkenv"; + +export default type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); +``` + +### Manual Configuration + +If you prefer explicit configuration or need to customize the schema location, you can use the plugin in your build script: + +```ts title="build.ts" +import arkenv from "@arkenv/bun-plugin"; +import { type } from "arkenv"; + +const env = type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); + +await Bun.build({ + entrypoints: ["./app.tsx"], + outdir: "./dist", + plugins: [arkenv(env)], +}); +``` + +### Zero-Config in Build Scripts + +You can also use the zero-config pattern in build scripts by passing the plugin object directly: + +```ts title="build.ts" +import arkenv from "@arkenv/bun-plugin"; + +await Bun.build({ + // ... + plugins: [arkenv], // Auto-discovers src/env.ts +}); +``` + +## Features + +- **Static Analysis**: Automatically replaces `process.env.VARIABLE` with validated values during the build. +- **Type Safety**: Ensures your code only accesses variables defined in your schema. +- **Hot Reloading**: Automatically reloads your schema when `env.ts` changes (in `bun --hot` mode). +- **Secure Defaults**: Only exposes variables prefixed with `BUN_PUBLIC_` to the client bundle. You can customize this prefix by setting the `env` option in your `Bun.build()` configuration (e.g., `env: "MY_PUBLIC_*"` to use `MY_PUBLIC_` instead). See [Bun's bundler documentation](https://bun.sh/docs/bundler#env) for more details. + +## Installation + +```package-install +@arkenv/bun-plugin arkenv arktype +``` diff --git a/apps/www/content/docs/bun-plugin/meta.json b/apps/www/content/docs/bun-plugin/meta.json new file mode 100644 index 000000000..c8e4e0eb6 --- /dev/null +++ b/apps/www/content/docs/bun-plugin/meta.json @@ -0,0 +1,7 @@ +{ + "title": "@arkenv/bun-plugin", + "description": "The Bun plugin", + "root": true, + "icon": "Bun", + "pages": ["---Guide---", "index"] +} diff --git a/apps/www/content/docs/meta.json b/apps/www/content/docs/meta.json index 1f4b8cd3a..6f40ecac2 100644 --- a/apps/www/content/docs/meta.json +++ b/apps/www/content/docs/meta.json @@ -1,3 +1,3 @@ { - "pages": ["arkenv", "vite-plugin"] + "pages": ["arkenv", "vite-plugin", "bun-plugin"] } diff --git a/arkenv.code-workspace b/arkenv.code-workspace index 1aa8973aa..f4a2299ef 100644 --- a/arkenv.code-workspace +++ b/arkenv.code-workspace @@ -32,6 +32,10 @@ "path": "packages/vite-plugin", "name": " @arkenv/vite-plugin" }, + { + "path": "packages/bun-plugin", + "name": " @arkenv/bun-plugin" + }, { "path": "packages/internal/types", "name": " @repo/types" diff --git a/openspec/changes/add-bun-plugin/design.md b/openspec/changes/add-bun-plugin/design.md new file mode 100644 index 000000000..3ffff2123 --- /dev/null +++ b/openspec/changes/add-bun-plugin/design.md @@ -0,0 +1,144 @@ + +### Requirement: Bun Plugin Configuration Patterns + +The Bun plugin SHALL support two primary configuration patterns depending on the usage context: + +1. **Direct Reference (Bun.build)**: The plugin SHALL be configurable by passing a configured plugin instance directly in the `plugins` array when using `Bun.build()`. +2. **Package Reference (Bun.serve)**: The plugin SHALL be configurable via a package name in `bunfig.toml` when using `Bun.serve()` for full-stack applications, with convention-based schema discovery. + +A future, more advanced configuration pattern using a custom static file MAY be supported, but is not required for the initial version. + +#### Scenario: Plugin configuration with Bun.build + +- **WHEN** a user wants to build an application using `Bun.build()` +- **AND** they configure the plugin with an environment variable schema +- **THEN** they can pass a configured plugin instance directly in the `plugins` array +- **AND** the plugin validates and transforms environment variables during the build process + +#### Scenario: Plugin configuration with Bun.serve via package reference + +- **WHEN** a user wants to use `Bun.serve()` for a full-stack application +- **AND** they configure `bunfig.toml` with: + - a `[serve.static]` section + - a `plugins` array that includes the package name `@arkenv/bun-plugin` +- **AND** their project contains an ArkEnv schema file in one of the supported default locations (for example, `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`) +- **AND** that schema file exports a schema using `type` from arktype (via a default export or an `env` named export) +- **THEN** the plugin SHALL locate the schema file via this convention-based search +- **AND** it SHALL load the schema at startup +- **AND** it SHALL use that schema to validate and transform environment variables during the bundling phase + +#### Scenario: Bun.serve configuration fails when no schema file is found + +- **WHEN** a user configures `bunfig.toml` with `[serve.static].plugins = ["@arkenv/bun-plugin"]` +- **AND** there is no schema file in any of the supported default locations +- **THEN** the plugin SHALL fail fast with a clear, descriptive error message +- **AND** the error message SHALL list the paths that were checked +- **AND** the error message SHALL show an example of a minimal `env` schema file the user can create + + + +### Decision: Configuration Modes and Schema Discovery + +**What**: The plugin supports two core configuration modes: + +1. **Direct Reference (Bun.build)** – pass a configured plugin instance directly in the `plugins` array. +2. **Package Reference with Convention (Bun.serve)** – declare `@arkenv/bun-plugin` in `bunfig.toml` and let the plugin discover the ArkEnv schema file using a set of well-known paths. + +A third, more advanced mode using a custom static plugin file may be added later for projects with non-standard layouts, but is not required for the initial release. + +#### Why + +- `Bun.build()` accepts plugins directly as JavaScript objects, which is ideal for explicit, programmatic configuration. +- `Bun.serve()` uses `bunfig.toml` where `plugins` is a list of strings (module specifiers) and does not support passing options inline. +- By using a package name (`"@arkenv/bun-plugin"`) plus convention-based schema discovery, we avoid forcing users to create extra “config glue” files for the common case, while still keeping an escape hatch for advanced setups. + +#### Implementation + +### Pattern 1: Bun.build (Direct Reference) + +```ts +// build.ts +import arkenv from "@arkenv/bun-plugin"; +import { type } from "arktype"; + +const env = type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); + +await Bun.build({ + entrypoints: ["./app.tsx"], + outdir: "./dist", + plugins: [arkenv(env)], +}); +``` + +### Pattern 2: Bun.serve (Package Reference with Convention) + +```toml +# bunfig.toml +[serve.static] +env = "BUN_PUBLIC_*" +plugins = ["@arkenv/bun-plugin"] +``` + +With a schema file at a conventional path, for example: + +```ts +// src/env.ts +import { type } from "arktype"; + +const env = type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); + +export default env; +``` + +At startup, `@arkenv/bun-plugin`: + +- Searches for a schema file in a small set of well-known locations (for example: `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`). +- Imports the first one it finds. +- Reads the default export (or an `env` named export) as the ArkEnv schema. +- Creates a Bun plugin and registers it with `Bun.plugin(...)`. + +If no schema file is found, or the module does not export a usable schema, the plugin fails fast with a clear error message that lists the paths checked and shows a minimal example. + +### Future / Advanced Mode (Custom Static File) + +In future iterations we MAY support a custom plugin entry file pattern for advanced layouts, for example: + +```toml +[serve.static] +plugins = ["./arkenv.bun-plugin.ts"] +``` + +```ts +// arkenv.bun-plugin.ts +import { Bun } from "bun"; +import { buildArkEnvBunPlugin } from "@arkenv/bun-plugin"; + +Bun.plugin( + await buildArkEnvBunPlugin({ + schemaPath: "./config/env/app.env.ts", + // future options (prefix overrides, strictness, etc.) + }), +); +``` + +This keeps the default experience zero-config for most users, while still allowing power users to override the schema location and other options when needed. + +#### Alternatives considered + +- **Static file reference as the only pattern** (previous design): Forces every project to create a separate `bun-plugin-config.ts` file even in simple setups, increasing boilerplate. +- **Schema definition via JSON/YAML or bunfig.toml**: Reduces type safety and breaks the “define once in TypeScript” story that ArkEnv aims for. +- **Only programmatic configuration** (no bunfig path): Would make full-stack `Bun.serve()` setups awkward compared to other Bun plugins that integrate via `bunfig.toml`. + + + +### Usage Patterns +- **Bun.build**: Pass a configured plugin instance directly in the `plugins` array (standard Bun plugin API), for example `plugins: [arkenv(env)]`. +- **Bun.serve (default)**: Configure `bunfig.toml` with `[serve.static].plugins = ["@arkenv/bun-plugin"]`. The plugin discovers the ArkEnv schema file from a small set of conventional locations (for example `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`) and uses it automatically. +- **Bun.serve (advanced, future)**: Optionally support a custom plugin entry file referenced from `bunfig.toml` (for example `plugins = ["./arkenv.bun-plugin.ts"]`) for projects that need a non-standard schema location or additional configuration. + diff --git a/openspec/changes/add-bun-plugin/proposal.md b/openspec/changes/add-bun-plugin/proposal.md new file mode 100644 index 000000000..03c0334d4 --- /dev/null +++ b/openspec/changes/add-bun-plugin/proposal.md @@ -0,0 +1,182 @@ + +### Requirement: Bun Plugin Configuration Patterns + +The Bun plugin SHALL support two primary configuration patterns depending on the usage context: + +1. **Direct Reference (Bun.build)**: The plugin SHALL be configurable by passing a configured plugin instance directly in the `plugins` array when using `Bun.build()`. +2. **Package Reference (Bun.serve)**: The plugin SHALL be configurable via a package name in `bunfig.toml` when using `Bun.serve()` for full-stack applications, with convention-based schema discovery. + +A future, more advanced configuration pattern using a custom static file MAY be supported, but is not required for the initial version. + +#### Scenario: Plugin configuration with Bun.build + +- **WHEN** a user wants to build an application using `Bun.build()` +- **AND** they configure the plugin with an environment variable schema +- **THEN** they can pass a configured plugin instance directly in the `plugins` array +- **AND** the plugin validates and transforms environment variables during the build process + +#### Scenario: Plugin configuration with Bun.serve via package reference + +- **WHEN** a user wants to use `Bun.serve()` for a full-stack application +- **AND** they configure `bunfig.toml` with: + - a `[serve.static]` section + - a `plugins` array that includes the package name `@arkenv/bun-plugin` +- **AND** their project contains an ArkEnv schema file in one of the supported default locations (for example, `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`) +- **AND** that schema file exports a schema using `type` from arktype (via a default export or an `env` named export) +- **THEN** the plugin SHALL locate the schema file via this convention-based search +- **AND** it SHALL load the schema at startup +- **AND** it SHALL use that schema to validate and transform environment variables during the bundling phase + +#### Scenario: Bun.serve configuration fails when no schema file is found + +- **WHEN** a user configures `bunfig.toml` with `[serve.static].plugins = ["@arkenv/bun-plugin"]` +- **AND** there is no schema file in any of the supported default locations +- **THEN** the plugin SHALL fail fast with a clear, descriptive error message +- **AND** the error message SHALL list the paths that were checked +- **AND** the error message SHALL show an example of a minimal `env` schema file the user can create + + + +### Decision: Configuration Modes and Schema Discovery + +**What**: The plugin supports two core configuration modes: + +1. **Direct Reference (Bun.build)** – pass a configured plugin instance directly in the `plugins` array. +2. **Package Reference with Convention (Bun.serve)** – declare `@arkenv/bun-plugin` in `bunfig.toml` and let the plugin discover the ArkEnv schema file using a set of well-known paths. + +A third, more advanced mode using a custom static plugin file may be added later for projects with non-standard layouts, but is not required for the initial release. + +**Why**: + +- `Bun.build()` accepts plugins directly as JavaScript objects, which is ideal for explicit, programmatic configuration. +- `Bun.serve()` uses `bunfig.toml` where `plugins` is a list of strings (module specifiers) and does not support passing options inline. +- By using a package name (`"@arkenv/bun-plugin"`) plus convention-based schema discovery, we avoid forcing users to create extra “config glue” files for the common case, while still keeping an escape hatch for advanced setups. + +**Implementation**: + +**Pattern 1: Bun.build (Direct Reference)** + +```ts +// build.ts +import arkenv from "@arkenv/bun-plugin"; +import { type } from "arktype"; + +const env = type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); + +await Bun.build({ + entrypoints: ["./app.tsx"], + outdir: "./dist", + plugins: [arkenv(env)], +}); +``` + +**Pattern 2: Bun.serve (Package Reference with Convention)** + +```toml +# bunfig.toml +[serve.static] +env = "BUN_PUBLIC_*" +plugins = ["@arkenv/bun-plugin"] +``` + +With a schema file at a conventional path, for example: + +```ts +// src/env.ts +import { type } from "arktype"; + +const env = type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean", +}); + +export default env; +``` + +At startup, `@arkenv/bun-plugin`: + +- Searches for a schema file in a small set of well-known locations (for example: `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`). +- Imports the first one it finds. +- Reads the default export (or an `env` named export) as the ArkEnv schema. +- Creates a Bun plugin and registers it with `Bun.plugin(...)`. + +If no schema file is found, or the module does not export a usable schema, the plugin fails fast with a clear error message that lists the paths checked and shows a minimal example. + +**Future / Advanced Mode (Custom Static File)** + +In future iterations we MAY support a custom plugin entry file pattern for advanced layouts, for example: + +```toml +[serve.static] +plugins = ["./arkenv.bun-plugin.ts"] +``` + +```ts +// arkenv.bun-plugin.ts +import { Bun } from "bun"; +import { buildArkEnvBunPlugin } from "@arkenv/bun-plugin"; + +Bun.plugin( + await buildArkEnvBunPlugin({ + schemaPath: "./config/env/app.env.ts", + // future options (prefix overrides, strictness, etc.) + }), +); +``` + +This keeps the default experience zero-config for most users, while still allowing power users to override the schema location and other options when needed. + +**Alternatives considered**: + +- **Static file reference as the only pattern** (previous design): Forces every project to create a separate `bun-plugin-config.ts` file even in simple setups, increasing boilerplate. +- **Schema definition via JSON/YAML or bunfig.toml**: Reduces type safety and breaks the “define once in TypeScript” story that ArkEnv aims for. +- **Only programmatic configuration** (no bunfig path): Would make full-stack `Bun.serve()` setups awkward compared to other Bun plugins that integrate via `bunfig.toml`. + + + +# Change: Add Bun Plugin for ArkEnv + +## Why + +ArkEnv currently provides a Vite plugin for build-time environment variable validation and type-safe access in client code. However, Bun users (especially those building full-stack React applications with Bun's `serve` function) need similar functionality. + +Bun's bundler statically replaces `process.env` variables during build, which means: +- Environment variables must be validated and transformed at build-time +- Only variables matching Bun's prefix (defaults to `BUN_PUBLIC_*`) should be exposed to client code +- Type augmentation is needed for type-safe access to `process.env` in client code +- The plugin must work within Bun's serve function for full-stack React apps + +Without a Bun plugin, users must manually validate environment variables or risk runtime errors, and they lose the type safety and build-time validation benefits that ArkEnv provides. + +## What Changes + +- **ADDED**: New `@arkenv/bun-plugin` package that provides Bun plugin integration +- **ADDED**: Build-time environment variable validation using Bun's plugin API +- **ADDED**: Automatic filtering of environment variables based on Bun's prefix (defaults to `BUN_PUBLIC_*`) +- **ADDED**: Static replacement of `process.env` variables with validated, transformed values during bundling +- **ADDED**: Type augmentation for `process.env` similar to Vite plugin's `ImportMetaEnvAugmented` +- **ADDED**: Support for Bun's serve function in full-stack React applications +- **ADDED**: Documentation and examples for using the Bun plugin + +The plugin will work very similarly to the Vite plugin: +- Uses Bun's `onLoad` hook to intercept and transform environment variable access +- Validates environment variables using ArkEnv's schema validation +- Filters variables to only expose those matching the configured prefix +- Replaces `process.env.VARIABLE` with validated, transformed values (e.g., string to boolean, default values) +- Provides TypeScript type augmentation for type-safe access + +### Usage Patterns +- **Bun.build**: Pass a configured plugin instance directly in the `plugins` array (standard Bun plugin API), for example `plugins: [arkenv(env)]`. +- **Bun.serve (default)**: Configure `bunfig.toml` with `[serve.static].plugins = ["@arkenv/bun-plugin"]`. The plugin discovers the ArkEnv schema file from a small set of conventional locations (for example `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`) and uses it automatically. +- **Bun.serve (advanced, future)**: Optionally support a custom plugin entry file referenced from `bunfig.toml` (for example `plugins = ["./arkenv.bun-plugin.ts"]`) for projects that need a non-standard schema location or additional configuration. + +## Impact + +- **New opt-in feature**: The Bun plugin is an entirely new package (@arkenv/bun-plugin) and does not affect existing arkenv users or workflows. +- **No breaking changes**: This is a pure addition and does not modify any public APIs or existing behavior. +- **Backward compatible**: All existing code continues to work unchanged. Bun users simply gain access to new functionality. +- **New dependency**: Projects using @arkenv/bun-plugin will add a new dev dependency to their package.json. + diff --git a/openspec/changes/add-bun-plugin/specs/bun-plugin/spec.md b/openspec/changes/add-bun-plugin/specs/bun-plugin/spec.md new file mode 100644 index 000000000..009ef520e --- /dev/null +++ b/openspec/changes/add-bun-plugin/specs/bun-plugin/spec.md @@ -0,0 +1,103 @@ +# bun-plugin Specification + +## ADDED Requirements + +### Requirement: Bun Plugin Configuration Patterns + +The Bun plugin SHALL support two primary configuration patterns depending on the usage context: + +1. **Direct Reference (Bun.build)**: The plugin SHALL be configurable by passing a configured plugin instance directly in the `plugins` array when using `Bun.build()`. +2. **Package Reference (Bun.serve)**: The plugin SHALL be configurable via a package name in `bunfig.toml` when using `Bun.serve()` for full-stack applications, with convention-based schema discovery. + +A future, more advanced configuration pattern using a custom static file MAY be supported, but is not required for the initial version. + +#### Scenario: Plugin configuration with Bun.build + +- **WHEN** a user wants to build an application using `Bun.build()` +- **AND** they configure the plugin with an environment variable schema +- **THEN** they can pass a configured plugin instance directly in the `plugins` array +- **AND** the plugin validates and transforms environment variables during the build process + +#### Scenario: Plugin configuration with Bun.serve via package reference + +- **WHEN** a user wants to use `Bun.serve()` for a full-stack application +- **AND** they configure `bunfig.toml` with: + - a `[serve.static]` section + - a `plugins` array that includes the package name `@arkenv/bun-plugin` +- **AND** their project contains an ArkEnv schema file in one of the supported default locations (for example, `./src/env.arkenv.ts`, `./src/env.ts`, `./env.arkenv.ts`, `./env.ts`) +- **AND** that schema file exports a schema using `type` from arktype (via a default export or an `env` named export) +- **THEN** the plugin SHALL locate the schema file via this convention-based search +- **AND** it SHALL load the schema at startup +- **AND** it SHALL use that schema to validate and transform environment variables during the bundling phase + +#### Scenario: Bun.serve configuration fails when no schema file is found + +- **WHEN** a user configures `bunfig.toml` with `[serve.static].plugins = ["@arkenv/bun-plugin"]` +- **AND** there is no schema file in any of the supported default locations +- **THEN** the plugin SHALL fail fast with a clear, descriptive error message +- **AND** the error message SHALL list the paths that were checked +- **AND** the error message SHALL show an example of a minimal `env` schema file the user can create + +### Requirement: Bun Plugin Environment Variable Validation and Transformation + +The Bun plugin SHALL validate environment variables at build-time using ArkEnv's schema validation and statically replace `process.env.VARIABLE` access with validated, transformed values during bundling. The plugin SHALL transform values according to the schema (e.g., string to boolean, apply default values). + +#### Scenario: Plugin validates and transforms environment variables +- **WHEN** a user configures the Bun plugin with a schema containing environment variables +- **AND** the schema includes variables with various types (e.g., `BUN_PUBLIC_API_URL: "string"`, `BUN_PUBLIC_DEBUG: "boolean"`) +- **THEN** the plugin validates all variables in the schema at build-time +- **AND** the plugin transforms values according to the schema (e.g., `"true"` string to `true` boolean) +- **AND** the plugin statically replaces `process.env.VARIABLE` with the validated, transformed value +- **AND** default values are applied when variables are missing + +#### Scenario: Plugin handles various process.env access patterns +- **WHEN** a user accesses environment variables via different patterns (e.g., `process.env.VAR`, `process.env["VAR"]`, destructuring) +- **AND** the plugin is configured with a schema +- **THEN** the plugin transforms all access patterns to use validated values +- **AND** the transformed code uses the validated, transformed values instead of raw `process.env` access + +### Requirement: Bun Plugin Environment Variable Filtering + +The Bun plugin SHALL automatically filter environment variables to only expose those matching Bun's configured prefix (defaults to `BUN_PUBLIC_*`) to client code. Server-only environment variables without the prefix SHALL NOT be exposed to the client bundle. + +#### Scenario: Only prefixed variables are exposed to client +- **WHEN** a user passes a schema containing both prefixed (`BUN_PUBLIC_*`) and unprefixed variables to the Bun plugin +- **AND** the schema includes server-only variables like `PORT` and client-safe variables like `BUN_PUBLIC_API_URL` +- **THEN** only variables starting with the Bun prefix are validated and exposed to client code via `process.env.*` +- **AND** server-only variables are not included in the client bundle +- **AND** the plugin respects Bun's prefix configuration (from bunfig.toml or defaults to `BUN_PUBLIC_*`) + +#### Scenario: Default prefix behavior +- **WHEN** a user does not configure a prefix in bunfig.toml +- **AND** they pass a schema to the Bun plugin +- **THEN** the plugin defaults to `"BUN_PUBLIC_"` as the prefix +- **AND** only variables starting with `BUN_PUBLIC_` are exposed to client code + +### Requirement: Bun Plugin Type Augmentation + +The Bun plugin SHALL provide TypeScript type augmentation for `process.env` similar to the Vite plugin's `ImportMetaEnvAugmented`, enabling type-safe access to environment variables in client code. + +#### Scenario: Type augmentation enables type-safe access +- **WHEN** a user configures the Bun plugin with a schema +- **AND** they augment `process.env` types using `ProcessEnvAugmented` +- **THEN** TypeScript provides type checking and autocomplete for environment variables +- **AND** only variables matching the configured prefix are included in the type +- **AND** types reflect the validated, transformed values (e.g., `boolean` instead of `string`) + +#### Scenario: Type augmentation filters by prefix +- **WHEN** a user defines a schema with both prefixed and unprefixed variables +- **AND** they use `ProcessEnvAugmented` for type augmentation +- **THEN** the type only includes variables matching the configured prefix +- **AND** server-only variables are excluded from the type + +### Requirement: Bun Plugin Integration with Bun's Serve Function + +The Bun plugin SHALL work correctly with Bun's `serve` function for full-stack React applications, validating and transforming environment variables during the bundling phase before the server starts. + +#### Scenario: Plugin works with Bun serve in full-stack React app +- **WHEN** a user uses Bun's `serve` function with a full-stack React application +- **AND** they configure the Bun plugin with a schema +- **THEN** the plugin validates environment variables during bundling +- **AND** the plugin transforms `process.env` access in both server and client code +- **AND** only prefixed variables are exposed to client code +- **AND** the server starts successfully with validated environment variables diff --git a/openspec/changes/add-bun-plugin/tasks.md b/openspec/changes/add-bun-plugin/tasks.md new file mode 100644 index 000000000..6c03b2844 --- /dev/null +++ b/openspec/changes/add-bun-plugin/tasks.md @@ -0,0 +1,10 @@ +# Implementation Checklist + +- [x] Implement Direct Reference (Bun.build) +- [x] Implement Package Reference (Bun.serve) with convention-based schema discovery +- [x] Add schema file path resolution for default locations +- [x] Implement error handling when schema file is not found +- [x] Add unit tests +- [x] Add integration tests +- [x] Update docs +- [x] Setup playground app diff --git a/package.json b/package.json index 10bb79725..00841a9f7 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@sentry/cli", "@tailwindcss/oxide", "@vercel/speed-insights", + "bun", "esbuild", "sharp" ] diff --git a/packages/bun-plugin/.gitignore b/packages/bun-plugin/.gitignore new file mode 100644 index 000000000..a14702c40 --- /dev/null +++ b/packages/bun-plugin/.gitignore @@ -0,0 +1,34 @@ +# dependencies (bun install) +node_modules + +# output +out +dist +*.tgz + +# code coverage +coverage +*.lcov + +# logs +logs +_.log +report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# caches +.eslintcache +.cache +*.tsbuildinfo + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/packages/bun-plugin/README.md b/packages/bun-plugin/README.md new file mode 100644 index 000000000..ab4aa5ce7 --- /dev/null +++ b/packages/bun-plugin/README.md @@ -0,0 +1,98 @@ +# `@arkenv/bun-plugin` + +[Bun](https://bun.sh/) plugin to validate environment variables at build-time with ArkEnv. + +
+
+
+ +## [Read the docs →](https://arkenv.js.org/docs/bun-plugin) + +
+
+ +## Features + +- Build-time validation - app won't start if environment variables are invalid +- Typesafe environment variables backed by TypeScript +- Access to ArkType's powerful type system +- Automatic filtering of client-exposed variables (defaults to `BUN_PUBLIC_*`) + +## Installation + +
+pnpm + +```sh +pnpm add @arkenv/bun-plugin arktype +``` +
+ +
+npm + +```sh +npm install @arkenv/bun-plugin arktype +``` +
+ +
+Yarn + +```sh +yarn add @arkenv/bun-plugin arktype +``` +
+ +
+Bun + +```sh +bun add @arkenv/bun-plugin arktype +``` +
+ +## Usage + +### Basic Example + +```ts +// bun.config.ts or in Bun.build() +import arkenv from '@arkenv/bun-plugin'; + +await Bun.build({ + entrypoints: ['./app.tsx'], + outdir: './dist', + plugins: [ + arkenv({ + BUN_PUBLIC_API_URL: 'string', + BUN_PUBLIC_DEBUG: 'boolean', + }), + ], +}); +``` + +### With Type Augmentation + +```ts +// src/env.d.ts +/// + +import type { ProcessEnvAugmented } from '@arkenv/bun-plugin'; + +declare global { + namespace NodeJS { + // Note: This assumes your env schema is the default export from "./env" + interface ProcessEnv extends ProcessEnvAugmented {} + } +} +``` + +## Examples + +* [bun-react](https://github.com/yamcodes/arkenv/tree/main/apps/playgrounds/bun-react) + +## Related + +- [ArkEnv](https://arkenv.js.org) - Core library and docs +- [ArkType](https://arktype.io/) - Underlying validator / type system diff --git a/packages/bun-plugin/package.json b/packages/bun-plugin/package.json new file mode 100644 index 000000000..8709bc115 --- /dev/null +++ b/packages/bun-plugin/package.json @@ -0,0 +1,68 @@ +{ + "name": "@arkenv/bun-plugin", + "version": "0.0.1", + "author": "Yam Borodetsky ", + "repository": { + "type": "git", + "url": "git+https://github.com/yamcodes/arkenv.git" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "dependencies": { + "arkenv": "workspace:*" + }, + "devDependencies": { + "@repo/types": "workspace:*", + "@size-limit/preset-small-lib": "11.2.0", + "@types/bun": "1.3.2", + "arktype": "2.1.27", + "bun": "1.1.0", + "size-limit": "11.2.0", + "tsdown": "0.16.5", + "typescript": "5.9.3", + "vitest": "4.0.10" + }, + "peerDependencies": { + "arktype": "^2.1.22", + "bun": "^1.0.0" + }, + "exports": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs" + }, + "bugs": "https://github.com/yamcodes/arkenv/labels/%40arkenv%2Fbun-plugin", + "description": "Bun plugin for ArkEnv", + "files": [ + "dist" + ], + "homepage": "https://arkenv.js.org", + "keywords": [ + "arktype", + "arkenv", + "environment", + "variables", + "bun", + "plugin", + "bun-plugin" + ], + "license": "MIT", + "scripts": { + "build": "tsdown", + "typecheck": "tsc --noEmit", + "clean": "rimraf dist node_modules", + "test": "vitest", + "fix": "pnpm -w run fix", + "changeset": "pnpm -w run changeset", + "size": "size-limit" + }, + "type": "module", + "types": "./dist/index.d.ts", + "size-limit": [ + { + "path": "dist/index.js", + "limit": "2 kB", + "import": "*" + } + ] +} diff --git a/packages/bun-plugin/src/__mocks__/bun.ts b/packages/bun-plugin/src/__mocks__/bun.ts new file mode 100644 index 000000000..6da02df2d --- /dev/null +++ b/packages/bun-plugin/src/__mocks__/bun.ts @@ -0,0 +1,23 @@ +// Mock Bun plugin API for testing +export function plugin(pluginConfig: { + name: string; + setup: (build: { + onLoad: ( + args: { filter: RegExp }, + callback: (args: { path: string }) => Promise< + | { + loader?: string; + contents?: string; + } + | undefined + >, + ) => void; + }) => void; +}) { + return { + name: pluginConfig.name, + setup: pluginConfig.setup, + }; +} + +export type BunPlugin = ReturnType; diff --git a/packages/bun-plugin/src/index.test.ts b/packages/bun-plugin/src/index.test.ts new file mode 100644 index 000000000..96a576a63 --- /dev/null +++ b/packages/bun-plugin/src/index.test.ts @@ -0,0 +1,69 @@ +import { afterEach, beforeEach, describe, expect, it } from "vitest"; +import arkenvPlugin, { processEnvSchema } from "./index.js"; + +describe("Bun Plugin", () => { + let originalEnv: NodeJS.ProcessEnv; + + beforeEach(() => { + originalEnv = { ...process.env }; + }); + + afterEach(() => { + process.env = originalEnv; + }); + + it("should create a plugin function", () => { + expect(typeof arkenvPlugin).toBe("function"); + }); + + it("should return a Bun plugin object", () => { + // Set up a valid environment variable + process.env.BUN_PUBLIC_TEST = "test-value"; + + const pluginInstance = arkenvPlugin({ BUN_PUBLIC_TEST: "string" }); + + expect(pluginInstance).toHaveProperty("name", "@arkenv/bun-plugin"); + expect(pluginInstance).toHaveProperty("setup"); + expect(typeof pluginInstance.setup).toBe("function"); + }); + + it("should validate environment variables at plugin creation", () => { + // Set up a valid environment variable + process.env.BUN_PUBLIC_TEST = "test-value"; + + expect(() => { + arkenvPlugin({ BUN_PUBLIC_TEST: "string" }); + }).not.toThrow(); + }); + + it("should throw if environment variable validation fails", () => { + // Don't set the required environment variable + delete process.env.BUN_PUBLIC_REQUIRED; + + expect(() => { + arkenvPlugin({ BUN_PUBLIC_REQUIRED: "string" }); + }).toThrow(); + }); + + it("should filter out non-prefixed variables", () => { + process.env.BUN_PUBLIC_API_URL = "https://api.example.com"; + process.env.PORT = "3000"; + process.env.DATABASE_URL = "postgres://localhost/db"; + + const envMap = processEnvSchema({ + BUN_PUBLIC_API_URL: "string", + PORT: "number.port", + DATABASE_URL: "string", + }); + + // Check that prefixed variables are present + expect(envMap.has("BUN_PUBLIC_API_URL")).toBe(true); + expect(envMap.get("BUN_PUBLIC_API_URL")).toBe( + JSON.stringify("https://api.example.com"), + ); + + // Check that non-prefixed variables are filtered out + expect(envMap.has("PORT")).toBe(false); + expect(envMap.has("DATABASE_URL")).toBe(false); + }); +}); diff --git a/packages/bun-plugin/src/index.ts b/packages/bun-plugin/src/index.ts new file mode 100644 index 000000000..3153ae79c --- /dev/null +++ b/packages/bun-plugin/src/index.ts @@ -0,0 +1,241 @@ +import { join } from "node:path"; +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"; + +/** + * Helper to process env schema and return envMap + */ +export function processEnvSchema(options: EnvSchema | type.Any) { + // Validate environment variables + 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), + ), + ); + + // Create a map of variable names to their JSON-stringified values + const envMap = new Map(); + for (const [key, value] of Object.entries(filteredEnv)) { + envMap.set(key, JSON.stringify(value)); + } + return envMap; +} + +/** + * Helper to register the onLoad handler + */ +function registerLoader(build: PluginBuilder, envMap: Map) { + build.onLoad({ filter: /\.(js|jsx|ts|tsx|mjs|cjs)$/ }, async (args) => { + // Skip node_modules and other non-source files + if (args.path.includes("node_modules")) { + return undefined; + } + + try { + // Read the file contents + const file = Bun.file(args.path); + const contents = await file.text(); + + // Replace process.env.VARIABLE patterns with validated values + let transformed = contents; + + // Pattern 1: process.env.VARIABLE + // Pattern 2: process.env["VARIABLE"] + // Pattern 3: process.env['VARIABLE'] + for (const [key, value] of envMap.entries()) { + // Replace process.env.KEY (word boundary to avoid partial matches) + const dotPattern = new RegExp( + `process\\.env\\.${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, + "g", + ); + transformed = transformed.replace(dotPattern, value); + + // Replace process.env["KEY"] and process.env['KEY'] + const bracketPattern = new RegExp( + `process\\.env\\[(["'])${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\1\\]`, + "g", + ); + transformed = transformed.replace(bracketPattern, value); + } + + // Determine loader based on file extension + const loader = ( + args.path.endsWith(".tsx") || args.path.endsWith(".jsx") + ? "tsx" + : args.path.endsWith(".ts") || args.path.endsWith(".mts") + ? "ts" + : "js" + ) as Loader; + + return { + loader, + contents: transformed, + }; + } catch (error) { + // If file can't be read, return undefined to let Bun handle it + return undefined; + } + }); +} + +/** + * Bun plugin to validate environment variables using ArkEnv and expose prefixed variables to client code. + * + * You can use this in one of two ways: + * + * 1. **Zero-config (Static Analysis)**: + * Automatically looks for `./src/env.ts` or `./env.ts`. + * + * In `bunfig.toml`: + * ```toml + * [serve.static] + * plugins = ["@arkenv/bun-plugin"] + * ``` + * and in `Bun.build`: + * ```ts + * import arkenv from "@arkenv/bun-plugin"; + * Bun.build({ + * plugins: [arkenv] + * }) + * ``` + * + * 2. **Manual Configuration**: + * Call it as a function in `Bun.build` with your schema. + * ```ts + * import arkenv from "@arkenv/bun-plugin"; + * import { Env } from "./src/env"; + * Bun.build({ + * plugins: [arkenv(Env)] + * }) + * ``` + */ +export function arkenv>( + options: EnvSchema, +): BunPlugin; +export function arkenv(options: type.Any): BunPlugin; +export function arkenv>( + options: EnvSchema | type.Any, +): BunPlugin { + const envMap = processEnvSchema(options); + + return { + name: "@arkenv/bun-plugin", + setup(build) { + registerLoader(build, envMap); + }, + } satisfies BunPlugin; +} + +// Attach static analysis properties to the function to make it a valid BunPlugin object +// This allows it to be used in bunfig.toml as `plugins = ["@arkenv/bun-plugin"]` +/** + * Bun plugin to validate environment variables using ArkEnv and expose prefixed variables to client code. + * + * You can use this in one of two ways: + * + * 1. **Zero-config (Static Analysis)**: + * Automatically looks for `./src/env.ts` or `./env.ts`. + * + * In `bunfig.toml`: + * ```toml + * [serve.static] + * plugins = ["@arkenv/bun-plugin"] + * ``` + * and in `Bun.build`: + * ```ts + * import arkenv from "@arkenv/bun-plugin"; + * Bun.build({ + * plugins: [arkenv] + * }) + * ``` + * + * 2. **Manual Configuration**: + * Call it as a function in `Bun.build` with your schema. + * ```ts + * import arkenv from "@arkenv/bun-plugin"; + * import { Env } from "./src/env"; + * Bun.build({ + * plugins: [arkenv(Env)] + * }) + * ``` + */ +const hybrid = arkenv as typeof arkenv & BunPlugin; + +Object.defineProperty(hybrid, "name", { + value: "@arkenv/bun-plugin", + writable: false, +}); + +hybrid.setup = (build) => { + const envMap = new Map(); + + build.onStart(async () => { + const cwd = process.cwd(); + let schema: any; + + const possiblePaths = [join(cwd, "src", "env.ts"), join(cwd, "env.ts")]; + + for (const p of possiblePaths) { + if (await Bun.file(p).exists()) { + try { + // Invalidate cache to support hot reloading of the schema file itself + // Note: Bun's require cache invalidation might be needed if using require + // For ESM import(), we might need a cache busting query param or similar if Bun caches it aggressively + // However, for now, we'll try standard import. + // To truly support hot reload of config, we might need to rely on Bun's watcher restarting the process + // when bunfig.toml or dependencies change. + const mod = await import(p); + if (mod.default) { + schema = mod.default; + break; + } + if (mod.env) { + schema = mod.env; + break; + } + } catch (e) { + console.error(`Failed to load env schema from ${p}:`, e); + } + } + } + + if (!schema) { + const pathsList = possiblePaths.map((p) => ` - ${p}`).join("\n"); + const example = ` +Example \`src/env.ts\`: +\`\`\`ts +import { type } from "arktype"; + +export default type({ + BUN_PUBLIC_API_URL: "string", + BUN_PUBLIC_DEBUG: "boolean" +}); +\`\`\` +`; + throw new Error( + `@arkenv/bun-plugin: No environment schema found.\n\nChecked paths:\n${pathsList}\n\nPlease create a schema file at one of these locations exporting your environment definition.\n${example}`, + ); + } + + // Update the shared envMap with new values + const newEnvMap = processEnvSchema(schema); + envMap.clear(); + for (const [k, v] of newEnvMap) { + envMap.set(k, v); + } + }); + + registerLoader(build, envMap); +}; + +export default hybrid; diff --git a/packages/bun-plugin/src/types.ts b/packages/bun-plugin/src/types.ts new file mode 100644 index 000000000..d17d16e86 --- /dev/null +++ b/packages/bun-plugin/src/types.ts @@ -0,0 +1,61 @@ +import type { InferType } from "@repo/types"; +import type { type } from "arktype"; + +/** + * Filter environment variables to only include those that start with the given prefix. + * This ensures only client-exposed variables (e.g., BUN_PUBLIC_*) are included in process.env. + */ +type FilterByPrefix< + T extends Record, + Prefix extends string = "BUN_PUBLIC_", +> = { + [K in keyof T as K extends `${Prefix}${string}` ? K : never]: T[K]; +}; + +/** + * Augment the `process.env` object with typesafe environment variables + * based on the schema validator. + * + * This type extracts the inferred type from the schema (result of `type()` from arkenv), + * filters it to only include variables matching the Bun prefix (defaults to "BUN_PUBLIC_"), + * and makes them available on `process.env`. + * + * @template TSchema - The environment variable schema (result of `type()` from arkenv) + * @template Prefix - The prefix to filter by (defaults to "BUN_PUBLIC_") + * + * @example + * ```ts + * // bun.config.ts or similar + * import arkenv from '@arkenv/bun-plugin'; + * import { type } from 'arkenv'; + * + * export const Env = type({ + * BUN_PUBLIC_API_URL: 'string', + * BUN_PUBLIC_DEBUG: 'boolean', + * PORT: 'number.port', // Server-only, won't be in ProcessEnvAugmented + * }); + * + * export default { + * plugins: [arkenv(Env)], + * }; + * ``` + * + * @example + * ```ts + * // src/env.d.ts + * /// + * + * import type { ProcessEnvAugmented } from '@arkenv/bun-plugin'; + * import type { Env } from './env'; // or from bun.config.ts + * + * declare global { + * namespace NodeJS { + * interface ProcessEnv extends ProcessEnvAugmented {} + * } + * } + * ``` + */ +export type ProcessEnvAugmented< + TSchema extends type.Any, + Prefix extends string = "BUN_PUBLIC_", +> = FilterByPrefix, Prefix>; diff --git a/packages/bun-plugin/tsconfig.json b/packages/bun-plugin/tsconfig.json new file mode 100644 index 000000000..0288a3e60 --- /dev/null +++ b/packages/bun-plugin/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "esnext", + "strict": true, + "esModuleInterop": true, + "moduleResolution": "bundler", + "skipLibCheck": true, + "noUnusedLocals": true, + "noImplicitAny": true, + "emitDeclarationOnly": true, + "outDir": "dist", + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true + } +} diff --git a/packages/bun-plugin/tsdown.config.ts b/packages/bun-plugin/tsdown.config.ts new file mode 100644 index 000000000..4bee4f713 --- /dev/null +++ b/packages/bun-plugin/tsdown.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "tsdown"; + +export default defineConfig({ + format: ["esm", "cjs"], + minify: true, + fixedExtension: false, + sourcemap: true, + dts: { + resolve: ["@repo/types"], + }, +}); diff --git a/packages/bun-plugin/turbo.json b/packages/bun-plugin/turbo.json new file mode 100644 index 000000000..3e34e8a2f --- /dev/null +++ b/packages/bun-plugin/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/bun-plugin/vitest.config.ts b/packages/bun-plugin/vitest.config.ts new file mode 100644 index 000000000..53978c91b --- /dev/null +++ b/packages/bun-plugin/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, + resolve: { + alias: { + // Mock bun module for testing + bun: new URL("./src/__mocks__/bun.ts", import.meta.url).pathname, + arkenv: new URL("../arkenv/src/index.ts", import.meta.url).pathname, + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 057d2a2b3..f12eb7717 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -128,6 +128,15 @@ importers: apps/playgrounds/bun-react: dependencies: + '@arkenv/bun-plugin': + specifier: workspace:* + version: link:../../../packages/bun-plugin + arkenv: + specifier: workspace:* + version: link:../../../packages/arkenv + arktype: + specifier: 'catalog:' + version: 2.1.27 react: specifier: 'catalog:' version: 19.2.0 @@ -418,6 +427,40 @@ importers: specifier: 'catalog:' version: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + packages/bun-plugin: + dependencies: + arkenv: + specifier: workspace:* + version: link:../arkenv + devDependencies: + '@repo/types': + specifier: workspace:* + version: link:../internal/types + '@size-limit/preset-small-lib': + specifier: 11.2.0 + version: 11.2.0(size-limit@11.2.0) + '@types/bun': + specifier: 1.3.2 + version: 1.3.2(@types/react@19.2.7) + arktype: + specifier: 2.1.27 + version: 2.1.27 + bun: + specifier: 1.1.0 + version: 1.1.0 + size-limit: + specifier: 11.2.0 + version: 11.2.0 + tsdown: + specifier: 0.16.5 + version: 0.16.5(typescript@5.9.3) + typescript: + specifier: 5.9.3 + version: 5.9.3 + vitest: + specifier: 4.0.10 + version: 4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + packages/internal/types: devDependencies: arktype: @@ -1817,6 +1860,46 @@ packages: resolution: {integrity: sha512-scSmQBD8eANlMUOglxHrN1JdSW8tDghsPuS83otqealBiIeMukCQMOf/wc0JJjDXomqwNdEQFLXLGHrU6PGxuA==} engines: {node: '>= 20.0.0'} + '@oven/bun-darwin-aarch64@1.1.0': + resolution: {integrity: sha512-8hDDBdQVHfFJ1KHXeHIB78SFs7rYG1Ff4n8SDv006ENc3uEIqynFVbHCKbqa5aE7zLkdBizoBzL50W76zAPmCQ==} + cpu: [arm64] + os: [darwin] + + '@oven/bun-darwin-x64-baseline@1.1.0': + resolution: {integrity: sha512-61rkVhpzU3PfoXRLrIpgKzLys4Qk9bvCI5DVM5cZgVxGq62jR+FwldCVcCoyfMdblGg07x3aUY9BXMwtSxvN0A==} + cpu: [x64] + os: [darwin] + + '@oven/bun-darwin-x64@1.1.0': + resolution: {integrity: sha512-KNEtlnEok6LB9ZNMe2aSxRVO8ps22hn4qTTjJc/s0josPNlIk4PvV2xusbUEGQS4XSNGcEH/Gh7VYE/W3u1gjg==} + cpu: [x64] + os: [darwin] + + '@oven/bun-linux-aarch64@1.1.0': + resolution: {integrity: sha512-UZhZU2mkC+OnbDB/Lq8MTJWiwhDMrnZvcW99cc+fymU5w/bOvpSuFOhpC/IeI1Dp8WGlkjDGyottSr++juWXOw==} + cpu: [arm64] + os: [linux] + + '@oven/bun-linux-x64-baseline@1.1.0': + resolution: {integrity: sha512-eAxK+x5dEzoVGR0f4FiiLYgtzHu8xB8uWB+Uc9h/6C46vODlOPsBT2Pwv6y5uPAX+dyKGHeMQsP+r2OFBkbkTA==} + cpu: [x64] + os: [linux] + + '@oven/bun-linux-x64@1.1.0': + resolution: {integrity: sha512-o2D9LGXyJqHTQ5tTF4GSctL20ZFw0EGwrhhCLPM4cGzHrhji3EP9euz28aREM+iYUqAWC6BOGz77up5yeMbBQw==} + cpu: [x64] + os: [linux] + + '@oven/bun-windows-x64-baseline@1.1.0': + resolution: {integrity: sha512-PqQToENBnHkzdcUnkCYI86397oNvs8Z7PZT8iyQEMSr/oYTMNCSArgrpFSEG6seN5HBPkSyhbj0fuU3U5oOhtQ==} + cpu: [x64] + os: [win32] + + '@oven/bun-windows-x64@1.1.0': + resolution: {integrity: sha512-SStu+4Zq0S/nfwaSz9QcBnk9CYjlSckzkzChoFt+vdIXfiaCT+uq0I57OXifRb67uOVtXl6N3PqmCivJPTh/kw==} + cpu: [x64] + os: [win32] + '@oxc-project/runtime@0.96.0': resolution: {integrity: sha512-34lh4o9CcSw09Hx6fKihPu85+m+4pmDlkXwJrLvN5nMq5JrcGhhihVM415zDqT8j8IixO1PYYdQZRN4SwQCncg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -1825,6 +1908,9 @@ packages: resolution: {integrity: sha512-8iE5/4OK0SLHqWzRxSvI1gjFPmIH6718s8iwkuco95rBZsCZIHq+5wy4lYsASxnH+8FOhbGndiUrcwsVG5i2zw==} engines: {node: ^20.19.0 || >=22.12.0} + '@oxc-project/types@0.97.0': + resolution: {integrity: sha512-lxmZK4xFrdvU0yZiDwgVQTCvh2gHWBJCBk5ALsrtsBWhs0uDIi+FTOnXRQeQfs304imdvTdaakT/lqwQ8hkOXQ==} + '@oxc-project/types@0.98.0': resolution: {integrity: sha512-Vzmd6FsqVuz5HQVcRC/hrx7Ujo3WEVeQP7C2UNP5uy1hUY4SQvMB+93jxkI1KRHz9a/6cni3glPOtvteN+zpsw==} @@ -2385,6 +2471,12 @@ packages: '@radix-ui/rect@1.1.1': resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + '@rolldown/binding-android-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-XlEkrOIHLyGT3avOgzfTFSjG+f+dZMw+/qd+Y3HLN86wlndrB/gSimrJCk4gOhr1XtRtEKfszpadI3Md4Z4/Ag==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + '@rolldown/binding-android-arm64@1.0.0-beta.51': resolution: {integrity: sha512-Ctn8FUXKWWQI9pWC61P1yumS9WjQtelNS9riHwV7oCkknPGaAry4o7eFx2KgoLMnI2BgFJYpW7Im8/zX3BuONg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2397,6 +2489,12 @@ packages: cpu: [arm64] os: [android] + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-+JRqKJhoFlt5r9q+DecAGPLZ5PxeLva+wCMtAuoFMWPoZzgcYrr599KQ+Ix0jwll4B4HGP43avu9My8KtSOR+w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + '@rolldown/binding-darwin-arm64@1.0.0-beta.51': resolution: {integrity: sha512-EL1aRW2Oq15ShUEkBPsDtLMO8GTqfb/ktM/dFaVzXKQiEE96Ss6nexMgfgQrg8dGnNpndFyffVDb5IdSibsu1g==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2409,6 +2507,12 @@ packages: cpu: [arm64] os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + resolution: {integrity: sha512-fFXDjXnuX7/gQZQm/1FoivVtRcyAzdjSik7Eo+9iwPQ9EgtA5/nB2+jmbzaKtMGG3q+BnZbdKHCtOacmNrkIDA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + '@rolldown/binding-darwin-x64@1.0.0-beta.51': resolution: {integrity: sha512-uGtYKlFen9pMIPvkHPWZVDtmYhMQi5g5Ddsndg1gf3atScKYKYgs5aDP4DhHeTwGXQglhfBG7lEaOIZ4UAIWww==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2421,6 +2525,12 @@ packages: cpu: [x64] os: [darwin] + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + resolution: {integrity: sha512-F1b6vARy49tjmT/hbloplzgJS7GIvwWZqt+tAHEstCh0JIh9sa8FAMVqEmYxDviqKBaAI8iVvUREm/Kh/PD26Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + '@rolldown/binding-freebsd-x64@1.0.0-beta.51': resolution: {integrity: sha512-JRoVTQtHYbZj1P07JLiuTuXjiBtIa7ag7/qgKA6CIIXnAcdl4LrOf7nfDuHPJcuRKaP5dzecMgY99itvWfmUFQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2433,6 +2543,12 @@ packages: cpu: [x64] os: [freebsd] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + resolution: {integrity: sha512-U6cR76N8T8M6lHj7EZrQ3xunLPxSvYYxA8vJsBKZiFZkT8YV4kjgCO3KwMJL0NOjQCPGKyiXO07U+KmJzdPGRw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.51': resolution: {integrity: sha512-BKATVnpPZ0TYBW9XfDwyd4kPGgvf964HiotIwUgpMrFOFYWqpZ+9ONNzMV4UFAYC7Hb5C2qgYQk/qj2OnAd4RQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2445,6 +2561,12 @@ packages: cpu: [arm] os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-ONgyjofCrrE3bnh5GZb8EINSFyR/hmwTzZ7oVuyUB170lboza1VMCnb8jgE6MsyyRgHYmN8Lb59i3NKGrxrYjw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.51': resolution: {integrity: sha512-xLd7da5jkfbVsBCm1buIRdWtuXY8+hU3+6ESXY/Tk5X5DPHaifrUblhYDgmA34dQt6WyNC2kfXGgrduPEvDI6Q==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2457,6 +2579,12 @@ packages: cpu: [arm64] os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.51': resolution: {integrity: sha512-EQFXTgHxxTzv3t5EmjUP/DfxzFYx9sMndfLsYaAY4DWF6KsK1fXGYsiupif6qPTViPC9eVmRm78q0pZU/kuIPg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2469,6 +2597,12 @@ packages: cpu: [arm64] os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + resolution: {integrity: sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.51': resolution: {integrity: sha512-p5P6Xpa68w3yFaAdSzIZJbj+AfuDnMDqNSeglBXM7UlJT14Q4zwK+rV+8Mhp9MiUb4XFISZtbI/seBprhkQbiQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2481,6 +2615,12 @@ packages: cpu: [x64] os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + resolution: {integrity: sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + '@rolldown/binding-linux-x64-musl@1.0.0-beta.51': resolution: {integrity: sha512-sNVVyLa8HB8wkFipdfz1s6i0YWinwpbMWk5hO5S+XAYH2UH67YzUT13gs6wZTKg2x/3gtgXzYnHyF5wMIqoDAw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2493,6 +2633,12 @@ packages: cpu: [x64] os: [linux] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + resolution: {integrity: sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + '@rolldown/binding-openharmony-arm64@1.0.0-beta.51': resolution: {integrity: sha512-e/JMTz9Q8+T3g/deEi8DK44sFWZWGKr9AOCW5e8C8SCVWzAXqYXAG7FXBWBNzWEZK0Rcwo9TQHTQ9Q0gXgdCaA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2505,6 +2651,11 @@ packages: cpu: [arm64] os: [openharmony] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + resolution: {integrity: sha512-nmCN0nIdeUnmgeDXiQ+2HU6FT162o+rxnF7WMkBm4M5Ds8qTU7Dzv2Wrf22bo4ftnlrb2hKK6FSwAJSAe2FWLg==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + '@rolldown/binding-wasm32-wasi@1.0.0-beta.51': resolution: {integrity: sha512-We3LWqSu6J9s5Y0MK+N7fUiiu37aBGPG3Pc347EoaROuAwkCS2u9xJ5dpIyLW4B49CIbS3KaPmn4kTgPb3EyPw==} engines: {node: '>=14.0.0'} @@ -2515,6 +2666,12 @@ packages: engines: {node: '>=14.0.0'} cpu: [wasm32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-7kcNLi7Ua59JTTLvbe1dYb028QEPaJPJQHqkmSZ5q3tJueUeb6yjRtx8mw4uIqgWZcnQHAR3PrLN4XRJxvgIkA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.51': resolution: {integrity: sha512-fj56buHRuMM+r/cb6ZYfNjNvO/0xeFybI6cTkTROJatdP4fvmQ1NS8D/Lm10FCSDEOkqIz8hK3TGpbAThbPHsA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2527,6 +2684,12 @@ packages: cpu: [arm64] os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-lL70VTNvSCdSZkDPPVMwWn/M2yQiYvSoXw9hTLgdIWdUfC3g72UaruezusR6ceRuwHCY1Ayu2LtKqXkBO5LIwg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.51': resolution: {integrity: sha512-fkqEqaeEx8AySXiDm54b/RdINb3C0VovzJA3osMhZsbn6FoD73H0AOIiaVAtGr6x63hefruVKTX8irAm4Jkt2w==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2539,6 +2702,12 @@ packages: cpu: [ia32] os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + resolution: {integrity: sha512-4qU4x5DXWB4JPjyTne/wBNPqkbQU8J45bl21geERBKtEittleonioACBL1R0PsBu0Aq21SwMK5a9zdBkWSlQtQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.51': resolution: {integrity: sha512-CWuLG/HMtrVcjKGa0C4GnuxONrku89g0+CsH8nT0SNhOtREXuzwgjIXNJImpE/A/DMf9JF+1Xkrq/YRr+F/rCg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -2554,6 +2723,9 @@ packages: '@rolldown/pluginutils@1.0.0-beta.47': resolution: {integrity: sha512-8QagwMH3kNCuzD8EWL8R2YPW5e4OrHNSAHRFDdmFqEwEaD/KcNKjVoumo+gP2vW5eKB2UPbM6vTYiGZX0ixLnw==} + '@rolldown/pluginutils@1.0.0-beta.50': + resolution: {integrity: sha512-5e76wQiQVeL1ICOZVUg4LSOVYg9jyhGCin+icYozhsUzM+fHE7kddi1bdiE0jwVqTfkjba3jUFbEkoC9WkdvyA==} + '@rolldown/pluginutils@1.0.0-beta.51': resolution: {integrity: sha512-51/8cNXMrqWqX3o8DZidhwz1uYq0BhHDDSfVygAND1Skx5s1TDw3APSSxCMcFFedwgqGcx34gRouwY+m404BBQ==} @@ -3350,6 +3522,9 @@ packages: '@types/braces@3.0.5': resolution: {integrity: sha512-SQFof9H+LXeWNz8wDe7oN5zu7ket0qwMu5vZubW4GCJ8Kkeh6nBWUz87+KTz/G3Kqsrp0j/W253XJb3KMEeg3w==} + '@types/bun@1.3.2': + resolution: {integrity: sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg==} + '@types/bun@1.3.3': resolution: {integrity: sha512-ogrKbJ2X5N0kWLLFKeytG0eHDleBYtngtlbu9cyBKFtNL3cnpDZkNdQj8flVf6WTZUX5ulI9AY1oa7ljhSrp+g==} @@ -3522,9 +3697,23 @@ packages: peerDependencies: vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + '@vitest/expect@4.0.10': + resolution: {integrity: sha512-3QkTX/lK39FBNwARCQRSQr0TP9+ywSdxSX+LgbJ2M1WmveXP72anTbnp2yl5fH+dU6SUmBzNMrDHs80G8G2DZg==} + '@vitest/expect@4.0.14': resolution: {integrity: sha512-RHk63V3zvRiYOWAV0rGEBRO820ce17hz7cI2kDmEdfQsBjT2luEKB5tCOc91u1oSQoUOZkSv3ZyzkdkSLD7lKw==} + '@vitest/mocker@4.0.10': + resolution: {integrity: sha512-e2OfdexYkjkg8Hh3L9NVEfbwGXq5IZbDovkf30qW2tOh7Rh9sVtmSr2ztEXOFbymNxS4qjzLXUQIvATvN4B+lg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + '@vitest/mocker@4.0.14': resolution: {integrity: sha512-RzS5NujlCzeRPF1MK7MXLiEFpkIXeMdQ+rN3Kk3tDI9j0mtbr7Nmuq67tpkOJQpgyClbOltCXMjLZicJHsH5Cg==} peerDependencies: @@ -3536,15 +3725,27 @@ packages: vite: optional: true + '@vitest/pretty-format@4.0.10': + resolution: {integrity: sha512-99EQbpa/zuDnvVjthwz5bH9o8iPefoQZ63WV8+bsRJZNw3qQSvSltfut8yu1Jc9mqOYi7pEbsKxYTi/rjaq6PA==} + '@vitest/pretty-format@4.0.14': resolution: {integrity: sha512-SOYPgujB6TITcJxgd3wmsLl+wZv+fy3av2PpiPpsWPZ6J1ySUYfScfpIt2Yv56ShJXR2MOA6q2KjKHN4EpdyRQ==} + '@vitest/runner@4.0.10': + resolution: {integrity: sha512-EXU2iSkKvNwtlL8L8doCpkyclw0mc/t4t9SeOnfOFPyqLmQwuceMPA4zJBa6jw0MKsZYbw7kAn+gl7HxrlB8UQ==} + '@vitest/runner@4.0.14': resolution: {integrity: sha512-BsAIk3FAqxICqREbX8SetIteT8PiaUL/tgJjmhxJhCsigmzzH8xeadtp7LRnTpCVzvf0ib9BgAfKJHuhNllKLw==} + '@vitest/snapshot@4.0.10': + resolution: {integrity: sha512-2N4X2ZZl7kZw0qeGdQ41H0KND96L3qX1RgwuCfy6oUsF2ISGD/HpSbmms+CkIOsQmg2kulwfhJ4CI0asnZlvkg==} + '@vitest/snapshot@4.0.14': resolution: {integrity: sha512-aQVBfT1PMzDSA16Y3Fp45a0q8nKexx6N5Amw3MX55BeTeZpoC08fGqEZqVmPcqN0ueZsuUQ9rriPMhZ3Mu19Ag==} + '@vitest/spy@4.0.10': + resolution: {integrity: sha512-AsY6sVS8OLb96GV5RoG8B6I35GAbNrC49AO+jNRF9YVGb/g9t+hzNm1H6kD0NDp8tt7VJLs6hb7YMkDXqu03iw==} + '@vitest/spy@4.0.14': resolution: {integrity: sha512-JmAZT1UtZooO0tpY3GRyiC/8W7dCs05UOq9rfsUUgEZEdq+DuHLmWhPsrTt0TiW7WYeL/hXpaE07AZ2RCk44hg==} @@ -3553,6 +3754,9 @@ packages: peerDependencies: vitest: 4.0.14 + '@vitest/utils@4.0.10': + resolution: {integrity: sha512-kOuqWnEwZNtQxMKg3WmPK1vmhZu9WcoX69iwWjVz+jvKTsF1emzsv3eoPcDr6ykA3qP2bsCQE7CwqfNtAVzsmg==} + '@vitest/utils@4.0.14': resolution: {integrity: sha512-hLqXZKAWNg8pI+SQXyXxWCTOpA3MvsqcbVeNgSi8x/CSN2wi26dSzn1wrOhmCmFjEvN9p8/kLFRHa6PI8jHazw==} @@ -3893,9 +4097,20 @@ packages: buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bun-types@1.3.2: + resolution: {integrity: sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg==} + peerDependencies: + '@types/react': ^19 + bun-types@1.3.3: resolution: {integrity: sha512-z3Xwlg7j2l9JY27x5Qn3Wlyos8YAp0kKRlrePAOjgjMGS5IG6E7Jnlx736vH9UVI4wUICwwhC9anYL++XeOgTQ==} + bun@1.1.0: + resolution: {integrity: sha512-vEfq5NXOWriLzVtA/gMC69MR/W48KE0G7SarZUbu7H89KH9u7FFozTvn8noyLKqpI1sRa2qu/laE21K+htFxCQ==} + cpu: [arm64, x64] + os: [darwin, linux, win32] + hasBin: true + bundle-name@4.1.0: resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==} engines: {node: '>=18'} @@ -6271,6 +6486,25 @@ packages: engines: {node: 20 || >=22} hasBin: true + rolldown-plugin-dts@0.17.8: + resolution: {integrity: sha512-76EEBlhF00yeY6M7VpMkWKI4r9WjuoMiOGey7j4D6zf3m0BR+ZrrY9hvSXdueJ3ljxSLq4DJBKFpX/X9+L7EKw==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@ts-macro/tsc': ^0.3.6 + '@typescript/native-preview': '>=7.0.0-dev.20250601.1' + rolldown: ^1.0.0-beta.44 + typescript: ^5.0.0 + vue-tsc: ~3.1.0 + peerDependenciesMeta: + '@ts-macro/tsc': + optional: true + '@typescript/native-preview': + optional: true + typescript: + optional: true + vue-tsc: + optional: true + rolldown-plugin-dts@0.18.1: resolution: {integrity: sha512-uIgNMix6OI+6bSkw0nw6O+G/ydPRCWKwvvcEyL6gWkVkSFVGWWO23DX4ZYVOqC7w5u2c8uPY9Q74U0QCKvegFA==} engines: {node: '>=20.19.0'} @@ -6330,6 +6564,11 @@ packages: yaml: optional: true + rolldown@1.0.0-beta.50: + resolution: {integrity: sha512-JFULvCNl/anKn99eKjOSEubi0lLmNqQDAjyEMME2T4CwezUDL0i6t1O9xZsu2OMehPnV2caNefWpGF+8TnzB6A==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + rolldown@1.0.0-beta.51: resolution: {integrity: sha512-ZRLgPlS91l4JztLYEZnmMcd3Umcla1hkXJgiEiR4HloRJBBoeaX8qogTu5Jfu36rRMVLndzqYv0h+M5gJAkUfg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -6834,6 +7073,31 @@ packages: typescript: optional: true + tsdown@0.16.5: + resolution: {integrity: sha512-jo/2MmJI1uNJ+QvwEfF/2DcICd2Bc/Gc/XIVJS9Gvfns7ji5TgUeu3kYfG8nA/mGgWXU8REpTNweIcVJQoSLAQ==} + engines: {node: '>=20.19.0'} + hasBin: true + peerDependencies: + '@arethetypeswrong/core': ^0.18.1 + '@vitejs/devtools': ^0.0.0-alpha.17 + publint: ^0.3.0 + typescript: ^5.0.0 + unplugin-lightningcss: ^0.4.0 + unplugin-unused: ^0.5.0 + peerDependenciesMeta: + '@arethetypeswrong/core': + optional: true + '@vitejs/devtools': + optional: true + publint: + optional: true + typescript: + optional: true + unplugin-lightningcss: + optional: true + unplugin-unused: + optional: true + tsdown@0.16.7: resolution: {integrity: sha512-3rGG74MGc16rJ70ID7WyKnJBM5TIqGX5uYbmUCdiFIc2c3+KXka4nCeji3RRxhBw/hL7Xbs8pCY8x2cn0J+O0w==} engines: {node: '>=20.19.0'} @@ -7257,6 +7521,40 @@ packages: vite: optional: true + vitest@4.0.10: + resolution: {integrity: sha512-2Fqty3MM9CDwOVet/jaQalYlbcjATZwPYGcqpiYQqgQ/dLC7GuHdISKgTYIVF/kaishKxLzleKWWfbSDklyIKg==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.10 + '@vitest/browser-preview': 4.0.10 + '@vitest/browser-webdriverio': 4.0.10 + '@vitest/ui': 4.0.10 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + vitest@4.0.14: resolution: {integrity: sha512-d9B2J9Cm9dN9+6nxMnnNJKJCtcyKfnHj15N6YNJfaFHRLua/d3sRKU9RuKmO9mB0XdFtUizlxfz/VPbd3OxGhw==} engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} @@ -9241,10 +9539,36 @@ snapshots: '@orama/orama@3.1.16': {} + '@oven/bun-darwin-aarch64@1.1.0': + optional: true + + '@oven/bun-darwin-x64-baseline@1.1.0': + optional: true + + '@oven/bun-darwin-x64@1.1.0': + optional: true + + '@oven/bun-linux-aarch64@1.1.0': + optional: true + + '@oven/bun-linux-x64-baseline@1.1.0': + optional: true + + '@oven/bun-linux-x64@1.1.0': + optional: true + + '@oven/bun-windows-x64-baseline@1.1.0': + optional: true + + '@oven/bun-windows-x64@1.1.0': + optional: true + '@oxc-project/runtime@0.96.0': {} '@oxc-project/runtime@0.99.0': {} + '@oxc-project/types@0.97.0': {} + '@oxc-project/types@0.98.0': {} '@oxc-project/types@0.99.0': {} @@ -9784,66 +10108,101 @@ snapshots: '@radix-ui/rect@1.1.1': {} + '@rolldown/binding-android-arm64@1.0.0-beta.50': + optional: true + '@rolldown/binding-android-arm64@1.0.0-beta.51': optional: true '@rolldown/binding-android-arm64@1.0.0-beta.52': optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.50': + optional: true + '@rolldown/binding-darwin-arm64@1.0.0-beta.51': optional: true '@rolldown/binding-darwin-arm64@1.0.0-beta.52': optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.50': + optional: true + '@rolldown/binding-darwin-x64@1.0.0-beta.51': optional: true '@rolldown/binding-darwin-x64@1.0.0-beta.52': optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.50': + optional: true + '@rolldown/binding-freebsd-x64@1.0.0-beta.51': optional: true '@rolldown/binding-freebsd-x64@1.0.0-beta.52': optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.50': + optional: true + '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.51': optional: true '@rolldown/binding-linux-arm-gnueabihf@1.0.0-beta.52': optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.50': + optional: true + '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.51': optional: true '@rolldown/binding-linux-arm64-gnu@1.0.0-beta.52': optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.50': + optional: true + '@rolldown/binding-linux-arm64-musl@1.0.0-beta.51': optional: true '@rolldown/binding-linux-arm64-musl@1.0.0-beta.52': optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.50': + optional: true + '@rolldown/binding-linux-x64-gnu@1.0.0-beta.51': optional: true '@rolldown/binding-linux-x64-gnu@1.0.0-beta.52': optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.50': + optional: true + '@rolldown/binding-linux-x64-musl@1.0.0-beta.51': optional: true '@rolldown/binding-linux-x64-musl@1.0.0-beta.52': optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.50': + optional: true + '@rolldown/binding-openharmony-arm64@1.0.0-beta.51': optional: true '@rolldown/binding-openharmony-arm64@1.0.0-beta.52': optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.50': + dependencies: + '@napi-rs/wasm-runtime': 1.0.7 + optional: true + '@rolldown/binding-wasm32-wasi@1.0.0-beta.51': dependencies: '@napi-rs/wasm-runtime': 1.0.7 @@ -9854,18 +10213,27 @@ snapshots: '@napi-rs/wasm-runtime': 1.0.7 optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.50': + optional: true + '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.51': optional: true '@rolldown/binding-win32-arm64-msvc@1.0.0-beta.52': optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.50': + optional: true + '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.51': optional: true '@rolldown/binding-win32-ia32-msvc@1.0.0-beta.52': optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.50': + optional: true + '@rolldown/binding-win32-x64-msvc@1.0.0-beta.51': optional: true @@ -9874,6 +10242,8 @@ snapshots: '@rolldown/pluginutils@1.0.0-beta.47': {} + '@rolldown/pluginutils@1.0.0-beta.50': {} + '@rolldown/pluginutils@1.0.0-beta.51': {} '@rolldown/pluginutils@1.0.0-beta.52': {} @@ -10883,6 +11253,12 @@ snapshots: '@types/braces@3.0.5': {} + '@types/bun@1.3.2(@types/react@19.2.7)': + dependencies: + bun-types: 1.3.2(@types/react@19.2.7) + transitivePeerDependencies: + - '@types/react' + '@types/bun@1.3.3': dependencies: bun-types: 1.3.3 @@ -11097,6 +11473,15 @@ snapshots: transitivePeerDependencies: - supports-color + '@vitest/expect@4.0.10': + dependencies: + '@standard-schema/spec': 1.0.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.10 + '@vitest/utils': 4.0.10 + chai: 6.2.1 + tinyrainbow: 3.0.3 + '@vitest/expect@4.0.14': dependencies: '@standard-schema/spec': 1.0.0 @@ -11106,6 +11491,14 @@ snapshots: chai: 6.2.1 tinyrainbow: 3.0.3 + '@vitest/mocker@4.0.10(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6))': + dependencies: + '@vitest/spy': 4.0.10 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + '@vitest/mocker@4.0.14(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6))': dependencies: '@vitest/spy': 4.0.14 @@ -11114,21 +11507,38 @@ snapshots: optionalDependencies: vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + '@vitest/pretty-format@4.0.10': + dependencies: + tinyrainbow: 3.0.3 + '@vitest/pretty-format@4.0.14': dependencies: tinyrainbow: 3.0.3 + '@vitest/runner@4.0.10': + dependencies: + '@vitest/utils': 4.0.10 + pathe: 2.0.3 + '@vitest/runner@4.0.14': dependencies: '@vitest/utils': 4.0.14 pathe: 2.0.3 + '@vitest/snapshot@4.0.10': + dependencies: + '@vitest/pretty-format': 4.0.10 + magic-string: 0.30.21 + pathe: 2.0.3 + '@vitest/snapshot@4.0.14': dependencies: '@vitest/pretty-format': 4.0.14 magic-string: 0.30.21 pathe: 2.0.3 + '@vitest/spy@4.0.10': {} + '@vitest/spy@4.0.14': {} '@vitest/ui@4.0.14(vitest@4.0.14)': @@ -11142,6 +11552,11 @@ snapshots: tinyrainbow: 3.0.3 vitest: 4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + '@vitest/utils@4.0.10': + dependencies: + '@vitest/pretty-format': 4.0.10 + tinyrainbow: 3.0.3 + '@vitest/utils@4.0.14': dependencies: '@vitest/pretty-format': 4.0.14 @@ -11500,10 +11915,26 @@ snapshots: base64-js: 1.5.1 ieee754: 1.2.1 + bun-types@1.3.2(@types/react@19.2.7): + dependencies: + '@types/node': 24.10.1 + '@types/react': 19.2.7 + bun-types@1.3.3: dependencies: '@types/node': 24.10.1 + bun@1.1.0: + optionalDependencies: + '@oven/bun-darwin-aarch64': 1.1.0 + '@oven/bun-darwin-x64': 1.1.0 + '@oven/bun-darwin-x64-baseline': 1.1.0 + '@oven/bun-linux-aarch64': 1.1.0 + '@oven/bun-linux-x64': 1.1.0 + '@oven/bun-linux-x64-baseline': 1.1.0 + '@oven/bun-windows-x64': 1.1.0 + '@oven/bun-windows-x64-baseline': 1.1.0 + bundle-name@4.1.0: dependencies: run-applescript: 7.1.0 @@ -14301,6 +14732,23 @@ snapshots: glob: 13.0.0 package-json-from-dist: 1.0.1 + rolldown-plugin-dts@0.17.8(rolldown@1.0.0-beta.50)(typescript@5.9.3): + dependencies: + '@babel/generator': 7.28.5 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + ast-kit: 2.2.0 + birpc: 2.8.0 + dts-resolver: 2.1.3 + get-tsconfig: 4.13.0 + magic-string: 0.30.21 + obug: 2.1.1 + rolldown: 1.0.0-beta.50 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - oxc-resolver + rolldown-plugin-dts@0.18.1(rolldown@1.0.0-beta.51)(typescript@5.9.3): dependencies: '@babel/generator': 7.28.5 @@ -14335,6 +14783,26 @@ snapshots: terser: 5.44.1 tsx: 4.20.6 + rolldown@1.0.0-beta.50: + dependencies: + '@oxc-project/types': 0.97.0 + '@rolldown/pluginutils': 1.0.0-beta.50 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-arm64': 1.0.0-beta.50 + '@rolldown/binding-darwin-x64': 1.0.0-beta.50 + '@rolldown/binding-freebsd-x64': 1.0.0-beta.50 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-arm64-musl': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-gnu': 1.0.0-beta.50 + '@rolldown/binding-linux-x64-musl': 1.0.0-beta.50 + '@rolldown/binding-openharmony-arm64': 1.0.0-beta.50 + '@rolldown/binding-wasm32-wasi': 1.0.0-beta.50 + '@rolldown/binding-win32-arm64-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-ia32-msvc': 1.0.0-beta.50 + '@rolldown/binding-win32-x64-msvc': 1.0.0-beta.50 + rolldown@1.0.0-beta.51: dependencies: '@oxc-project/types': 0.98.0 @@ -14910,6 +15378,32 @@ snapshots: optionalDependencies: typescript: 5.9.3 + tsdown@0.16.5(typescript@5.9.3): + dependencies: + ansis: 4.2.0 + cac: 6.7.14 + chokidar: 4.0.3 + diff: 8.0.2 + empathic: 2.0.0 + hookable: 5.5.3 + obug: 2.1.1 + rolldown: 1.0.0-beta.50 + rolldown-plugin-dts: 0.17.8(rolldown@1.0.0-beta.50)(typescript@5.9.3) + semver: 7.7.3 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tree-kill: 1.2.2 + unconfig-core: 7.4.1 + unrun: 0.2.11 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@ts-macro/tsc' + - '@typescript/native-preview' + - oxc-resolver + - synckit + - vue-tsc + tsdown@0.16.7(typescript@5.9.3): dependencies: ansis: 4.2.0 @@ -15365,6 +15859,47 @@ snapshots: optionalDependencies: vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + vitest@4.0.10(@types/debug@4.1.12)(@types/node@24.10.1)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6): + dependencies: + '@vitest/expect': 4.0.10 + '@vitest/mocker': 4.0.10(vite@7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6)) + '@vitest/pretty-format': 4.0.10 + '@vitest/runner': 4.0.10 + '@vitest/snapshot': 4.0.10 + '@vitest/spy': 4.0.10 + '@vitest/utils': 4.0.10 + debug: 4.4.3 + es-module-lexer: 1.7.0 + expect-type: 1.2.2 + magic-string: 0.30.21 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.2.4(@types/node@24.10.1)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/debug': 4.1.12 + '@types/node': 24.10.1 + '@vitest/ui': 4.0.14(vitest@4.0.14) + jsdom: 27.2.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + vitest@4.0.14(@opentelemetry/api@1.9.0)(@types/node@24.10.1)(@vitest/ui@4.0.14)(jiti@2.6.1)(jsdom@27.2.0)(lightningcss@1.30.2)(terser@5.44.1)(tsx@4.20.6): dependencies: '@vitest/expect': 4.0.14