@sentry/wizard 6.11.0 → 6.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/dist/e2e-tests/tests/cloudflare-worker.test.js +5 -0
  3. package/dist/e2e-tests/tests/cloudflare-worker.test.js.map +1 -1
  4. package/dist/e2e-tests/tests/pnpm-workspace.test.js +2 -1
  5. package/dist/e2e-tests/tests/pnpm-workspace.test.js.map +1 -1
  6. package/dist/e2e-tests/tests/react-router-instrumentation-api.test.d.ts +1 -0
  7. package/dist/e2e-tests/tests/react-router-instrumentation-api.test.js +96 -0
  8. package/dist/e2e-tests/tests/react-router-instrumentation-api.test.js.map +1 -0
  9. package/dist/e2e-tests/tests/react-router.test.js +3 -1
  10. package/dist/e2e-tests/tests/react-router.test.js.map +1 -1
  11. package/dist/e2e-tests/tests/sveltekit-tracing.test.js +2 -1
  12. package/dist/e2e-tests/tests/sveltekit-tracing.test.js.map +1 -1
  13. package/dist/src/apple/code-tools.js +17 -3
  14. package/dist/src/apple/code-tools.js.map +1 -1
  15. package/dist/src/apple/configure-package-manager.js +18 -5
  16. package/dist/src/apple/configure-package-manager.js.map +1 -1
  17. package/dist/src/cloudflare/cloudflare-wizard.js +5 -0
  18. package/dist/src/cloudflare/cloudflare-wizard.js.map +1 -1
  19. package/dist/src/cloudflare/sdk-setup.d.ts +1 -0
  20. package/dist/src/cloudflare/sdk-setup.js.map +1 -1
  21. package/dist/src/cloudflare/templates.d.ts +1 -0
  22. package/dist/src/cloudflare/templates.js +7 -1
  23. package/dist/src/cloudflare/templates.js.map +1 -1
  24. package/dist/src/cloudflare/wrap-worker.d.ts +1 -0
  25. package/dist/src/cloudflare/wrap-worker.js +7 -0
  26. package/dist/src/cloudflare/wrap-worker.js.map +1 -1
  27. package/dist/src/react-native/expo.d.ts +6 -0
  28. package/dist/src/react-native/expo.js +27 -1
  29. package/dist/src/react-native/expo.js.map +1 -1
  30. package/dist/src/react-native/git.d.ts +5 -0
  31. package/dist/src/react-native/git.js +32 -1
  32. package/dist/src/react-native/git.js.map +1 -1
  33. package/dist/src/react-native/javascript.js +3 -1
  34. package/dist/src/react-native/javascript.js.map +1 -1
  35. package/dist/src/react-native/react-native-wizard.js +12 -6
  36. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  37. package/dist/src/react-router/codemods/client.entry.d.ts +1 -1
  38. package/dist/src/react-router/codemods/client.entry.js +69 -12
  39. package/dist/src/react-router/codemods/client.entry.js.map +1 -1
  40. package/dist/src/react-router/codemods/react-router-config.js +1 -1
  41. package/dist/src/react-router/codemods/react-router-config.js.map +1 -1
  42. package/dist/src/react-router/codemods/root.js +1 -2
  43. package/dist/src/react-router/codemods/root.js.map +1 -1
  44. package/dist/src/react-router/codemods/server-entry.d.ts +1 -1
  45. package/dist/src/react-router/codemods/server-entry.js +39 -4
  46. package/dist/src/react-router/codemods/server-entry.js.map +1 -1
  47. package/dist/src/react-router/codemods/vite.js +46 -1
  48. package/dist/src/react-router/codemods/vite.js.map +1 -1
  49. package/dist/src/react-router/react-router-wizard.js +52 -5
  50. package/dist/src/react-router/react-router-wizard.js.map +1 -1
  51. package/dist/src/react-router/sdk-setup.d.ts +4 -2
  52. package/dist/src/react-router/sdk-setup.js +32 -5
  53. package/dist/src/react-router/sdk-setup.js.map +1 -1
  54. package/dist/src/react-router/templates.d.ts +2 -2
  55. package/dist/src/react-router/templates.js +72 -2
  56. package/dist/src/react-router/templates.js.map +1 -1
  57. package/dist/src/sourcemaps/tools/vite.js +1 -1
  58. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  59. package/dist/src/sveltekit/sdk-setup/vite.js +1 -1
  60. package/dist/src/sveltekit/sdk-setup/vite.js.map +1 -1
  61. package/dist/src/utils/ast-utils.d.ts +10 -0
  62. package/dist/src/utils/ast-utils.js +19 -1
  63. package/dist/src/utils/ast-utils.js.map +1 -1
  64. package/dist/src/version.d.ts +1 -1
  65. package/dist/src/version.js +1 -1
  66. package/dist/src/version.js.map +1 -1
  67. package/dist/test/apple/code-tools.test.js +78 -0
  68. package/dist/test/apple/code-tools.test.js.map +1 -1
  69. package/dist/test/apple/configure-package-manager.test.d.ts +1 -0
  70. package/dist/test/apple/configure-package-manager.test.js +161 -0
  71. package/dist/test/apple/configure-package-manager.test.js.map +1 -0
  72. package/dist/test/cloudflare/sdk-setup.test.js +20 -2
  73. package/dist/test/cloudflare/sdk-setup.test.js.map +1 -1
  74. package/dist/test/cloudflare/templates.test.js +54 -0
  75. package/dist/test/cloudflare/templates.test.js.map +1 -1
  76. package/dist/test/cloudflare/wrap-worker.test.js +74 -11
  77. package/dist/test/cloudflare/wrap-worker.test.js.map +1 -1
  78. package/dist/test/react-native/expo.test.js +140 -0
  79. package/dist/test/react-native/expo.test.js.map +1 -1
  80. package/dist/test/react-native/git.test.d.ts +1 -0
  81. package/dist/test/react-native/git.test.js +160 -0
  82. package/dist/test/react-native/git.test.js.map +1 -0
  83. package/dist/test/react-router/codemods/client-entry.test.js +29 -0
  84. package/dist/test/react-router/codemods/client-entry.test.js.map +1 -1
  85. package/dist/test/react-router/codemods/root.test.js +4 -0
  86. package/dist/test/react-router/codemods/root.test.js.map +1 -1
  87. package/dist/test/react-router/codemods/server-entry.test.js +70 -0
  88. package/dist/test/react-router/codemods/server-entry.test.js.map +1 -1
  89. package/dist/test/react-router/codemods/vite.test.js +89 -0
  90. package/dist/test/react-router/codemods/vite.test.js.map +1 -1
  91. package/dist/test/react-router/sdk-setup.test.js +62 -6
  92. package/dist/test/react-router/sdk-setup.test.js.map +1 -1
  93. package/dist/test/react-router/templates.test.js +50 -0
  94. package/dist/test/react-router/templates.test.js.map +1 -1
  95. package/dist/test/sourcemaps/tools/vite.test.js +12 -8
  96. package/dist/test/sourcemaps/tools/vite.test.js.map +1 -1
  97. package/dist/test/utils/ast-utils.test.js +22 -0
  98. package/dist/test/utils/ast-utils.test.js.map +1 -1
  99. package/package.json +2 -2
@@ -92,7 +92,51 @@ const getSentryInstrumentationServerContent = (dsn, enableTracing, enableProfili
92
92
  return generateServerInstrumentationCode(dsn, enableTracing, enableProfiling, enableLogs);
93
93
  };
94
94
  exports.getSentryInstrumentationServerContent = getSentryInstrumentationServerContent;
95
- const getManualClientEntryContent = (dsn, enableTracing, enableReplay, enableLogs) => {
95
+ const getManualClientEntryContent = (dsn, enableTracing, enableReplay, enableLogs, useInstrumentationAPI = false) => {
96
+ if (useInstrumentationAPI && enableTracing) {
97
+ const integrations = ['tracing'];
98
+ if (enableReplay) {
99
+ integrations.push('Sentry.replayIntegration()');
100
+ }
101
+ const integrationsStr = integrations.join(',\n ');
102
+ return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
103
+ import { startTransition, StrictMode } from 'react';
104
+ import { hydrateRoot } from 'react-dom/client';
105
+ import { HydratedRouter } from 'react-router/dom';
106
+
107
+ ${plus(`const tracing = Sentry.reactRouterTracingIntegration({ useInstrumentationAPI: true });`)}
108
+
109
+ ${plus(`Sentry.init({
110
+ dsn: "${dsn}",
111
+
112
+ // Adds request headers and IP for users, for more info visit:
113
+ // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii
114
+ sendDefaultPii: true,
115
+
116
+ integrations: [
117
+ ${integrationsStr}
118
+ ],
119
+
120
+ ${enableLogs
121
+ ? '// Enable logs to be sent to Sentry\n enableLogs: true,\n\n '
122
+ : ''}tracesSampleRate: 1.0, // Capture 100% of the transactions
123
+
124
+ // Set \`tracePropagationTargets\` to declare which URL(s) should have trace propagation enabled
125
+ // In production, replace "yourserver.io" with your actual backend domain
126
+ tracePropagationTargets: [/^\\//, /^https:\\/\\/yourserver\\.io\\/api/],${enableReplay
127
+ ? '\n\n // Capture Replay for 10% of all sessions,\n // plus 100% of sessions with an error\n replaysSessionSampleRate: 0.1,\n replaysOnErrorSampleRate: 1.0,'
128
+ : ''}
129
+ });`)}
130
+
131
+ startTransition(() => {
132
+ hydrateRoot(
133
+ document,
134
+ <StrictMode>
135
+ ${plus('<HydratedRouter unstable_instrumentations={[tracing.clientInstrumentation]} />')}
136
+ </StrictMode>
137
+ );
138
+ });`));
139
+ }
96
140
  const integrations = [];
97
141
  if (enableTracing) {
98
142
  integrations.push('Sentry.reactRouterTracingIntegration()');
@@ -136,7 +180,30 @@ startTransition(() => {
136
180
  });`));
137
181
  };
138
182
  exports.getManualClientEntryContent = getManualClientEntryContent;
139
- const getManualServerEntryContent = () => {
183
+ const getManualServerEntryContent = (useInstrumentationAPI = false) => {
184
+ if (useInstrumentationAPI) {
185
+ return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
186
+ import { createReadableStreamFromReadable } from '@react-router/node';
187
+ import { renderToPipeableStream } from 'react-dom/server';
188
+ import { ServerRouter } from 'react-router';
189
+
190
+ ${plus(`const handleRequest = Sentry.createSentryHandleRequest({
191
+ ServerRouter,
192
+ renderToPipeableStream,
193
+ createReadableStreamFromReadable,
194
+ });`)}
195
+
196
+ export default handleRequest;
197
+
198
+ ${plus(`export const handleError = Sentry.createSentryHandleError({
199
+ logErrors: false
200
+ });`)}
201
+
202
+ ${plus(`// Enable automatic server-side instrumentation for loaders, actions, middleware
203
+ export const unstable_instrumentations = [Sentry.createSentryServerInstrumentation()];`)}
204
+
205
+ // ... rest of your server entry`));
206
+ }
140
207
  return (0, clack_1.makeCodeSnippet)(true, (unchanged, plus) => unchanged(`${plus("import * as Sentry from '@sentry/react-router';")}
141
208
  import { createReadableStreamFromReadable } from '@react-router/node';
142
209
  import { renderToPipeableStream } from 'react-dom/server';
@@ -266,6 +333,9 @@ export default defineConfig(config => {
266
333
  authToken: process.env.SENTRY_AUTH_TOKEN,
267
334
  }, config),`)}
268
335
  ],
336
+ ${plus(`optimizeDeps: {
337
+ exclude: ['@sentry/react-router'],
338
+ },`)}
269
339
  };
270
340
  });`));
271
341
  };
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/react-router/templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { makeCodeSnippet } from '../utils/clack';\n\nfunction generateErrorBoundaryTemplate(\n isTypeScript: boolean,\n forManualInstructions = false,\n): string {\n const typeAnnotations = isTypeScript\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n const commentLine = forManualInstructions\n ? '// you only want to capture non 404-errors that reach the boundary\\n '\n : '// Only capture non-404 errors (all errors here are already non-RouteErrorResponse)\\n ';\n\n return `function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n ${commentLine}Sentry.captureException(error);\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}`;\n}\n\nexport const ERROR_BOUNDARY_TEMPLATE = generateErrorBoundaryTemplate(false);\n\nexport const EXAMPLE_PAGE_TEMPLATE_TSX = `import type { Route } from \"./+types/sentry-example-page\";\n\nexport async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nexport const EXAMPLE_PAGE_TEMPLATE_JSX = `export async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nfunction generateServerInstrumentationCode(\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs: boolean,\n): string {\n return `import * as Sentry from '@sentry/react-router';${\n enableProfiling\n ? `\\nimport { nodeProfilingIntegration } from '@sentry/profiling-node';`\n : ''\n }\n\nSentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,${\n enableLogs\n ? '\\n\\n // Enable logs to be sent to Sentry\\n enableLogs: true,'\n : ''\n }${enableProfiling ? '\\n\\n integrations: [nodeProfilingIntegration()],' : ''}\n tracesSampleRate: ${enableTracing ? '1.0' : '0'}, ${\n enableTracing ? '// Capture 100% of the transactions' : ''\n }${\n enableProfiling\n ? '\\n profilesSampleRate: 1.0, // profile every transaction'\n : ''\n }${\n enableTracing\n ? `\n\n // Set up performance monitoring\n beforeSend(event) {\n // Filter out 404s from error reporting\n if (event.exception) {\n const error = event.exception.values?.[0];\n if (error?.type === \"NotFoundException\" || error?.value?.includes(\"404\")) {\n return null;\n }\n }\n return event;\n },`\n : ''\n }\n});`;\n}\n\nexport const getSentryInstrumentationServerContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling = false,\n enableLogs = false,\n) => {\n return generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n );\n};\n\nexport const getManualClientEntryContent = (\n dsn: string,\n enableTracing: boolean,\n enableReplay: boolean,\n enableLogs: boolean,\n) => {\n const integrations = [];\n\n if (enableTracing) {\n integrations.push('Sentry.reactRouterTracingIntegration()');\n }\n\n if (enableReplay) {\n integrations.push('Sentry.replayIntegration()');\n }\n\n const integrationsStr =\n integrations.length > 0 ? integrations.join(',\\n ') : '';\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { startTransition, StrictMode } from 'react';\nimport { hydrateRoot } from 'react-dom/client';\nimport { HydratedRouter } from 'react-router/dom';\n\n${plus(`Sentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,\n\n integrations: [\n ${integrationsStr}\n ],\n\n ${\n enableLogs\n ? '// Enable logs to be sent to Sentry\\n enableLogs: true,\\n\\n '\n : ''\n }tracesSampleRate: ${enableTracing ? '1.0' : '0'},${\n enableTracing ? ' // Capture 100% of the transactions' : ''\n}${\n enableTracing\n ? '\\n\\n // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled\\n // In production, replace \"yourserver.io\" with your actual backend domain\\n tracePropagationTargets: [/^\\\\//, /^https:\\\\/\\\\/yourserver\\\\.io\\\\/api/],'\n : ''\n}${\n enableReplay\n ? '\\n\\n // Capture Replay for 10% of all sessions,\\n // plus 100% of sessions with an error\\n replaysSessionSampleRate: 0.1,\\n replaysOnErrorSampleRate: 1.0,'\n : ''\n}\n});`)}\n\nstartTransition(() => {\n hydrateRoot(\n document,\n <StrictMode>\n <HydratedRouter />\n </StrictMode>\n );\n});`),\n );\n};\n\nexport const getManualServerEntryContent = () => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`const handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\nexport default handleRequest;\n\n${plus(`export const handleError = Sentry.createSentryHandleError({\n logErrors: false\n});`)}\n\n// ... rest of your server entry`),\n );\n};\n\nexport const getManualHandleRequestContent = () => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`// Replace your existing handleRequest function with this Sentry-wrapped version:\nconst handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\n${plus(`// If you have a custom handleRequest implementation, wrap it like this:\n// export default Sentry.wrapSentryHandleRequest(yourCustomHandleRequest);`)}\n\nexport default handleRequest;`),\n );\n};\n\nexport const getManualRootContent = (isTs: boolean) => {\n const typeAnnotations = isTs\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\n\nexport function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n // you only want to capture non 404-errors that reach the boundary\n ${plus('Sentry.captureException(error);')}\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}\n// ...`),\n );\n};\n\nexport const getManualServerInstrumentContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs = false,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n plus(\n generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n ),\n ),\n );\n};\n\nexport const getManualReactRouterConfigContent = (isTS = true) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n isTS\n ? unchanged(`${plus(\n 'import type { Config } from \"@react-router/dev/config\";',\n )}\n${plus(\"import { sentryOnBuildEnd } from '@sentry/react-router';\")}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n} satisfies Config;\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`)\n : unchanged(`${plus(\n \"import { sentryOnBuildEnd } from '@sentry/react-router';\",\n )}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n};\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`),\n );\n};\n\nexport const getManualViteConfigContent = (\n orgSlug: string,\n projectSlug: string,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\n \"import { sentryReactRouter } from '@sentry/react-router';\",\n )}\nimport { defineConfig } from 'vite';\n\nexport default defineConfig(config => {\n return {\n plugins: [\n // ... your existing plugins\n ${plus(`sentryReactRouter({\n org: \"${orgSlug}\",\n project: \"${projectSlug}\",\n authToken: process.env.SENTRY_AUTH_TOKEN,\n }, config),`)}\n ],\n };\n});`),\n );\n};\n"]}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../../../src/react-router/templates.ts"],"names":[],"mappings":"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","sourcesContent":["import { makeCodeSnippet } from '../utils/clack';\n\nfunction generateErrorBoundaryTemplate(\n isTypeScript: boolean,\n forManualInstructions = false,\n): string {\n const typeAnnotations = isTypeScript\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n const commentLine = forManualInstructions\n ? '// you only want to capture non 404-errors that reach the boundary\\n '\n : '// Only capture non-404 errors (all errors here are already non-RouteErrorResponse)\\n ';\n\n return `function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n ${commentLine}Sentry.captureException(error);\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}`;\n}\n\nexport const ERROR_BOUNDARY_TEMPLATE = generateErrorBoundaryTemplate(false);\n\nexport const EXAMPLE_PAGE_TEMPLATE_TSX = `import type { Route } from \"./+types/sentry-example-page\";\n\nexport async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nexport const EXAMPLE_PAGE_TEMPLATE_JSX = `export async function loader() {\n throw new Error(\"some error thrown in a loader\");\n}\n\nexport default function SentryExamplePage() {\n return <div>Loading this page will throw an error</div>;\n}`;\n\nfunction generateServerInstrumentationCode(\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs: boolean,\n): string {\n return `import * as Sentry from '@sentry/react-router';${\n enableProfiling\n ? `\\nimport { nodeProfilingIntegration } from '@sentry/profiling-node';`\n : ''\n }\n\nSentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,${\n enableLogs\n ? '\\n\\n // Enable logs to be sent to Sentry\\n enableLogs: true,'\n : ''\n }${enableProfiling ? '\\n\\n integrations: [nodeProfilingIntegration()],' : ''}\n tracesSampleRate: ${enableTracing ? '1.0' : '0'}, ${\n enableTracing ? '// Capture 100% of the transactions' : ''\n }${\n enableProfiling\n ? '\\n profilesSampleRate: 1.0, // profile every transaction'\n : ''\n }${\n enableTracing\n ? `\n\n // Set up performance monitoring\n beforeSend(event) {\n // Filter out 404s from error reporting\n if (event.exception) {\n const error = event.exception.values?.[0];\n if (error?.type === \"NotFoundException\" || error?.value?.includes(\"404\")) {\n return null;\n }\n }\n return event;\n },`\n : ''\n }\n});`;\n}\n\nexport const getSentryInstrumentationServerContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling = false,\n enableLogs = false,\n) => {\n return generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n );\n};\n\nexport const getManualClientEntryContent = (\n dsn: string,\n enableTracing: boolean,\n enableReplay: boolean,\n enableLogs: boolean,\n useInstrumentationAPI = false,\n) => {\n if (useInstrumentationAPI && enableTracing) {\n const integrations = ['tracing'];\n if (enableReplay) {\n integrations.push('Sentry.replayIntegration()');\n }\n\n const integrationsStr = integrations.join(',\\n ');\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { startTransition, StrictMode } from 'react';\nimport { hydrateRoot } from 'react-dom/client';\nimport { HydratedRouter } from 'react-router/dom';\n\n${plus(\n `const tracing = Sentry.reactRouterTracingIntegration({ useInstrumentationAPI: true });`,\n)}\n\n${plus(`Sentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,\n\n integrations: [\n ${integrationsStr}\n ],\n\n ${\n enableLogs\n ? '// Enable logs to be sent to Sentry\\n enableLogs: true,\\n\\n '\n : ''\n }tracesSampleRate: 1.0, // Capture 100% of the transactions\n\n // Set \\`tracePropagationTargets\\` to declare which URL(s) should have trace propagation enabled\n // In production, replace \"yourserver.io\" with your actual backend domain\n tracePropagationTargets: [/^\\\\//, /^https:\\\\/\\\\/yourserver\\\\.io\\\\/api/],${\n enableReplay\n ? '\\n\\n // Capture Replay for 10% of all sessions,\\n // plus 100% of sessions with an error\\n replaysSessionSampleRate: 0.1,\\n replaysOnErrorSampleRate: 1.0,'\n : ''\n }\n});`)}\n\nstartTransition(() => {\n hydrateRoot(\n document,\n <StrictMode>\n ${plus(\n '<HydratedRouter unstable_instrumentations={[tracing.clientInstrumentation]} />',\n )}\n </StrictMode>\n );\n});`),\n );\n }\n\n const integrations = [];\n\n if (enableTracing) {\n integrations.push('Sentry.reactRouterTracingIntegration()');\n }\n\n if (enableReplay) {\n integrations.push('Sentry.replayIntegration()');\n }\n\n const integrationsStr =\n integrations.length > 0 ? integrations.join(',\\n ') : '';\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { startTransition, StrictMode } from 'react';\nimport { hydrateRoot } from 'react-dom/client';\nimport { HydratedRouter } from 'react-router/dom';\n\n${plus(`Sentry.init({\n dsn: \"${dsn}\",\n\n // Adds request headers and IP for users, for more info visit:\n // https://docs.sentry.io/platforms/javascript/guides/react-router/configuration/options/#sendDefaultPii\n sendDefaultPii: true,\n\n integrations: [\n ${integrationsStr}\n ],\n\n ${\n enableLogs\n ? '// Enable logs to be sent to Sentry\\n enableLogs: true,\\n\\n '\n : ''\n }tracesSampleRate: ${enableTracing ? '1.0' : '0'},${\n enableTracing ? ' // Capture 100% of the transactions' : ''\n}${\n enableTracing\n ? '\\n\\n // Set `tracePropagationTargets` to declare which URL(s) should have trace propagation enabled\\n // In production, replace \"yourserver.io\" with your actual backend domain\\n tracePropagationTargets: [/^\\\\//, /^https:\\\\/\\\\/yourserver\\\\.io\\\\/api/],'\n : ''\n}${\n enableReplay\n ? '\\n\\n // Capture Replay for 10% of all sessions,\\n // plus 100% of sessions with an error\\n replaysSessionSampleRate: 0.1,\\n replaysOnErrorSampleRate: 1.0,'\n : ''\n}\n});`)}\n\nstartTransition(() => {\n hydrateRoot(\n document,\n <StrictMode>\n <HydratedRouter />\n </StrictMode>\n );\n});`),\n );\n};\n\nexport const getManualServerEntryContent = (useInstrumentationAPI = false) => {\n if (useInstrumentationAPI) {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`const handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\nexport default handleRequest;\n\n${plus(`export const handleError = Sentry.createSentryHandleError({\n logErrors: false\n});`)}\n\n${plus(`// Enable automatic server-side instrumentation for loaders, actions, middleware\nexport const unstable_instrumentations = [Sentry.createSentryServerInstrumentation()];`)}\n\n// ... rest of your server entry`),\n );\n }\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`const handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\nexport default handleRequest;\n\n${plus(`export const handleError = Sentry.createSentryHandleError({\n logErrors: false\n});`)}\n\n// ... rest of your server entry`),\n );\n};\n\nexport const getManualHandleRequestContent = () => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\nimport { createReadableStreamFromReadable } from '@react-router/node';\nimport { renderToPipeableStream } from 'react-dom/server';\nimport { ServerRouter } from 'react-router';\n\n${plus(`// Replace your existing handleRequest function with this Sentry-wrapped version:\nconst handleRequest = Sentry.createSentryHandleRequest({\n ServerRouter,\n renderToPipeableStream,\n createReadableStreamFromReadable,\n});`)}\n\n${plus(`// If you have a custom handleRequest implementation, wrap it like this:\n// export default Sentry.wrapSentryHandleRequest(yourCustomHandleRequest);`)}\n\nexport default handleRequest;`),\n );\n};\n\nexport const getManualRootContent = (isTs: boolean) => {\n const typeAnnotations = isTs\n ? { stack: ': string | undefined', props: ': Route.ErrorBoundaryProps' }\n : { stack: '', props: '' };\n\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\"import * as Sentry from '@sentry/react-router';\")}\n\nexport function ErrorBoundary({ error }${typeAnnotations.props}) {\n let message = \"Oops!\";\n let details = \"An unexpected error occurred.\";\n let stack${typeAnnotations.stack};\n\n if (isRouteErrorResponse(error)) {\n message = error.status === 404 ? \"404\" : \"Error\";\n details =\n error.status === 404\n ? \"The requested page could not be found.\"\n : error.statusText || details;\n } else if (error && error instanceof Error) {\n // you only want to capture non 404-errors that reach the boundary\n ${plus('Sentry.captureException(error);')}\n details = error.message;\n stack = error.stack;\n }\n\n return (\n <main>\n <h1>{message}</h1>\n <p>{details}</p>\n {stack && (\n <pre>\n <code>{stack}</code>\n </pre>\n )}\n </main>\n );\n}\n// ...`),\n );\n};\n\nexport const getManualServerInstrumentContent = (\n dsn: string,\n enableTracing: boolean,\n enableProfiling: boolean,\n enableLogs = false,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n plus(\n generateServerInstrumentationCode(\n dsn,\n enableTracing,\n enableProfiling,\n enableLogs,\n ),\n ),\n );\n};\n\nexport const getManualReactRouterConfigContent = (isTS = true) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n isTS\n ? unchanged(`${plus(\n 'import type { Config } from \"@react-router/dev/config\";',\n )}\n${plus(\"import { sentryOnBuildEnd } from '@sentry/react-router';\")}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n} satisfies Config;\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`)\n : unchanged(`${plus(\n \"import { sentryOnBuildEnd } from '@sentry/react-router';\",\n )}\n\nexport default {\n ${plus('ssr: true,')}\n ${plus(`buildEnd: async ({ viteConfig, reactRouterConfig, buildManifest }) => {\n await sentryOnBuildEnd({ viteConfig, reactRouterConfig, buildManifest });\n },`)}\n};\n\n// If you already have a buildEnd hook, modify it to call sentryOnBuildEnd:\n// buildEnd: async (args) => {\n// await yourExistingLogic(args);\n// await sentryOnBuildEnd(args);\n// }`),\n );\n};\n\nexport const getManualViteConfigContent = (\n orgSlug: string,\n projectSlug: string,\n) => {\n return makeCodeSnippet(true, (unchanged, plus) =>\n unchanged(`${plus(\n \"import { sentryReactRouter } from '@sentry/react-router';\",\n )}\nimport { defineConfig } from 'vite';\n\nexport default defineConfig(config => {\n return {\n plugins: [\n // ... your existing plugins\n ${plus(`sentryReactRouter({\n org: \"${orgSlug}\",\n project: \"${projectSlug}\",\n authToken: process.env.SENTRY_AUTH_TOKEN,\n }, config),`)}\n ],\n ${plus(`optimizeDeps: {\n exclude: ['@sentry/react-router'],\n },`)}\n };\n});`),\n );\n};\n"]}
@@ -128,7 +128,7 @@ async function addVitePluginToConfig(viteConfigPath, options) {
128
128
  ...(selfHosted && { url }),
129
129
  },
130
130
  });
131
- const code = (0, magicast_1.generateCode)(mod.$ast).code;
131
+ const code = (0, ast_utils_1.preserveTrailingNewline)(viteConfigContent, (0, magicast_1.generateCode)(mod.$ast).code);
132
132
  await fs.promises.writeFile(viteConfigPath, code);
133
133
  clack.log.success(`Added the Sentry Vite plugin to ${prettyViteConfigFilename} and enabled source maps`);
134
134
  return true;
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../../../src/sourcemaps/tools/vite.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport * as clack from '@clack/prompts';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, parseModule } from 'magicast';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { addVitePlugin } from 'magicast/helpers';\n\nimport type { namedTypes as t } from 'ast-types';\n\nimport * as recast from 'recast';\n\nimport * as Sentry from '@sentry/node';\n\nimport chalk from 'chalk';\nimport {\n abortIfCancelled,\n addDotEnvSentryBuildPluginFile,\n askForToolConfigPath,\n createNewConfigFile,\n getPackageDotJson,\n installPackage,\n makeCodeSnippet,\n showCopyPasteInstructions,\n} from '../../utils/clack';\nimport { hasPackageInstalled } from '../../utils/package-json';\n\nimport {\n SourceMapUploadToolConfigurationFunction,\n SourceMapUploadToolConfigurationOptions,\n} from './types';\nimport { findFile, hasSentryContent } from '../../utils/ast-utils';\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { debug } from '../../utils/debug';\n\nconst getViteConfigSnippet = (\n options: SourceMapUploadToolConfigurationOptions,\n colors: boolean,\n) =>\n makeCodeSnippet(colors, (unchanged, plus, _) =>\n unchanged(`import { defineConfig } from \"vite\";\n${plus('import { sentryVitePlugin } from \"@sentry/vite-plugin\";')}\n\nexport default defineConfig({\n build: {\n ${plus('sourcemap: true, // Source map generation must be turned on')}\n },\n plugins: [\n // Put the Sentry vite plugin after all other plugins\n ${plus(`sentryVitePlugin({\n authToken: process.env.SENTRY_AUTH_TOKEN,\n org: \"${options.orgSlug}\",\n project: \"${options.projectSlug}\",${\n options.selfHosted ? `\\n url: \"${options.url}\",` : ''\n }\n }),`)}\n ],\n});`),\n );\n\nexport const configureVitePlugin: SourceMapUploadToolConfigurationFunction =\n async (options) => {\n await installPackage({\n packageName: '@sentry/vite-plugin',\n alreadyInstalled: hasPackageInstalled(\n '@sentry/vite-plugin',\n await getPackageDotJson(),\n ),\n });\n\n const viteConfigPath =\n findFile(path.resolve(process.cwd(), 'vite.config')) ??\n (await askForToolConfigPath('Vite', 'vite.config.js'));\n\n let successfullyAdded = false;\n if (viteConfigPath) {\n successfullyAdded = await addVitePluginToConfig(viteConfigPath, options);\n } else {\n successfullyAdded = await createNewConfigFile(\n path.join(process.cwd(), 'vite.config.js'),\n getViteConfigSnippet(options, false),\n 'More information about vite configs: https://vitejs.dev/config/',\n );\n Sentry.setTag(\n 'created-new-config',\n successfullyAdded ? 'success' : 'fail',\n );\n }\n\n if (successfullyAdded) {\n clack.log.info(\n `We recommend checking the ${\n viteConfigPath ? 'modified' : 'added'\n } file after the wizard finished to ensure it works with your build setup.`,\n );\n\n Sentry.setTag('ast-mod', 'success');\n } else {\n Sentry.setTag('ast-mod', 'fail');\n await showCopyPasteInstructions({\n filename: path.basename(viteConfigPath || 'vite.config.js'),\n codeSnippet: getViteConfigSnippet(options, true),\n });\n }\n\n await addDotEnvSentryBuildPluginFile(options.authToken);\n };\n\nexport async function addVitePluginToConfig(\n viteConfigPath: string,\n options: SourceMapUploadToolConfigurationOptions,\n): Promise<boolean> {\n try {\n const prettyViteConfigFilename = chalk.cyan(path.basename(viteConfigPath));\n\n const viteConfigContent = (\n await fs.promises.readFile(viteConfigPath)\n ).toString();\n\n const mod = parseModule(viteConfigContent);\n\n if (hasSentryContent(mod.$ast as t.Program)) {\n const shouldContinue = await abortIfCancelled(\n clack.select({\n message: `${prettyViteConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,\n options: [\n {\n label: 'Yes, add the Sentry Vite plugin',\n value: true,\n },\n {\n label: 'No, show me instructions to manually add the plugin',\n value: false,\n },\n ],\n initialValue: true,\n }),\n );\n\n if (!shouldContinue) {\n Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');\n return false;\n }\n }\n\n const enabledSourcemaps = enableSourcemapGeneration(mod.$ast as t.Program);\n if (!enabledSourcemaps) {\n Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');\n return false;\n }\n\n const { orgSlug: org, projectSlug: project, selfHosted, url } = options;\n\n addVitePlugin(mod, {\n imported: 'sentryVitePlugin',\n from: '@sentry/vite-plugin',\n constructor: 'sentryVitePlugin',\n options: {\n org,\n project,\n ...(selfHosted && { url }),\n },\n });\n\n const code = generateCode(mod.$ast).code;\n\n await fs.promises.writeFile(viteConfigPath, code);\n\n clack.log.success(\n `Added the Sentry Vite plugin to ${prettyViteConfigFilename} and enabled source maps`,\n );\n\n return true;\n } catch (e) {\n debug(e);\n Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');\n return false;\n }\n}\n\nfunction enableSourcemapGeneration(program: t.Program): boolean {\n const configObj = getViteConfigObject(program);\n\n if (!configObj) {\n return false;\n }\n\n const b = recast.types.builders;\n\n const buildProp = configObj.properties.find(\n (p: t.ObjectProperty) =>\n p.key.type === 'Identifier' && p.key.name === 'build',\n );\n\n // case 1: build property doesn't exist yet, so we can just add it\n if (!buildProp) {\n configObj.properties.push(\n b.objectProperty(\n b.identifier('build'),\n b.objectExpression([\n b.objectProperty(b.identifier('sourcemap'), b.booleanLiteral(true)),\n ]),\n ),\n );\n return true;\n }\n\n const isValidBuildProp =\n buildProp.type === 'ObjectProperty' &&\n buildProp.value.type === 'ObjectExpression';\n\n if (!isValidBuildProp) {\n return false;\n }\n\n const sourceMapsProp =\n buildProp.value.type === 'ObjectExpression' &&\n buildProp.value.properties.find(\n (p: t.ObjectProperty) =>\n p.key.type === 'Identifier' && p.key.name === 'sourcemap',\n );\n\n // case 2: build.sourcemap property doesn't exist yet, so we just add it\n if (!sourceMapsProp && buildProp.value.type === 'ObjectExpression') {\n buildProp.value.properties.push(\n b.objectProperty(b.identifier('sourcemap'), b.booleanLiteral(true)),\n );\n return true;\n }\n\n if (!sourceMapsProp || sourceMapsProp.type !== 'ObjectProperty') {\n return false;\n }\n\n // case 3: build.sourcemap property exists, and it's set to 'hidden'\n if (\n sourceMapsProp.value.type === 'StringLiteral' &&\n sourceMapsProp.value.value === 'hidden'\n ) {\n // nothing to do for us\n return true;\n }\n\n // case 4: build.sourcemap property exists, but it's not enabled, so we set it to true\n // or it is already true in which case this is a noop\n sourceMapsProp.value = b.booleanLiteral(true);\n return true;\n}\n\nfunction getViteConfigObject(\n program: t.Program,\n): t.ObjectExpression | undefined {\n const defaultExport = program.body.find(\n (s) => s.type === 'ExportDefaultDeclaration',\n ) as t.ExportDefaultDeclaration;\n\n if (!defaultExport) {\n return undefined;\n }\n\n if (defaultExport.declaration.type === 'ObjectExpression') {\n return defaultExport.declaration;\n }\n\n if (\n defaultExport.declaration.type === 'CallExpression' &&\n defaultExport.declaration.arguments[0].type === 'ObjectExpression'\n ) {\n return defaultExport.declaration.arguments[0];\n }\n\n if (defaultExport.declaration.type === 'Identifier') {\n const configId = defaultExport.declaration.name;\n return findConfigNode(configId, program);\n }\n\n return undefined;\n}\n\nfunction findConfigNode(\n configId: string,\n program: t.Program,\n): t.ObjectExpression | undefined {\n for (const node of program.body) {\n if (node.type === 'VariableDeclaration') {\n for (const declaration of node.declarations) {\n if (\n declaration.type === 'VariableDeclarator' &&\n declaration.id.type === 'Identifier' &&\n declaration.id.name === configId &&\n declaration.init?.type === 'ObjectExpression'\n ) {\n return declaration.init;\n }\n }\n }\n }\n return undefined;\n}\n"]}
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../../../src/sourcemaps/tools/vite.ts"],"names":[],"mappings":"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","sourcesContent":["// @ts-expect-error - clack is ESM and TS complains about that. It works though\nimport * as clack from '@clack/prompts';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, parseModule } from 'magicast';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { addVitePlugin } from 'magicast/helpers';\n\nimport type { namedTypes as t } from 'ast-types';\n\nimport * as recast from 'recast';\n\nimport * as Sentry from '@sentry/node';\n\nimport chalk from 'chalk';\nimport {\n abortIfCancelled,\n addDotEnvSentryBuildPluginFile,\n askForToolConfigPath,\n createNewConfigFile,\n getPackageDotJson,\n installPackage,\n makeCodeSnippet,\n showCopyPasteInstructions,\n} from '../../utils/clack';\nimport { hasPackageInstalled } from '../../utils/package-json';\n\nimport {\n SourceMapUploadToolConfigurationFunction,\n SourceMapUploadToolConfigurationOptions,\n} from './types';\nimport {\n findFile,\n hasSentryContent,\n preserveTrailingNewline,\n} from '../../utils/ast-utils';\n\nimport * as path from 'path';\nimport * as fs from 'fs';\nimport { debug } from '../../utils/debug';\n\nconst getViteConfigSnippet = (\n options: SourceMapUploadToolConfigurationOptions,\n colors: boolean,\n) =>\n makeCodeSnippet(colors, (unchanged, plus, _) =>\n unchanged(`import { defineConfig } from \"vite\";\n${plus('import { sentryVitePlugin } from \"@sentry/vite-plugin\";')}\n\nexport default defineConfig({\n build: {\n ${plus('sourcemap: true, // Source map generation must be turned on')}\n },\n plugins: [\n // Put the Sentry vite plugin after all other plugins\n ${plus(`sentryVitePlugin({\n authToken: process.env.SENTRY_AUTH_TOKEN,\n org: \"${options.orgSlug}\",\n project: \"${options.projectSlug}\",${\n options.selfHosted ? `\\n url: \"${options.url}\",` : ''\n }\n }),`)}\n ],\n});`),\n );\n\nexport const configureVitePlugin: SourceMapUploadToolConfigurationFunction =\n async (options) => {\n await installPackage({\n packageName: '@sentry/vite-plugin',\n alreadyInstalled: hasPackageInstalled(\n '@sentry/vite-plugin',\n await getPackageDotJson(),\n ),\n });\n\n const viteConfigPath =\n findFile(path.resolve(process.cwd(), 'vite.config')) ??\n (await askForToolConfigPath('Vite', 'vite.config.js'));\n\n let successfullyAdded = false;\n if (viteConfigPath) {\n successfullyAdded = await addVitePluginToConfig(viteConfigPath, options);\n } else {\n successfullyAdded = await createNewConfigFile(\n path.join(process.cwd(), 'vite.config.js'),\n getViteConfigSnippet(options, false),\n 'More information about vite configs: https://vitejs.dev/config/',\n );\n Sentry.setTag(\n 'created-new-config',\n successfullyAdded ? 'success' : 'fail',\n );\n }\n\n if (successfullyAdded) {\n clack.log.info(\n `We recommend checking the ${\n viteConfigPath ? 'modified' : 'added'\n } file after the wizard finished to ensure it works with your build setup.`,\n );\n\n Sentry.setTag('ast-mod', 'success');\n } else {\n Sentry.setTag('ast-mod', 'fail');\n await showCopyPasteInstructions({\n filename: path.basename(viteConfigPath || 'vite.config.js'),\n codeSnippet: getViteConfigSnippet(options, true),\n });\n }\n\n await addDotEnvSentryBuildPluginFile(options.authToken);\n };\n\nexport async function addVitePluginToConfig(\n viteConfigPath: string,\n options: SourceMapUploadToolConfigurationOptions,\n): Promise<boolean> {\n try {\n const prettyViteConfigFilename = chalk.cyan(path.basename(viteConfigPath));\n\n const viteConfigContent = (\n await fs.promises.readFile(viteConfigPath)\n ).toString();\n\n const mod = parseModule(viteConfigContent);\n\n if (hasSentryContent(mod.$ast as t.Program)) {\n const shouldContinue = await abortIfCancelled(\n clack.select({\n message: `${prettyViteConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,\n options: [\n {\n label: 'Yes, add the Sentry Vite plugin',\n value: true,\n },\n {\n label: 'No, show me instructions to manually add the plugin',\n value: false,\n },\n ],\n initialValue: true,\n }),\n );\n\n if (!shouldContinue) {\n Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');\n return false;\n }\n }\n\n const enabledSourcemaps = enableSourcemapGeneration(mod.$ast as t.Program);\n if (!enabledSourcemaps) {\n Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');\n return false;\n }\n\n const { orgSlug: org, projectSlug: project, selfHosted, url } = options;\n\n addVitePlugin(mod, {\n imported: 'sentryVitePlugin',\n from: '@sentry/vite-plugin',\n constructor: 'sentryVitePlugin',\n options: {\n org,\n project,\n ...(selfHosted && { url }),\n },\n });\n\n const code = preserveTrailingNewline(\n viteConfigContent,\n generateCode(mod.$ast).code,\n );\n\n await fs.promises.writeFile(viteConfigPath, code);\n\n clack.log.success(\n `Added the Sentry Vite plugin to ${prettyViteConfigFilename} and enabled source maps`,\n );\n\n return true;\n } catch (e) {\n debug(e);\n Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');\n return false;\n }\n}\n\nfunction enableSourcemapGeneration(program: t.Program): boolean {\n const configObj = getViteConfigObject(program);\n\n if (!configObj) {\n return false;\n }\n\n const b = recast.types.builders;\n\n const buildProp = configObj.properties.find(\n (p: t.ObjectProperty) =>\n p.key.type === 'Identifier' && p.key.name === 'build',\n );\n\n // case 1: build property doesn't exist yet, so we can just add it\n if (!buildProp) {\n configObj.properties.push(\n b.objectProperty(\n b.identifier('build'),\n b.objectExpression([\n b.objectProperty(b.identifier('sourcemap'), b.booleanLiteral(true)),\n ]),\n ),\n );\n return true;\n }\n\n const isValidBuildProp =\n buildProp.type === 'ObjectProperty' &&\n buildProp.value.type === 'ObjectExpression';\n\n if (!isValidBuildProp) {\n return false;\n }\n\n const sourceMapsProp =\n buildProp.value.type === 'ObjectExpression' &&\n buildProp.value.properties.find(\n (p: t.ObjectProperty) =>\n p.key.type === 'Identifier' && p.key.name === 'sourcemap',\n );\n\n // case 2: build.sourcemap property doesn't exist yet, so we just add it\n if (!sourceMapsProp && buildProp.value.type === 'ObjectExpression') {\n buildProp.value.properties.push(\n b.objectProperty(b.identifier('sourcemap'), b.booleanLiteral(true)),\n );\n return true;\n }\n\n if (!sourceMapsProp || sourceMapsProp.type !== 'ObjectProperty') {\n return false;\n }\n\n // case 3: build.sourcemap property exists, and it's set to 'hidden'\n if (\n sourceMapsProp.value.type === 'StringLiteral' &&\n sourceMapsProp.value.value === 'hidden'\n ) {\n // nothing to do for us\n return true;\n }\n\n // case 4: build.sourcemap property exists, but it's not enabled, so we set it to true\n // or it is already true in which case this is a noop\n sourceMapsProp.value = b.booleanLiteral(true);\n return true;\n}\n\nfunction getViteConfigObject(\n program: t.Program,\n): t.ObjectExpression | undefined {\n const defaultExport = program.body.find(\n (s) => s.type === 'ExportDefaultDeclaration',\n ) as t.ExportDefaultDeclaration;\n\n if (!defaultExport) {\n return undefined;\n }\n\n if (defaultExport.declaration.type === 'ObjectExpression') {\n return defaultExport.declaration;\n }\n\n if (\n defaultExport.declaration.type === 'CallExpression' &&\n defaultExport.declaration.arguments[0].type === 'ObjectExpression'\n ) {\n return defaultExport.declaration.arguments[0];\n }\n\n if (defaultExport.declaration.type === 'Identifier') {\n const configId = defaultExport.declaration.name;\n return findConfigNode(configId, program);\n }\n\n return undefined;\n}\n\nfunction findConfigNode(\n configId: string,\n program: t.Program,\n): t.ObjectExpression | undefined {\n for (const node of program.body) {\n if (node.type === 'VariableDeclaration') {\n for (const declaration of node.declarations) {\n if (\n declaration.type === 'VariableDeclarator' &&\n declaration.id.type === 'Identifier' &&\n declaration.id.name === configId &&\n declaration.init?.type === 'ObjectExpression'\n ) {\n return declaration.init;\n }\n }\n }\n }\n return undefined;\n}\n"]}
@@ -66,7 +66,7 @@ Skipping adding Sentry functionality to.`);
66
66
  index: 0,
67
67
  }), 'add-vite-plugin', 'vite-cfg');
68
68
  await (0, utils_1.modifyAndRecordFail)(async () => {
69
- const code = (0, magicast_1.generateCode)(viteModule.$ast).code;
69
+ const code = (0, ast_utils_1.preserveTrailingNewline)(viteConfigContent, (0, magicast_1.generateCode)(viteModule.$ast).code);
70
70
  await fs.promises.writeFile(viteConfigPath, code);
71
71
  }, 'write-file', 'vite-cfg');
72
72
  }
@@ -1 +1 @@
1
- {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../../../src/sveltekit/sdk-setup/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Sentry from '@sentry/node';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport chalk from 'chalk';\n\n//@ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, parseModule } from 'magicast';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { addVitePlugin } from 'magicast/helpers';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nimport { hasSentryContent } from '../../utils/ast-utils';\nimport { debug } from '../../utils/debug';\nimport { abortIfCancelled } from '../../utils/clack';\nimport type { ProjectInfo } from './types';\nimport { modifyAndRecordFail } from './utils';\n\nexport async function modifyViteConfig(\n viteConfigPath: string,\n projectInfo: ProjectInfo,\n): Promise<void> {\n const viteConfigContent = (\n await fs.promises.readFile(viteConfigPath, 'utf-8')\n ).toString();\n\n const { org, project, url, selfHosted } = projectInfo;\n\n const prettyViteConfigFilename = chalk.cyan(path.basename(viteConfigPath));\n\n try {\n const viteModule = parseModule(viteConfigContent);\n\n if (hasSentryContent(viteModule.$ast as t.Program)) {\n clack.log.warn(\n `File ${prettyViteConfigFilename} already contains Sentry code.\nSkipping adding Sentry functionality to.`,\n );\n Sentry.setTag(`modified-vite-cfg`, 'fail');\n Sentry.setTag(`vite-cfg-fail-reason`, 'has-sentry-content');\n return;\n }\n\n await modifyAndRecordFail(\n () =>\n addVitePlugin(viteModule, {\n imported: 'sentrySvelteKit',\n from: '@sentry/sveltekit',\n constructor: 'sentrySvelteKit',\n options: {\n org,\n project,\n ...(selfHosted && { url }),\n },\n index: 0,\n }),\n 'add-vite-plugin',\n 'vite-cfg',\n );\n\n await modifyAndRecordFail(\n async () => {\n const code = generateCode(viteModule.$ast).code;\n await fs.promises.writeFile(viteConfigPath, code);\n },\n 'write-file',\n 'vite-cfg',\n );\n } catch (e) {\n debug(e);\n await showFallbackViteCopyPasteSnippet(\n viteConfigPath,\n getViteConfigCodeSnippet(org, project, selfHosted, url),\n );\n Sentry.captureException('Sveltekit Vite Config Modification Fail');\n }\n\n clack.log.success(`Added Sentry code to ${prettyViteConfigFilename}`);\n Sentry.setTag(`modified-vite-cfg`, 'success');\n}\n\nasync function showFallbackViteCopyPasteSnippet(\n viteConfigPath: string,\n codeSnippet: string,\n) {\n const viteConfigFilename = path.basename(viteConfigPath);\n\n clack.log.warning(\n `Couldn't automatically modify your ${chalk.cyan(viteConfigFilename)}\n${chalk.dim(`This sometimes happens when we encounter more complex vite configs.\nIt may not seem like it but sometimes our magical powers are limited ;)`)}`,\n );\n\n clack.log.info(\"But don't worry - it's super easy to do this yourself!\");\n\n clack.log.step(\n `Add the following code to your ${chalk.cyan(viteConfigFilename)}:`,\n );\n\n // Intentionally logging to console here for easier copy/pasting\n // eslint-disable-next-line no-console\n console.log(codeSnippet);\n\n await abortIfCancelled(\n clack.select({\n message: 'Did you copy the snippet above?',\n options: [\n { label: 'Yes!', value: true, hint: \"Great, that's already it!\" },\n ],\n initialValue: true,\n }),\n );\n}\n\nconst getViteConfigCodeSnippet = (\n org: string,\n project: string,\n selfHosted: boolean,\n url: string,\n) =>\n chalk.gray(`\nimport { sveltekit } from '@sveltejs/kit/vite';\nimport { defineConfig } from 'vite';\n${chalk.greenBright(\"import { sentrySvelteKit } from '@sentry/sveltekit'\")}\n\nexport default defineConfig({\n plugins: [\n // Make sure \\`sentrySvelteKit\\` is registered before \\`sveltekit\\`\n ${chalk.greenBright(`sentrySvelteKit({\n org: '${org}',\n project: '${project}',${selfHosted ? `\\n url: '${url}',` : ''}\n }),`)}\n sveltekit(),\n ]\n});\n`);\n"]}
1
+ {"version":3,"file":"vite.js","sourceRoot":"","sources":["../../../../src/sveltekit/sdk-setup/vite.ts"],"names":[],"mappings":"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","sourcesContent":["import * as Sentry from '@sentry/node';\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport chalk from 'chalk';\n\n//@ts-expect-error - clack is ESM and TS complains about that. It works though\nimport clack from '@clack/prompts';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { generateCode, parseModule } from 'magicast';\n// @ts-expect-error - magicast is ESM and TS complains about that. It works though\nimport { addVitePlugin } from 'magicast/helpers';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nimport {\n hasSentryContent,\n preserveTrailingNewline,\n} from '../../utils/ast-utils';\nimport { debug } from '../../utils/debug';\nimport { abortIfCancelled } from '../../utils/clack';\nimport type { ProjectInfo } from './types';\nimport { modifyAndRecordFail } from './utils';\n\nexport async function modifyViteConfig(\n viteConfigPath: string,\n projectInfo: ProjectInfo,\n): Promise<void> {\n const viteConfigContent = (\n await fs.promises.readFile(viteConfigPath, 'utf-8')\n ).toString();\n\n const { org, project, url, selfHosted } = projectInfo;\n\n const prettyViteConfigFilename = chalk.cyan(path.basename(viteConfigPath));\n\n try {\n const viteModule = parseModule(viteConfigContent);\n\n if (hasSentryContent(viteModule.$ast as t.Program)) {\n clack.log.warn(\n `File ${prettyViteConfigFilename} already contains Sentry code.\nSkipping adding Sentry functionality to.`,\n );\n Sentry.setTag(`modified-vite-cfg`, 'fail');\n Sentry.setTag(`vite-cfg-fail-reason`, 'has-sentry-content');\n return;\n }\n\n await modifyAndRecordFail(\n () =>\n addVitePlugin(viteModule, {\n imported: 'sentrySvelteKit',\n from: '@sentry/sveltekit',\n constructor: 'sentrySvelteKit',\n options: {\n org,\n project,\n ...(selfHosted && { url }),\n },\n index: 0,\n }),\n 'add-vite-plugin',\n 'vite-cfg',\n );\n\n await modifyAndRecordFail(\n async () => {\n const code = preserveTrailingNewline(\n viteConfigContent,\n generateCode(viteModule.$ast).code,\n );\n await fs.promises.writeFile(viteConfigPath, code);\n },\n 'write-file',\n 'vite-cfg',\n );\n } catch (e) {\n debug(e);\n await showFallbackViteCopyPasteSnippet(\n viteConfigPath,\n getViteConfigCodeSnippet(org, project, selfHosted, url),\n );\n Sentry.captureException('Sveltekit Vite Config Modification Fail');\n }\n\n clack.log.success(`Added Sentry code to ${prettyViteConfigFilename}`);\n Sentry.setTag(`modified-vite-cfg`, 'success');\n}\n\nasync function showFallbackViteCopyPasteSnippet(\n viteConfigPath: string,\n codeSnippet: string,\n) {\n const viteConfigFilename = path.basename(viteConfigPath);\n\n clack.log.warning(\n `Couldn't automatically modify your ${chalk.cyan(viteConfigFilename)}\n${chalk.dim(`This sometimes happens when we encounter more complex vite configs.\nIt may not seem like it but sometimes our magical powers are limited ;)`)}`,\n );\n\n clack.log.info(\"But don't worry - it's super easy to do this yourself!\");\n\n clack.log.step(\n `Add the following code to your ${chalk.cyan(viteConfigFilename)}:`,\n );\n\n // Intentionally logging to console here for easier copy/pasting\n // eslint-disable-next-line no-console\n console.log(codeSnippet);\n\n await abortIfCancelled(\n clack.select({\n message: 'Did you copy the snippet above?',\n options: [\n { label: 'Yes!', value: true, hint: \"Great, that's already it!\" },\n ],\n initialValue: true,\n }),\n );\n}\n\nconst getViteConfigCodeSnippet = (\n org: string,\n project: string,\n selfHosted: boolean,\n url: string,\n) =>\n chalk.gray(`\nimport { sveltekit } from '@sveltejs/kit/vite';\nimport { defineConfig } from 'vite';\n${chalk.greenBright(\"import { sentrySvelteKit } from '@sentry/sveltekit'\")}\n\nexport default defineConfig({\n plugins: [\n // Make sure \\`sentrySvelteKit\\` is registered before \\`sveltekit\\`\n ${chalk.greenBright(`sentrySvelteKit({\n org: '${org}',\n project: '${project}',${selfHosted ? `\\n url: '${url}',` : ''}\n }),`)}\n sveltekit(),\n ]\n});\n`);\n"]}
@@ -116,4 +116,14 @@ export declare function safeInsertBeforeReturn(body: t.Statement[], statement: t
116
116
  * @returns The matching ObjectProperty, or undefined if not found
117
117
  */
118
118
  export declare function findProperty(configObj: t.ObjectExpression, name: string): t.ObjectProperty | undefined;
119
+ /**
120
+ * Preserves trailing newline from original content if present.
121
+ * Code generators like magicast/recast don't preserve trailing newlines,
122
+ * so this ensures we don't unnecessarily modify user files.
123
+ *
124
+ * @param originalContent - The original file content
125
+ * @param generatedCode - The code generated by AST transformation
126
+ * @returns The generated code with trailing newline preserved if original had one
127
+ */
128
+ export declare function preserveTrailingNewline(originalContent: string, generatedCode: string): string;
119
129
  export {};
@@ -23,7 +23,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
23
23
  return result;
24
24
  };
25
25
  Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.findProperty = exports.safeInsertBeforeReturn = exports.safeGetFunctionBody = exports.safeGetIdentifierName = exports.safeCalleeIdentifierMatch = exports.getLastRequireIndex = exports.printJsonC = exports.parseJsonC = exports.setOrUpdateObjectProperty = exports.getOrSetObjectProperty = exports.getObjectProperty = exports.hasSentryContent = exports.findFile = void 0;
26
+ exports.preserveTrailingNewline = exports.findProperty = exports.safeInsertBeforeReturn = exports.safeGetFunctionBody = exports.safeGetIdentifierName = exports.safeCalleeIdentifierMatch = exports.getLastRequireIndex = exports.printJsonC = exports.parseJsonC = exports.setOrUpdateObjectProperty = exports.getOrSetObjectProperty = exports.getObjectProperty = exports.hasSentryContent = exports.findFile = void 0;
27
27
  const fs = __importStar(require("fs"));
28
28
  const recast = __importStar(require("recast"));
29
29
  const b = recast.types.builders;
@@ -285,4 +285,22 @@ function findProperty(configObj, name) {
285
285
  p.key.name === name); // Safe: predicate guarantees type
286
286
  }
287
287
  exports.findProperty = findProperty;
288
+ /**
289
+ * Preserves trailing newline from original content if present.
290
+ * Code generators like magicast/recast don't preserve trailing newlines,
291
+ * so this ensures we don't unnecessarily modify user files.
292
+ *
293
+ * @param originalContent - The original file content
294
+ * @param generatedCode - The code generated by AST transformation
295
+ * @returns The generated code with trailing newline preserved if original had one
296
+ */
297
+ function preserveTrailingNewline(originalContent, generatedCode) {
298
+ const hadTrailingNewline = originalContent.endsWith('\n');
299
+ const hasTrailingNewline = generatedCode.endsWith('\n');
300
+ if (hadTrailingNewline && !hasTrailingNewline) {
301
+ return generatedCode + '\n';
302
+ }
303
+ return generatedCode;
304
+ }
305
+ exports.preserveTrailingNewline = preserveTrailingNewline;
288
306
  //# sourceMappingURL=ast-utils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../../../src/utils/ast-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nconst b = recast.types.builders;\n\n/**\n * Checks if a file where we don't know its concrete file type yet exists\n * and returns the full path to the file with the correct file type.\n */\nexport function findFile(\n filePath: string,\n fileTypes: string[] = ['.js', '.ts', '.mjs', '.cjs'],\n): string | undefined {\n return fileTypes\n .map((type) => `${filePath}${type}`)\n .find((file) => fs.existsSync(file));\n}\n\n/**\n * checks for require('@sentry/*') syntax\n */\nexport function hasSentryContent(program: t.Program): boolean {\n let foundSentry: boolean | undefined = false;\n recast.visit(program, {\n visitStringLiteral(path) {\n foundSentry = foundSentry || path.node.value.startsWith('@sentry/');\n this.traverse(path);\n },\n visitLiteral(path) {\n foundSentry =\n foundSentry || path.node.value?.toString().startsWith('@sentry/');\n this.traverse(path);\n },\n });\n\n return !!foundSentry;\n}\n\n/**\n * Searches for a property of an ObjectExpression by name\n *\n * @param object the ObjectExpression to search in\n * @param name the name of the property to search for\n *\n * @returns the property if it exists\n */\nexport function getObjectProperty(\n object: t.ObjectExpression,\n name: string,\n): t.Property | undefined {\n return object.properties.find((p): p is t.Property => {\n const isObjectProp = p.type === 'Property' || p.type === 'ObjectProperty';\n\n if (!isObjectProp) {\n return false;\n }\n\n const hasMatchingLiteralKey =\n isObjectProp &&\n (p.key.type === 'Literal' || p.key.type === 'StringLiteral') &&\n p.key.value === name;\n\n if (hasMatchingLiteralKey) {\n return true;\n }\n\n // has matching identifier key\n return isObjectProp && p.key.type === 'Identifier' && p.key.name === name;\n });\n}\n\n/**\n * Attempts to find a property of an ObjectExpression by name. If it doesn't exist,\n * the property will be added to the ObjectExpression with the provided default value.\n *\n * @param object the parent object expression to search in\n * @param name the name of the property to search for\n * @param defaultValue the default value to set if the property doesn't exist\n *\n * @returns the\n */\nexport function getOrSetObjectProperty(\n object: t.ObjectExpression,\n name: string,\n defaultValue:\n | t.Literal\n | t.BooleanLiteral\n | t.StringLiteral\n | t.ObjectExpression,\n): t.Property {\n const existingProperty = getObjectProperty(object, name);\n\n if (existingProperty) {\n return existingProperty;\n }\n\n const newProperty = b.property.from({\n kind: 'init',\n key: b.stringLiteral(name),\n value: defaultValue,\n });\n\n object.properties.push(newProperty);\n\n return newProperty;\n}\n\n/**\n * Sets a property of an ObjectExpression if it exists, otherwise adds it\n * to the ObjectExpression. Optionally, a comment can be added to the\n * property.\n *\n * @param object the ObjectExpression to set the property on\n * @param name the name of the property to set\n * @param value the value of the property to set\n * @param comment (optional) a comment to add to the property\n */\nexport function setOrUpdateObjectProperty(\n object: t.ObjectExpression,\n name: string,\n value:\n | t.Literal\n | t.BooleanLiteral\n | t.StringLiteral\n | t.ObjectExpression\n | t.ArrayExpression,\n comment?: string,\n): void {\n const newComments =\n comment &&\n comment.split('\\n').map((c) => b.commentLine(` ${c}`, true, false));\n\n const existingProperty = getObjectProperty(object, name);\n\n if (existingProperty) {\n existingProperty.value = value;\n if (newComments) {\n existingProperty.comments = [\n ...(existingProperty?.comments || []),\n ...newComments,\n ];\n }\n } else {\n object.properties.push(\n b.objectProperty.from({\n key: b.stringLiteral(name),\n value,\n ...(newComments && {\n comments: newComments,\n }),\n }),\n );\n }\n}\n\ntype JsonCParseResult =\n | {\n jsonObject: t.ObjectExpression;\n ast: t.Program;\n }\n | {\n jsonObject: undefined;\n ast: undefined;\n };\n\n/**\n * Parses a JSON string with (potential) comments (JSON-C) and returns the JS AST\n * that can be walked and modified with recast like a normal JS AST.\n *\n * This is done by wrapping the JSON-C string in parentheses, thereby making it\n * a JS `Program` with an `ExpressionStatement` as its body. The expression is then\n * extracted from the AST and returned alongside the AST.\n *\n * To preserve as much original formatting as possible, the returned `ast`\n * property should be passed to {@link `printJsonC`} to get the JSON-C string back.\n *\n * If the input is not valid JSON-C, the result will be undefined.\n *\n * @see {@link JsonCParseResult}\n *\n * @param jsonString a JSON-C string\n *\n * @returns a {@link JsonCParseResult}, containing either the JSON-C object and the AST or undefined in both cases\n */\nexport function parseJsonC(jsonString: string): JsonCParseResult {\n try {\n const jsTsConfig = `(${jsonString})`;\n // no idea why recast returns any here, this is dumb :/\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const ast = recast.parse(jsTsConfig.toString()).program as t.Program;\n\n const jsonObject =\n (ast.body[0].type === 'ExpressionStatement' &&\n ast.body[0].expression.type === 'ObjectExpression' &&\n ast.body[0].expression) ||\n undefined;\n\n if (jsonObject) {\n return { jsonObject, ast };\n }\n } catch {\n /* empty */\n }\n return { jsonObject: undefined, ast: undefined };\n}\n\n/**\n * Takes the AST of a parsed JSON-C \"program\" and returns the JSON-C string without\n * any of the temporary JS wrapper code that was previously applied.\n *\n * Only use this in conjunction with {@link `parseJsonC`}\n *\n * @param ast the `ast` returned from {@link `parseJsonC`}\n *\n * @returns the JSON-C string\n */\nexport function printJsonC(ast: t.Program): string {\n const js = recast.print(ast).code;\n return js.substring(1, js.length - 1);\n}\n\n/**\n * Walks the program body and returns index of the last variable assignment initialized by require statement.\n * Only counts top level require statements.\n *\n * @returns index of the last `const foo = require('bar');` statement or -1 if none was found.\n */\nexport function getLastRequireIndex(program: t.Program): number {\n let lastRequireIdex = -1;\n program.body.forEach((s, i) => {\n if (\n s.type === 'VariableDeclaration' &&\n s.declarations[0].type === 'VariableDeclarator' &&\n s.declarations[0].init !== null &&\n typeof s.declarations[0].init !== 'undefined' &&\n s.declarations[0].init.type === 'CallExpression' &&\n s.declarations[0].init.callee.type === 'Identifier' &&\n s.declarations[0].init.callee.name === 'require'\n ) {\n lastRequireIdex = i;\n }\n });\n return lastRequireIdex;\n}\n\n/**\n * Safely checks if a callee is an identifier with the given name\n * Prevents crashes when accessing callee.name on non-identifier nodes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeCalleeIdentifierMatch(callee: any, name: string): boolean {\n return Boolean(\n callee &&\n typeof callee === 'object' &&\n 'type' in callee &&\n (callee as { type: string }).type === 'Identifier' &&\n 'name' in callee &&\n (callee as { name: string }).name === name,\n );\n}\n\n/**\n * Safely gets the name of an identifier node\n * Returns null if the node is not an identifier or is undefined\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeGetIdentifierName(node: any): string | null {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return node && node.type === 'Identifier' ? String(node.name) : null;\n}\n\n/**\n * Safely access function body array with proper validation\n * Prevents crashes when accessing body.body on nodes that don't have a body\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeGetFunctionBody(node: any): t.Statement[] | null {\n if (!node || typeof node !== 'object' || !('body' in node)) {\n return null;\n }\n\n const nodeBody = (node as { body: unknown }).body;\n if (!nodeBody || typeof nodeBody !== 'object' || !('body' in nodeBody)) {\n return null;\n }\n\n const bodyArray = (nodeBody as { body: unknown }).body;\n return Array.isArray(bodyArray) ? (bodyArray as t.Statement[]) : null;\n}\n\n/**\n * Safely insert statement before last statement in function body\n * Typically used to insert code before a return statement\n * Returns true if insertion was successful, false otherwise\n */\nexport function safeInsertBeforeReturn(\n body: t.Statement[],\n statement: t.Statement,\n): boolean {\n if (!body || !Array.isArray(body) || body.length === 0) {\n return false;\n }\n\n // Insert before the last statement (typically a return statement)\n const insertIndex = Math.max(0, body.length - 1);\n body.splice(insertIndex, 0, statement);\n return true;\n}\n\n/**\n * Finds a property in an ObjectExpression by name.\n * Commonly used for traversing Vite/React Router config objects.\n *\n * @param configObj - The ObjectExpression to search\n * @param name - The property name to find\n * @returns The matching ObjectProperty, or undefined if not found\n */\nexport function findProperty(\n configObj: t.ObjectExpression,\n name: string,\n): t.ObjectProperty | undefined {\n return configObj.properties.find(\n (p) =>\n p.type === 'ObjectProperty' &&\n p.key.type === 'Identifier' &&\n p.key.name === name,\n ) as t.ObjectProperty | undefined; // Safe: predicate guarantees type\n}\n"]}
1
+ {"version":3,"file":"ast-utils.js","sourceRoot":"","sources":["../../../src/utils/ast-utils.ts"],"names":[],"mappings":"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","sourcesContent":["import * as fs from 'fs';\n\nimport * as recast from 'recast';\nimport x = recast.types;\nimport t = x.namedTypes;\n\nconst b = recast.types.builders;\n\n/**\n * Checks if a file where we don't know its concrete file type yet exists\n * and returns the full path to the file with the correct file type.\n */\nexport function findFile(\n filePath: string,\n fileTypes: string[] = ['.js', '.ts', '.mjs', '.cjs'],\n): string | undefined {\n return fileTypes\n .map((type) => `${filePath}${type}`)\n .find((file) => fs.existsSync(file));\n}\n\n/**\n * checks for require('@sentry/*') syntax\n */\nexport function hasSentryContent(program: t.Program): boolean {\n let foundSentry: boolean | undefined = false;\n recast.visit(program, {\n visitStringLiteral(path) {\n foundSentry = foundSentry || path.node.value.startsWith('@sentry/');\n this.traverse(path);\n },\n visitLiteral(path) {\n foundSentry =\n foundSentry || path.node.value?.toString().startsWith('@sentry/');\n this.traverse(path);\n },\n });\n\n return !!foundSentry;\n}\n\n/**\n * Searches for a property of an ObjectExpression by name\n *\n * @param object the ObjectExpression to search in\n * @param name the name of the property to search for\n *\n * @returns the property if it exists\n */\nexport function getObjectProperty(\n object: t.ObjectExpression,\n name: string,\n): t.Property | undefined {\n return object.properties.find((p): p is t.Property => {\n const isObjectProp = p.type === 'Property' || p.type === 'ObjectProperty';\n\n if (!isObjectProp) {\n return false;\n }\n\n const hasMatchingLiteralKey =\n isObjectProp &&\n (p.key.type === 'Literal' || p.key.type === 'StringLiteral') &&\n p.key.value === name;\n\n if (hasMatchingLiteralKey) {\n return true;\n }\n\n // has matching identifier key\n return isObjectProp && p.key.type === 'Identifier' && p.key.name === name;\n });\n}\n\n/**\n * Attempts to find a property of an ObjectExpression by name. If it doesn't exist,\n * the property will be added to the ObjectExpression with the provided default value.\n *\n * @param object the parent object expression to search in\n * @param name the name of the property to search for\n * @param defaultValue the default value to set if the property doesn't exist\n *\n * @returns the\n */\nexport function getOrSetObjectProperty(\n object: t.ObjectExpression,\n name: string,\n defaultValue:\n | t.Literal\n | t.BooleanLiteral\n | t.StringLiteral\n | t.ObjectExpression,\n): t.Property {\n const existingProperty = getObjectProperty(object, name);\n\n if (existingProperty) {\n return existingProperty;\n }\n\n const newProperty = b.property.from({\n kind: 'init',\n key: b.stringLiteral(name),\n value: defaultValue,\n });\n\n object.properties.push(newProperty);\n\n return newProperty;\n}\n\n/**\n * Sets a property of an ObjectExpression if it exists, otherwise adds it\n * to the ObjectExpression. Optionally, a comment can be added to the\n * property.\n *\n * @param object the ObjectExpression to set the property on\n * @param name the name of the property to set\n * @param value the value of the property to set\n * @param comment (optional) a comment to add to the property\n */\nexport function setOrUpdateObjectProperty(\n object: t.ObjectExpression,\n name: string,\n value:\n | t.Literal\n | t.BooleanLiteral\n | t.StringLiteral\n | t.ObjectExpression\n | t.ArrayExpression,\n comment?: string,\n): void {\n const newComments =\n comment &&\n comment.split('\\n').map((c) => b.commentLine(` ${c}`, true, false));\n\n const existingProperty = getObjectProperty(object, name);\n\n if (existingProperty) {\n existingProperty.value = value;\n if (newComments) {\n existingProperty.comments = [\n ...(existingProperty?.comments || []),\n ...newComments,\n ];\n }\n } else {\n object.properties.push(\n b.objectProperty.from({\n key: b.stringLiteral(name),\n value,\n ...(newComments && {\n comments: newComments,\n }),\n }),\n );\n }\n}\n\ntype JsonCParseResult =\n | {\n jsonObject: t.ObjectExpression;\n ast: t.Program;\n }\n | {\n jsonObject: undefined;\n ast: undefined;\n };\n\n/**\n * Parses a JSON string with (potential) comments (JSON-C) and returns the JS AST\n * that can be walked and modified with recast like a normal JS AST.\n *\n * This is done by wrapping the JSON-C string in parentheses, thereby making it\n * a JS `Program` with an `ExpressionStatement` as its body. The expression is then\n * extracted from the AST and returned alongside the AST.\n *\n * To preserve as much original formatting as possible, the returned `ast`\n * property should be passed to {@link `printJsonC`} to get the JSON-C string back.\n *\n * If the input is not valid JSON-C, the result will be undefined.\n *\n * @see {@link JsonCParseResult}\n *\n * @param jsonString a JSON-C string\n *\n * @returns a {@link JsonCParseResult}, containing either the JSON-C object and the AST or undefined in both cases\n */\nexport function parseJsonC(jsonString: string): JsonCParseResult {\n try {\n const jsTsConfig = `(${jsonString})`;\n // no idea why recast returns any here, this is dumb :/\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n const ast = recast.parse(jsTsConfig.toString()).program as t.Program;\n\n const jsonObject =\n (ast.body[0].type === 'ExpressionStatement' &&\n ast.body[0].expression.type === 'ObjectExpression' &&\n ast.body[0].expression) ||\n undefined;\n\n if (jsonObject) {\n return { jsonObject, ast };\n }\n } catch {\n /* empty */\n }\n return { jsonObject: undefined, ast: undefined };\n}\n\n/**\n * Takes the AST of a parsed JSON-C \"program\" and returns the JSON-C string without\n * any of the temporary JS wrapper code that was previously applied.\n *\n * Only use this in conjunction with {@link `parseJsonC`}\n *\n * @param ast the `ast` returned from {@link `parseJsonC`}\n *\n * @returns the JSON-C string\n */\nexport function printJsonC(ast: t.Program): string {\n const js = recast.print(ast).code;\n return js.substring(1, js.length - 1);\n}\n\n/**\n * Walks the program body and returns index of the last variable assignment initialized by require statement.\n * Only counts top level require statements.\n *\n * @returns index of the last `const foo = require('bar');` statement or -1 if none was found.\n */\nexport function getLastRequireIndex(program: t.Program): number {\n let lastRequireIdex = -1;\n program.body.forEach((s, i) => {\n if (\n s.type === 'VariableDeclaration' &&\n s.declarations[0].type === 'VariableDeclarator' &&\n s.declarations[0].init !== null &&\n typeof s.declarations[0].init !== 'undefined' &&\n s.declarations[0].init.type === 'CallExpression' &&\n s.declarations[0].init.callee.type === 'Identifier' &&\n s.declarations[0].init.callee.name === 'require'\n ) {\n lastRequireIdex = i;\n }\n });\n return lastRequireIdex;\n}\n\n/**\n * Safely checks if a callee is an identifier with the given name\n * Prevents crashes when accessing callee.name on non-identifier nodes\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeCalleeIdentifierMatch(callee: any, name: string): boolean {\n return Boolean(\n callee &&\n typeof callee === 'object' &&\n 'type' in callee &&\n (callee as { type: string }).type === 'Identifier' &&\n 'name' in callee &&\n (callee as { name: string }).name === name,\n );\n}\n\n/**\n * Safely gets the name of an identifier node\n * Returns null if the node is not an identifier or is undefined\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeGetIdentifierName(node: any): string | null {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access\n return node && node.type === 'Identifier' ? String(node.name) : null;\n}\n\n/**\n * Safely access function body array with proper validation\n * Prevents crashes when accessing body.body on nodes that don't have a body\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function safeGetFunctionBody(node: any): t.Statement[] | null {\n if (!node || typeof node !== 'object' || !('body' in node)) {\n return null;\n }\n\n const nodeBody = (node as { body: unknown }).body;\n if (!nodeBody || typeof nodeBody !== 'object' || !('body' in nodeBody)) {\n return null;\n }\n\n const bodyArray = (nodeBody as { body: unknown }).body;\n return Array.isArray(bodyArray) ? (bodyArray as t.Statement[]) : null;\n}\n\n/**\n * Safely insert statement before last statement in function body\n * Typically used to insert code before a return statement\n * Returns true if insertion was successful, false otherwise\n */\nexport function safeInsertBeforeReturn(\n body: t.Statement[],\n statement: t.Statement,\n): boolean {\n if (!body || !Array.isArray(body) || body.length === 0) {\n return false;\n }\n\n // Insert before the last statement (typically a return statement)\n const insertIndex = Math.max(0, body.length - 1);\n body.splice(insertIndex, 0, statement);\n return true;\n}\n\n/**\n * Finds a property in an ObjectExpression by name.\n * Commonly used for traversing Vite/React Router config objects.\n *\n * @param configObj - The ObjectExpression to search\n * @param name - The property name to find\n * @returns The matching ObjectProperty, or undefined if not found\n */\nexport function findProperty(\n configObj: t.ObjectExpression,\n name: string,\n): t.ObjectProperty | undefined {\n return configObj.properties.find(\n (p) =>\n p.type === 'ObjectProperty' &&\n p.key.type === 'Identifier' &&\n p.key.name === name,\n ) as t.ObjectProperty | undefined; // Safe: predicate guarantees type\n}\n\n/**\n * Preserves trailing newline from original content if present.\n * Code generators like magicast/recast don't preserve trailing newlines,\n * so this ensures we don't unnecessarily modify user files.\n *\n * @param originalContent - The original file content\n * @param generatedCode - The code generated by AST transformation\n * @returns The generated code with trailing newline preserved if original had one\n */\nexport function preserveTrailingNewline(\n originalContent: string,\n generatedCode: string,\n): string {\n const hadTrailingNewline = originalContent.endsWith('\\n');\n const hasTrailingNewline = generatedCode.endsWith('\\n');\n\n if (hadTrailingNewline && !hasTrailingNewline) {\n return generatedCode + '\\n';\n }\n\n return generatedCode;\n}\n"]}
@@ -1 +1 @@
1
- export declare const WIZARD_VERSION = "6.11.0";
1
+ export declare const WIZARD_VERSION = "6.12.0";
@@ -3,5 +3,5 @@
3
3
  // This is file is updated at release time.
4
4
  Object.defineProperty(exports, "__esModule", { value: true });
5
5
  exports.WIZARD_VERSION = void 0;
6
- exports.WIZARD_VERSION = '6.11.0';
6
+ exports.WIZARD_VERSION = '6.12.0';
7
7
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";AAAA,oCAAoC;AACpC,2CAA2C;;;AAE9B,QAAA,cAAc,GAAG,QAAQ,CAAC","sourcesContent":["// DO NOT modify this file manually!\n// This is file is updated at release time.\n\nexport const WIZARD_VERSION = '6.11.0';\n"]}
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/version.ts"],"names":[],"mappings":";AAAA,oCAAoC;AACpC,2CAA2C;;;AAE9B,QAAA,cAAc,GAAG,QAAQ,CAAC","sourcesContent":["// DO NOT modify this file manually!\n// This is file is updated at release time.\n\nexport const WIZARD_VERSION = '6.12.0';\n"]}
@@ -203,6 +203,63 @@ struct TestApp: App {
203
203
  }
204
204
  }
205
205
  }`;
206
+ const validAppDelegateSwiftUIWithExistingInit = `
207
+ import SwiftUI
208
+
209
+ @main
210
+ struct TestApp: App {
211
+ init() {
212
+ // Some existing initialization
213
+ print("App initialized")
214
+ }
215
+ var body: some Scene {
216
+ WindowGroup {
217
+ ContentView()
218
+ }
219
+ }
220
+ }`;
221
+ // Expected output after wizard injects Sentry into existing init()
222
+ const validAppDelegateSwiftUIWithExistingInitAndSentry = `
223
+ import SwiftUI
224
+ import Sentry
225
+
226
+
227
+ @main
228
+ struct TestApp: App {
229
+ init() {
230
+ SentrySDK.start { options in
231
+ options.dsn = "https://example.com/sentry-dsn"
232
+
233
+ // Adds IP for users.
234
+ // For more information, visit: https://docs.sentry.io/platforms/apple/data-management/data-collected/
235
+ options.sendDefaultPii = true
236
+
237
+ // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring.
238
+ // We recommend adjusting this value in production.
239
+ options.tracesSampleRate = 1.0
240
+
241
+ // Configure profiling. Visit https://docs.sentry.io/platforms/apple/profiling/ to learn more.
242
+ options.configureProfiling = {
243
+ $0.sessionSampleRate = 1.0 // We recommend adjusting this value in production.
244
+ $0.lifecycle = .trace
245
+ }
246
+
247
+ // Uncomment the following lines to add more data to your events
248
+ // options.attachScreenshot = true // This adds a screenshot to the error events
249
+ // options.attachViewHierarchy = true // This adds the view hierarchy to the error events
250
+ }
251
+ // Remove the next line after confirming that your Sentry integration is working.
252
+ SentrySDK.capture(message: "This app uses Sentry! :)")
253
+
254
+ // Some existing initialization
255
+ print("App initialized")
256
+ }
257
+ var body: some Scene {
258
+ WindowGroup {
259
+ ContentView()
260
+ }
261
+ }
262
+ }`;
206
263
  const prepareTempDir = () => {
207
264
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'code-tools-test'));
208
265
  return tempDir;
@@ -747,6 +804,27 @@ vitest_1.vi.mock('../../src/utils/bash');
747
804
  (0, vitest_1.expect)(modifiedFileContent).toBe(validAppDelegateSwiftUIWithSentry);
748
805
  });
749
806
  });
807
+ (0, vitest_1.describe)('has existing init() but no Sentry', () => {
808
+ (0, vitest_1.it)('should inject into existing init() instead of creating duplicate', () => {
809
+ // -- Arrange --
810
+ const tempDir = prepareTempDir();
811
+ const filePath = prepareAppDelegateFile(tempDir, validAppDelegateSwiftUIWithExistingInit, 'swift');
812
+ // -- Act --
813
+ const result = (0, code_tools_1.addCodeSnippetToProject)([filePath], dsn, false);
814
+ // -- Assert --
815
+ (0, vitest_1.expect)(result).toBeTruthy();
816
+ const modifiedFileContent = fs.readFileSync(filePath, 'utf8');
817
+ // Should NOT have duplicate init()
818
+ const initCount = (modifiedFileContent.match(/init\s*\(\s*\)\s*\{/g) || []).length;
819
+ (0, vitest_1.expect)(initCount).toBe(1);
820
+ // Should have Sentry initialized
821
+ (0, vitest_1.expect)(modifiedFileContent).toContain('SentrySDK.start');
822
+ // Should preserve existing init() content
823
+ (0, vitest_1.expect)(modifiedFileContent).toContain('print("App initialized")');
824
+ // Full content check
825
+ (0, vitest_1.expect)(modifiedFileContent).toBe(validAppDelegateSwiftUIWithExistingInitAndSentry);
826
+ });
827
+ });
750
828
  });
751
829
  (0, vitest_1.describe)('is not matching SwiftUI regex', () => {
752
830
  (0, vitest_1.it)('should not add the code snippet', () => {