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
6 changes: 6 additions & 0 deletions .changeset/shaggy-rocks-lick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/tanstack-react-start': minor
'@clerk/shared': minor
---

Introduce Keyless quickstart for TanStack. This allows the Clerk SDK to be used without having to sign up and paste your keys manually.
9 changes: 8 additions & 1 deletion integration/models/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const application = (
) => {
const { name, scripts, envWriter, copyKeylessToEnv } = config;
const logger = createLogger({ prefix: `${appDirName}` });
const state = { completedSetup: false, serverUrl: '', env: {} as EnvironmentConfig };
const state = { completedSetup: false, serverUrl: '', env: {} as EnvironmentConfig, lastDevPort: 0 };
const cleanupFns: { (): unknown }[] = [];
const now = Date.now();
const stdoutFilePath = path.resolve(appDirPath, `e2e.${now}.log`);
Expand Down Expand Up @@ -119,8 +119,15 @@ export const application = (
}
}

state.lastDevPort = port;
return { port, serverUrl: runtimeServerUrl, pid: proc.pid };
},
restart: async () => {
const log = logger.child({ prefix: 'restart' }).info;
log('Restarting dev server...');
await self.stop();
return self.dev({ port: state.lastDevPort });
},
build: async () => {
const log = logger.child({ prefix: 'build' }).info;
await run(scripts.build, {
Expand Down
6 changes: 6 additions & 0 deletions integration/templates/tanstack-react-start/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,10 @@ export default defineConfig({
tailwindcss(),
viteReact(),
],
resolve: {
dedupe: ['react', 'react-dom'],
},
ssr: {
noExternal: [/@clerk\/.*/, 'swr'],
},
});
131 changes: 131 additions & 0 deletions integration/tests/tanstack-start/keyless.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import type { Page } from '@playwright/test';
import { expect, test } from '@playwright/test';

import type { Application } from '../../models/application';
import { appConfigs } from '../../presets';
import { createTestUtils } from '../../testUtils';

const commonSetup = appConfigs.tanstack.reactStart.clone();

const mockClaimedInstanceEnvironmentCall = async (page: Page) => {
await page.route('*/**/v1/environment*', async route => {
const response = await route.fetch();
const json = await response.json();
const newJson = {
...json,
auth_config: {
...json.auth_config,
claimed_at: Date.now(),
},
};
await route.fulfill({ response, json: newJson });
});
};

test.describe('Keyless mode @tanstack-react-start', () => {
test.describe.configure({ mode: 'serial' });
test.setTimeout(90_000);

test.use({
extraHTTPHeaders: {
'x-vercel-protection-bypass': process.env.VERCEL_AUTOMATION_BYPASS_SECRET || '',
},
});

let app: Application;
let dashboardUrl = 'https://dashboard.clerk.com/';

test.beforeAll(async () => {
app = await commonSetup.commit();
await app.setup();
await app.withEnv(appConfigs.envs.withKeyless);
if (appConfigs.envs.withKeyless.privateVariables.get('CLERK_API_URL')?.includes('clerkstage')) {
dashboardUrl = 'https://dashboard.clerkstage.dev/';
}
await app.dev();
});

test.afterAll(async () => {
// Keep files for debugging
await app?.teardown();
});

test('Toggle collapse popover and claim.', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
await u.page.waitForClerkJsLoaded();
await u.po.expect.toBeSignedOut();

await u.po.keylessPopover.waitForMounted();

expect(await u.po.keylessPopover.isExpanded()).toBe(false);
await u.po.keylessPopover.toggle();
expect(await u.po.keylessPopover.isExpanded()).toBe(true);

const claim = await u.po.keylessPopover.promptsToClaim();

const [newPage] = await Promise.all([context.waitForEvent('page'), claim.click()]);

await newPage.waitForLoadState();

await newPage.waitForURL(url => {
const urlToReturnTo = `${dashboardUrl}apps/claim?token=`;

const signUpForceRedirectUrl = url.searchParams.get('sign_up_force_redirect_url');

const signUpForceRedirectUrlCheck =
signUpForceRedirectUrl?.startsWith(urlToReturnTo) ||
(signUpForceRedirectUrl?.startsWith(`${dashboardUrl}prepare-account`) &&
signUpForceRedirectUrl?.includes(encodeURIComponent('apps/claim?token=')));

return (
url.pathname === '/apps/claim/sign-in' &&
url.searchParams.get('sign_in_force_redirect_url')?.startsWith(urlToReturnTo) &&
signUpForceRedirectUrlCheck
);
});
});

test('Lands on claimed application with missing explicit keys, expanded by default, click to get keys from dashboard.', async ({
page,
context,
}) => {
await mockClaimedInstanceEnvironmentCall(page);
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();
await u.page.waitForClerkJsLoaded();

await u.po.keylessPopover.waitForMounted();
expect(await u.po.keylessPopover.isExpanded()).toBe(true);
await expect(u.po.keylessPopover.promptToUseClaimedKeys()).toBeVisible();

const [newPage] = await Promise.all([
context.waitForEvent('page'),
u.po.keylessPopover.promptToUseClaimedKeys().click(),
]);

await newPage.waitForLoadState();
await newPage.waitForURL(url => {
return url.href.startsWith(`${dashboardUrl}sign-in?redirect_url=${encodeURIComponent(dashboardUrl)}apps%2Fapp_`);
});
});

test('Keyless popover is removed after adding keys to .env and restarting.', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToAppHome();

await u.po.keylessPopover.waitForMounted();
expect(await u.po.keylessPopover.isExpanded()).toBe(false);

// Copy keys from keyless.json to .env
await app.keylessToEnv();

// Restart the dev server to pick up new env vars (Vite doesn't hot-reload .env)
await app.restart();

await u.page.goToAppHome();

// Keyless popover should no longer be present since we now have explicit keys
await u.po.keylessPopover.waitForUnmounted();
});
});
Loading
Loading