Skip to content

Commit

Permalink
feat(shared): Support passing env source into runtime environment hel…
Browse files Browse the repository at this point in the history
…pers

We can support cases like per-request env in Cloudflare workers
  • Loading branch information
dimkl committed Oct 14, 2023
1 parent a11f962 commit a2149aa
Show file tree
Hide file tree
Showing 2 changed files with 290 additions and 18 deletions.
219 changes: 219 additions & 0 deletions packages/shared/src/utils/runtimeEnvironment.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import { isDevelopmentEnvironment, isProductionEnvironment, isTestEnvironment } from './runtimeEnvironment';

async function withEnv(name: string, value: any, cb: () => any | Promise<any>) {
const currentValue = process.env[name];

process.env[name] = value;
const res = await cb();

process.env[name] = currentValue;
return res;
}

describe('isDevelopmentEnvironment(env)', () => {
test('is false for undefined process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', undefined, () => {
return isDevelopmentEnvironment();
});

expect(isDev).toBe(false);
});

test('is false for non-development process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isDevelopmentEnvironment();
});

expect(isDev).toBe(false);
});

test('is true for development process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'development', () => {
return isDevelopmentEnvironment();
});

expect(isDev).toBe(true);
});

describe('when env is passed as parameter', () => {
test('is false for non-development process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isDevelopmentEnvironment(process.env);
});

expect(isDev).toBe(false);
});

test('is true for development process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'development', () => {
return isDevelopmentEnvironment(process.env);
});

expect(isDev).toBe(true);
});
});

// This case is related to per-request env on Cloudflare fetch handler
describe('when env-like object is passed as parameter', () => {
test('is false for non-development process.env.NODE_ENV', async () => {
const isDev = isDevelopmentEnvironment({ NODE_ENV: 'whatever' });
expect(isDev).toBe(false);
});

test('is true for development process.env.NODE_ENV', async () => {
const isDev = isDevelopmentEnvironment({ NODE_ENV: 'development' });
expect(isDev).toBe(true);
});
});

describe('when publishableKey is passed as parameter', () => {
test('is false for non-development process.env.NODE_ENV', async () => {
const isDev = isDevelopmentEnvironment('pk_live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(false);
});

test('is true for development process.env.NODE_ENV', async () => {
const isDev = isDevelopmentEnvironment('pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(true);
});
});
});

describe('isProductionEnvironment(env)', () => {
test('is false for undefined process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', undefined, () => {
return isProductionEnvironment();
});

expect(isDev).toBe(false);
});

test('is false for non-production process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isProductionEnvironment();
});

expect(isDev).toBe(false);
});

test('is true for production process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'production', () => {
return isProductionEnvironment();
});

expect(isDev).toBe(true);
});

describe('when env is passed as parameter', () => {
test('is false for non-production process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isProductionEnvironment(process.env);
});

expect(isDev).toBe(false);
});

test('is true for production process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'production', () => {
return isProductionEnvironment(process.env);
});

expect(isDev).toBe(true);
});
});

// This case is related to per-request env on Cloudflare fetch handler
describe('when env-like object is passed as parameter', () => {
test('is false for non-production process.env.NODE_ENV', async () => {
const isDev = isProductionEnvironment({ NODE_ENV: 'whatever' });
expect(isDev).toBe(false);
});

test('is true for production process.env.NODE_ENV', async () => {
const isDev = isProductionEnvironment({ NODE_ENV: 'production' });
expect(isDev).toBe(true);
});
});

describe('when publishableKey is passed as parameter', () => {
test('is false for non-production process.env.NODE_ENV', async () => {
const isDev = isProductionEnvironment('pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(false);
});

test('is true for production process.env.NODE_ENV', async () => {
const isDev = isProductionEnvironment('pk_live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(true);
});
});
});

describe('isTestEnvironment(env)', () => {
test('is false for undefined process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', undefined, () => {
return isTestEnvironment();
});

expect(isDev).toBe(false);
});

test('is false for non-test process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isTestEnvironment();
});

expect(isDev).toBe(false);
});

test('is true for test process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'test', () => {
return isTestEnvironment();
});

expect(isDev).toBe(true);
});

describe('when env is passed as parameter', () => {
test('is false non-test process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'whatever', () => {
return isTestEnvironment(process.env);
});

expect(isDev).toBe(false);
});

test('is true for test process.env.NODE_ENV', async () => {
const isDev = await withEnv('NODE_ENV', 'test', () => {
return isTestEnvironment(process.env);
});

expect(isDev).toBe(true);
});
});

// This case is related to per-request env on Cloudflare fetch handler
describe('when env-like object is passed as parameter', () => {
test('is false for non-test process.env.NODE_ENV', async () => {
const isDev = isTestEnvironment({ NODE_ENV: 'whatever' });
expect(isDev).toBe(false);
});

test('is false for test process.env.NODE_ENV', async () => {
const isDev = isTestEnvironment({ NODE_ENV: 'test' });
expect(isDev).toBe(true);
});
});

// There is no publishableKey for test env (only prod /dev)
describe('when publishableKey is passed as parameter', () => {
test('is false for non-test process.env.NODE_ENV', async () => {
const isDev = isTestEnvironment('pk_test_Zm9vLWJhci0xMy5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(false);
});

test('is false for test process.env.NODE_ENV', async () => {
const isDev = isTestEnvironment('pk_live_ZXhhbXBsZS5jbGVyay5hY2NvdW50cy5kZXYk');
expect(isDev).toBe(false);
});
});
});
89 changes: 71 additions & 18 deletions packages/shared/src/utils/runtimeEnvironment.ts
Original file line number Diff line number Diff line change
@@ -1,30 +1,83 @@
export const isDevelopmentEnvironment = (): boolean => {
try {
return process.env.NODE_ENV === 'development';
// eslint-disable-next-line no-empty
} catch (err) {}
import { parsePublishableKey } from './keys';

// TODO: add support for import.meta.env.DEV that is being used by vite
/**
* Check for development runtime environment using the environment variables of the Clerk publishableKey.
*
* Examples:
*
* 1. using process.env: `isDevelopmentEnvironment()`
* 2. passing custom process.env source: `isDevelopmentEnvironment(import.meta.env)`
* 3. using publishableKey: `isDevelopmentEnvironment('pk_********')`
*
* @param envOrPublishableKey
* @returns boolean
*/
export const isDevelopmentEnvironment = (envOrPublishableKey?: EnvironmentOrPublishableKey): boolean => {
return isEnvironment('development', envOrPublishableKey);
};

return false;
/**
* Check for testing runtime environment using the environment variables of the Clerk publishableKey.
*
* Examples:
*
* 1. using process.env: `isTestEnvironment()`
* 2. passing custom process.env source: `isTestEnvironment(import.meta.env)`
* 3. using publishableKey: `isTestEnvironment('pk_test_********')`
*
* The `isTestEnvironment()` will always be false for any publishableKey since development and production
* publishable keys are only supported.
*
* @param envOrPublishableKey
* @returns boolean
*/
export const isTestEnvironment = (envOrPublishableKey?: EnvironmentOrPublishableKey): boolean => {
return isEnvironment('test', envOrPublishableKey);
};

export const isTestEnvironment = (): boolean => {
try {
return process.env.NODE_ENV === 'test';
// eslint-disable-next-line no-empty
} catch (err) {}
/**
* Check for production runtime environment using the environment variables of the Clerk publishableKey.
*
* Examples:
*
* 1. using process.env: `isProductionEnvironment()`
* 2. passing custom process.env source: `isProductionEnvironment(import.meta.env)`
* 3. using publishableKey: `isProductionEnvironment('pk_live_********')`
*
* @param envOrPublishableKey
* @returns boolean
*/
export const isProductionEnvironment = (envOrPublishableKey?: EnvironmentOrPublishableKey): boolean => {
return isEnvironment('production', envOrPublishableKey);
};

// TODO: add support for import.meta.env.DEV that is being used by vite
return false;
const getRuntimeFromProcess = (env?: typeof process.env) => {
return env?.NODE_ENV;
};

const getRuntimeFromPublishableKey = (publishableKey: string) => {
return parsePublishableKey(publishableKey)?.instanceType;
};

const isPublishableKey = (envOrPublishableKey?: string | typeof process.env): envOrPublishableKey is string => {
return typeof envOrPublishableKey === 'string';
};

export const isProductionEnvironment = (): boolean => {
type Environment = 'development' | 'production' | 'test';
type EnvironmentOrPublishableKey = typeof process.env | string;

// Allow passing env as arg to support Cloudflare workers case
function isEnvironment(environment: Environment, envOrPublishableKey?: EnvironmentOrPublishableKey) {
try {
return process.env.NODE_ENV === 'production';
envOrPublishableKey ||= process?.env;
// eslint-disable-next-line no-empty
} catch (err) {}

if (isPublishableKey(envOrPublishableKey)) {
return getRuntimeFromPublishableKey(envOrPublishableKey) === environment;
}

// TODO: add support for import.meta.env.DEV that is being used by vite
return false;
};

return getRuntimeFromProcess(envOrPublishableKey) === environment;
}

0 comments on commit a2149aa

Please sign in to comment.