Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 9 additions & 0 deletions .bumpy/static-dynamic-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
varlock: minor
'@varlock/astro-integration': minor
'@varlock/expo-integration': minor
'@varlock/nextjs-integration': minor
'@varlock/vite-integration': minor
---

Add static/dynamic config controls and dynamic+public framework/runtime support
2 changes: 2 additions & 0 deletions framework-tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bun run test
bun run test:astro
bun run test:expo
bun run test:nextjs
bun run test:sveltekit

# Watch mode (re-runs on file changes)
bun run test:watch
Expand Down Expand Up @@ -73,3 +74,4 @@ Each scenario uses `describeScenario()` which:
- **Astro** — tests static builds and SSR dev server, verifying env injection, leak detection in static output / client scripts / server pages / API endpoints, log redaction, and env vars in astro config
- **Next.js** — tests multiple versions (14, 15, 16) and bundlers (webpack, turbopack), verifying env injection, leak detection, log redaction, and sourcemap scrubbing
- **Expo** — tests the babel plugin transform pipeline, verifying static replacement of public vars, protection of sensitive vars, and correct handling of server (+api) routes
- **SvelteKit** — tests static replacement behavior for static vs dynamic public vars and runtime access to dynamic public vars via server routes
5 changes: 5 additions & 0 deletions framework-tests/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 71 additions & 0 deletions framework-tests/frameworks/astro/astro-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,44 @@ export function defineAstroTests(astroVersion: number, testDir: string, opts: {
],
});

astroEnv.describeDevScenario('dynamic public env endpoint', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
readyTimeout: 30_000,
templateFiles: {
'src/pages/index.astro': 'pages/basic-page.astro',
'astro.config.mts': 'configs/astro.config.server.mts',
},
requests: [
{
path: '/__varlock/public-env',
bodyAssertions: {
shouldContain: ['"PUBLIC_DYNAMIC_VAR":"public-dynamic-var--dev"'],
shouldNotContain: ['super-secret-value'],
},
},
],
});

astroEnv.describeDevScenario('dynamic public env endpoint - custom path', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
readyTimeout: 30_000,
templateFiles: {
'src/pages/index.astro': 'pages/basic-page.astro',
'astro.config.mts': 'configs/astro.config.server.custom-public-path.mts',
},
requests: [
{
path: '/api/public-env',
bodyAssertions: {
shouldContain: ['"PUBLIC_DYNAMIC_VAR":"public-dynamic-var--dev"'],
shouldNotContain: ['super-secret-value'],
},
},
],
});

astroEnv.describeDevScenario('leaky API endpoint', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
Expand All @@ -286,6 +324,39 @@ export function defineAstroTests(astroVersion: number, testDir: string, opts: {
],
});

astroEnv.describeDevScenario('dynamic public env endpoint - disabled', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
readyTimeout: 30_000,
templateFiles: {
'src/pages/index.astro': 'pages/basic-page.astro',
'astro.config.mts': 'configs/astro.config.server.no-public-endpoint.mts',
},
requests: [
{
path: '/__varlock/public-env',
expectedStatus: 404,
},
],
});

astroEnv.describeDevScenario('dynamic public env endpoint - auto disabled when none exist', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
readyTimeout: 30_000,
templateFiles: {
'src/pages/index.astro': 'pages/basic-page.astro',
'astro.config.mts': 'configs/astro.config.server.mts',
'.env.schema': 'schemas/.env.schema.no-public-dynamic',
},
requests: [
{
path: '/__varlock/public-env',
expectedStatus: 404,
},
],
});

astroEnv.describeDevScenario('non-existent var access in API endpoint', {
command: `astro dev --port ${port()}`,
readyPattern: /http:\/\/localhost/,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import varlockAstroIntegration from '@varlock/astro-integration';

export default defineConfig({
integrations: [
varlockAstroIntegration({
publicDynamicEndpoint: { path: '/api/public-env' },
}),
],
output: 'server',
adapter: node({ mode: 'standalone' }),
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { defineConfig } from 'astro/config';
import node from '@astrojs/node';
import varlockAstroIntegration from '@varlock/astro-integration';

export default defineConfig({
integrations: [
varlockAstroIntegration({
publicDynamicEndpoint: false,
}),
],
output: 'server',
adapter: node({ mode: 'standalone' }),
});
1 change: 1 addition & 0 deletions framework-tests/frameworks/astro/files/schemas/.env.dev
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ENV_SPECIFIC_VAR=env-specific-var--dev
PUBLIC_DYNAMIC_VAR=public-dynamic-var--dev
1 change: 1 addition & 0 deletions framework-tests/frameworks/astro/files/schemas/.env.prod
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
ENV_SPECIFIC_VAR=env-specific-var--prod
PUBLIC_DYNAMIC_VAR=public-dynamic-var--prod
4 changes: 3 additions & 1 deletion framework-tests/frameworks/astro/files/schemas/.env.schema
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @defaultSensitive=false @defaultRequired=infer
# @defaultSensitive=false @defaultRequired=infer @defaultDynamic=sensitive
# @generateTypes(lang="ts", path="env.d.ts")
# @currentEnv=$APP_ENV
# ---
Expand All @@ -9,6 +9,8 @@ APP_ENV=dev
PUBLIC_VAR=public-var-value
UNPREFIXED_PUBLIC=unprefixed-public-var
ENV_SPECIFIC_VAR=env-specific-var--default
# @dynamic
PUBLIC_DYNAMIC_VAR=public-dynamic-var--default

# @sensitive
SENSITIVE_VAR=super-secret-value
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @defaultSensitive=false @defaultRequired=infer
# @defaultSensitive=false @defaultRequired=infer @defaultDynamic=sensitive
# @generateTypes(lang="ts", path="env.d.ts")
# @currentEnv=$APP_ENV
# ---
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# @defaultSensitive=false @defaultRequired=infer
# @defaultSensitive=false @defaultRequired=infer @defaultDynamic=sensitive
# @generateTypes(lang="ts", path="env.d.ts")
# @currentEnv=$APP_ENV
# ---
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# @defaultSensitive=false @defaultRequired=infer
# @defaultDynamic=sensitive
# @generateTypes(lang="ts", path="env.d.ts")
# @currentEnv=$APP_ENV
# ---

# @type=enum(dev, preview, prod, test)
APP_ENV=dev

PUBLIC_VAR=public-var-value
UNPREFIXED_PUBLIC=unprefixed-public-var
ENV_SPECIFIC_VAR=env-specific-var--default

# @sensitive
SENSITIVE_VAR=super-secret-value
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ENV } from 'varlock/env';

export default function HomePage() {
return (
<main>
<h1>Varlock Framework Test - Next.js (direct dynamic)</h1>
<p>Dynamic public: {ENV.PUBLIC_DYNAMIC_VAR}</p>
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { ENV } from 'varlock/env';

export function NestedDynamicValue() {
return <p>Nested dynamic public: {ENV.PUBLIC_DYNAMIC_VAR}</p>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { NestedDynamicValue } from './components/nested-dynamic-value';

export default function HomePage() {
return (
<main>
<h1>Varlock Framework Test - Next.js (nested dynamic)</h1>
<NestedDynamicValue />
</main>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# @defaultSensitive=false @defaultRequired=infer
# @generateTypes(lang="ts", path="env.d.ts")
# @currentEnv=$APP_ENV
# ---

# @type=enum(dev, preview, prod, test)
APP_ENV=dev

NEXT_PUBLIC_VAR=next-prefixed-public-var
PUBLIC_VAR=unprefixed-public-var
ENV_SPECIFIC_VAR=env-specific-var--default

# @sensitive
SENSITIVE_VAR=super-secret-var

PUBLIC_DYNAMIC_VAR=public-dynamic-var # @dynamic
31 changes: 31 additions & 0 deletions framework-tests/frameworks/nextjs/nextjs-shared.ts
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,37 @@ export function defineNextjsTests(nextVersion: number, testDir: string) {
});

describe('default output mode', () => {
nextEnv.describeScenario('dynamic public access in page marks route dynamic', {
command: buildCommand,
skip: nextVersion === 14,
templateFiles: {
'app/page.tsx': 'pages/dynamic-direct-page.tsx',
'.env.schema': 'schemas/.env.schema.dynamic-public',
},
outputAssertions: [
{
description: 'route is treated as dynamic (not prerendered)',
shouldContain: ['┌ ƒ /'],
},
],
});

nextEnv.describeScenario('nested dynamic public access marks route dynamic', {
command: buildCommand,
skip: nextVersion === 14,
templateFiles: {
'app/page.tsx': 'pages/dynamic-nested-page.tsx',
'.env.schema': 'schemas/.env.schema.dynamic-public',
'app/components/nested-dynamic-value.tsx': 'pages/dynamic-nested-component.tsx',
},
outputAssertions: [
{
description: 'route is treated as dynamic (not prerendered)',
shouldContain: ['┌ ƒ /'],
},
],
});

nextEnv.describeScenario('basic static page', {
command: buildCommand,
templateFiles: {
Expand Down
10 changes: 10 additions & 0 deletions framework-tests/frameworks/sveltekit/files/_base/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"name": "sveltekit-framework-test",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"build": "vite build",
"dev": "vite dev"
}
}
11 changes: 11 additions & 0 deletions framework-tests/frameworks/sveltekit/files/_base/src/app.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
</body>
</html>
12 changes: 12 additions & 0 deletions framework-tests/frameworks/sveltekit/files/_base/svelte.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import adapter from '@sveltejs/adapter-static';
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';

/** @type {import('@sveltejs/kit').Config} */
const config = {
preprocess: vitePreprocess(),
kit: {
adapter: adapter(),
},
};

export default config;
10 changes: 10 additions & 0 deletions framework-tests/frameworks/sveltekit/files/_base/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { sveltekit } from '@sveltejs/kit/vite';
import { varlockVitePlugin } from '@varlock/vite-integration';
import { defineConfig } from 'vite';

export default defineConfig({
plugins: [
varlockVitePlugin(),
sveltekit(),
],
});
16 changes: 16 additions & 0 deletions framework-tests/frameworks/sveltekit/files/pages/basic-page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<script lang="ts">
import { onMount } from 'svelte';
import { ENV, loadPublicDynamicEnv } from 'varlock/env';

const staticVar = ENV.PUBLIC_STATIC_VAR;

let dynamicVar = 'client-not-hydrated';
onMount(async () => {
await loadPublicDynamicEnv();
dynamicVar = ENV.PUBLIC_DYNAMIC_VAR;
});
</script>

<h1>SvelteKit Varlock Test</h1>
<p class="static">{staticVar}</p>
<p class="dynamic">{dynamicVar}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<script lang="ts">
import { ENV } from 'varlock/env';
</script>

<h1>Prerender Dynamic Test</h1>
<p>{ENV.PUBLIC_DYNAMIC_VAR}</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { ENV, getPublicDynamicEnv } from 'varlock/env';

export const GET = async () => {
const keys = ['PUBLIC_DYNAMIC_VAR'];
const payload = getPublicDynamicEnv(keys) as Record<string, unknown>;
for (const key of keys) {
if (payload[key] !== undefined) continue;
if (process.env[key] !== undefined) {
payload[key] = process.env[key];
continue;
}
const envVal = (ENV as any)[key];
if (envVal !== undefined) payload[key] = envVal;
}
return new Response(JSON.stringify(payload), {
headers: {
'content-type': 'application/json; charset=utf-8',
'cache-control': 'no-store',
},
});
};
3 changes: 3 additions & 0 deletions framework-tests/frameworks/sveltekit/files/schemas/.env.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PUBLIC_STATIC_VAR=public-static-dev
PUBLIC_DYNAMIC_VAR=public-dynamic-dev
SECRET_VAR=super-secret-dev
3 changes: 3 additions & 0 deletions framework-tests/frameworks/sveltekit/files/schemas/.env.prod
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
PUBLIC_STATIC_VAR=public-static-prod
PUBLIC_DYNAMIC_VAR=public-dynamic-prod
SECRET_VAR=super-secret-prod
Loading
Loading