@sentry/wizard 3.23.2 → 3.24.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 (38) hide show
  1. package/CHANGELOG.md +14 -2
  2. package/dist/package.json +2 -2
  3. package/dist/src/nextjs/nextjs-wizard.js +1 -1
  4. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  5. package/dist/src/nextjs/templates.d.ts +1 -1
  6. package/dist/src/nextjs/templates.js +4 -2
  7. package/dist/src/nextjs/templates.js.map +1 -1
  8. package/dist/src/react-native/glob.js +24 -4
  9. package/dist/src/react-native/glob.js.map +1 -1
  10. package/dist/src/remix/codemods/express-server.d.ts +0 -4
  11. package/dist/src/remix/codemods/express-server.js +1 -80
  12. package/dist/src/remix/codemods/express-server.js.map +1 -1
  13. package/dist/src/remix/codemods/handle-error.js +2 -2
  14. package/dist/src/remix/codemods/handle-error.js.map +1 -1
  15. package/dist/src/remix/remix-wizard.js +62 -19
  16. package/dist/src/remix/remix-wizard.js.map +1 -1
  17. package/dist/src/remix/sdk-example.js +1 -1
  18. package/dist/src/remix/sdk-example.js.map +1 -1
  19. package/dist/src/remix/sdk-setup.d.ts +5 -3
  20. package/dist/src/remix/sdk-setup.js +98 -38
  21. package/dist/src/remix/sdk-setup.js.map +1 -1
  22. package/dist/src/remix/templates.d.ts +1 -1
  23. package/dist/src/remix/templates.js +1 -1
  24. package/dist/src/remix/templates.js.map +1 -1
  25. package/dist/src/remix/utils.d.ts +9 -3
  26. package/dist/src/remix/utils.js +39 -10
  27. package/dist/src/remix/utils.js.map +1 -1
  28. package/package.json +2 -2
  29. package/src/nextjs/nextjs-wizard.ts +1 -1
  30. package/src/nextjs/templates.ts +32 -4
  31. package/src/react-native/glob.ts +1 -1
  32. package/src/remix/codemods/express-server.ts +0 -121
  33. package/src/remix/codemods/handle-error.ts +3 -2
  34. package/src/remix/remix-wizard.ts +45 -17
  35. package/src/remix/sdk-example.ts +0 -2
  36. package/src/remix/sdk-setup.ts +125 -33
  37. package/src/remix/templates.ts +1 -1
  38. package/src/remix/utils.ts +57 -7
@@ -15,18 +15,26 @@ import clack from '@clack/prompts';
15
15
  import chalk from 'chalk';
16
16
  import { gte, minVersion } from 'semver';
17
17
 
18
- // @ts-expect-error - magicast is ESM and TS complains about that. It works though
19
- import { builders, generateCode, loadFile, writeFile } from 'magicast';
20
- import { PackageDotJson, getPackageVersion } from '../utils/package-json';
21
- import { getInitCallInsertionIndex, hasSentryContent } from './utils';
18
+ import {
19
+ builders,
20
+ generateCode,
21
+ loadFile,
22
+ parseModule,
23
+ writeFile,
24
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
25
+ } from 'magicast';
26
+ import type { PackageDotJson } from '../utils/package-json';
27
+ import { getPackageVersion } from '../utils/package-json';
28
+ import {
29
+ getAfterImportsInsertionIndex,
30
+ hasSentryContent,
31
+ serverHasInstrumentationImport,
32
+ } from './utils';
22
33
  import { instrumentRootRouteV1 } from './codemods/root-v1';
23
34
  import { instrumentRootRouteV2 } from './codemods/root-v2';
24
35
  import { instrumentHandleError } from './codemods/handle-error';
25
- import {
26
- findCustomExpressServerImplementation,
27
- instrumentExpressCreateRequestHandler,
28
- } from './codemods/express-server';
29
36
  import { getPackageDotJson } from '../utils/clack-utils';
37
+ import { findCustomExpressServerImplementation } from './codemods/express-server';
30
38
 
31
39
  export type PartialRemixConfig = {
32
40
  unstable_dev?: boolean;
@@ -87,7 +95,8 @@ function insertClientInitCall(
87
95
  });
88
96
 
89
97
  const originalHooksModAST = originalHooksMod.$ast as Program;
90
- const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
98
+ const initCallInsertionIndex =
99
+ getAfterImportsInsertionIndex(originalHooksModAST);
91
100
 
92
101
  originalHooksModAST.body.splice(
93
102
  initCallInsertionIndex,
@@ -98,26 +107,76 @@ function insertClientInitCall(
98
107
  );
99
108
  }
100
109
 
101
- function insertServerInitCall(
102
- dsn: string,
103
- originalHooksMod: ProxifiedModule<any>,
104
- ) {
110
+ export async function createServerInstrumentationFile(dsn: string) {
111
+ // create an empty file named `instrument.server.mjs`
112
+ const instrumentationFile = 'instrumentation.server.mjs';
113
+ const instrumentationFileMod = parseModule('');
114
+
115
+ instrumentationFileMod.imports.$add({
116
+ from: '@sentry/remix',
117
+ imported: '*',
118
+ local: 'Sentry',
119
+ });
120
+
105
121
  const initCall = builders.functionCall('Sentry.init', {
106
122
  dsn,
107
123
  tracesSampleRate: 1.0,
124
+ autoInstrumentRemix: true,
108
125
  });
109
126
 
110
- const originalHooksModAST = originalHooksMod.$ast as Program;
127
+ const instrumentationFileModAST = instrumentationFileMod.$ast as Program;
111
128
 
112
- const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
129
+ const initCallInsertionIndex = getAfterImportsInsertionIndex(
130
+ instrumentationFileModAST,
131
+ );
113
132
 
114
- originalHooksModAST.body.splice(
133
+ instrumentationFileModAST.body.splice(
115
134
  initCallInsertionIndex,
116
135
  0,
117
136
  // @ts-expect-error - string works here because the AST is proxified by magicast
118
137
  // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
119
138
  generateCode(initCall).code,
120
139
  );
140
+
141
+ await writeFile(instrumentationFileModAST, instrumentationFile);
142
+
143
+ return instrumentationFile;
144
+ }
145
+
146
+ export async function insertServerInstrumentationFile(dsn: string) {
147
+ const instrumentationFile = await createServerInstrumentationFile(dsn);
148
+
149
+ const expressServerPath = await findCustomExpressServerImplementation();
150
+
151
+ if (!expressServerPath) {
152
+ return false;
153
+ }
154
+
155
+ const originalExpressServerMod = await loadFile(expressServerPath);
156
+
157
+ if (
158
+ serverHasInstrumentationImport(
159
+ expressServerPath,
160
+ originalExpressServerMod.$code,
161
+ )
162
+ ) {
163
+ clack.log.warn(
164
+ `File ${chalk.cyan(
165
+ path.basename(expressServerPath),
166
+ )} already contains instrumentation import.
167
+ Skipping adding instrumentation functionality to ${chalk.cyan(
168
+ path.basename(expressServerPath),
169
+ )}.`,
170
+ );
171
+
172
+ return true;
173
+ }
174
+
175
+ originalExpressServerMod.$code = `import './${instrumentationFile}';\n${originalExpressServerMod.$code}`;
176
+
177
+ fs.writeFileSync(expressServerPath, originalExpressServerMod.$code);
178
+
179
+ return true;
121
180
  }
122
181
 
123
182
  export function isRemixV2(
@@ -294,8 +353,56 @@ export async function initializeSentryOnEntryClient(
294
353
  );
295
354
  }
296
355
 
297
- export async function initializeSentryOnEntryServer(
298
- dsn: string,
356
+ export async function updateStartScript(instrumentationFile: string) {
357
+ const packageJson = await getPackageDotJson();
358
+
359
+ if (!packageJson.scripts || !packageJson.scripts.start) {
360
+ throw new Error(
361
+ "Couldn't find a `start` script in your package.json. Please add one manually.",
362
+ );
363
+ }
364
+
365
+ if (packageJson.scripts.start.includes('NODE_OPTIONS')) {
366
+ clack.log.warn(
367
+ `Found existing NODE_OPTIONS in ${chalk.cyan(
368
+ 'start',
369
+ )} script. Skipping adding Sentry initialization.`,
370
+ );
371
+
372
+ return;
373
+ }
374
+
375
+ if (
376
+ !packageJson.scripts.start.includes('remix-serve') &&
377
+ // Adding a following empty space not to match a path that includes `node`
378
+ !packageJson.scripts.start.includes('node ')
379
+ ) {
380
+ clack.log.warn(
381
+ `Found a ${chalk.cyan('start')} script that doesn't use ${chalk.cyan(
382
+ 'remix-serve',
383
+ )} or ${chalk.cyan('node')}. Skipping adding Sentry initialization.`,
384
+ );
385
+
386
+ return;
387
+ }
388
+
389
+ const startCommand = packageJson.scripts.start;
390
+
391
+ packageJson.scripts.start = `NODE_OPTIONS='--import ./${instrumentationFile}' ${startCommand}`;
392
+
393
+ await fs.promises.writeFile(
394
+ path.join(process.cwd(), 'package.json'),
395
+ JSON.stringify(packageJson, null, 2),
396
+ );
397
+
398
+ clack.log.success(
399
+ `Successfully updated ${chalk.cyan('start')} script in ${chalk.cyan(
400
+ 'package.json',
401
+ )} to include Sentry initialization on start.`,
402
+ );
403
+ }
404
+
405
+ export async function instrumentSentryOnEntryServer(
299
406
  isV2: boolean,
300
407
  isTS: boolean,
301
408
  ): Promise<void> {
@@ -319,8 +426,6 @@ export async function initializeSentryOnEntryServer(
319
426
  local: 'Sentry',
320
427
  });
321
428
 
322
- insertServerInitCall(dsn, originalEntryServerMod);
323
-
324
429
  if (isV2) {
325
430
  const handleErrorInstrumented = instrumentHandleError(
326
431
  originalEntryServerMod,
@@ -347,16 +452,3 @@ export async function initializeSentryOnEntryServer(
347
452
  )}.`,
348
453
  );
349
454
  }
350
-
351
- export async function instrumentExpressServer() {
352
- const expressServerPath = await findCustomExpressServerImplementation();
353
-
354
- if (!expressServerPath) {
355
- clack.log.warn(
356
- `Could not find custom Express server implementation. Please instrument it manually.`,
357
- );
358
- return;
359
- }
360
-
361
- await instrumentExpressCreateRequestHandler(expressServerPath);
362
- }
@@ -6,6 +6,6 @@ export const ERROR_BOUNDARY_TEMPLATE_V2 = `const ErrorBoundary = () => {
6
6
  `;
7
7
 
8
8
  export const HANDLE_ERROR_TEMPLATE_V2 = `function handleError(error, { request }) {
9
- Sentry.captureRemixServerException(error, 'remix.server', request);
9
+ Sentry.captureRemixServerException(error, 'remix.server', request, true);
10
10
  }
11
11
  `;
@@ -7,16 +7,23 @@ import clack from '@clack/prompts';
7
7
  import chalk from 'chalk';
8
8
  import { PackageDotJson, hasPackageInstalled } from '../utils/package-json';
9
9
 
10
- // Copied from sveltekit wizard
10
+ export const POSSIBLE_SERVER_INSTRUMENTATION_PATHS = [
11
+ './instrumentation',
12
+ './instrumentation.server',
13
+ ];
14
+
11
15
  export function hasSentryContent(
12
16
  fileName: string,
13
17
  fileContent: string,
18
+ expectedContent = '@sentry/remix',
14
19
  ): boolean {
15
- const includesContent = fileContent.includes('@sentry/remix');
20
+ const includesContent = fileContent.includes(expectedContent);
16
21
 
17
22
  if (includesContent) {
18
23
  clack.log.warn(
19
- `File ${chalk.cyan(path.basename(fileName))} already contains Sentry code.
24
+ `File ${chalk.cyan(
25
+ path.basename(fileName),
26
+ )} already contains ${expectedContent}.
20
27
  Skipping adding Sentry functionality to ${chalk.cyan(
21
28
  path.basename(fileName),
22
29
  )}.`,
@@ -26,14 +33,57 @@ Skipping adding Sentry functionality to ${chalk.cyan(
26
33
  return includesContent;
27
34
  }
28
35
 
36
+ export function serverHasInstrumentationImport(
37
+ serverFileName: string,
38
+ serverFileContent: string,
39
+ ): boolean {
40
+ const includesServerInstrumentationImport =
41
+ POSSIBLE_SERVER_INSTRUMENTATION_PATHS.some((path) =>
42
+ serverFileContent.includes(path),
43
+ );
44
+
45
+ if (includesServerInstrumentationImport) {
46
+ clack.log.warn(
47
+ `File ${chalk.cyan(
48
+ path.basename(serverFileName),
49
+ )} already contains instrumentation import.
50
+ Skipping adding instrumentation functionality to ${chalk.cyan(
51
+ path.basename(serverFileName),
52
+ )}.`,
53
+ );
54
+ }
55
+
56
+ return includesServerInstrumentationImport;
57
+ }
58
+
29
59
  /**
30
- * We want to insert the init call on top of the file but after all import statements
60
+ * We want to insert the init call on top of the file, before any other imports.
31
61
  */
32
- export function getInitCallInsertionIndex(
62
+ export function getBeforeImportsInsertionIndex(
33
63
  originalHooksModAST: Program,
34
64
  ): number {
35
- for (let x = originalHooksModAST.body.length - 1; x >= 0; x--) {
36
- if (originalHooksModAST.body[x].type === 'ImportDeclaration') {
65
+ for (let x = 0; x < originalHooksModAST.body.length - 1; x++) {
66
+ if (
67
+ originalHooksModAST.body[x].type === 'ImportDeclaration' &&
68
+ // @ts-expect-error - source is available in body
69
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
70
+ originalHooksModAST.body[x].source.value === '@sentry/remix'
71
+ ) {
72
+ return x + 1;
73
+ }
74
+ }
75
+
76
+ return 0;
77
+ }
78
+
79
+ /**
80
+ * We want to insert the handleError function just after all imports
81
+ */
82
+ export function getAfterImportsInsertionIndex(
83
+ originalEntryServerModAST: Program,
84
+ ): number {
85
+ for (let x = originalEntryServerModAST.body.length - 1; x >= 0; x--) {
86
+ if (originalEntryServerModAST.body[x].type === 'ImportDeclaration') {
37
87
  return x + 1;
38
88
  }
39
89
  }