@sentry/wizard 3.28.0 → 3.30.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 (42) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/package.json +1 -1
  3. package/dist/src/nextjs/nextjs-wizard.js +9 -4
  4. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  5. package/dist/src/nextjs/templates.js +2 -2
  6. package/dist/src/nextjs/templates.js.map +1 -1
  7. package/dist/src/react-native/react-native-wizard.js +4 -1
  8. package/dist/src/react-native/react-native-wizard.js.map +1 -1
  9. package/dist/src/remix/remix-wizard.d.ts +1 -1
  10. package/dist/src/remix/remix-wizard.js +27 -11
  11. package/dist/src/remix/remix-wizard.js.map +1 -1
  12. package/dist/src/remix/sdk-setup.d.ts +24 -3
  13. package/dist/src/remix/sdk-setup.js +95 -61
  14. package/dist/src/remix/sdk-setup.js.map +1 -1
  15. package/dist/src/run.d.ts +2 -0
  16. package/dist/src/run.js +2 -0
  17. package/dist/src/run.js.map +1 -1
  18. package/dist/src/sveltekit/sveltekit-wizard.js +3 -1
  19. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  20. package/dist/src/utils/clack-utils.d.ts +3 -0
  21. package/dist/src/utils/clack-utils.js +86 -14
  22. package/dist/src/utils/clack-utils.js.map +1 -1
  23. package/dist/src/utils/types.d.ts +12 -0
  24. package/dist/src/utils/types.js.map +1 -1
  25. package/dist/test/remix/client-entry.test.d.ts +1 -0
  26. package/dist/test/remix/client-entry.test.js +41 -0
  27. package/dist/test/remix/client-entry.test.js.map +1 -0
  28. package/dist/test/remix/server-instrumentation.test.d.ts +1 -0
  29. package/dist/test/remix/server-instrumentation.test.js +22 -0
  30. package/dist/test/remix/server-instrumentation.test.js.map +1 -0
  31. package/package.json +1 -1
  32. package/src/nextjs/nextjs-wizard.ts +9 -5
  33. package/src/nextjs/templates.ts +12 -3
  34. package/src/react-native/react-native-wizard.ts +4 -0
  35. package/src/remix/remix-wizard.ts +32 -6
  36. package/src/remix/sdk-setup.ts +145 -48
  37. package/src/run.ts +4 -0
  38. package/src/sveltekit/sveltekit-wizard.ts +3 -0
  39. package/src/utils/clack-utils.ts +93 -3
  40. package/src/utils/types.ts +14 -0
  41. package/test/remix/client-entry.test.ts +122 -0
  42. package/test/remix/server-instrumentation.test.ts +38 -0
@@ -670,6 +670,52 @@ async function addCliConfigFileToGitIgnore(filename: string): Promise<void> {
670
670
  }
671
671
  }
672
672
 
673
+ export async function runPrettierIfInstalled(): Promise<void> {
674
+ return traceStep('run-prettier', async () => {
675
+ const packageJson = await getPackageDotJson();
676
+ const prettierInstalled = hasPackageInstalled('prettier', packageJson);
677
+
678
+ if (prettierInstalled) {
679
+ // prompt the user if they want to run prettier
680
+ const shouldRunPrettier = await abortIfCancelled(
681
+ clack.confirm({
682
+ message:
683
+ 'Looks like you have Prettier in your project. Do you want to run it on your files?',
684
+ }),
685
+ );
686
+
687
+ if (!shouldRunPrettier) {
688
+ return;
689
+ }
690
+ } else {
691
+ return;
692
+ }
693
+
694
+ const prettierSpinner = clack.spinner();
695
+ prettierSpinner.start('Running Prettier on your files.');
696
+
697
+ try {
698
+ await new Promise<void>((resolve, reject) => {
699
+ childProcess.exec('npx prettier --write .', (err) => {
700
+ if (err) {
701
+ reject(err);
702
+ } else {
703
+ resolve();
704
+ }
705
+ });
706
+ });
707
+ } catch {
708
+ prettierSpinner.stop('Prettier failed to run.');
709
+ clack.log.error(
710
+ 'Prettier failed to run. There may be formatting issues in your updated files.',
711
+ );
712
+ return;
713
+ }
714
+
715
+ prettierSpinner.stop('Prettier has formatted your files.');
716
+ });
717
+ }
718
+
673
719
  /**
674
720
  * Checks if @param packageId is listed as a dependency in @param packageJson.
675
721
  * If not, it will ask users if they want to continue without the package.
@@ -734,7 +780,7 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
734
780
  return packageJson || {};
735
781
  }
736
782
 
737
- async function getPackageManager(): Promise<PackageManager> {
783
+ export async function getPackageManager(): Promise<PackageManager> {
738
784
  const detectedPackageManager = detectPackageManger();
739
785
 
740
786
  if (detectedPackageManager) {
@@ -823,7 +869,7 @@ export async function getOrAskForProjectData(
823
869
  }
824
870
 
825
871
  const selectedProject = await traceStep('select-project', () =>
826
- askForProjectSelection(projects),
872
+ askForProjectSelection(projects, options.orgSlug, options.projectSlug),
827
873
  );
828
874
 
829
875
  const { token } = apiKeys ?? {};
@@ -1041,14 +1087,38 @@ async function askForWizardLogin(options: {
1041
1087
 
1042
1088
  async function askForProjectSelection(
1043
1089
  projects: SentryProjectData[],
1090
+ orgSlug?: string,
1091
+ projectSlug?: string,
1044
1092
  ): Promise<SentryProjectData> {
1045
1093
  const label = (project: SentryProjectData): string => {
1046
1094
  return `${project.organization.slug}/${project.slug}`;
1047
1095
  };
1048
- const sortedProjects = [...projects];
1096
+
1097
+ const filteredProjects = filterProjectsBySlugs(
1098
+ projects,
1099
+ orgSlug,
1100
+ projectSlug,
1101
+ );
1102
+
1103
+ if (filteredProjects.length === 1) {
1104
+ const selection = filteredProjects[0];
1105
+
1106
+ Sentry.setTag('project', selection.slug);
1107
+ Sentry.setUser({ id: selection.organization.slug });
1108
+ clack.log.step(`Selected project ${label(selection)}`);
1109
+
1110
+ return selection;
1111
+ }
1112
+
1113
+ if (filteredProjects.length === 0) {
1114
+ clack.log.warn('Could not find a project with the provided slugs.');
1115
+ }
1116
+
1117
+ const sortedProjects = filteredProjects.length ? filteredProjects : projects;
1049
1118
  sortedProjects.sort((a: SentryProjectData, b: SentryProjectData) => {
1050
1119
  return label(a).localeCompare(label(b));
1051
1120
  });
1121
+
1052
1122
  const selection: SentryProjectData | symbol = await abortIfCancelled(
1053
1123
  clack.select({
1054
1124
  maxItems: 12,
@@ -1068,6 +1138,26 @@ async function askForProjectSelection(
1068
1138
  return selection;
1069
1139
  }
1070
1140
 
1141
+ function filterProjectsBySlugs(
1142
+ projects: SentryProjectData[],
1143
+ orgSlug?: string,
1144
+ projectSlug?: string,
1145
+ ): SentryProjectData[] {
1146
+ if (!orgSlug && !projectSlug) {
1147
+ return projects;
1148
+ }
1149
+ if (orgSlug && !projectSlug) {
1150
+ return projects.filter((p) => p.organization.slug === orgSlug);
1151
+ }
1152
+ if (!orgSlug && projectSlug) {
1153
+ return projects.filter((p) => p.slug === projectSlug);
1154
+ }
1155
+
1156
+ return projects.filter(
1157
+ (p) => p.organization.slug === orgSlug && p.slug === projectSlug,
1158
+ );
1159
+ }
1160
+
1071
1161
  /**
1072
1162
  * Asks users if they have a config file for @param tool (e.g. Vite).
1073
1163
  * If yes, asks users to specify the path to their config file.
@@ -33,6 +33,20 @@ export type WizardOptions = {
33
33
  */
34
34
  url?: string;
35
35
 
36
+ /**
37
+ * The org to pre-select in the wizard.
38
+ * This can be passed via the `--org` arg.
39
+ * Example: `--org my-org`
40
+ */
41
+ orgSlug?: string;
42
+
43
+ /**
44
+ * Project slug to pre-select in the wizard.
45
+ * This can be passed via the `--project` arg.
46
+ * Example: `--project my-project`
47
+ */
48
+ projectSlug?: string;
49
+
36
50
  /**
37
51
  * If this is set, the wizard will skip the login and project selection step.
38
52
  * (This can not yet be set externally but for example when redirecting from
@@ -0,0 +1,122 @@
1
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
2
+ import { parseModule } from 'magicast';
3
+ import { updateEntryClientMod } from '../../src/remix/sdk-setup';
4
+
5
+ describe('initializeSentryOnEntryClient', () => {
6
+ it('should initialize Sentry on client entry with all features enabled', () => {
7
+ // Empty entry.client.tsx file for testing
8
+ const originalEntryClientMod = parseModule('');
9
+
10
+ const dsn = 'https://sentry.io/123';
11
+ const selectedFeatures = {
12
+ performance: true,
13
+ replay: true,
14
+ };
15
+
16
+ const result = updateEntryClientMod(
17
+ originalEntryClientMod,
18
+ dsn,
19
+ selectedFeatures,
20
+ );
21
+
22
+ expect(result.generate().code).toMatchInlineSnapshot(`
23
+ "import { useEffect,} from "react";
24
+
25
+ import {
26
+ useLocation,
27
+ useMatches,
28
+ } from "@remix-run/react";
29
+
30
+ import * as Sentry from "@sentry/remix";
31
+
32
+ Sentry.init({
33
+ dsn: "https://sentry.io/123",
34
+ tracesSampleRate: 1,
35
+
36
+ integrations: [Sentry.browserTracingIntegration({
37
+ useEffect,
38
+ useLocation,
39
+ useMatches
40
+ }), Sentry.replayIntegration({
41
+ maskAllText: true,
42
+ blockAllMedia: true
43
+ })],
44
+
45
+ replaysSessionSampleRate: 0.1,
46
+ replaysOnErrorSampleRate: 1
47
+ })"
48
+ `);
49
+ });
50
+
51
+ it('should initialize Sentry on client entry when performance disabled', () => {
52
+ // Empty entry.client.tsx file for testing
53
+ const originalEntryClientMod = parseModule('');
54
+
55
+ const dsn = 'https://sentry.io/123';
56
+ const selectedFeatures = {
57
+ performance: false,
58
+ replay: true,
59
+ };
60
+
61
+ const result = updateEntryClientMod(
62
+ originalEntryClientMod,
63
+ dsn,
64
+ selectedFeatures,
65
+ );
66
+
67
+ expect(result.generate().code).toMatchInlineSnapshot(`
68
+ "import * as Sentry from "@sentry/remix";
69
+
70
+ Sentry.init({
71
+ dsn: "https://sentry.io/123",
72
+
73
+ integrations: [Sentry.replayIntegration({
74
+ maskAllText: true,
75
+ blockAllMedia: true
76
+ })],
77
+
78
+ replaysSessionSampleRate: 0.1,
79
+ replaysOnErrorSampleRate: 1
80
+ })"
81
+ `);
82
+ });
83
+
84
+ it('should initialize Sentry on client entry when replay disabled', () => {
85
+ // Empty entry.client.tsx file for testing
86
+ const originalEntryClientMod = parseModule('');
87
+
88
+ const dsn = 'https://sentry.io/123';
89
+ const selectedFeatures = {
90
+ performance: true,
91
+ replay: false,
92
+ };
93
+
94
+ const result = updateEntryClientMod(
95
+ originalEntryClientMod,
96
+ dsn,
97
+ selectedFeatures,
98
+ );
99
+
100
+ expect(result.generate().code).toMatchInlineSnapshot(`
101
+ "import { useEffect,} from "react";
102
+
103
+ import {
104
+ useLocation,
105
+ useMatches,
106
+ } from "@remix-run/react";
107
+
108
+ import * as Sentry from "@sentry/remix";
109
+
110
+ Sentry.init({
111
+ dsn: "https://sentry.io/123",
112
+ tracesSampleRate: 1,
113
+
114
+ integrations: [Sentry.browserTracingIntegration({
115
+ useEffect,
116
+ useLocation,
117
+ useMatches
118
+ })]
119
+ })"
120
+ `);
121
+ });
122
+ });
@@ -0,0 +1,38 @@
1
+ import { generateServerInstrumentationFile } from '../../src/remix/sdk-setup';
2
+
3
+ describe('generateServerInstrumentationFile', () => {
4
+ it('should generate server instrumentation file', () => {
5
+ const result = generateServerInstrumentationFile('https://sentry.io/123', {
6
+ performance: true,
7
+ replay: true,
8
+ });
9
+
10
+ expect(result.instrumentationFileMod.generate().code)
11
+ .toMatchInlineSnapshot(`
12
+ "import * as Sentry from "@sentry/remix";
13
+
14
+ Sentry.init({
15
+ dsn: "https://sentry.io/123",
16
+ tracesSampleRate: 1,
17
+ autoInstrumentRemix: true
18
+ })"
19
+ `);
20
+ });
21
+
22
+ it('should generate server instrumentation file when performance is disabled', () => {
23
+ const result = generateServerInstrumentationFile('https://sentry.io/123', {
24
+ performance: false,
25
+ replay: true,
26
+ });
27
+
28
+ expect(result.instrumentationFileMod.generate().code)
29
+ .toMatchInlineSnapshot(`
30
+ "import * as Sentry from "@sentry/remix";
31
+
32
+ Sentry.init({
33
+ dsn: "https://sentry.io/123",
34
+ autoInstrumentRemix: true
35
+ })"
36
+ `);
37
+ });
38
+ });