Skip to content
Open
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
5 changes: 5 additions & 0 deletions .changeset/tame-parents-drive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/astro': patch
---

Fix `PUBLIC_CLERK_PUBLISHABLE_KEY` not readable from runtime environment when using the Astro Node adapter. Added `process.env` as a fallback in `getContextEnvVar()` for cases where `import.meta.env.PUBLIC_*` is statically replaced at build time by Vite.
133 changes: 133 additions & 0 deletions packages/astro/src/server/__tests__/get-safe-env.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';

import { getClientSafeEnv, getSafeEnv } from '../get-safe-env';

function createLocals(overrides: Partial<App.Locals> = {}): App.Locals {
return {
runtime: { env: {} as InternalEnv },
...overrides,
} as unknown as App.Locals;
}

describe('getSafeEnv', () => {
beforeEach(() => {
vi.stubEnv('PUBLIC_CLERK_PUBLISHABLE_KEY', '');
vi.stubEnv('CLERK_SECRET_KEY', '');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('reads from locals.runtime.env first (Cloudflare)', () => {
const locals = createLocals({
runtime: {
env: {
PUBLIC_CLERK_PUBLISHABLE_KEY: 'pk_from_runtime',
CLERK_SECRET_KEY: 'sk_from_runtime',
} as InternalEnv,
},
});

// Also set process.env to verify runtime.env takes priority
process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process';
process.env.CLERK_SECRET_KEY = 'sk_from_process';

const env = getSafeEnv(locals);

expect(env.pk).toBe('pk_from_runtime');
expect(env.sk).toBe('sk_from_runtime');

delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
delete process.env.CLERK_SECRET_KEY;
});

it('reads from import.meta.env when runtime.env is not available', () => {
vi.stubEnv('PUBLIC_CLERK_PUBLISHABLE_KEY', 'pk_from_meta');
vi.stubEnv('CLERK_SECRET_KEY', 'sk_from_meta');

const locals = createLocals({ runtime: { env: undefined as unknown as InternalEnv } });
const env = getSafeEnv(locals);

expect(env.pk).toBe('pk_from_meta');
expect(env.sk).toBe('sk_from_meta');
});

it('falls back to process.env when import.meta.env has no value', () => {
process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process';
process.env.CLERK_SECRET_KEY = 'sk_from_process';

const locals = createLocals({ runtime: { env: undefined as unknown as InternalEnv } });
const env = getSafeEnv(locals);

expect(env.pk).toBe('pk_from_process');
expect(env.sk).toBe('sk_from_process');

delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
delete process.env.CLERK_SECRET_KEY;
});

it('returns undefined when no env source has the value', () => {
// Clean process.env so the fallback finds nothing
delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
delete process.env.CLERK_SECRET_KEY;

const locals = createLocals({ runtime: { env: undefined as unknown as InternalEnv } });
const env = getSafeEnv(locals);

expect(env.pk).toBeUndefined();
expect(env.sk).toBeUndefined();
});

it('prefers keylessPublishableKey over all env sources', () => {
process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process';

const locals = createLocals({
runtime: { env: undefined as unknown as InternalEnv },
keylessPublishableKey: 'pk_keyless',
});
const env = getSafeEnv(locals);

expect(env.pk).toBe('pk_keyless');

delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
});
});

describe('getClientSafeEnv', () => {
beforeEach(() => {
vi.stubEnv('PUBLIC_CLERK_PUBLISHABLE_KEY', '');
});

afterEach(() => {
vi.unstubAllEnvs();
});

it('falls back to process.env for publishableKey', () => {
process.env.PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_from_process';

const locals = createLocals({ runtime: { env: undefined as unknown as InternalEnv } });
const env = getClientSafeEnv(locals);

expect(env.publishableKey).toBe('pk_from_process');

delete process.env.PUBLIC_CLERK_PUBLISHABLE_KEY;
});

it('falls back to process.env for all public env vars', () => {
process.env.PUBLIC_CLERK_DOMAIN = 'test.domain.com';
process.env.PUBLIC_CLERK_SIGN_IN_URL = '/sign-in';
process.env.PUBLIC_CLERK_SIGN_UP_URL = '/sign-up';

const locals = createLocals({ runtime: { env: undefined as unknown as InternalEnv } });
const env = getClientSafeEnv(locals);

expect(env.domain).toBe('test.domain.com');
expect(env.signInUrl).toBe('/sign-in');
expect(env.signUpUrl).toBe('/sign-up');

delete process.env.PUBLIC_CLERK_DOMAIN;
delete process.env.PUBLIC_CLERK_SIGN_IN_URL;
delete process.env.PUBLIC_CLERK_SIGN_UP_URL;
});
});
13 changes: 12 additions & 1 deletion packages/astro/src/server/get-safe-env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,18 @@ function getContextEnvVar(envVarName: keyof InternalEnv, contextOrLocals: Contex
return locals.runtime.env[envVarName];
}

return import.meta.env[envVarName];
const envValue = import.meta.env[envVarName];
if (envValue) {
return envValue;
}

// Fallback to process.env for runtime environments (e.g., Node.js adapter)
// where import.meta.env.PUBLIC_* is statically replaced at build time by Vite
if (typeof process !== 'undefined' && process.env) {
return process.env[envVarName];
}

return undefined;
}

/**
Expand Down
Loading