Skip to content

Commit c6f5a20

Browse files
committed
chore: merge main
2 parents 251dc65 + 5bf0327 commit c6f5a20

File tree

14 files changed

+148
-87
lines changed

14 files changed

+148
-87
lines changed

packages/cta-cli/src/cli.ts

+15-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { createUIEnvironment } from './ui-environment.js'
2323

2424
import type {
2525
Mode,
26+
Options,
2627
PackageManager,
2728
TemplateOptions,
2829
} from '@tanstack/cta-engine'
@@ -179,6 +180,7 @@ export function cli({
179180
return value
180181
},
181182
)
183+
.option('--interactive', 'interactive mode', false)
182184
.option('--tailwind', 'add Tailwind CSS', false)
183185
.option<Array<string> | boolean>(
184186
'--add-ons [...add-ons]',
@@ -227,11 +229,17 @@ export function cli({
227229
cliOptions.template = forcedMode as TemplateOptions
228230
}
229231

230-
let finalOptions = await normalizeOptions(
231-
cliOptions,
232-
forcedMode,
233-
forcedAddOns,
234-
)
232+
let finalOptions: Options | undefined
233+
if (cliOptions.interactive) {
234+
cliOptions.addOns = true
235+
} else {
236+
finalOptions = await normalizeOptions(
237+
cliOptions,
238+
forcedMode,
239+
forcedAddOns,
240+
)
241+
}
242+
235243
if (finalOptions) {
236244
intro(`Creating a new ${appName} app in ${projectName}...`)
237245
} else {
@@ -241,7 +249,8 @@ export function cli({
241249
forcedAddOns,
242250
})
243251
}
244-
await createApp(finalOptions, {
252+
253+
await createApp(finalOptions!, {
245254
environment: createUIEnvironment(),
246255
cwd: options.targetDir || undefined,
247256
name,

packages/cta-cli/src/options.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export async function promptForOptions(
178178
forcedAddOns?: Array<string>
179179
forcedMode?: TemplateOptions
180180
},
181-
): Promise<Required<Options>> {
181+
): Promise<Required<Options> | undefined> {
182182
const options = {} as Required<Options>
183183

184184
const framework = getFrameworkById(cliOptions.framework || 'react-cra')!
@@ -208,6 +208,8 @@ export async function promptForOptions(
208208
process.exit(0)
209209
}
210210
options.projectName = value
211+
} else {
212+
options.projectName = cliOptions.projectName
211213
}
212214

213215
// Router type selection

packages/cta-cli/src/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ export interface CliOptions {
1414
mcpSse?: boolean
1515
starter?: string
1616
targetDir?: string
17+
interactive?: boolean
1718
}

templates/react-cra/add-ons/sentry/assets/_dot_cursorrules.append

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ Error collection is automatic and configured in `src/router.tsx`.
66

77
## Instrumentation
88

9-
We want our server functions intstrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
9+
We want our server functions instrumented. So if you see a function name like `createServerFn`, you can instrument it with Sentry. You'll need to import `Sentry`:
1010

1111
```tsx
12-
import * as Sentry from '@sentry/browser'
12+
import * as Sentry from '@sentry/tanstackstart-react'
1313
```
1414

15-
And then wrap the implementation of the server function with `Sentry.startSpan`, liks so:
15+
And then wrap the implementation of the server function with `Sentry.startSpan`, like so:
1616

1717
```tsx
1818
Sentry.startSpan({ name: 'Requesting all the pokemon' }, async () => {
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,11 @@
11
# Your Sentry DSN (from your Sentry account)
22
VITE_SENTRY_DSN=
3+
4+
# Your Sentry organization (from your Sentry account)
5+
VITE_SENTRY_ORG=
6+
7+
# Your Sentry project (from your Sentry account)
8+
VITE_SENTRY_PROJECT=
9+
10+
# Your Sentry authentication token (from your Sentry account)
11+
SENTRY_AUTH_TOKEN=
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,11 @@
1-
import { createIsomorphicFn } from '@tanstack/react-start'
2-
import * as Sentry from "@sentry/react";
3-
import * as SentryServer from "@sentry/node";
1+
import * as Sentry from "@sentry/tanstackstart-react";
2+
import {
3+
createMiddleware,
4+
registerGlobalMiddleware,
5+
} from "@tanstack/react-start";
46

5-
createIsomorphicFn().server(() => {
6-
console.log('Sentry init server')
7-
SentryServer.init({
8-
dsn: import.meta.env.VITE_SENTRY_DSN,
9-
tracesSampleRate: 1.0,
10-
profilesSampleRate: 1.0,
11-
})
12-
}).client(() => {
13-
console.log('Sentry init client')
14-
Sentry.init({
15-
dsn: import.meta.env.VITE_SENTRY_DSN,
16-
tracesSampleRate: 1.0,
17-
profilesSampleRate: 1.0,
18-
integrations: [
19-
Sentry.replayIntegration({
20-
maskAllText: false,
21-
blockAllMedia: false,
22-
})
23-
]
24-
})
25-
})()
7+
registerGlobalMiddleware({
8+
middleware: [
9+
createMiddleware().server(Sentry.sentryGlobalServerMiddlewareHandler()),
10+
],
11+
});

templates/react-cra/add-ons/sentry/assets/src/routes/demo.sentry.testing.tsx

+23-14
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@
99
import * as fs from 'node:fs/promises'
1010
import { createFileRoute } from '@tanstack/react-router'
1111
import { createServerFn } from '@tanstack/react-start'
12-
import * as Sentry from '@sentry/react'
13-
import * as SentryServer from '@sentry/node'
12+
import * as Sentry from "@sentry/tanstackstart-react";
1413
import { useState, useEffect, useRef } from 'react'
1514

1615
export const Route = createFileRoute('/demo/sentry/testing')({
@@ -21,7 +20,7 @@ export const Route = createFileRoute('/demo/sentry/testing')({
2120
const badServerFunc = createServerFn({
2221
method: 'GET',
2322
}).handler(async () => {
24-
return await SentryServer.startSpan(
23+
return await Sentry.startSpan(
2524
{
2625
name: 'Reading non-existent file',
2726
op: 'file.read'
@@ -31,7 +30,7 @@ const badServerFunc = createServerFn({
3130
await fs.readFile('./doesnt-exist', 'utf-8')
3231
return true
3332
} catch (error) {
34-
SentryServer.captureException(error)
33+
Sentry.captureException(error)
3534
throw error
3635
}
3736
}
@@ -42,7 +41,7 @@ const badServerFunc = createServerFn({
4241
const goodServerFunc = createServerFn({
4342
method: 'GET',
4443
}).handler(async () => {
45-
return await SentryServer.startSpan(
44+
return await Sentry.startSpan(
4645
{
4746
name: 'Successful server operation',
4847
op: 'demo.success'
@@ -244,6 +243,7 @@ function RouteComponent() {
244243
<div className="space-y-6">
245244
<div>
246245
<button
246+
type="button"
247247
onClick={() => {
248248
setDemoStep(prev => prev + 1)
249249
handleClientError()
@@ -267,22 +267,24 @@ function RouteComponent() {
267267
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-2">
268268
<div className="flex items-center text-red-400 text-sm">
269269
<svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
270-
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
270+
<title>Red Warning Sign</title>
271+
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
271272
</svg>
272273
Client-side error captured and traced
273274
</div>
274275
</div>
275276
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
276277
<div className="flex items-center justify-between">
277278
<div className="relative">
278-
<div
279+
<button
280+
type="button"
279281
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.clientError ? 'scale-95' : ''}`}
280282
onClick={() => handleCopy(spanOps.clientError)}
281283
title="Click to copy operation name"
282284
>
283285
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
284286
<code className="text-purple-400 text-sm font-mono">{spanOps.clientError}</code>
285-
</div>
287+
</button>
286288
{copiedSpan === spanOps.clientError && (
287289
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
288290
Copied!
@@ -297,6 +299,7 @@ function RouteComponent() {
297299

298300
<div>
299301
<button
302+
type="button"
300303
onClick={() => {
301304
setDemoStep(prev => prev + 1)
302305
handleClientTrace()
@@ -332,14 +335,15 @@ function RouteComponent() {
332335
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
333336
<div className="flex items-center justify-between">
334337
<div className="relative">
335-
<div
338+
<button
339+
type="button"
336340
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.client ? 'scale-95' : ''}`}
337341
onClick={() => handleCopy(spanOps.client)}
338342
title="Click to copy operation name"
339343
>
340344
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
341345
<code className="text-purple-400 text-sm font-mono">{spanOps.client}</code>
342-
</div>
346+
</button>
343347
{copiedSpan === spanOps.client && (
344348
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
345349
Copied!
@@ -361,6 +365,7 @@ function RouteComponent() {
361365
<div className="space-y-6">
362366
<div>
363367
<button
368+
type="button"
364369
onClick={() => {
365370
setDemoStep(prev => prev + 1)
366371
handleServerError()
@@ -384,6 +389,7 @@ function RouteComponent() {
384389
<div className="bg-red-900/20 border border-red-500/50 rounded-lg p-3">
385390
<div className="flex items-center text-red-400 text-sm">
386391
<svg className="w-4 h-4 mr-2" fill="none" strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" viewBox="0 0 24 24" stroke="currentColor">
392+
<title>Red Warning Sign</title>
387393
<path d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
388394
</svg>
389395
Server-side error captured and traced
@@ -392,14 +398,15 @@ function RouteComponent() {
392398
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
393399
<div className="flex items-center justify-between">
394400
<div className="relative">
395-
<div
401+
<button
402+
type="button"
396403
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.serverError ? 'scale-95' : ''}`}
397404
onClick={() => handleCopy(spanOps.serverError)}
398405
title="Click to copy operation name"
399406
>
400407
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
401408
<code className="text-purple-400 text-sm font-mono">{spanOps.serverError}</code>
402-
</div>
409+
</button>
403410
{copiedSpan === spanOps.serverError && (
404411
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
405412
Copied!
@@ -414,6 +421,7 @@ function RouteComponent() {
414421

415422
<div>
416423
<button
424+
type="button"
417425
onClick={() => {
418426
setDemoStep(prev => prev + 1)
419427
handleServerTrace()
@@ -449,14 +457,15 @@ function RouteComponent() {
449457
<div className="bg-purple-900/20 border border-purple-500/50 rounded-lg p-3">
450458
<div className="flex items-center justify-between">
451459
<div className="relative">
452-
<div
460+
<button
461+
type="button"
453462
className={`inline-flex items-center bg-purple-900/40 px-3 py-1.5 rounded-lg border border-purple-500/50 cursor-pointer hover:bg-purple-900/60 transition-all ${copiedSpan === spanOps.server ? 'scale-95' : ''}`}
454463
onClick={() => handleCopy(spanOps.server)}
455464
title="Click to copy operation name"
456465
>
457466
<span className="text-purple-300 text-sm font-medium mr-2">span.op</span>
458467
<code className="text-purple-400 text-sm font-mono">{spanOps.server}</code>
459-
</div>
468+
</button>
460469
{copiedSpan === spanOps.server && (
461470
<div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-green-500/90 text-white text-xs px-2 py-1 rounded animate-fade-out">
462471
Copied!
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
{
22
"dependencies": {
3-
"@sentry/node": "^9.1.0",
4-
"@sentry/profiling-node": "^9.1.0",
5-
"@sentry/react": "^9.1.0"
3+
"@sentry/tanstackstart-react": "^9.12.0"
64
}
75
}

templates/react-cra/add-ons/start/assets/app.config.ts.ejs

+15-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { defineConfig } from '@tanstack/react-start/config'
22
import viteTsConfigPaths from 'vite-tsconfig-paths'<% if (tailwind) { %>
33
import tailwindcss from "@tailwindcss/vite"
4+
<% } %><% if (addOnEnabled.sentry) { %>
5+
import { wrapVinxiConfigWithSentry } from "@sentry/tanstackstart-react";
46
<% } %>
5-
6-
export default defineConfig({
7+
const config = defineConfig({
78
tsr: {
89
appDirectory: 'src',
910
},
@@ -17,3 +18,15 @@ export default defineConfig({
1718
],
1819
},
1920
})
21+
22+
<% if (addOnEnabled.sentry) { %>
23+
export default wrapVinxiConfigWithSentry(config, {
24+
org: process.env.VITE_SENTRY_ORG,
25+
project: process.env.VITE_SENTRY_PROJECT,
26+
authToken: process.env.SENTRY_AUTH_TOKEN,
27+
// Only print logs for uploading source maps in CI
28+
// Set to `true` to suppress logs
29+
silent: !process.env.CI,
30+
});<% } else { %>
31+
export default config
32+
<% } %>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { hydrateRoot } from 'react-dom/client'
2+
import { StartClient } from '@tanstack/react-start'
3+
<% if (addOnEnabled.sentry) { %>
4+
import * as Sentry from "@sentry/tanstackstart-react";
5+
<% } %>
6+
import { createRouter } from './router'
7+
8+
const router = createRouter()
9+
10+
<% if (addOnEnabled.sentry) { %>
11+
Sentry.init({
12+
dsn: import.meta.env.VITE_SENTRY_DSN,
13+
integrations: [
14+
Sentry.tanstackRouterBrowserTracingIntegration(router),
15+
Sentry.replayIntegration({
16+
maskAllText: false,
17+
blockAllMedia: false,
18+
}),
19+
],
20+
// Set tracesSampleRate to 1.0 to capture 100%
21+
// of transactions for tracing.
22+
// We recommend adjusting this value in production.
23+
// Learn more at https://docs.sentry.io/platforms/javascript/configuration/options/#traces-sample-rate
24+
tracesSampleRate: 1.0,
25+
// Capture Replay for 10% of all sessions,
26+
// plus for 100% of sessions with an error.
27+
// Learn more at https://docs.sentry.io/platforms/javascript/session-replay/configuration/#general-integration-configuration
28+
replaysSessionSampleRate: 0.1,
29+
replaysOnErrorSampleRate: 1.0,
30+
});
31+
<% } %>
32+
33+
hydrateRoot(document, <StartClient router={router} />)

0 commit comments

Comments
 (0)