@sentry/wizard 3.11.0 → 3.13.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 (91) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/lib/Steps/ChooseIntegration.js +1 -0
  3. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  4. package/dist/package.json +1 -1
  5. package/dist/src/android/android-wizard.js +14 -4
  6. package/dist/src/android/android-wizard.js.map +1 -1
  7. package/dist/src/android/code-tools.d.ts +8 -0
  8. package/dist/src/android/code-tools.js +20 -8
  9. package/dist/src/android/code-tools.js.map +1 -1
  10. package/dist/src/android/gradle.js +6 -1
  11. package/dist/src/android/gradle.js.map +1 -1
  12. package/dist/src/nextjs/nextjs-wizard.js +5 -2
  13. package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
  14. package/dist/src/nextjs/templates.d.ts +1 -1
  15. package/dist/src/nextjs/templates.js +2 -2
  16. package/dist/src/nextjs/templates.js.map +1 -1
  17. package/dist/src/remix/remix-wizard.js +8 -4
  18. package/dist/src/remix/remix-wizard.js.map +1 -1
  19. package/dist/src/remix/sdk-setup.d.ts +5 -1
  20. package/dist/src/remix/sdk-setup.js +3 -2
  21. package/dist/src/remix/sdk-setup.js.map +1 -1
  22. package/dist/src/sourcemaps/tools/sentry-cli.d.ts +9 -0
  23. package/dist/src/sourcemaps/tools/sentry-cli.js +26 -22
  24. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  25. package/dist/src/sourcemaps/tools/tsc.d.ts +6 -0
  26. package/dist/src/sourcemaps/tools/tsc.js +98 -17
  27. package/dist/src/sourcemaps/tools/tsc.js.map +1 -1
  28. package/dist/src/sourcemaps/tools/vite.js +39 -124
  29. package/dist/src/sourcemaps/tools/vite.js.map +1 -1
  30. package/dist/src/sourcemaps/tools/webpack.d.ts +6 -1
  31. package/dist/src/sourcemaps/tools/webpack.js +280 -25
  32. package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
  33. package/dist/src/sveltekit/sdk-setup.js +123 -49
  34. package/dist/src/sveltekit/sdk-setup.js.map +1 -1
  35. package/dist/src/sveltekit/sveltekit-wizard.d.ts +1 -0
  36. package/dist/src/sveltekit/sveltekit-wizard.js +119 -44
  37. package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
  38. package/dist/src/sveltekit/utils.d.ts +2 -0
  39. package/dist/src/sveltekit/utils.js +48 -0
  40. package/dist/src/sveltekit/utils.js.map +1 -0
  41. package/dist/src/utils/ast-utils.d.ts +77 -3
  42. package/dist/src/utils/ast-utils.js +172 -6
  43. package/dist/src/utils/ast-utils.js.map +1 -1
  44. package/dist/src/utils/clack-utils.d.ts +85 -1
  45. package/dist/src/utils/clack-utils.js +214 -51
  46. package/dist/src/utils/clack-utils.js.map +1 -1
  47. package/dist/src/utils/package-manager.d.ts +5 -0
  48. package/dist/src/utils/package-manager.js +11 -7
  49. package/dist/src/utils/package-manager.js.map +1 -1
  50. package/dist/test/android/code-tools.test.d.ts +1 -0
  51. package/dist/test/android/code-tools.test.js +34 -0
  52. package/dist/test/android/code-tools.test.js.map +1 -0
  53. package/dist/test/sourcemaps/tools/sentry-cli.test.d.ts +1 -0
  54. package/dist/test/sourcemaps/tools/sentry-cli.test.js +112 -0
  55. package/dist/test/sourcemaps/tools/sentry-cli.test.js.map +1 -0
  56. package/dist/test/sourcemaps/tools/tsc.test.d.ts +1 -0
  57. package/dist/test/sourcemaps/tools/tsc.test.js +121 -0
  58. package/dist/test/sourcemaps/tools/tsc.test.js.map +1 -0
  59. package/dist/test/sourcemaps/tools/webpack.test.d.ts +1 -0
  60. package/dist/test/sourcemaps/tools/webpack.test.js +179 -0
  61. package/dist/test/sourcemaps/tools/webpack.test.js.map +1 -0
  62. package/dist/test/utils/ast-utils.test.js +181 -15
  63. package/dist/test/utils/ast-utils.test.js.map +1 -1
  64. package/dist/test/utils/clack-utils.test.d.ts +1 -0
  65. package/dist/test/utils/clack-utils.test.js +200 -0
  66. package/dist/test/utils/clack-utils.test.js.map +1 -0
  67. package/lib/Steps/ChooseIntegration.ts +1 -0
  68. package/package.json +1 -1
  69. package/src/android/android-wizard.ts +16 -5
  70. package/src/android/code-tools.ts +21 -7
  71. package/src/android/gradle.ts +6 -1
  72. package/src/nextjs/nextjs-wizard.ts +15 -3
  73. package/src/nextjs/templates.ts +3 -2
  74. package/src/remix/remix-wizard.ts +8 -11
  75. package/src/remix/sdk-setup.ts +8 -2
  76. package/src/sourcemaps/tools/sentry-cli.ts +16 -9
  77. package/src/sourcemaps/tools/tsc.ts +133 -28
  78. package/src/sourcemaps/tools/vite.ts +37 -127
  79. package/src/sourcemaps/tools/webpack.ts +343 -27
  80. package/src/sveltekit/sdk-setup.ts +115 -39
  81. package/src/sveltekit/sveltekit-wizard.ts +93 -25
  82. package/src/sveltekit/utils.ts +50 -0
  83. package/src/utils/ast-utils.ts +203 -7
  84. package/src/utils/clack-utils.ts +211 -44
  85. package/src/utils/package-manager.ts +12 -6
  86. package/test/android/code-tools.test.ts +49 -0
  87. package/test/sourcemaps/tools/sentry-cli.test.ts +51 -0
  88. package/test/sourcemaps/tools/tsc.test.ts +181 -0
  89. package/test/sourcemaps/tools/webpack.test.ts +303 -0
  90. package/test/utils/ast-utils.test.ts +240 -20
  91. package/test/utils/clack-utils.test.ts +142 -0
@@ -17,6 +17,7 @@ import {
17
17
  installPackageWithPackageManager,
18
18
  packageManagers,
19
19
  } from './package-manager';
20
+ import { debug } from './debug';
20
21
 
21
22
  const opn = require('opn') as (
22
23
  url: string,
@@ -124,18 +125,23 @@ export function printWelcome(options: {
124
125
 
125
126
  let welcomeText =
126
127
  options.message ||
127
- 'This Wizard will help you set up Sentry for your application.\nThank you for using Sentry :)';
128
+ `The ${options.wizardName} will help you set up Sentry for your application.\nThank you for using Sentry :)`;
128
129
 
129
130
  if (options.promoCode) {
130
- welcomeText += `\n\nUsing promo-code: ${options.promoCode}`;
131
+ welcomeText = `${welcomeText}\n\nUsing promo-code: ${options.promoCode}`;
131
132
  }
132
133
 
133
134
  if (wizardPackage.version) {
134
- welcomeText += `\n\nVersion: ${wizardPackage.version}`;
135
+ welcomeText = `${welcomeText}\n\nVersion: ${wizardPackage.version}`;
135
136
  }
136
137
 
137
138
  if (options.telemetryEnabled) {
138
- welcomeText += `\n\nYou are using the Sentry Wizard with telemetry enabled. This helps us improve the Wizard.\nYou can disable it at any time by running \`sentry-wizard --disable-telemetry\`.`;
139
+ welcomeText = `${welcomeText}
140
+
141
+ This wizard sends telemetry data and crash reports to Sentry. This helps us improve the Wizard.
142
+ You can turn this off at any time by running ${chalk.cyanBright(
143
+ 'sentry-wizard --disable-telemetry',
144
+ )}.`;
139
145
  }
140
146
 
141
147
  clack.note(welcomeText);
@@ -247,43 +253,9 @@ export async function installPackage({
247
253
  );
248
254
  }
249
255
 
250
- async function addOrgAndProjectToSentryCliRc(
251
- org: string,
252
- project: string,
253
- setupConfig: CliSetupConfig,
254
- ): Promise<void> {
255
- const configContents = fs.readFileSync(
256
- path.join(process.cwd(), setupConfig.filename),
257
- 'utf8',
258
- );
259
-
260
- if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) {
261
- clack.log.warn(
262
- `${chalk.bold(
263
- setupConfig.filename,
264
- )} already has org and project. Will not add them.`,
265
- );
266
- } else {
267
- try {
268
- await fs.promises.appendFile(
269
- path.join(process.cwd(), setupConfig.filename),
270
- `\n${setupConfig.orgAndProjContent(org, project)}\n`,
271
- );
272
- } catch (e) {
273
- clack.log.warn(
274
- `${chalk.bold(
275
- setupConfig.filename,
276
- )} could not be updated with org and project.`,
277
- );
278
- }
279
- }
280
- }
281
-
282
256
  export async function addSentryCliConfig(
283
257
  authToken: string,
284
258
  setupConfig: CliSetupConfig = sourceMapsCliSetupConfig,
285
- orgSlug?: string,
286
- projectSlug?: string,
287
259
  ): Promise<void> {
288
260
  const configExists = fs.existsSync(
289
261
  path.join(process.cwd(), setupConfig.filename),
@@ -351,10 +323,6 @@ export async function addSentryCliConfig(
351
323
  }
352
324
  }
353
325
 
354
- if (orgSlug && projectSlug) {
355
- await addOrgAndProjectToSentryCliRc(orgSlug, projectSlug, setupConfig);
356
- }
357
-
358
326
  await addAuthTokenFileToGitIgnore(setupConfig.filename);
359
327
  }
360
328
 
@@ -456,6 +424,7 @@ export async function ensurePackageIsInstalled(
456
424
  packageName: string,
457
425
  ) {
458
426
  if (!hasPackageInstalled(packageId, packageJson)) {
427
+ Sentry.setTag('package-installed', false);
459
428
  const continueWithoutPackage = await abortIfCancelled(
460
429
  clack.confirm({
461
430
  message: `${packageName} does not seem to be installed. Do you still want to continue?`,
@@ -466,6 +435,8 @@ export async function ensurePackageIsInstalled(
466
435
  if (!continueWithoutPackage) {
467
436
  await abort(undefined, 0);
468
437
  }
438
+ } else {
439
+ Sentry.setTag('package-installed', true);
469
440
  }
470
441
  }
471
442
 
@@ -778,14 +749,21 @@ async function askForWizardLogin(options: {
778
749
  async function askForProjectSelection(
779
750
  projects: SentryProjectData[],
780
751
  ): Promise<SentryProjectData> {
752
+ const label = (project: SentryProjectData): string => {
753
+ return `${project.organization.slug}/${project.slug}`;
754
+ };
755
+ const sortedProjects = [...projects];
756
+ sortedProjects.sort((a: SentryProjectData, b: SentryProjectData) => {
757
+ return label(a).localeCompare(label(b));
758
+ });
781
759
  const selection: SentryProjectData | symbol = await abortIfCancelled(
782
760
  clack.select({
783
761
  maxItems: 12,
784
762
  message: 'Select your Sentry project.',
785
- options: projects.map((project) => {
763
+ options: sortedProjects.map((project) => {
786
764
  return {
787
765
  value: project,
788
- label: `${project.organization.slug}/${project.slug}`,
766
+ label: label(project),
789
767
  };
790
768
  }),
791
769
  }),
@@ -797,3 +775,192 @@ async function askForProjectSelection(
797
775
 
798
776
  return selection;
799
777
  }
778
+
779
+ /**
780
+ * Asks users if they have a config file for @param tool (e.g. Vite).
781
+ * If yes, asks users to specify the path to their config file.
782
+ *
783
+ * Use this helper function as a fallback mechanism if the lookup for
784
+ * a config file with its most usual location/name fails.
785
+ *
786
+ * @param toolName Name of the tool for which we're looking for the config file
787
+ * @param configFileName Name of the most common config file name (e.g. vite.config.js)
788
+ *
789
+ * @returns a user path to the config file or undefined if the user doesn't have a config file
790
+ */
791
+ export async function askForToolConfigPath(
792
+ toolName: string,
793
+ configFileName: string,
794
+ ): Promise<string | undefined> {
795
+ const hasConfig = await abortIfCancelled(
796
+ clack.confirm({
797
+ message: `Do you have a ${toolName} config file (e.g. ${chalk.cyan(
798
+ configFileName,
799
+ )})?`,
800
+ initialValue: true,
801
+ }),
802
+ );
803
+
804
+ if (!hasConfig) {
805
+ return undefined;
806
+ }
807
+
808
+ return await abortIfCancelled(
809
+ clack.text({
810
+ message: `Please enter the path to your ${toolName} config file:`,
811
+ placeholder: path.join('.', configFileName),
812
+ validate: (value) => {
813
+ if (!value) {
814
+ return 'Please enter a path.';
815
+ }
816
+
817
+ try {
818
+ fs.accessSync(value);
819
+ } catch {
820
+ return 'Could not access the file at this path.';
821
+ }
822
+ },
823
+ }),
824
+ );
825
+ }
826
+
827
+ /**
828
+ * Prints copy/paste-able instructions to the console.
829
+ * Afterwards asks the user if they added the code snippet to their file.
830
+ *
831
+ * While there's no point in providing a "no" answer here, it gives users time to fulfill the
832
+ * task before the wizard continues with additional steps.
833
+ *
834
+ * Use this function if you want to show users instructions on how to add/modify
835
+ * code in their file. This is helpful if automatic insertion failed or is not possible/feasible.
836
+ *
837
+ * @param filename the name of the file to which the code snippet should be applied.
838
+ * If a path is provided, only the filename will be used.
839
+ *
840
+ * @param codeSnippet the snippet to be printed. Use {@link makeCodeSnippet} to create the
841
+ * diff-like format for visually highlighting unchanged or modified lines of code.
842
+ *
843
+ * @param hint (optional) a hint to be printed after the main instruction to add
844
+ * the code from @param codeSnippet to their @param filename.
845
+ *
846
+ * More guidelines on copy/paste instructions:
847
+ * @see {@link https://develop.sentry.dev/sdk/setup-wizards/#copy--paste-snippets}
848
+ *
849
+ * TODO: refactor copy paste instructions across different wizards to use this function.
850
+ * this might require adding a custom message parameter to the function
851
+ */
852
+ export async function showCopyPasteInstructions(
853
+ filename: string,
854
+ codeSnippet: string,
855
+ hint?: string,
856
+ ): Promise<void> {
857
+ clack.log.step(
858
+ `Add the following code to your ${chalk.cyan(
859
+ path.basename(filename),
860
+ )} file:${hint ? chalk.dim(` (${chalk.dim(hint)})`) : ''}`,
861
+ );
862
+
863
+ // Padding the code snippet to be printed with a \n at the beginning and end
864
+ // This makes it easier to distinguish the snippet from the rest of the output
865
+ // Intentionally logging directly to console here so that the code can be copied/pasted directly
866
+ // eslint-disable-next-line no-console
867
+ console.log(`\n${codeSnippet}\n`);
868
+
869
+ await abortIfCancelled(
870
+ clack.select({
871
+ message: 'Did you apply the snippet above?',
872
+ options: [{ label: 'Yes, continue!', value: true }],
873
+ initialValue: true,
874
+ }),
875
+ );
876
+ }
877
+
878
+ /**
879
+ * Callback that exposes formatting helpers for a code snippet.
880
+ * @param unchanged - Formats text as old code.
881
+ * @param plus - Formats text as new code.
882
+ * @param minus - Formats text as removed code.
883
+ */
884
+ type CodeSnippetFormatter = (
885
+ unchanged: (txt: string) => string,
886
+ plus: (txt: string) => string,
887
+ minus: (txt: string) => string,
888
+ ) => string;
889
+
890
+ /**
891
+ * Crafts a code snippet that can be used to e.g.
892
+ * - print copy/paste instructions to the console
893
+ * - create a new config file.
894
+ *
895
+ * @param colors set this to true if you want the final snippet to be colored.
896
+ * This is useful for printing the snippet to the console as part of copy/paste instructions.
897
+ *
898
+ * @param callback the callback that returns the formatted code snippet.
899
+ * It exposes takes the helper functions for marking code as unchaned, new or removed.
900
+ * These functions no-op if no special formatting should be applied
901
+ * and otherwise apply the appropriate formatting/coloring.
902
+ * (@see {@link CodeSnippetFormatter})
903
+ *
904
+ * @see {@link showCopyPasteInstructions} for the helper with which to display the snippet in the console.
905
+ *
906
+ * @returns a string containing the final, formatted code snippet.
907
+ */
908
+ export function makeCodeSnippet(
909
+ colors: boolean,
910
+ callback: CodeSnippetFormatter,
911
+ ): string {
912
+ const unchanged = (txt: string) => (colors ? chalk.grey(txt) : txt);
913
+ const plus = (txt: string) => (colors ? chalk.greenBright(txt) : txt);
914
+ const minus = (txt: string) => (colors ? chalk.redBright(txt) : txt);
915
+
916
+ return callback(unchanged, plus, minus);
917
+ }
918
+
919
+ /**
920
+ * Creates a new config file with the given @param filepath and @param codeSnippet.
921
+ *
922
+ * Use this function to create a new config file for users. This is useful
923
+ * when users answered that they don't yet have a config file for a tool.
924
+ *
925
+ * (This doesn't mean that they don't yet have some other way of configuring
926
+ * their tool but we can leave it up to them to figure out how to merge configs
927
+ * here.)
928
+ *
929
+ * @param filepath absolute path to the new config file
930
+ * @param codeSnippet the snippet to be inserted into the file
931
+ * @param moreInformation (optional) the message to be printed after the file was created
932
+ * For example, this can be a link to more information about configuring the tool.
933
+ *
934
+ * @returns true on sucess, false otherwise
935
+ */
936
+ export async function createNewConfigFile(
937
+ filepath: string,
938
+ codeSnippet: string,
939
+ moreInformation?: string,
940
+ ): Promise<boolean> {
941
+ if (!path.isAbsolute(filepath)) {
942
+ debug(`createNewConfigFile: filepath is not absolute: ${filepath}`);
943
+ return false;
944
+ }
945
+
946
+ const prettyFilename = chalk.cyan(path.relative(process.cwd(), filepath));
947
+
948
+ try {
949
+ await fs.promises.writeFile(filepath, codeSnippet);
950
+
951
+ clack.log.success(`Added new ${prettyFilename} file.`);
952
+
953
+ if (moreInformation) {
954
+ clack.log.info(chalk.gray(moreInformation));
955
+ }
956
+
957
+ return true;
958
+ } catch (e) {
959
+ debug(e);
960
+ clack.log.warn(
961
+ `Could not create a new ${prettyFilename} file. Please create one manually and follow the instructions below.`,
962
+ );
963
+ }
964
+
965
+ return false;
966
+ }
@@ -10,38 +10,44 @@ export interface PackageManager {
10
10
  lockFile: string;
11
11
  installCommand: string;
12
12
  buildCommand: string;
13
+ /* The command that the package manager uses to run a script from package.json */
14
+ runScriptCommand: string;
13
15
  }
14
16
 
15
- const bun: PackageManager = {
17
+ export const BUN: PackageManager = {
16
18
  name: 'bun',
17
19
  label: 'Bun',
18
20
  lockFile: 'bun.lockb',
19
21
  installCommand: 'bun add',
20
- buildCommand: 'bun build',
22
+ buildCommand: 'bun run build',
23
+ runScriptCommand: 'bun run',
21
24
  };
22
- const yarn: PackageManager = {
25
+ export const YARN: PackageManager = {
23
26
  name: 'yarn',
24
27
  label: 'Yarn',
25
28
  lockFile: 'yarn.lock',
26
29
  installCommand: 'yarn add',
27
30
  buildCommand: 'yarn build',
31
+ runScriptCommand: 'yarn',
28
32
  };
29
- const pnpm: PackageManager = {
33
+ export const PNPM: PackageManager = {
30
34
  name: 'pnpm',
31
35
  label: 'PNPM',
32
36
  lockFile: 'pnpm-lock.yaml',
33
37
  installCommand: 'pnpm add',
34
38
  buildCommand: 'pnpm build',
39
+ runScriptCommand: 'pnpm',
35
40
  };
36
- const npm: PackageManager = {
41
+ export const NPM: PackageManager = {
37
42
  name: 'npm',
38
43
  label: 'NPM',
39
44
  lockFile: 'package-lock.json',
40
45
  installCommand: 'npm add',
41
46
  buildCommand: 'npm run build',
47
+ runScriptCommand: 'npm run',
42
48
  };
43
49
 
44
- export const packageManagers = [bun, yarn, pnpm, npm];
50
+ export const packageManagers = [BUN, YARN, PNPM, NPM];
45
51
 
46
52
  export function detectPackageManger(): PackageManager | null {
47
53
  for (const packageManager of packageManagers) {
@@ -0,0 +1,49 @@
1
+ //@ts-ignore
2
+ import { getLastImportLineLocation } from '../../src/android/code-tools';
3
+
4
+ describe('code-tools', () => {
5
+ describe('getLastImportLineLocation', () => {
6
+ it('returns proper line index', () => {
7
+ const code = `import a.b.c;\n` + `//<insert-location>\n` + `class X {}`;
8
+ expect(getLastImportLineLocation(code)).toBe(
9
+ code.indexOf('//<insert-location>'),
10
+ );
11
+ });
12
+
13
+ it('returns proper line index when static import is used', () => {
14
+ const code =
15
+ `import static a.b.c;\n` + `//<insert-location>\n` + `class X {}`;
16
+ expect(getLastImportLineLocation(code)).toBe(
17
+ code.indexOf('//<insert-location>'),
18
+ );
19
+ });
20
+
21
+ it('returns proper line index when wildcard import is used', () => {
22
+ const code = `import a.b.*\n` + `//<insert-location>\n` + `class X {}`;
23
+ expect(getLastImportLineLocation(code)).toBe(
24
+ code.indexOf('//<insert-location>'),
25
+ );
26
+ });
27
+
28
+ it('returns proper line index when alias import is used', () => {
29
+ const code =
30
+ `import static a.b.c as d\n` + `//<insert-location>\n` + `class X {}`;
31
+ expect(getLastImportLineLocation(code)).toBe(
32
+ code.indexOf('//<insert-location>'),
33
+ );
34
+ });
35
+
36
+ it('returns proper line index when multiple imports are present', () => {
37
+ const code =
38
+ `import static a.b.c as d\n` +
39
+ `import a.b.*\n` +
40
+ `import static a.b.c;\n` +
41
+ `import a.b.c;\n` +
42
+ `//<insert-location>\n` +
43
+ `class X {}`;
44
+ expect(getLastImportLineLocation(code)).toBe(
45
+ code.indexOf('//<insert-location>'),
46
+ );
47
+ });
48
+ });
49
+ });
@@ -0,0 +1,51 @@
1
+ import * as fs from 'fs';
2
+
3
+ import { addSentryCommandToBuildCommand } from '../../../src/sourcemaps/tools/sentry-cli';
4
+
5
+ import * as packageManagerHelpers from '../../../src/utils/package-manager';
6
+
7
+ const writeFileSpy = jest
8
+ .spyOn(fs.promises, 'writeFile')
9
+ .mockImplementation(() => Promise.resolve());
10
+
11
+ jest.mock('@clack/prompts', () => {
12
+ return {
13
+ log: {
14
+ info: jest.fn(),
15
+ success: jest.fn(),
16
+ },
17
+ confirm: jest.fn().mockResolvedValue(true),
18
+ isCancel: jest.fn().mockReturnValue(false),
19
+ };
20
+ });
21
+
22
+ describe('addSentryCommandToBuildCommand', () => {
23
+ afterEach(() => {
24
+ jest.clearAllMocks();
25
+ });
26
+ it.each([
27
+ [
28
+ packageManagerHelpers.NPM,
29
+ packageManagerHelpers.PNPM,
30
+ packageManagerHelpers.YARN,
31
+ packageManagerHelpers.BUN,
32
+ ],
33
+ ])('adds the cli command to the script command (%s)', async (_, pacMan) => {
34
+ jest
35
+ .spyOn(packageManagerHelpers, 'detectPackageManger')
36
+ .mockReturnValue(pacMan);
37
+ const packageJson = {
38
+ scripts: {
39
+ build: 'tsc',
40
+ },
41
+ version: '1.0.0',
42
+ };
43
+ await addSentryCommandToBuildCommand(packageJson);
44
+ expect(writeFileSpy).toHaveBeenCalledWith(
45
+ expect.stringContaining('package.json'),
46
+ expect.stringContaining(
47
+ `tsc && ${pacMan.runScriptCommand} sentry:sourcemaps`,
48
+ ),
49
+ );
50
+ });
51
+ });
@@ -0,0 +1,181 @@
1
+ import * as fs from 'fs';
2
+ import { enableSourcemaps } from '../../../src/sourcemaps/tools/tsc';
3
+
4
+ function updateFileContent(content: string): void {
5
+ fileContent = content;
6
+ }
7
+
8
+ let fileContent = '';
9
+
10
+ jest.mock('@clack/prompts', () => {
11
+ return {
12
+ log: {
13
+ info: jest.fn(),
14
+ success: jest.fn(),
15
+ },
16
+ };
17
+ });
18
+
19
+ jest
20
+ .spyOn(fs.promises, 'readFile')
21
+ .mockImplementation(() => Promise.resolve(fileContent));
22
+
23
+ const writeFileSpy = jest
24
+ .spyOn(fs.promises, 'writeFile')
25
+ .mockImplementation(() => Promise.resolve(void 0));
26
+
27
+ describe('enableSourcemaps', () => {
28
+ afterEach(() => {
29
+ fileContent = '';
30
+ jest.clearAllMocks();
31
+ });
32
+
33
+ it.each([
34
+ [
35
+ 'no sourcemaps options',
36
+ `
37
+ /**
38
+ * My TS config with comments
39
+ */
40
+ {
41
+ "extends": "./tsconfig.build.json",
42
+
43
+ "compilerOptions": {
44
+ // line comment which should stay
45
+ "moduleResolution": "node16",
46
+ "outDir": "dist" // another inline comment
47
+ },
48
+
49
+ "include": [
50
+ "src/**/*",
51
+ "test/**/*"
52
+ ]
53
+ }
54
+ `,
55
+ `
56
+ /**
57
+ * My TS config with comments
58
+ */
59
+ {
60
+ "extends": "./tsconfig.build.json",
61
+
62
+ "compilerOptions": {
63
+ // line comment which should stay
64
+ "moduleResolution": "node16",
65
+
66
+ // another inline comment
67
+ "outDir": "dist",
68
+
69
+ "sourceMap": true,
70
+ "inlineSources": true,
71
+
72
+ // Set \`sourceRoot\` to "/" to strip the build path prefix
73
+ // from generated source code references.
74
+ // This improves issue grouping in Sentry.
75
+ "sourceRoot": "/"
76
+ },
77
+
78
+ "include": [
79
+ "src/**/*",
80
+ "test/**/*"
81
+ ]
82
+ }
83
+ `,
84
+ ],
85
+ [
86
+ 'a few sourcemaps options',
87
+ `
88
+ /**
89
+ * My TS config with comments
90
+ */
91
+ {
92
+ "extends": "./tsconfig.build.json",
93
+
94
+ "compilerOptions": {
95
+ // line comment which should stay
96
+ "moduleResolution": "node16",
97
+ "outDir": "dist", // another inline comment
98
+ "sourceMap": false,
99
+ "sourceRoot": "/src"
100
+ },
101
+
102
+ "include": [
103
+ "src/**/*",
104
+ "test/**/*"
105
+ ]
106
+ }
107
+ `,
108
+ `
109
+ /**
110
+ * My TS config with comments
111
+ */
112
+ {
113
+ "extends": "./tsconfig.build.json",
114
+
115
+ "compilerOptions": {
116
+ // line comment which should stay
117
+ "moduleResolution": "node16",
118
+
119
+ // another inline comment
120
+ "outDir": "dist",
121
+
122
+ "sourceMap": true,
123
+
124
+ // Set \`sourceRoot\` to "/" to strip the build path prefix
125
+ // from generated source code references.
126
+ // This improves issue grouping in Sentry.
127
+ "sourceRoot": "/",
128
+
129
+ "inlineSources": true
130
+ },
131
+
132
+ "include": [
133
+ "src/**/*",
134
+ "test/**/*"
135
+ ]
136
+ }
137
+ `,
138
+ ],
139
+ [
140
+ 'no compiler options',
141
+ `
142
+ {
143
+ "include": [
144
+ "src/**/*",
145
+ "test/**/*"
146
+ ]
147
+ }
148
+ `,
149
+ `
150
+ {
151
+ "include": [
152
+ "src/**/*",
153
+ "test/**/*"
154
+ ],
155
+
156
+ "compilerOptions": {
157
+ "sourceMap": true,
158
+ "inlineSources": true,
159
+
160
+ // Set \`sourceRoot\` to "/" to strip the build path prefix
161
+ // from generated source code references.
162
+ // This improves issue grouping in Sentry.
163
+ "sourceRoot": "/"
164
+ }
165
+ }
166
+ `,
167
+ ],
168
+ ])(
169
+ 'adds the plugin and enables source maps generation (%s)',
170
+ async (_, originalCode, expectedCode) => {
171
+ updateFileContent(originalCode);
172
+
173
+ const addedCode = await enableSourcemaps('');
174
+
175
+ expect(writeFileSpy).toHaveBeenCalledTimes(1);
176
+ const [[, fileContent]] = writeFileSpy.mock.calls;
177
+ expect(fileContent).toBe(expectedCode);
178
+ expect(addedCode).toBe(true);
179
+ },
180
+ );
181
+ });