@sentry/wizard 3.1.0-beta.0 → 3.2.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 (108) hide show
  1. package/CHANGELOG.md +17 -2
  2. package/bin.ts +5 -1
  3. package/dist/bin.js +6 -1
  4. package/dist/bin.js.map +1 -1
  5. package/dist/lib/Constants.d.ts +2 -1
  6. package/dist/lib/Constants.js +5 -0
  7. package/dist/lib/Constants.js.map +1 -1
  8. package/dist/lib/Helper/File.js +25 -2
  9. package/dist/lib/Helper/File.js.map +1 -1
  10. package/dist/lib/Helper/Git.d.ts +7 -0
  11. package/dist/lib/Helper/Git.js +94 -0
  12. package/dist/lib/Helper/Git.js.map +1 -0
  13. package/dist/lib/Helper/Logging.d.ts +1 -0
  14. package/dist/lib/Helper/Logging.js +9 -2
  15. package/dist/lib/Helper/Logging.js.map +1 -1
  16. package/dist/lib/Helper/MergeConfig.js +24 -1
  17. package/dist/lib/Helper/MergeConfig.js.map +1 -1
  18. package/dist/lib/Helper/Package.d.ts +9 -0
  19. package/dist/lib/Helper/Package.js +39 -2
  20. package/dist/lib/Helper/Package.js.map +1 -1
  21. package/dist/lib/Helper/PackageManager.d.ts +1 -1
  22. package/dist/lib/Helper/PackageManager.js +32 -11
  23. package/dist/lib/Helper/PackageManager.js.map +1 -1
  24. package/dist/lib/Helper/SentryCli.d.ts +11 -0
  25. package/dist/lib/Helper/SentryCli.js +141 -2
  26. package/dist/lib/Helper/SentryCli.js.map +1 -1
  27. package/dist/lib/Helper/Wizard.js +24 -1
  28. package/dist/lib/Helper/Wizard.js.map +1 -1
  29. package/dist/lib/Helper/__tests__/MergeConfig.js +25 -2
  30. package/dist/lib/Helper/__tests__/MergeConfig.js.map +1 -1
  31. package/dist/lib/Setup.js +25 -2
  32. package/dist/lib/Setup.js.map +1 -1
  33. package/dist/lib/Steps/ChooseIntegration.js +28 -1
  34. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  35. package/dist/lib/Steps/Initial.js +25 -2
  36. package/dist/lib/Steps/Initial.js.map +1 -1
  37. package/dist/lib/Steps/Integrations/BaseIntegration.js +24 -1
  38. package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
  39. package/dist/lib/Steps/Integrations/Cordova.js +25 -2
  40. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  41. package/dist/lib/Steps/Integrations/Electron.js +26 -3
  42. package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
  43. package/dist/lib/Steps/Integrations/MobileProject.js +24 -1
  44. package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
  45. package/dist/lib/Steps/Integrations/NextJs.d.ts +5 -11
  46. package/dist/lib/Steps/Integrations/NextJs.js +14 -343
  47. package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
  48. package/dist/lib/Steps/Integrations/ReactNative.d.ts +1 -0
  49. package/dist/lib/Steps/Integrations/ReactNative.js +67 -6
  50. package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
  51. package/dist/lib/Steps/Integrations/SvelteKit.d.ts +13 -0
  52. package/dist/lib/Steps/Integrations/SvelteKit.js +95 -0
  53. package/dist/lib/Steps/Integrations/SvelteKit.js.map +1 -0
  54. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +28 -5
  55. package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +1 -1
  56. package/dist/lib/Steps/PromptForParameters.js +24 -1
  57. package/dist/lib/Steps/PromptForParameters.js.map +1 -1
  58. package/dist/lib/Steps/SentryProjectSelector.js +25 -1
  59. package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
  60. package/dist/lib/__tests__/Setup.js +24 -1
  61. package/dist/lib/__tests__/Setup.js.map +1 -1
  62. package/dist/src/nextjs/nextjs-wizard.js +326 -0
  63. package/dist/src/nextjs/nextjs-wizard.js.map +1 -0
  64. package/dist/src/sveltekit/sdk-example.d.ts +10 -0
  65. package/dist/src/sveltekit/sdk-example.js +106 -0
  66. package/dist/src/sveltekit/sdk-example.js.map +1 -0
  67. package/dist/src/sveltekit/sdk-setup.d.ts +13 -0
  68. package/dist/src/sveltekit/sdk-setup.js +451 -0
  69. package/dist/src/sveltekit/sdk-setup.js.map +1 -0
  70. package/dist/src/sveltekit/sentry-cli-setup.d.ts +2 -0
  71. package/dist/src/sveltekit/sentry-cli-setup.js +71 -0
  72. package/dist/src/sveltekit/sentry-cli-setup.js.map +1 -0
  73. package/dist/src/sveltekit/sveltekit-wizard.d.ts +5 -0
  74. package/dist/src/sveltekit/sveltekit-wizard.js +147 -0
  75. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -0
  76. package/dist/src/templates/nextjs-templates.d.ts +13 -0
  77. package/dist/src/templates/nextjs-templates.js +54 -0
  78. package/dist/src/templates/nextjs-templates.js.map +1 -0
  79. package/dist/src/templates/sveltekit-templates.d.ts +12 -0
  80. package/dist/src/templates/sveltekit-templates.js +26 -0
  81. package/dist/src/templates/sveltekit-templates.js.map +1 -0
  82. package/dist/src/{clack-utils.d.ts → utils/clack-utils.d.ts} +7 -0
  83. package/dist/src/{clack-utils.js → utils/clack-utils.js} +127 -42
  84. package/dist/src/utils/clack-utils.js.map +1 -0
  85. package/lib/Constants.ts +5 -0
  86. package/lib/Helper/Git.ts +39 -0
  87. package/lib/Helper/Logging.ts +4 -0
  88. package/lib/Helper/Package.ts +17 -0
  89. package/lib/Helper/PackageManager.ts +4 -9
  90. package/lib/Helper/SentryCli.ts +74 -0
  91. package/lib/Steps/ChooseIntegration.ts +4 -0
  92. package/lib/Steps/Integrations/NextJs.ts +7 -397
  93. package/lib/Steps/Integrations/ReactNative.ts +49 -3
  94. package/lib/Steps/Integrations/SvelteKit.ts +29 -0
  95. package/lib/Steps/SentryProjectSelector.ts +1 -0
  96. package/package.json +1 -1
  97. package/src/{nextjs-wizard.ts → nextjs/nextjs-wizard.ts} +45 -273
  98. package/src/sveltekit/sdk-example.ts +56 -0
  99. package/src/sveltekit/sdk-setup.ts +430 -0
  100. package/src/sveltekit/sentry-cli-setup.ts +27 -0
  101. package/src/sveltekit/sveltekit-wizard.ts +116 -0
  102. package/src/templates/nextjs-templates.ts +252 -0
  103. package/src/templates/sveltekit-templates.ts +172 -0
  104. package/src/{clack-utils.ts → utils/clack-utils.ts} +73 -11
  105. package/dist/src/clack-utils.js.map +0 -1
  106. package/dist/src/nextjs-wizard.js +0 -346
  107. package/dist/src/nextjs-wizard.js.map +0 -1
  108. /package/dist/src/{nextjs-wizard.d.ts → nextjs/nextjs-wizard.d.ts} +0 -0
@@ -0,0 +1,430 @@
1
+ import type { ExportNamedDeclaration, Program } from '@babel/types';
2
+ import * as fs from 'fs';
3
+ import * as path from 'path';
4
+ import * as url from 'url';
5
+ import chalk from 'chalk';
6
+
7
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
8
+ import clack from '@clack/prompts';
9
+ // @ts-ignore - magicast is ESM and TS complains about that. It works though
10
+ import type { ProxifiedModule } from 'magicast';
11
+ // @ts-ignore - magicast is ESM and TS complains about that. It works though
12
+ import { builders, generateCode, loadFile, parseModule } from 'magicast';
13
+ // @ts-ignore - magicast is ESM and TS complains about that. It works though
14
+ import { addVitePlugin } from 'magicast/helpers';
15
+ import {
16
+ getClientHooksTemplate,
17
+ getServerHooksTemplate,
18
+ } from '../templates/sveltekit-templates';
19
+
20
+ const SVELTE_CONFIG_FILE = 'svelte.config.js';
21
+
22
+ export type PartialSvelteConfig = {
23
+ kit?: {
24
+ files?: {
25
+ hooks?: {
26
+ client?: string;
27
+ server?: string;
28
+ };
29
+ routes?: string;
30
+ };
31
+ };
32
+ };
33
+
34
+ export async function createOrMergeSvelteKitFiles(
35
+ dsn: string,
36
+ svelteConfig: PartialSvelteConfig,
37
+ ): Promise<void> {
38
+ const { clientHooksPath, serverHooksPath } = getHooksConfigDirs(svelteConfig);
39
+
40
+ // full file paths with correct file ending (or undefined if not found)
41
+ const originalClientHooksFile = findHooksFile(clientHooksPath);
42
+ const originalServerHooksFile = findHooksFile(serverHooksPath);
43
+
44
+ const viteConfig = findHooksFile(path.resolve(process.cwd(), 'vite.config'));
45
+
46
+ if (!originalClientHooksFile) {
47
+ clack.log.info('No client hooks file found, creating a new one.');
48
+ await createNewHooksFile(`${clientHooksPath}.js`, 'client', dsn);
49
+ }
50
+ if (!originalServerHooksFile) {
51
+ clack.log.info('No server hooks file found, creating a new one.');
52
+ await createNewHooksFile(`${serverHooksPath}.js`, 'client', dsn);
53
+ }
54
+
55
+ if (originalClientHooksFile) {
56
+ await mergeHooksFile(originalClientHooksFile, 'client', dsn);
57
+ }
58
+ if (originalServerHooksFile) {
59
+ await mergeHooksFile(originalServerHooksFile, 'server', dsn);
60
+ }
61
+
62
+ if (viteConfig) {
63
+ await modifyViteConfig(viteConfig);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Attempts to read the svelte.config.js file to find the location of the hooks files.
69
+ * If users specified a custom location, we'll use that. Otherwise, we'll use the default.
70
+ */
71
+ function getHooksConfigDirs(svelteConfig: PartialSvelteConfig): {
72
+ clientHooksPath: string;
73
+ serverHooksPath: string;
74
+ } {
75
+ const relativeUserClientHooksPath = svelteConfig?.kit?.files?.hooks?.client;
76
+ const relativeUserServerHooksPath = svelteConfig?.kit?.files?.hooks?.server;
77
+ const userClientHooksPath =
78
+ relativeUserClientHooksPath &&
79
+ path.resolve(process.cwd(), relativeUserClientHooksPath);
80
+ const userServerHooksPath =
81
+ relativeUserServerHooksPath &&
82
+ path.resolve(process.cwd(), relativeUserServerHooksPath);
83
+
84
+ const defaulHooksDir = path.resolve(process.cwd(), 'src');
85
+ const defaultClientHooksPath = path.resolve(defaulHooksDir, 'hooks.client'); // file ending missing on purpose
86
+ const defaultServerHooksPath = path.resolve(defaulHooksDir, 'hooks.server'); // same here
87
+
88
+ return {
89
+ clientHooksPath: userClientHooksPath || defaultClientHooksPath,
90
+ serverHooksPath: userServerHooksPath || defaultServerHooksPath,
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Checks if a hooks file exists and returns the full path to the file with the correct file type.
96
+ */
97
+ function findHooksFile(hooksFile: string): string | undefined {
98
+ const possibleFileTypes = ['.js', '.ts', '.mjs'];
99
+ return possibleFileTypes
100
+ .map((type) => `${hooksFile}${type}`)
101
+ .find((file) => fs.existsSync(file));
102
+ }
103
+
104
+ /**
105
+ * Reads the template, replaces the dsn placeholder with the actual dsn and writes the file to @param hooksFileDest
106
+ */
107
+ async function createNewHooksFile(
108
+ hooksFileDest: string,
109
+ hooktype: 'client' | 'server',
110
+ dsn: string,
111
+ ): Promise<void> {
112
+ const filledTemplate =
113
+ hooktype === 'client'
114
+ ? getClientHooksTemplate(dsn)
115
+ : getServerHooksTemplate(dsn);
116
+
117
+ await fs.promises.mkdir(path.dirname(hooksFileDest), { recursive: true });
118
+ await fs.promises.writeFile(hooksFileDest, filledTemplate);
119
+
120
+ clack.log.success(`Created ${hooksFileDest}`);
121
+ }
122
+
123
+ /**
124
+ * Merges the users' hooks file with Sentry-related code.
125
+ *
126
+ * Both hooks:
127
+ * - add import * as Sentry
128
+ * - add Sentry.init
129
+ * - add handleError hook wrapper
130
+ *
131
+ * Additionally in Server hook:
132
+ * - add handle hook handler
133
+ */
134
+ async function mergeHooksFile(
135
+ hooksFile: string,
136
+ hookType: 'client' | 'server',
137
+ dsn: string,
138
+ ): Promise<void> {
139
+ const originalHooksMod = await loadFile(hooksFile);
140
+ if (hasSentryContent(path.basename(hooksFile), originalHooksMod.$code)) {
141
+ // We don't want to mess with files that already have Sentry content.
142
+ // Let's just bail out at this point.
143
+ return;
144
+ }
145
+
146
+ originalHooksMod.imports.$add({
147
+ from: '@sentry/sveltekit',
148
+ imported: '*',
149
+ local: 'Sentry',
150
+ });
151
+
152
+ if (hookType === 'client') {
153
+ insertClientInitCall(dsn, originalHooksMod);
154
+ } else {
155
+ insertServerInitCall(dsn, originalHooksMod);
156
+ }
157
+
158
+ wrapHandleError(originalHooksMod);
159
+
160
+ if (hookType === 'server') {
161
+ wrapHandle(originalHooksMod);
162
+ }
163
+
164
+ const modifiedCode = originalHooksMod.generate().code;
165
+
166
+ await fs.promises.writeFile(hooksFile, modifiedCode);
167
+
168
+ clack.log.success(`Added Sentry code to ${hooksFile}`);
169
+ }
170
+
171
+ function insertClientInitCall(
172
+ dsn: string,
173
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
174
+ originalHooksMod: ProxifiedModule<any>,
175
+ ): void {
176
+ const initCallComment = `
177
+ // If you don't want to use Session Replay, remove the \`Replay\` integration,
178
+ // \`replaysSessionSampleRate\` and \`replaysOnErrorSampleRate\` options.`;
179
+
180
+ // This assignment of any values is fine because we're just creating a function call in magicast
181
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
182
+ const initCall = builders.functionCall('Sentry.init', {
183
+ dsn,
184
+ tracesSampleRate: 1.0,
185
+ replaysSessionSampleRate: 0.1,
186
+ replaysOnErrorSampleRate: 1.0,
187
+ integrations: [builders.newExpression('Sentry.Replay')],
188
+ });
189
+
190
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
191
+ const initCallWithComment = builders.raw(
192
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
193
+ `${initCallComment}\n${generateCode(initCall).code}`,
194
+ );
195
+
196
+ const originalHooksModAST = originalHooksMod.$ast as Program;
197
+
198
+ const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
199
+
200
+ originalHooksModAST.body.splice(
201
+ initCallInsertionIndex,
202
+ 0,
203
+ // @ts-ignore - string works here because the AST is proxified by magicast
204
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
205
+ generateCode(initCallWithComment).code,
206
+ );
207
+ }
208
+
209
+ function insertServerInitCall(
210
+ dsn: string,
211
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
212
+ originalHooksMod: ProxifiedModule<any>,
213
+ ): void {
214
+ // This assignment of any values is fine because we're just creating a function call in magicast
215
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
216
+ const initCall = builders.functionCall('Sentry.init', {
217
+ dsn,
218
+ tracesSampleRate: 1.0,
219
+ });
220
+
221
+ const originalHooksModAST = originalHooksMod.$ast as Program;
222
+
223
+ const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
224
+
225
+ originalHooksModAST.body.splice(
226
+ initCallInsertionIndex,
227
+ 0,
228
+ // @ts-ignore - string works here because the AST is proxified by magicast
229
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
230
+ generateCode(initCall).code,
231
+ );
232
+ }
233
+
234
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
235
+ function wrapHandleError(mod: ProxifiedModule<any>): void {
236
+ const modAst = mod.exports.$ast as Program;
237
+ const namedExports = modAst.body.filter(
238
+ (node) => node.type === 'ExportNamedDeclaration',
239
+ ) as ExportNamedDeclaration[];
240
+
241
+ let foundHandleError = false;
242
+
243
+ namedExports.forEach((modExport) => {
244
+ const declaration = modExport.declaration;
245
+ if (!declaration) {
246
+ return;
247
+ }
248
+ if (declaration.type === 'FunctionDeclaration') {
249
+ if (!declaration.id || declaration.id.name !== 'handleError') {
250
+ return;
251
+ }
252
+ foundHandleError = true;
253
+ const userCode = generateCode(declaration).code;
254
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
255
+ mod.exports.handleError = builders.raw(
256
+ `Sentry.handleErrorWithSentry(${userCode.replace(
257
+ 'handleError',
258
+ '_handleError',
259
+ )})`,
260
+ );
261
+ // because magicast doesn't overwrite the original function export, we need to remove it manually
262
+ modAst.body = modAst.body.filter((node) => node !== modExport);
263
+ } else if (declaration.type === 'VariableDeclaration') {
264
+ const declarations = declaration.declarations;
265
+ declarations.forEach((declaration) => {
266
+ // @ts-ignore - id should always have a name in this case
267
+ if (!declaration.id || declaration.id.name !== 'handleError') {
268
+ return;
269
+ }
270
+ foundHandleError = true;
271
+ const userCode = declaration.init;
272
+ const stringifiedUserCode = userCode ? generateCode(userCode).code : '';
273
+ // @ts-ignore - we can just place a string here, magicast will convert it to a node
274
+ declaration.init = `Sentry.handleErrorWithSentry(${stringifiedUserCode})`;
275
+ });
276
+ }
277
+ });
278
+
279
+ if (!foundHandleError) {
280
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
281
+ mod.exports.handleError = builders.functionCall(
282
+ 'Sentry.handleErrorWithSentry',
283
+ );
284
+ }
285
+ }
286
+
287
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
288
+ function wrapHandle(mod: ProxifiedModule<any>): void {
289
+ const modAst = mod.exports.$ast as Program;
290
+ const namedExports = modAst.body.filter(
291
+ (node) => node.type === 'ExportNamedDeclaration',
292
+ ) as ExportNamedDeclaration[];
293
+
294
+ let foundHandle = false;
295
+
296
+ namedExports.forEach((modExport) => {
297
+ const declaration = modExport.declaration;
298
+ if (!declaration) {
299
+ return;
300
+ }
301
+ if (declaration.type === 'FunctionDeclaration') {
302
+ if (!declaration.id || declaration.id.name !== 'handle') {
303
+ return;
304
+ }
305
+ foundHandle = true;
306
+ const userCode = generateCode(declaration).code;
307
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
308
+ mod.exports.handle = builders.raw(
309
+ `sequence(Sentry.sentryHandle(), ${userCode.replace(
310
+ 'handle',
311
+ '_handle',
312
+ )})`,
313
+ );
314
+ // because of an issue with magicast, we need to remove the original export
315
+ modAst.body = modAst.body.filter((node) => node !== modExport);
316
+ } else if (declaration.type === 'VariableDeclaration') {
317
+ const declarations = declaration.declarations;
318
+ declarations.forEach((declaration) => {
319
+ // @ts-ignore - id should always have a name in this case
320
+ if (!declaration.id || declaration.id.name !== 'handle') {
321
+ return;
322
+ }
323
+ const userCode = declaration.init;
324
+ const stringifiedUserCode = userCode ? generateCode(userCode).code : '';
325
+ // @ts-ignore - we can just place a string here, magicast will convert it to a node
326
+ declaration.init = `sequence(Sentry.sentryHandle(), ${stringifiedUserCode})`;
327
+ foundHandle = true;
328
+ });
329
+ }
330
+ });
331
+
332
+ if (!foundHandle) {
333
+ // can't use builders.functionCall here because it doesn't yet
334
+ // support member expressions (Sentry.sentryHandle()) in args
335
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
336
+ mod.exports.handle = builders.raw('sequence(Sentry.sentryHandle())');
337
+ }
338
+
339
+ try {
340
+ mod.imports.$add({
341
+ from: '@sveltejs/kit/hooks',
342
+ imported: 'sequence',
343
+ local: 'sequence',
344
+ });
345
+ } catch (_) {
346
+ // It's possible sequence is already imported. in this case, magicast throws but that's fine.
347
+ }
348
+ }
349
+
350
+ /** Checks if the Sentry SvelteKit SDK is already mentioned in the file */
351
+ function hasSentryContent(fileName: string, fileContent: string): boolean {
352
+ if (fileContent.includes('@sentry/sveltekit')) {
353
+ clack.log.warn(
354
+ `File ${chalk.cyan(path.basename(fileName))} already contains Sentry code.
355
+ Skipping adding Sentry functionality to ${chalk.cyan(
356
+ path.basename(fileName),
357
+ )}.`,
358
+ );
359
+ return true;
360
+ }
361
+ return false;
362
+ }
363
+
364
+ export async function loadSvelteConfig(): Promise<PartialSvelteConfig> {
365
+ const configFilePath = path.join(process.cwd(), SVELTE_CONFIG_FILE);
366
+
367
+ try {
368
+ if (!fs.existsSync(configFilePath)) {
369
+ return {};
370
+ }
371
+
372
+ const configUrl = url.pathToFileURL(configFilePath).href;
373
+ const svelteConfigModule = (await import(configUrl)) as {
374
+ default: PartialSvelteConfig;
375
+ };
376
+
377
+ return svelteConfigModule?.default || {};
378
+ } catch (e: unknown) {
379
+ clack.log.error(`Couldn't load ${SVELTE_CONFIG_FILE}.
380
+ Please make sure, you're running this wizard with Node 16 or newer`);
381
+ clack.log.info(
382
+ chalk.dim(
383
+ typeof e === 'object' && e != null && 'toString' in e
384
+ ? e.toString()
385
+ : typeof e === 'string'
386
+ ? e
387
+ : 'Unknown error',
388
+ ),
389
+ );
390
+
391
+ return {};
392
+ }
393
+ }
394
+
395
+ async function modifyViteConfig(viteConfigPath: string): Promise<void> {
396
+ const viteConfigContent = (
397
+ await fs.promises.readFile(viteConfigPath, 'utf-8')
398
+ ).toString();
399
+
400
+ if (hasSentryContent(viteConfigPath, viteConfigContent)) {
401
+ return;
402
+ }
403
+
404
+ const viteModule = parseModule(viteConfigContent);
405
+
406
+ addVitePlugin(viteModule, {
407
+ imported: 'sentrySvelteKit',
408
+ from: '@sentry/sveltekit',
409
+ constructor: 'sentrySvelteKit',
410
+ });
411
+
412
+ const code = generateCode(viteModule.$ast).code;
413
+ await fs.promises.writeFile(viteConfigPath, code);
414
+ }
415
+
416
+ /**
417
+ * We want to insert the init call on top of the file but after all import statements
418
+ */
419
+ function getInitCallInsertionIndex(originalHooksModAST: Program): number {
420
+ // We need to deep-copy here because reverse mutates in place
421
+ const copiedBodyNodes = [...originalHooksModAST.body];
422
+ const lastImportDeclaration = copiedBodyNodes
423
+ .reverse()
424
+ .find((node) => node.type === 'ImportDeclaration');
425
+
426
+ const initCallInsertionIndex = lastImportDeclaration
427
+ ? originalHooksModAST.body.indexOf(lastImportDeclaration) + 1
428
+ : 0;
429
+ return initCallInsertionIndex;
430
+ }
@@ -0,0 +1,27 @@
1
+ import { Args } from '../../lib/Constants';
2
+ import { SentryCli } from '../../lib/Helper/SentryCli';
3
+ import { SentryProjectData } from '../utils/clack-utils';
4
+
5
+ export async function setupCLIConfig(
6
+ authToken: string,
7
+ selectedProject: SentryProjectData,
8
+ sentryUrl: string,
9
+ ): Promise<void> {
10
+ const cli = new SentryCli({ url: sentryUrl } as Args);
11
+
12
+ const answers = {
13
+ config: {
14
+ organization: {
15
+ slug: selectedProject.organization.slug,
16
+ },
17
+ project: {
18
+ slug: selectedProject.slug,
19
+ },
20
+ auth: {
21
+ token: authToken,
22
+ },
23
+ },
24
+ };
25
+ const props = cli.convertAnswersToProperties(answers);
26
+ await cli.createSentryCliConfig(props);
27
+ }
@@ -0,0 +1,116 @@
1
+ // @ts-ignore - clack is ESM and TS complains about that. It works though
2
+ import clack from '@clack/prompts';
3
+ import chalk from 'chalk';
4
+
5
+ import {
6
+ abortIfCancelled,
7
+ askForSelfHosted,
8
+ askForWizardLogin,
9
+ confirmContinueEvenThoughNoGitRepo,
10
+ ensurePackageIsInstalled,
11
+ getPackageDotJson,
12
+ hasPackageInstalled,
13
+ installPackage,
14
+ printWelcome,
15
+ SentryProjectData,
16
+ } from '../utils/clack-utils';
17
+ import { createExamplePage } from './sdk-example';
18
+ import { createOrMergeSvelteKitFiles, loadSvelteConfig } from './sdk-setup';
19
+
20
+ import { setupCLIConfig } from './sentry-cli-setup';
21
+
22
+ interface SvelteKitWizardOptions {
23
+ promoCode?: string;
24
+ }
25
+
26
+ export async function runSvelteKitWizard(
27
+ options: SvelteKitWizardOptions,
28
+ ): Promise<void> {
29
+ printWelcome({
30
+ wizardName: 'Sentry SvelteKit Wizard',
31
+ promoCode: options.promoCode,
32
+ });
33
+
34
+ await confirmContinueEvenThoughNoGitRepo();
35
+
36
+ const packageJson = await getPackageDotJson();
37
+ await ensurePackageIsInstalled(packageJson, '@sveltejs/kit', 'Sveltekit');
38
+
39
+ const { url: sentryUrl, selfHosted } = await askForSelfHosted();
40
+
41
+ const { projects, apiKeys } = await askForWizardLogin({
42
+ promoCode: options.promoCode,
43
+ url: sentryUrl,
44
+ });
45
+
46
+ const selectedProject: SentryProjectData | symbol = await clack.select({
47
+ message: 'Select your Sentry project.',
48
+ options: projects.map((project) => {
49
+ return {
50
+ value: project,
51
+ label: `${project.organization.slug}/${project.slug}`,
52
+ };
53
+ }),
54
+ });
55
+
56
+ abortIfCancelled(selectedProject);
57
+
58
+ await installPackage({
59
+ packageName: '@sentry/sveltekit',
60
+ alreadyInstalled: hasPackageInstalled('@sentry/sveltekit', packageJson),
61
+ });
62
+
63
+ await setupCLIConfig(apiKeys.token, selectedProject, sentryUrl);
64
+
65
+ const dsn = selectedProject.keys[0].dsn.public;
66
+
67
+ const svelteConfig = await loadSvelteConfig();
68
+
69
+ try {
70
+ await createOrMergeSvelteKitFiles(dsn, svelteConfig);
71
+ } catch (e: unknown) {
72
+ clack.log.error('Error while setting up the SvelteKit SDK:');
73
+ clack.log.info(
74
+ chalk.dim(
75
+ typeof e === 'object' && e != null && 'toString' in e
76
+ ? e.toString()
77
+ : typeof e === 'string'
78
+ ? e
79
+ : 'Unknown error',
80
+ ),
81
+ );
82
+ return;
83
+ }
84
+
85
+ try {
86
+ await createExamplePage(svelteConfig, {
87
+ selfHosted,
88
+ url: sentryUrl,
89
+ orgSlug: selectedProject.organization.slug,
90
+ projectId: selectedProject.id,
91
+ });
92
+ } catch (e: unknown) {
93
+ clack.log.error('Error while creating an example page to test Sentry:');
94
+ clack.log.info(
95
+ chalk.dim(
96
+ typeof e === 'object' && e != null && 'toString' in e
97
+ ? e.toString()
98
+ : typeof e === 'string'
99
+ ? e
100
+ : 'Unknown error',
101
+ ),
102
+ );
103
+ return;
104
+ }
105
+
106
+ clack.outro(`
107
+ ${chalk.green('Successfully installed the Sentry SvelteKit SDK!')}
108
+
109
+ ${chalk.cyan(
110
+ 'You can validate your setup by starting your dev environment (`npm run dev`) and visiting "/sentry-example".',
111
+ )}
112
+
113
+ Check out the SDK documentation for further configuration:
114
+ https://docs.sentry.io/platforms/javascript/guides/sveltekit/
115
+ `);
116
+ }