Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
bca5d56
refactor(vite.config.ts): enhance configuration with environment load…
yamcodes Nov 12, 2025
25a3d33
Merge branch 'main' into 365-arkenv-in-viteconfigts
yamcodes Nov 18, 2025
a984baa
Update dependencies and improve test setup
yamcodes Nov 18, 2025
999f21f
Merge branch 'main' into 365-arkenv-in-viteconfigts
yamcodes Nov 18, 2025
8c590b2
Add type safety requirements for Vite config usage
yamcodes Nov 18, 2025
7f9e968
Refactor Vite config to enhance environment variable schema handling
yamcodes Nov 18, 2025
499e130
Merge branch 'main' into 365-arkenv-in-viteconfigts
yamcodes Nov 19, 2025
ffbadb0
Merge branch 'main' into 365-arkenv-in-viteconfigts
yamcodes Nov 20, 2025
45632dd
Update pnpm-lock.yaml and Vite configuration for improved environment…
yamcodes Nov 20, 2025
0c3aafe
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
7a22926
Refactor environment schema usage across the project
yamcodes Nov 20, 2025
f8bfd5f
Update pnpm-lock.yaml to upgrade Vite to version 7.2.4
yamcodes Nov 20, 2025
093c433
Refactor environment type from `Env` to `EnvSchema` for consistency
yamcodes Nov 20, 2025
7576ce6
Enhance ArkEnv and Vite integration with comprehensive tests and docu…
yamcodes Nov 20, 2025
df389e9
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
e09b1d9
Remove outdated design, proposal, tasks, and specs documents for ArkE…
yamcodes Nov 20, 2025
f65115d
Update validation tasks for ArkEnv and Vite integration documentation
yamcodes Nov 20, 2025
38b55ab
Update documentation for ArkEnv and Vite integration
yamcodes Nov 20, 2025
1610173
Update ArkEnv in Vite config documentation
yamcodes Nov 20, 2025
34ac14b
Update Quickstart tests to reflect ArkEnv documentation links
yamcodes Nov 20, 2025
e1de716
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 20, 2025
6eaa578
Update documentation links for environment variable setup in README f…
yamcodes Nov 20, 2025
e1e0d70
Update documentation navigation tests to reflect new ArkEnv paths
yamcodes Nov 20, 2025
39cd975
Update tests to reflect new ArkEnv documentation paths
yamcodes Nov 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .changeset/clever-ties-search.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@
import arkenv, { type } from 'arkenv';

// Define schema once
const envSchema = type({
const Env = type({
PORT: "number.port",
HOST: "string.host",
});

// Reuse it in multiple places
const configEnv = arkenv(envSchema, process.env);
const testEnv = arkenv(envSchema, { PORT: "3000", HOST: "localhost" });
const configEnv = arkenv(Env, process.env);
const testEnv = arkenv(Env, { PORT: "3000", HOST: "localhost" });
```
4 changes: 2 additions & 2 deletions apps/playgrounds/node/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import arkenv, { type } from "arkenv";

const envSchema = type({
const Env = type({
HOST: "string.host",
PORT: "number.port",
NODE_ENV: "'development' | 'production' | 'test' = 'development'",
ALLOWED_ORIGINS: type("string[]").default(() => []),
DEBUG: "boolean = true",
});

const env = arkenv(envSchema, process.env);
const env = arkenv(Env, process.env);

// Automatically validate and parse process.env
// TypeScript knows the ✨exact✨ types!
Expand Down
46 changes: 33 additions & 13 deletions apps/playgrounds/vite/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,36 @@
import arkenv from "@arkenv/vite-plugin";
import react from "@vitejs/plugin-react";
import { type } from "arkenv";
import { defineConfig } from "vite";
import arkenvVitePlugin from "@arkenv/vite-plugin";
import reactPlugin from "@vitejs/plugin-react";
import arkenv, { type } from "arkenv";
import { defineConfig, loadEnv } from "vite";

// Define the schema once, outside of defineConfig using type()
// This schema is used for both:
// 1. Validating unprefixed config variables (PORT) via loadEnv
// 2. Validating VITE_* variables via the plugin
const Env = type({
PORT: "number.port",
VITE_MY_VAR: "string",
VITE_MY_NUMBER: type("string").pipe((str) => Number.parseInt(str, 10)),
VITE_MY_BOOLEAN: type("string").pipe((str) => str === "true"),
});

// https://vite.dev/config/
export default defineConfig({
plugins: [
react(),
arkenv({
VITE_MY_VAR: "string",
VITE_MY_NUMBER: type("string").pipe((str) => Number.parseInt(str, 10)),
VITE_MY_BOOLEAN: type("string").pipe((str) => str === "true"),
}),
],
export default defineConfig(({ mode }) => {
// Validate unprefixed config variables (e.g., PORT) using loadEnv
// These are server-only and not exposed to client code
// The schema defined with type() can be passed directly to arkenv()
const env = arkenv(Env, loadEnv(mode, process.cwd(), ""));

console.log(env.VITE_MY_NUMBER + " " + typeof env.VITE_MY_NUMBER);
return {
plugins: [
reactPlugin(),
// The plugin validates VITE_* variables and exposes them to client code
// The same schema is reused here to avoid duplication
arkenvVitePlugin(Env),
],
server: {
port: env.PORT,
},
};
});
2 changes: 1 addition & 1 deletion apps/www/app/(home)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export default function Layout({ children }: { children: ReactNode }) {
links={[
{
text: "Documentation",
url: "/docs",
url: "/docs/arkenv",
active: "none",
},
]}
Expand Down
2 changes: 1 addition & 1 deletion apps/www/components/page/sail-button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe("SailButton", () => {
it("renders as a link to docs/quickstart", () => {
render(<SailButton />);
const link = screen.getByRole("link");
expect(link).toHaveAttribute("href", "/docs/quickstart");
expect(link).toHaveAttribute("href", "/docs/arkenv/quickstart");
});

it("handles left click and triggers animation", async () => {
Expand Down
4 changes: 2 additions & 2 deletions apps/www/components/page/sail-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,14 @@ export function SailButton() {
className="w-full sm:w-auto text-lg relative overflow-hidden cursor-pointer dark:bg-primary dark:text-primary-foreground dark:hover:bg-primary/80 dark:hover:text-primary-foreground transition-all duration-300 shadow-[0_4px_20px_rgba(96,165,250,0.6)] dark:shadow-[0_16px_20px_rgba(96,165,250,0.6)] hover:bg-blue-500/10 bg-gradient-to-r from-white/20 to-transparent"
>
<a
href="/docs/quickstart"
href="/docs/arkenv/quickstart"
onClick={handleSailClick}
className="inline-flex items-center justify-center gap-2 whitespace-nowrap"
>
<Sailboat
aria-hidden="true"
className={`transition-transform duration-[700ms] ease-in ${isSailing ? "translate-x-[1000%]" : ""}`}
onTransitionEnd={() => router.push("/docs/quickstart")}
onTransitionEnd={() => router.push("/docs/arkenv/quickstart")}
/>
<span className="font-semibold">
Set sail{" "}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ That's where type definitions start making sense: define your schema once with `
```ts
import arkenv, { type } from 'arkenv';

const envSchema = type({
const Env = type({
HOST: "string.host",
PORT: "number.port",
DEBUG: "boolean",
});

// Use it in multiple places with full type inference
const env1 = arkenv(envSchema, process.env);
const env2 = arkenv(envSchema, { HOST: "localhost", PORT: "3000", DEBUG: "true" });
const env1 = arkenv(Env, process.env);
const env2 = arkenv(Env, { HOST: "localhost", PORT: "3000", DEBUG: "true" });

// TypeScript knows the exact types
const host: string = env1.HOST;
Expand All @@ -53,7 +53,7 @@ Export a type definition from one module and import it in others:
```ts
import { type } from 'arkenv';

export const envSchema = type({
export const Env = type({
DATABASE_HOST: "string.host",
DATABASE_PORT: "number.port",
API_KEY: "string",
Expand All @@ -63,17 +63,17 @@ Export a type definition from one module and import it in others:
<Tab value="config/database.ts">
```ts
import arkenv from 'arkenv';
import { envSchema } from './env-schema';
import { Env } from './env-schema';

export const dbEnv = arkenv(envSchema, process.env);
export const dbEnv = arkenv(Env, process.env);
```
</Tab>
<Tab value="config/api.ts">
```ts
import arkenv from 'arkenv';
import { envSchema } from './env-schema';
import { Env } from './env-schema';

export const apiEnv = arkenv(envSchema, process.env);
export const apiEnv = arkenv(Env, process.env);
```
</Tab>
</Tabs>
Expand All @@ -85,13 +85,13 @@ Type definitions provide the same type safety as raw schema objects:
```ts
import arkenv, { type } from 'arkenv';

const envSchema = type({
const Env = type({
PORT: "number.port",
HOST: "string.host",
TIMEOUT: "number >= 0",
});

const env = arkenv(envSchema, process.env);
const env = arkenv(Env, process.env);

const port: number = env.PORT;
const host: string = env.HOST;
Expand All @@ -105,13 +105,13 @@ Use the same schema with different environment sources:
```ts
import arkenv, { type } from 'arkenv';

const envSchema = type({
const Env = type({
API_URL: "string",
API_KEY: "string",
});

const productionEnv = arkenv(envSchema, process.env);
const testEnv = arkenv(envSchema, {
const productionEnv = arkenv(Env, process.env);
const testEnv = arkenv(Env, {
API_URL: "http://localhost:3000",
API_KEY: "test-key",
});
Expand Down
File renamed without changes.
19 changes: 19 additions & 0 deletions apps/www/content/docs/arkenv/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"title": "arkenv",
"description": "The core library",
"root": true,
"pages": [
"---Introduction---",
"index",
"quickstart",
"examples",
"---API---",
"morphs",
"---Integrations---",
"[VS Code & Cursor](/docs/arkenv/integrations/vscode)",
"[JetBrains IDEs](/docs/arkenv/integrations/jetbrains)",
"---How-to---",
"[Load environment variables](/docs/arkenv/how-to/load-environment-variables)",
"[New][Reuse your schema](/docs/arkenv/how-to/reuse-schemas)"
]
}
15 changes: 1 addition & 14 deletions apps/www/content/docs/meta.json
Original file line number Diff line number Diff line change
@@ -1,16 +1,3 @@
{
"pages": [
"---Introduction---",
"index",
"quickstart",
"examples",
"---API---",
"morphs",
"---Integrations---",
"[VS Code & Cursor](/docs/integrations/vscode)",
"[JetBrains IDEs](/docs/integrations/jetbrains)",
"---How-to---",
"[Load environment variables](/docs/how-to/load-environment-variables)",
"[New][Reuse your schema](/docs/how-to/reuse-schemas)"
]
"pages": ["arkenv", "vite-plugin"]
}
45 changes: 45 additions & 0 deletions apps/www/content/docs/vite-plugin/arkenv-in-viteconfig.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
---
title: Using ArkEnv in Vite config
---

import { Globe } from "lucide-react";


Vite doesn't automatically load `.env*` files when evaluating your `vite.config.ts` - those variables are only available later in your application code via `import.meta.env`. If you need environment variables in your config (like setting `server.port` or conditionally enabling plugins), you'll need to load them manually using Vite's `loadEnv` helper.

> [!IMPORTANT]
> You must have the core `arkenv` package installed in your project for this to work. See [arkenv](/docs/arkenv) for more information.


Here's how to validate those variables with ArkEnv. The key is defining your schema *once* with ArkEnv's`type()` and reusing it for both server-side config variables and client-exposed `VITE_*` variables:

```ts title="vite.config.ts"
import arkenvVitePlugin from "@arkenv/vite-plugin";
import arkenv, { type } from "arkenv";
import { defineConfig, loadEnv } from "vite";

const Env = type({
PORT: "number.port",
VITE_API_URL: "string",
});

export default defineConfig(({ mode }) => {
const env = arkenv(Env, loadEnv(mode, process.cwd(), ""));

return {
plugins: [arkenvVitePlugin(Env)],
server: {
port: env.PORT,
},
};
});
```

Note how the schema is defined once but used in two different places:

1. Environment variables needed for the Vite config (`PORT`) are available via `loadEnv()` and validated by ArkEnv.
2. Public `VITE_*` variables that should be available in your client code (like `VITE_API_URL`) are validated by the `@arkenv/vite-plugin`.

<Cards>
<Card icon={<Globe aria-hidden="true" />} title="Using Environment Variables in Config (Vite docs)" href="https://vite.dev/config/#using-environment-variables-in-config" />
</Cards>
20 changes: 20 additions & 0 deletions apps/www/content/docs/vite-plugin/index.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
title: What is the Vite plugin?
---

This is the Vite plugin for ArkEnv. It validates environment variables at build-time with ArkEnv.

```ts title="vite.config.ts"
import arkenv from "@arkenv/vite-plugin";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [
arkenv({
VITE_API_URL: "string",
VITE_APP_NAME: "'MyApp' | 'TestApp'",
"VITE_DEBUG": 'boolean = false'
}),
],
});
```
6 changes: 6 additions & 0 deletions apps/www/content/docs/vite-plugin/meta.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"title": "@arkenv/vite-plugin",
"description": "The vite plugin",
"root": true,
"pages": ["index", "arkenv-in-viteconfig"]
}
51 changes: 51 additions & 0 deletions apps/www/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { source } from "~/lib/source";

export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl;

// Only handle /docs paths (but not /docs/arkenv/* which should work normally)
if (!pathname.startsWith("/docs")) {
return NextResponse.next();
}

// Skip if already under /docs/arkenv
if (pathname.startsWith("/docs/arkenv")) {
return NextResponse.next();
}

// Skip the root /docs redirect (handled by next.config.ts)
if (pathname === "/docs") {
return NextResponse.next();
}

// Extract the slug from the path
// e.g., /docs/quickstart -> ["quickstart"]
// e.g., /docs/how-to/load -> ["how-to", "load"]
const slug = pathname.replace("/docs", "").split("/").filter(Boolean);

// Check if the page exists with the original slug
const page = source.getPage(slug);
if (page) {
// Page exists, let it render normally
return NextResponse.next();
}

// Page doesn't exist, try with "arkenv" prefix
const arkenvSlug = ["arkenv", ...slug];
const arkenvPage = source.getPage(arkenvSlug);
if (arkenvPage) {
// Found it under arkenv, redirect there
const newUrl = request.nextUrl.clone();
newUrl.pathname = `/docs/arkenv/${slug.join("/")}`;
return NextResponse.redirect(newUrl);
}

// Not found even under arkenv, let it fall through to 404
return NextResponse.next();
}

export const config = {
matcher: "/docs/:path*",
};
10 changes: 10 additions & 0 deletions apps/www/next.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ const config = {
// We check typesafety on ci
ignoreBuildErrors: true,
},
// Redirect /docs to /docs/arkenv
async redirects() {
return [
{
source: "/docs",
destination: "/docs/arkenv",
permanent: true,
},
];
},
// PostHog rewrites to support analytics ingestion proxy
async rewrites() {
return [
Expand Down
4 changes: 3 additions & 1 deletion apps/www/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"dependencies": {
"@ark/util": "0.53.0",
"@arkenv/vite-plugin": "^0.0.16",
"@fumadocs/mdx-remote": "1.4.3",
"@icons-pack/react-simple-icons": "13.8.0",
"@radix-ui/react-separator": "1.1.8",
Expand Down Expand Up @@ -44,7 +45,8 @@
"shiki": "3.15.0",
"tailwind-merge": "3.4.0",
"tailwindcss-animate": "1.0.7",
"twoslash": "0.3.4"
"twoslash": "0.3.4",
"vite": "^7.2.4"
},
"devDependencies": {
"@swc/plugin-styled-jsx": "10.0.0",
Expand Down
Loading
Loading