From 4d088919828e49fd859714e784086804332638dd Mon Sep 17 00:00:00 2001 From: Drew Sing Date: Mon, 21 Sep 2026 21:21:00 +0000 Subject: [PATCH 1/2] fix(shared): reword missing and invalid key errors around the two CLI commands --- .changeset/env-keys-error-copy.md | 5 ++++ .../tests/next-middleware-keyless.test.ts | 2 +- .../tests/next-quickstart-keyless.test.ts | 2 +- .../src/__tests__/createRedirect.test.ts | 4 +-- .../__tests__/clerkMiddlewareKeyless.test.ts | 4 +-- packages/shared/src/__tests__/error.spec.ts | 10 +++++-- packages/shared/src/__tests__/keys.spec.ts | 2 +- .../src/__tests__/loadClerkJsScript.spec.ts | 4 +-- packages/shared/src/errors/errorThrower.ts | 28 ++++++++----------- packages/shared/src/keys.ts | 18 ++++++------ 10 files changed, 44 insertions(+), 35 deletions(-) create mode 100644 .changeset/env-keys-error-copy.md diff --git a/.changeset/env-keys-error-copy.md b/.changeset/env-keys-error-copy.md new file mode 100644 index 00000000000..08d89546e1f --- /dev/null +++ b/.changeset/env-keys-error-copy.md @@ -0,0 +1,5 @@ +--- +'@clerk/shared': patch +--- + +Reword the missing and invalid key errors around the two Clerk CLI commands. The message now opens with "You're ready to set up your Clerk .env keys.", then offers `npx clerk@latest init` for a new application and `npx clerk@latest env pull` for an existing one, each on its own labelled line, followed by a short paragraph explaining what each command does and where to find keys in the Dashboard. The missing secret key error keeps its `Missing secretKey.` lead so it stays distinguishable from the missing publishable key error. diff --git a/integration/tests/next-middleware-keyless.test.ts b/integration/tests/next-middleware-keyless.test.ts index 4b726ec0116..ddb9a9754e4 100644 --- a/integration/tests/next-middleware-keyless.test.ts +++ b/integration/tests/next-middleware-keyless.test.ts @@ -32,7 +32,7 @@ test.describe('Keyless mode | middleware authorization @nextjs', () => { const response = await page.goto(`${app.serverUrl}/protected`); expect(response?.status()).toBe(500); const content = await page.content(); - expect(content).toContain('Missing publishableKey'); + expect(content).toContain('ready to set up your Clerk .env keys'); expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts index f75845a384a..fa1fcea8361 100644 --- a/integration/tests/next-quickstart-keyless.test.ts +++ b/integration/tests/next-quickstart-keyless.test.ts @@ -39,7 +39,7 @@ test.describe('Keyless mode @quickstart', () => { const response = await page.goto(`${app.serverUrl}/`); expect(response?.status()).toBe(500); const content = await page.content(); - expect(content).toContain('Missing publishableKey'); + expect(content).toContain('ready to set up your Clerk .env keys'); expect(content).toContain('npx clerk@latest init'); }); diff --git a/packages/backend/src/__tests__/createRedirect.test.ts b/packages/backend/src/__tests__/createRedirect.test.ts index 481f80a043b..42db37dc92e 100644 --- a/packages/backend/src/__tests__/createRedirect.test.ts +++ b/packages/backend/src/__tests__/createRedirect.test.ts @@ -28,7 +28,7 @@ describe('redirect(redirectAdapter)', () => { } as any); expect(() => redirectToSignIn({ returnBackUrl })).toThrowError( - '@clerk/backend: Missing publishableKey. To set up Clerk for this project, in your terminal run:\n\nnpx clerk@latest init', + "@clerk/backend: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", ); }); }); @@ -258,7 +258,7 @@ describe('redirect(redirectAdapter)', () => { }); expect(() => redirectToSignUp({ returnBackUrl })).toThrowError( - '@clerk/backend: Missing publishableKey. To set up Clerk for this project, in your terminal run:\n\nnpx clerk@latest init', + "@clerk/backend: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", ); }); diff --git a/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts index 8f4e8842cf4..71092882ea9 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts @@ -38,7 +38,7 @@ describe('clerkMiddleware when Clerk env vars are missing', () => { }; it('throws the missing key error pointing at the CLI instead of bootstrapping keyless', async () => { - await expect(runMiddleware()).rejects.toThrow(/Missing publishableKey/); + await expect(runMiddleware()).rejects.toThrow(/ready to set up your Clerk \.env keys/); await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/); }); @@ -49,6 +49,6 @@ describe('clerkMiddleware when Clerk env vars are missing', () => { it('throws the same error regardless of NODE_ENV', async () => { vi.stubEnv('NODE_ENV', 'production'); await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/); - await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest deploy/); + await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest env pull/); }); }); diff --git a/packages/shared/src/__tests__/error.spec.ts b/packages/shared/src/__tests__/error.spec.ts index 0c644d295b7..629d7b11db9 100644 --- a/packages/shared/src/__tests__/error.spec.ts +++ b/packages/shared/src/__tests__/error.spec.ts @@ -16,13 +16,19 @@ describe('ErrorThrower', () => { it('throws the correct error message and interpolates pkg and known parameters', () => { expect(() => errorThrower.throwInvalidPublishableKeyError({ key: 'whatever' })).toThrow( - '@clerk/test-package: The publishableKey passed to Clerk is invalid (key=whatever, expected format: pk_test_... or pk_live_...). To create a Clerk application with valid keys, in your terminal run:\n\nnpx clerk@latest init', + '@clerk/test-package: The publishableKey passed to Clerk is invalid (key=whatever, expected format: pk_test_... or pk_live_...).\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init', ); }); it('throws the correct error message and interpolates pkg if no parameters are provided', () => { expect(() => errorThrower.throwMissingPublishableKeyError()).toThrow( - '@clerk/test-package: Missing publishableKey. To set up Clerk for this project, in your terminal run:\n\nnpx clerk@latest init', + "@clerk/test-package: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + ); + }); + + it('names the missing key so the secret key error is distinguishable from the publishable key error', () => { + expect(() => errorThrower.throwMissingSecretKeyError()).toThrow( + "@clerk/test-package: Missing secretKey. You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", ); }); diff --git a/packages/shared/src/__tests__/keys.spec.ts b/packages/shared/src/__tests__/keys.spec.ts index c9ec4d42acc..f02db3f45fd 100644 --- a/packages/shared/src/__tests__/keys.spec.ts +++ b/packages/shared/src/__tests__/keys.spec.ts @@ -81,7 +81,7 @@ describe('parsePublishableKey(key)', () => { it('throws an error if the publishable key is missing, when fatal: true', () => { expect(() => parsePublishableKey(undefined, { fatal: true })).toThrowError( - 'Publishable key is missing. To create a Clerk application with valid keys, in your terminal run:\n\nnpx clerk@latest init', + 'Publishable key is missing.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init', ); }); diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index 7ee8d815c43..db2b0ea31ac 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -46,7 +46,7 @@ describe('loadClerkJsScript(options)', () => { test('throws error when publishableKey is missing', async () => { await expect(loadClerkJsScript({} as any)).rejects.toThrow( - '@clerk/react: Missing publishableKey. To set up Clerk for this project, in your terminal run:\n\nnpx clerk@latest init', + "@clerk/react: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", ); }); @@ -310,7 +310,7 @@ describe('loadClerkUIScript(options)', () => { test('throws error when publishableKey is missing', async () => { await expect(loadClerkUIScript({} as any)).rejects.toThrow( - '@clerk/react: Missing publishableKey. To set up Clerk for this project, in your terminal run:\n\nnpx clerk@latest init', + "@clerk/react: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", ); }); diff --git a/packages/shared/src/errors/errorThrower.ts b/packages/shared/src/errors/errorThrower.ts index 9375e994eff..53abf9a900e 100644 --- a/packages/shared/src/errors/errorThrower.ts +++ b/packages/shared/src/errors/errorThrower.ts @@ -1,26 +1,22 @@ -const DefaultMessages = Object.freeze({ - InvalidProxyUrlErrorMessage: `The proxyUrl passed to Clerk is invalid. The expected value for proxyUrl is an absolute URL or a relative path with a leading '/'. (key={{url}})`, - InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid (key={{key}}, expected format: pk_test_... or pk_live_...). To create a Clerk application with valid keys, in your terminal run: - +const keySetupGuidance = `New to Clerk or not sure? Simply run: npx clerk@latest init -\`npx clerk@latest init\` creates a Clerk application and writes keys to your .env file. No Clerk account or login required and the command is non-interactive. +Already have a Clerk app? Run: +npx clerk@latest env pull -If you have a Clerk application, run \`npx clerk@latest env pull\` to write the keys (\`--instance prod\` for production keys). Or copy its Publishable key from https://dashboard.clerk.com/~/api-keys.`, - MissingPublishableKeyErrorMessage: `Missing publishableKey. To set up Clerk for this project, in your terminal run: - -npx clerk@latest init +'npx clerk@latest init' creates a Clerk application and automatically writes API keys to your .env file. It creates an accountless Clerk app, which means you don't have to sign up or have an account to use Clerk. 'npx clerk@latest env pull' pulls API keys into your project's .env file. Keys are also at https://dashboard.clerk.com/~/api-keys in the Clerk Dashboard.`; -\`npx clerk@latest init\` creates a Clerk application and writes keys to your .env file. No Clerk account or login required and the command is non-interactive. - -If you have a Clerk application, run \`npx clerk@latest env pull\` to write the keys. Or copy them from https://dashboard.clerk.com/~/api-keys. Deploy a production instance by running \`npx clerk@latest deploy\`, or \`npx clerk@latest env pull --instance prod\` to use an existing one.`, - MissingSecretKeyErrorMessage: `Missing secretKey. To set up Clerk for this project, in your terminal run: +const DefaultMessages = Object.freeze({ + InvalidProxyUrlErrorMessage: `The proxyUrl passed to Clerk is invalid. The expected value for proxyUrl is an absolute URL or a relative path with a leading '/'. (key={{url}})`, + InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid (key={{key}}, expected format: pk_test_... or pk_live_...). -npx clerk@latest init +${keySetupGuidance}`, + MissingPublishableKeyErrorMessage: `You're ready to set up your Clerk .env keys. -\`npx clerk@latest init\` creates a Clerk application and writes keys to your .env file. No Clerk account or login required and the command is non-interactive. +${keySetupGuidance}`, + MissingSecretKeyErrorMessage: `Missing secretKey. You're ready to set up your Clerk .env keys. -If you have a Clerk application, run \`npx clerk@latest env pull\` to write the keys. Or copy them from https://dashboard.clerk.com/~/api-keys. Deploy a production instance by running \`npx clerk@latest deploy\`, or \`npx clerk@latest env pull --instance prod\` to use an existing one.`, +${keySetupGuidance}`, MissingClerkProvider: `{{source}} can only be used within the component. Learn more: https://clerk.com/docs/components/clerk-provider`, }); diff --git a/packages/shared/src/keys.ts b/packages/shared/src/keys.ts index 9949277f0da..2ee1fb5b47c 100644 --- a/packages/shared/src/keys.ts +++ b/packages/shared/src/keys.ts @@ -98,13 +98,13 @@ function isValidDecodedPublishableKey(decoded: string): boolean { return withoutTrailing.includes('.'); } -const fatalKeyGuidance = `To create a Clerk application with valid keys, in your terminal run: - +const fatalKeyGuidance = `New to Clerk or not sure? Simply run: npx clerk@latest init -\`npx clerk@latest init\` creates a Clerk application and writes keys to your .env file. No Clerk account or login required and the command is non-interactive. +Already have a Clerk app? Run: +npx clerk@latest env pull -If you have a Clerk application, run \`npx clerk@latest env pull\` to write the keys (\`--instance prod\` for production keys). Or copy them from https://dashboard.clerk.com/~/api-keys.`; +'npx clerk@latest init' creates a Clerk application and automatically writes API keys to your .env file. It creates an accountless Clerk app, which means you don't have to sign up or have an account to use Clerk. 'npx clerk@latest env pull' pulls API keys into your project's .env file. Keys are also at https://dashboard.clerk.com/~/api-keys in the Clerk Dashboard.`; export function parsePublishableKey( key: string | undefined, @@ -135,10 +135,12 @@ export function parsePublishableKey( if (!key || !isPublishableKey(key)) { if (options.fatal && !key) { - throw new Error(`Publishable key is missing. ${fatalKeyGuidance}`); + throw new Error(`Publishable key is missing.\n\n${fatalKeyGuidance}`); } if (options.fatal && !isPublishableKey(key)) { - throw new Error(`Publishable key not valid (expected format: pk_test_... or pk_live_...). ${fatalKeyGuidance}`); + throw new Error( + `Publishable key not valid (expected format: pk_test_... or pk_live_...).\n\n${fatalKeyGuidance}`, + ); } return null; } @@ -150,14 +152,14 @@ export function parsePublishableKey( decodedFrontendApi = isomorphicAtob(key.split('_')[2]); } catch { if (options.fatal) { - throw new Error(`Publishable key not valid: Failed to decode key. ${fatalKeyGuidance}`); + throw new Error(`Publishable key not valid: Failed to decode key.\n\n${fatalKeyGuidance}`); } return null; } if (!isValidDecodedPublishableKey(decodedFrontendApi)) { if (options.fatal) { - throw new Error(`Publishable key not valid: Decoded key has invalid format. ${fatalKeyGuidance}`); + throw new Error(`Publishable key not valid: Decoded key has invalid format.\n\n${fatalKeyGuidance}`); } return null; } From 4a8b5117daa8ede7f433bdbb6285b3d4dfff4bd6 Mon Sep 17 00:00:00 2001 From: Michael Novotny Date: Wed, 23 Sep 2026 10:31:41 -0500 Subject: [PATCH 2/2] fix(shared): correct CLI steps in missing and invalid key errors Run `clerk link` before `env pull` for existing apps, add a production step, drop `init` from the missing secret key error, and share one copy of the guidance between errorThrower.ts and keys.ts. Co-Authored-By: Claude Opus 5.5 --- .changeset/env-keys-error-copy.md | 2 +- .../tests/next-middleware-keyless.test.ts | 2 +- .../tests/next-quickstart-keyless.test.ts | 2 +- .../src/__tests__/createRedirect.test.ts | 4 ++-- .../__tests__/clerkMiddlewareKeyless.test.ts | 4 ++-- packages/shared/src/__tests__/error.spec.ts | 7 ++++--- packages/shared/src/__tests__/keys.spec.ts | 2 +- .../src/__tests__/loadClerkJsScript.spec.ts | 4 ++-- packages/shared/src/errors/errorThrower.ts | 14 ++++---------- .../shared/src/errors/keySetupGuidance.ts | 19 +++++++++++++++++++ packages/shared/src/keys.ts | 17 +++++------------ 11 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 packages/shared/src/errors/keySetupGuidance.ts diff --git a/.changeset/env-keys-error-copy.md b/.changeset/env-keys-error-copy.md index 08d89546e1f..7bddf3b0186 100644 --- a/.changeset/env-keys-error-copy.md +++ b/.changeset/env-keys-error-copy.md @@ -2,4 +2,4 @@ '@clerk/shared': patch --- -Reword the missing and invalid key errors around the two Clerk CLI commands. The message now opens with "You're ready to set up your Clerk .env keys.", then offers `npx clerk@latest init` for a new application and `npx clerk@latest env pull` for an existing one, each on its own labelled line, followed by a short paragraph explaining what each command does and where to find keys in the Dashboard. The missing secret key error keeps its `Missing secretKey.` lead so it stays distinguishable from the missing publishable key error. +Missing and invalid key errors now list the Clerk CLI commands that fix them: `npx clerk@latest init` for a new app, `npx clerk@latest link` and `npx clerk@latest env pull` for an existing one, and `npx clerk@latest env pull --instance prod` for production keys. The missing secret key error skips `init`, since the publishable key already points to an existing app. diff --git a/integration/tests/next-middleware-keyless.test.ts b/integration/tests/next-middleware-keyless.test.ts index ddb9a9754e4..d699b7790f8 100644 --- a/integration/tests/next-middleware-keyless.test.ts +++ b/integration/tests/next-middleware-keyless.test.ts @@ -32,7 +32,7 @@ test.describe('Keyless mode | middleware authorization @nextjs', () => { const response = await page.goto(`${app.serverUrl}/protected`); expect(response?.status()).toBe(500); const content = await page.content(); - expect(content).toContain('ready to set up your Clerk .env keys'); + expect(content).toContain('Clerk keys are missing from your environment'); expect(content).toContain('npx clerk@latest init'); }); }); diff --git a/integration/tests/next-quickstart-keyless.test.ts b/integration/tests/next-quickstart-keyless.test.ts index fa1fcea8361..7ff28369c58 100644 --- a/integration/tests/next-quickstart-keyless.test.ts +++ b/integration/tests/next-quickstart-keyless.test.ts @@ -39,7 +39,7 @@ test.describe('Keyless mode @quickstart', () => { const response = await page.goto(`${app.serverUrl}/`); expect(response?.status()).toBe(500); const content = await page.content(); - expect(content).toContain('ready to set up your Clerk .env keys'); + expect(content).toContain('Clerk keys are missing from your environment'); expect(content).toContain('npx clerk@latest init'); }); diff --git a/packages/backend/src/__tests__/createRedirect.test.ts b/packages/backend/src/__tests__/createRedirect.test.ts index 42db37dc92e..41fe6f5f9d1 100644 --- a/packages/backend/src/__tests__/createRedirect.test.ts +++ b/packages/backend/src/__tests__/createRedirect.test.ts @@ -28,7 +28,7 @@ describe('redirect(redirectAdapter)', () => { } as any); expect(() => redirectToSignIn({ returnBackUrl })).toThrowError( - "@clerk/backend: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/backend: Clerk keys are missing from your environment.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); }); @@ -258,7 +258,7 @@ describe('redirect(redirectAdapter)', () => { }); expect(() => redirectToSignUp({ returnBackUrl })).toThrowError( - "@clerk/backend: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/backend: Clerk keys are missing from your environment.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); diff --git a/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts b/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts index 71092882ea9..da6dae6e201 100644 --- a/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts +++ b/packages/nextjs/src/server/__tests__/clerkMiddlewareKeyless.test.ts @@ -38,7 +38,7 @@ describe('clerkMiddleware when Clerk env vars are missing', () => { }; it('throws the missing key error pointing at the CLI instead of bootstrapping keyless', async () => { - await expect(runMiddleware()).rejects.toThrow(/ready to set up your Clerk \.env keys/); + await expect(runMiddleware()).rejects.toThrow(/Clerk keys are missing from your environment/); await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/); }); @@ -49,6 +49,6 @@ describe('clerkMiddleware when Clerk env vars are missing', () => { it('throws the same error regardless of NODE_ENV', async () => { vi.stubEnv('NODE_ENV', 'production'); await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest init/); - await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest env pull/); + await expect(runMiddleware()).rejects.toThrow(/npx clerk@latest env pull --instance prod/); }); }); diff --git a/packages/shared/src/__tests__/error.spec.ts b/packages/shared/src/__tests__/error.spec.ts index 629d7b11db9..2e4b7e09cea 100644 --- a/packages/shared/src/__tests__/error.spec.ts +++ b/packages/shared/src/__tests__/error.spec.ts @@ -16,20 +16,21 @@ describe('ErrorThrower', () => { it('throws the correct error message and interpolates pkg and known parameters', () => { expect(() => errorThrower.throwInvalidPublishableKeyError({ key: 'whatever' })).toThrow( - '@clerk/test-package: The publishableKey passed to Clerk is invalid (key=whatever, expected format: pk_test_... or pk_live_...).\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init', + '@clerk/test-package: The publishableKey passed to Clerk is invalid (key=whatever, expected format: pk_test_... or pk_live_...).\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); it('throws the correct error message and interpolates pkg if no parameters are provided', () => { expect(() => errorThrower.throwMissingPublishableKeyError()).toThrow( - "@clerk/test-package: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/test-package: Clerk keys are missing from your environment.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); it('names the missing key so the secret key error is distinguishable from the publishable key error', () => { expect(() => errorThrower.throwMissingSecretKeyError()).toThrow( - "@clerk/test-package: Missing secretKey. You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/test-package: Missing secretKey.\n\nTo use an existing Clerk app, run:\nnpx clerk@latest link\nnpx clerk@latest env pull', ); + expect(() => errorThrower.throwMissingSecretKeyError()).not.toThrow(/npx clerk@latest init/); }); it('throws a custom error message and interpolates pkg and known parameters', () => { diff --git a/packages/shared/src/__tests__/keys.spec.ts b/packages/shared/src/__tests__/keys.spec.ts index f02db3f45fd..37f51fcf825 100644 --- a/packages/shared/src/__tests__/keys.spec.ts +++ b/packages/shared/src/__tests__/keys.spec.ts @@ -81,7 +81,7 @@ describe('parsePublishableKey(key)', () => { it('throws an error if the publishable key is missing, when fatal: true', () => { expect(() => parsePublishableKey(undefined, { fatal: true })).toThrowError( - 'Publishable key is missing.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init', + 'Publishable key is missing.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); diff --git a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts index db2b0ea31ac..75304bb6dba 100644 --- a/packages/shared/src/__tests__/loadClerkJsScript.spec.ts +++ b/packages/shared/src/__tests__/loadClerkJsScript.spec.ts @@ -46,7 +46,7 @@ describe('loadClerkJsScript(options)', () => { test('throws error when publishableKey is missing', async () => { await expect(loadClerkJsScript({} as any)).rejects.toThrow( - "@clerk/react: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/react: Clerk keys are missing from your environment.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); @@ -310,7 +310,7 @@ describe('loadClerkUIScript(options)', () => { test('throws error when publishableKey is missing', async () => { await expect(loadClerkUIScript({} as any)).rejects.toThrow( - "@clerk/react: You're ready to set up your Clerk .env keys.\n\nNew to Clerk or not sure? Simply run:\nnpx clerk@latest init", + '@clerk/react: Clerk keys are missing from your environment.\n\nTo create a new Clerk app, run:\nnpx clerk@latest init', ); }); diff --git a/packages/shared/src/errors/errorThrower.ts b/packages/shared/src/errors/errorThrower.ts index 53abf9a900e..589866d666c 100644 --- a/packages/shared/src/errors/errorThrower.ts +++ b/packages/shared/src/errors/errorThrower.ts @@ -1,22 +1,16 @@ -const keySetupGuidance = `New to Clerk or not sure? Simply run: -npx clerk@latest init - -Already have a Clerk app? Run: -npx clerk@latest env pull - -'npx clerk@latest init' creates a Clerk application and automatically writes API keys to your .env file. It creates an accountless Clerk app, which means you don't have to sign up or have an account to use Clerk. 'npx clerk@latest env pull' pulls API keys into your project's .env file. Keys are also at https://dashboard.clerk.com/~/api-keys in the Clerk Dashboard.`; +import { existingAppKeyGuidance, keySetupGuidance } from './keySetupGuidance'; const DefaultMessages = Object.freeze({ InvalidProxyUrlErrorMessage: `The proxyUrl passed to Clerk is invalid. The expected value for proxyUrl is an absolute URL or a relative path with a leading '/'. (key={{url}})`, InvalidPublishableKeyErrorMessage: `The publishableKey passed to Clerk is invalid (key={{key}}, expected format: pk_test_... or pk_live_...). ${keySetupGuidance}`, - MissingPublishableKeyErrorMessage: `You're ready to set up your Clerk .env keys. + MissingPublishableKeyErrorMessage: `Clerk keys are missing from your environment. ${keySetupGuidance}`, - MissingSecretKeyErrorMessage: `Missing secretKey. You're ready to set up your Clerk .env keys. + MissingSecretKeyErrorMessage: `Missing secretKey. -${keySetupGuidance}`, +${existingAppKeyGuidance}`, MissingClerkProvider: `{{source}} can only be used within the component. Learn more: https://clerk.com/docs/components/clerk-provider`, }); diff --git a/packages/shared/src/errors/keySetupGuidance.ts b/packages/shared/src/errors/keySetupGuidance.ts new file mode 100644 index 00000000000..cda3ba1e7ca --- /dev/null +++ b/packages/shared/src/errors/keySetupGuidance.ts @@ -0,0 +1,19 @@ +const existingAppSteps = `To use an existing Clerk app, run: +npx clerk@latest link +npx clerk@latest env pull + +For production keys, run: +npx clerk@latest env pull --instance prod`; + +const dashboardFallback = `Or copy keys from https://dashboard.clerk.com/~/api-keys into your .env file.`; + +export const keySetupGuidance = `To create a new Clerk app, run: +npx clerk@latest init + +${existingAppSteps} + +${dashboardFallback}`; + +export const existingAppKeyGuidance = `${existingAppSteps} + +${dashboardFallback}`; diff --git a/packages/shared/src/keys.ts b/packages/shared/src/keys.ts index 2ee1fb5b47c..378301ecd1f 100644 --- a/packages/shared/src/keys.ts +++ b/packages/shared/src/keys.ts @@ -1,4 +1,5 @@ import { DEV_OR_STAGING_SUFFIXES, LEGACY_DEV_INSTANCE_SUFFIXES } from './constants'; +import { keySetupGuidance } from './errors/keySetupGuidance'; import { isomorphicAtob } from './isomorphicAtob'; import { isomorphicBtoa } from './isomorphicBtoa'; import type { PublishableKey } from './types'; @@ -98,14 +99,6 @@ function isValidDecodedPublishableKey(decoded: string): boolean { return withoutTrailing.includes('.'); } -const fatalKeyGuidance = `New to Clerk or not sure? Simply run: -npx clerk@latest init - -Already have a Clerk app? Run: -npx clerk@latest env pull - -'npx clerk@latest init' creates a Clerk application and automatically writes API keys to your .env file. It creates an accountless Clerk app, which means you don't have to sign up or have an account to use Clerk. 'npx clerk@latest env pull' pulls API keys into your project's .env file. Keys are also at https://dashboard.clerk.com/~/api-keys in the Clerk Dashboard.`; - export function parsePublishableKey( key: string | undefined, options: ParsePublishableKeyOptions & { fatal: true }, @@ -135,11 +128,11 @@ export function parsePublishableKey( if (!key || !isPublishableKey(key)) { if (options.fatal && !key) { - throw new Error(`Publishable key is missing.\n\n${fatalKeyGuidance}`); + throw new Error(`Publishable key is missing.\n\n${keySetupGuidance}`); } if (options.fatal && !isPublishableKey(key)) { throw new Error( - `Publishable key not valid (expected format: pk_test_... or pk_live_...).\n\n${fatalKeyGuidance}`, + `Publishable key not valid (expected format: pk_test_... or pk_live_...).\n\n${keySetupGuidance}`, ); } return null; @@ -152,14 +145,14 @@ export function parsePublishableKey( decodedFrontendApi = isomorphicAtob(key.split('_')[2]); } catch { if (options.fatal) { - throw new Error(`Publishable key not valid: Failed to decode key.\n\n${fatalKeyGuidance}`); + throw new Error(`Publishable key not valid: Failed to decode key.\n\n${keySetupGuidance}`); } return null; } if (!isValidDecodedPublishableKey(decodedFrontendApi)) { if (options.fatal) { - throw new Error(`Publishable key not valid: Decoded key has invalid format.\n\n${fatalKeyGuidance}`); + throw new Error(`Publishable key not valid: Decoded key has invalid format.\n\n${keySetupGuidance}`); } return null; }