-
Notifications
You must be signed in to change notification settings - Fork 297
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(shared): Support passing env source into runtime environment helpers [SDK-773] #1881
Closed
Closed
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
'@clerk/shared': patch | ||
--- | ||
|
||
Support passing the env as argument in runtime environment helpers (eg isDevelopmentEnvironment()) | ||
to allow using those helpers with per-request environment runtimes (eg Cloudflare workers). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ @dimkl this one applies only on node environments, right?
Or, if you pass an
env
object, it should include aNODE_ENV
property for this to work, right?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it applies for all runtimes (Node, Cloudflare Workers, ...).
If you pass an
env
object with theNODE_ENV
property, it should work.I probably need to test if the
process.env
type works for Vite and Cloudflare workers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dimkl That's what I meant. You need to have
NODE_ENV
in your passed object for this to work.NODE_ENV
usually refers to node environments.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that this name is a bit misleading. At the moment the function assumes that
NODE_ENV
exists and that it's Node.jsYou can do a check for the runtime like this: https://github.com/unjs/std-env/blob/main/src/runtimes.ts
Probably the whole "runtime" name (also for the file name) is a bit misleading as we only check the
NODE_ENV
environment 🤔