Skip to content

Commit fb59492

Browse files
fix: modernize partner integration add-ons (#490)
* fix: modernize partner integration add-ons * fix: address partner add-on review
1 parent 75db7e8 commit fb59492

45 files changed

Lines changed: 686 additions & 186 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.changeset/fresh-partner-paths.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@tanstack/create': patch
3+
---
4+
5+
Modernize the Clerk and WorkOS add-ons with their full-stack TanStack Start SDKs,
6+
update Railway projects for Railpack, and use safer Sentry defaults. Secret
7+
environment values are no longer stored in `.cta.json` or overwritten by
8+
`tanstack add`, and pnpm 11 projects receive the build approvals their selected
9+
integrations require.

packages/create/src/add-to-app.ts

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,61 @@ import { setupIntent } from './integrations/intent.js'
2323
import type { Environment, Options } from './types.js'
2424
import type { PersistedOptions } from './config-file.js'
2525

26+
const ENV_FILE_NAMES = new Set(['.env', '.env.local', '.env.example'])
27+
const ENV_VARIABLE_PATTERN = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/
28+
29+
function mergeEnvFileContents(existing: string, generated: string) {
30+
const declaredVariables = new Set<string>()
31+
for (const line of existing.split(/\r?\n/)) {
32+
const match = line.match(ENV_VARIABLE_PATTERN)
33+
if (match) {
34+
declaredVariables.add(match[1])
35+
}
36+
}
37+
38+
const additions: Array<string> = []
39+
for (const block of generated.split(/\r?\n(?:[ \t]*\r?\n)+/)) {
40+
const blockLines = block.split(/\r?\n/)
41+
while (blockLines.at(-1) === '') {
42+
blockLines.pop()
43+
}
44+
const lines: Array<string> = []
45+
let addedVariableCount = 0
46+
for (const line of blockLines) {
47+
const match = line.match(ENV_VARIABLE_PATTERN)
48+
if (!match) {
49+
lines.push(line)
50+
continue
51+
}
52+
if (declaredVariables.has(match[1])) {
53+
continue
54+
}
55+
56+
declaredVariables.add(match[1])
57+
addedVariableCount++
58+
lines.push(line)
59+
}
60+
61+
if (addedVariableCount > 0) {
62+
additions.push(lines.join('\n'))
63+
}
64+
}
65+
66+
if (additions.length === 0) {
67+
return existing
68+
}
69+
70+
const separator = existing.length
71+
? existing.endsWith('\n\n')
72+
? ''
73+
: existing.endsWith('\n')
74+
? '\n'
75+
: '\n\n'
76+
: ''
77+
const trailingNewline = generated.endsWith('\n') ? '\n' : ''
78+
return `${existing}${separator}${additions.join('\n\n')}${trailingNewline}`
79+
}
80+
2681
export async function hasPendingGitChanges(
2782
environment: Environment,
2883
cwd: string,
@@ -134,6 +189,20 @@ export async function writeFiles(
134189
false,
135190
)
136191

192+
for (const [relativeFile, generatedContents] of Object.entries(
193+
relativeOutputFiles,
194+
)) {
195+
if (
196+
ENV_FILE_NAMES.has(basename(relativeFile)) &&
197+
relativeFile in currentFiles
198+
) {
199+
relativeOutputFiles[relativeFile] = mergeEnvFileContents(
200+
currentFiles[relativeFile],
201+
generatedContents,
202+
)
203+
}
204+
}
205+
137206
const overwrittenFiles: Array<string> = []
138207
const changedFiles: Array<string> = []
139208
for (const relativeFile of Object.keys(relativeOutputFiles)) {
@@ -146,22 +215,23 @@ export async function writeFiles(
146215
}
147216
}
148217

149-
if (!forced && overwrittenFiles.length) {
218+
const deletedFiles = output.deletedFiles
219+
.map(toRelativePath)
220+
.filter((file) => environment.exists(resolve(cwd, file)))
221+
222+
if (!forced && (overwrittenFiles.length || deletedFiles.length)) {
150223
environment.warn(
151-
'The following will be overwritten',
152-
[...overwrittenFiles, ...output.deletedFiles].join('\n'),
224+
'The following files will be changed or deleted',
225+
[...overwrittenFiles, ...deletedFiles].join('\n'),
153226
)
154227
const shouldContinue = await environment.confirm('Do you want to continue?')
155228
if (!shouldContinue) {
156229
throw new Error('User cancelled')
157230
}
158231
}
159232

160-
for (const filePath of output.deletedFiles) {
161-
const relativeFilePath = toRelativePath(filePath)
162-
if (environment.exists(resolve(cwd, relativeFilePath))) {
163-
await environment.deleteFile(resolve(cwd, relativeFilePath))
164-
}
233+
for (const relativeFilePath of deletedFiles) {
234+
await environment.deleteFile(resolve(cwd, relativeFilePath))
165235
}
166236

167237
environment.startStep({

packages/create/src/config-file.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@ import type { Environment, Options } from './types.js'
77

88
export type PersistedOptions = Omit<
99
Partial<Options>,
10-
'addOns' | 'chosenAddOns' | 'framework' | 'starter' | 'targetDir'
10+
| 'addOns'
11+
| 'chosenAddOns'
12+
| 'envVarValues'
13+
| 'framework'
14+
| 'starter'
15+
| 'targetDir'
1116
> & {
1217
framework: string
1318
version: number
@@ -17,7 +22,7 @@ export type PersistedOptions = Omit<
1722

1823
function createPersistedOptions(options: Options): PersistedOptions {
1924
/* eslint-disable unused-imports/no-unused-vars */
20-
const { chosenAddOns, framework, targetDir, ...rest } = options
25+
const { chosenAddOns, envVarValues, framework, targetDir, ...rest } = options
2126
/* eslint-enable unused-imports/no-unused-vars */
2227
return {
2328
...rest,
@@ -67,6 +72,8 @@ export async function readConfigFileFromEnvironment(
6772
originalJSON.framework = 'react'
6873
}
6974

75+
delete originalJSON.envVarValues
76+
7077
return originalJSON
7178
} catch {
7279
return null
Lines changed: 32 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,50 @@
11
## Setting up Clerk
22

3-
1. Sign up at [clerk.com](https://clerk.com) and create an application
4-
2. Copy the **Publishable Key** from the Clerk dashboard
5-
3. Set it in your `.env.local`:
3+
1. Create an application in the [Clerk dashboard](https://dashboard.clerk.com).
4+
2. Copy its publishable and secret keys into `.env.local`:
5+
66
```bash
77
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
8+
CLERK_SECRET_KEY=sk_test_...
89
```
9-
4. Visit the demo route at `/demo/clerk` once `npm run dev` is running
10+
11+
3. Start the app and visit `/demo/clerk`.
1012

1113
### What's wired up
1214

13-
- **`<ClerkProvider>`** at the app root (`src/integrations/clerk/provider.tsx`) handles auth context for the whole tree
14-
- **`<SignInButton>` / `<UserButton>`** in the header swap based on auth state
15-
- **`/demo/clerk`** shows Clerk's prebuilt sign-in UI and a signed-in greeting
15+
- `clerkMiddleware()` authenticates each server request from `src/start.ts`.
16+
- `<ClerkProvider>` supplies auth state throughout the app.
17+
- `<SignInButton>` and `<UserButton>` in the header respond to the session.
18+
- `/demo/clerk` shows Clerk's prebuilt sign-in UI and signed-in user data.
1619

1720
### Protecting a route
1821

19-
Wrap any component in `<SignedIn>` / `<SignedOut>`:
22+
Use `auth()` in a loader or server function when authorization must happen on the
23+
server:
2024

2125
```tsx
22-
import { SignedIn, SignedOut, RedirectToSignIn } from '@clerk/clerk-react'
23-
24-
function ProtectedPage() {
25-
return (
26-
<>
27-
<SignedIn>
28-
<YourPageContent />
29-
</SignedIn>
30-
<SignedOut>
31-
<RedirectToSignIn />
32-
</SignedOut>
33-
</>
34-
)
35-
}
26+
import { createFileRoute, redirect } from '@tanstack/react-router'
27+
import { createServerFn } from '@tanstack/react-start'
28+
import { auth } from '@clerk/tanstack-react-start/server'
29+
30+
const getAuth = createServerFn({ method: 'GET' }).handler(async () => {
31+
const { userId } = await auth()
32+
return { userId }
33+
})
34+
35+
export const Route = createFileRoute('/dashboard')({
36+
beforeLoad: async () => {
37+
const { userId } = await getAuth()
38+
if (!userId) throw redirect({ to: '/' })
39+
},
40+
})
3641
```
3742

38-
For server-side checks (route loaders, server functions), see the Clerk docs on [`auth()`](https://clerk.com/docs/references/backend/auth).
43+
`<Show when="signed-in">` remains useful for presentation, but server-side checks
44+
are the security boundary. See Clerk's [TanStack Start docs](https://clerk.com/docs/tanstack-react-start/getting-started/quickstart).
3945

4046
### Production checklist
4147

42-
- Replace the test keys with **production keys** from a dedicated production Clerk instance
43-
- Configure your production domain under **Domains** in the Clerk dashboard
44-
- Set up social providers (Google, GitHub, etc.) under **User & Authentication → Social Connections**
48+
- Set both keys in the production environment; never expose `CLERK_SECRET_KEY`.
49+
- Use production keys from a dedicated production Clerk instance.
50+
- Configure the production domain and any social connections in the Clerk dashboard.
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
# Clerk configuration, get this key from your [Dashboard](dashboard.clerk.com)
1+
# Clerk configuration: https://dashboard.clerk.com/last-active?path=api-keys
22
VITE_CLERK_PUBLISHABLE_KEY=
3+
CLERK_SECRET_KEY=
Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
import {
2-
SignedIn,
3-
SignInButton,
4-
SignedOut,
5-
UserButton,
6-
} from '@clerk/clerk-react'
1+
import { Show, SignInButton, UserButton } from '@clerk/tanstack-react-start'
72

83
export default function HeaderUser() {
94
return (
105
<>
11-
<SignedIn>
6+
<Show when="signed-in">
127
<UserButton />
13-
</SignedIn>
14-
<SignedOut>
8+
</Show>
9+
<Show when="signed-out">
1510
<SignInButton />
16-
</SignedOut>
11+
</Show>
1712
</>
1813
)
1914
}
Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,9 @@
1-
import { ClerkProvider } from '@clerk/clerk-react'
2-
3-
const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
4-
if (!PUBLISHABLE_KEY) {
5-
throw new Error('Add your Clerk Publishable Key to the .env.local file')
6-
}
1+
import { ClerkProvider } from '@clerk/tanstack-react-start'
72

83
export default function AppClerkProvider({
94
children,
105
}: {
116
children: React.ReactNode
127
}) {
13-
return (
14-
<ClerkProvider publishableKey={PUBLISHABLE_KEY} afterSignOutUrl="/">
15-
{children}
16-
</ClerkProvider>
17-
)
8+
return <ClerkProvider>{children}</ClerkProvider>
189
}

packages/create/src/frameworks/react/add-ons/clerk/assets/src/routes/demo/clerk.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createFileRoute } from '@tanstack/react-router'
2-
import { SignIn, SignedIn, SignedOut, useUser } from '@clerk/clerk-react'
2+
import { Show, SignIn, useUser } from '@clerk/tanstack-react-start'
33

44
export const Route = createFileRoute('/demo/clerk')({
55
component: ClerkDemo,
@@ -9,7 +9,7 @@ function ClerkDemo() {
99
return (
1010
<main className="demo-page demo-center">
1111
<section className="demo-panel w-full max-w-md space-y-6">
12-
<SignedOut>
12+
<Show when="signed-out">
1313
<div className="space-y-1.5">
1414
<p className="island-kicker mb-2">Clerk</p>
1515
<h1 className="demo-title">Sign in to continue</h1>
@@ -33,11 +33,11 @@ function ClerkDemo() {
3333
</a>
3434
.
3535
</p>
36-
</SignedOut>
36+
</Show>
3737

38-
<SignedIn>
38+
<Show when="signed-in">
3939
<SignedInGreeting />
40-
</SignedIn>
40+
</Show>
4141
</section>
4242
</main>
4343
)

packages/create/src/frameworks/react/add-ons/clerk/info.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@
3737
"required": true,
3838
"secret": false,
3939
"file": ".env.local"
40+
},
41+
{
42+
"name": "CLERK_SECRET_KEY",
43+
"description": "Clerk secret key",
44+
"required": true,
45+
"secret": true,
46+
"file": ".env.local"
4047
}
4148
]
4249
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"dependencies": {
3-
"@clerk/clerk-react": "^5.61.3"
3+
"@clerk/tanstack-react-start": "^1.4.21"
44
}
55
}

0 commit comments

Comments
 (0)