@sentry/wizard 3.9.1 → 3.10.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 (63) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/dist/lib/Constants.d.ts +1 -0
  3. package/dist/lib/Constants.js +5 -0
  4. package/dist/lib/Constants.js.map +1 -1
  5. package/dist/lib/Steps/ChooseIntegration.js +7 -0
  6. package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
  7. package/dist/lib/Steps/Integrations/Cordova.js +5 -1
  8. package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
  9. package/dist/lib/Steps/Integrations/Remix.d.ts +12 -0
  10. package/dist/lib/Steps/Integrations/Remix.js +98 -0
  11. package/dist/lib/Steps/Integrations/Remix.js.map +1 -0
  12. package/dist/package.json +1 -1
  13. package/dist/src/remix/codemods/handle-error.d.ts +2 -0
  14. package/dist/src/remix/codemods/handle-error.js +70 -0
  15. package/dist/src/remix/codemods/handle-error.js.map +1 -0
  16. package/dist/src/remix/codemods/root-v1.d.ts +1 -0
  17. package/dist/src/remix/codemods/root-v1.js +133 -0
  18. package/dist/src/remix/codemods/root-v1.js.map +1 -0
  19. package/dist/src/remix/codemods/root-v2.d.ts +1 -0
  20. package/dist/src/remix/codemods/root-v2.js +134 -0
  21. package/dist/src/remix/codemods/root-v2.js.map +1 -0
  22. package/dist/src/remix/remix-wizard.d.ts +2 -0
  23. package/dist/src/remix/remix-wizard.js +206 -0
  24. package/dist/src/remix/remix-wizard.js.map +1 -0
  25. package/dist/src/remix/sdk-setup.d.ts +18 -0
  26. package/dist/src/remix/sdk-setup.js +293 -0
  27. package/dist/src/remix/sdk-setup.js.map +1 -0
  28. package/dist/src/remix/templates.d.ts +2 -0
  29. package/dist/src/remix/templates.js +6 -0
  30. package/dist/src/remix/templates.js.map +1 -0
  31. package/dist/src/remix/utils.d.ts +6 -0
  32. package/dist/src/remix/utils.js +55 -0
  33. package/dist/src/remix/utils.js.map +1 -0
  34. package/dist/src/sourcemaps/sourcemaps-wizard.js +23 -12
  35. package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
  36. package/dist/src/sourcemaps/tools/remix.d.ts +3 -0
  37. package/dist/src/sourcemaps/tools/remix.js +125 -0
  38. package/dist/src/sourcemaps/tools/remix.js.map +1 -0
  39. package/dist/src/sourcemaps/tools/sentry-cli.js +6 -3
  40. package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
  41. package/dist/src/sourcemaps/utils/detect-tool.d.ts +1 -1
  42. package/dist/src/sourcemaps/utils/detect-tool.js +1 -0
  43. package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -1
  44. package/dist/src/utils/clack-utils.d.ts +3 -2
  45. package/dist/src/utils/clack-utils.js +39 -2
  46. package/dist/src/utils/clack-utils.js.map +1 -1
  47. package/lib/Constants.ts +5 -0
  48. package/lib/Steps/ChooseIntegration.ts +7 -0
  49. package/lib/Steps/Integrations/Cordova.ts +5 -1
  50. package/lib/Steps/Integrations/Remix.ts +32 -0
  51. package/package.json +1 -1
  52. package/src/remix/codemods/handle-error.ts +67 -0
  53. package/src/remix/codemods/root-v1.ts +91 -0
  54. package/src/remix/codemods/root-v2.ts +84 -0
  55. package/src/remix/remix-wizard.ts +137 -0
  56. package/src/remix/sdk-setup.ts +300 -0
  57. package/src/remix/templates.ts +15 -0
  58. package/src/remix/utils.ts +41 -0
  59. package/src/sourcemaps/sourcemaps-wizard.ts +9 -0
  60. package/src/sourcemaps/tools/remix.ts +90 -0
  61. package/src/sourcemaps/tools/sentry-cli.ts +5 -3
  62. package/src/sourcemaps/utils/detect-tool.ts +3 -1
  63. package/src/utils/clack-utils.ts +56 -2
@@ -0,0 +1,32 @@
1
+ import type { Answers } from 'inquirer';
2
+
3
+ import type { Args } from '../../Constants';
4
+ import { BaseIntegration } from './BaseIntegration';
5
+ import { runRemixWizard } from '../../../src/remix/remix-wizard';
6
+
7
+ /**
8
+ * This class just redirects to the new `remix-wizard.ts` flow.
9
+ */
10
+ export class Remix extends BaseIntegration {
11
+ public constructor(protected _argv: Args) {
12
+ super(_argv);
13
+ }
14
+
15
+ public async emit(_answers: Answers): Promise<Answers> {
16
+ await runRemixWizard({
17
+ promoCode: this._argv.promoCode,
18
+ url: this._argv.url,
19
+ telemetryEnabled: !this._argv.disableTelemetry,
20
+ });
21
+ return {};
22
+ }
23
+
24
+ public async shouldConfigure(_answers: Answers): Promise<Answers> {
25
+ // eslint-disable-next-line @typescript-eslint/no-misused-promises
26
+ if (this._shouldConfigure) {
27
+ return this._shouldConfigure;
28
+ }
29
+ // eslint-disable-next-line @typescript-eslint/unbound-method
30
+ return this.shouldConfigure;
31
+ }
32
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sentry/wizard",
3
- "version": "3.9.1",
3
+ "version": "3.10.0",
4
4
  "homepage": "https://github.com/getsentry/sentry-wizard",
5
5
  "repository": "https://github.com/getsentry/sentry-wizard",
6
6
  "description": "Sentry wizard helping you to configure your project",
@@ -0,0 +1,67 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+
3
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
4
+ import type { ProxifiedModule } from 'magicast';
5
+ import type { Program } from '@babel/types';
6
+
7
+ import * as recast from 'recast';
8
+
9
+ import { HANDLE_ERROR_TEMPLATE_V2 } from '../templates';
10
+ import { getInitCallInsertionIndex, hasSentryContent } from '../utils';
11
+
12
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
13
+ import clack from '@clack/prompts';
14
+ import chalk from 'chalk';
15
+
16
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
17
+ import { generateCode } from 'magicast';
18
+
19
+ export function instrumentHandleError(
20
+ originalEntryServerMod: ProxifiedModule<any>,
21
+ serverEntryFilename: string,
22
+ ): boolean {
23
+ const originalEntryServerModAST = originalEntryServerMod.$ast as Program;
24
+
25
+ const handleErrorFunction = originalEntryServerModAST.body.find(
26
+ (node) =>
27
+ node.type === 'ExportNamedDeclaration' &&
28
+ node.declaration?.type === 'FunctionDeclaration' &&
29
+ node.declaration.id?.name === 'handleError',
30
+ );
31
+
32
+ if (!handleErrorFunction) {
33
+ clack.log.warn(
34
+ `Could not find function ${chalk.cyan('handleError')} in ${chalk.cyan(
35
+ serverEntryFilename,
36
+ )}. Creating one for you.`,
37
+ );
38
+
39
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
40
+ const implementation = recast.parse(HANDLE_ERROR_TEMPLATE_V2).program
41
+ .body[0];
42
+
43
+ originalEntryServerModAST.body.splice(
44
+ getInitCallInsertionIndex(originalEntryServerModAST),
45
+ 0,
46
+ // @ts-expect-error - string works here because the AST is proxified by magicast
47
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
48
+ recast.types.builders.exportNamedDeclaration(implementation),
49
+ );
50
+ } else if (
51
+ hasSentryContent(
52
+ generateCode(handleErrorFunction).code,
53
+ originalEntryServerMod.$code,
54
+ )
55
+ ) {
56
+ return false;
57
+ } else {
58
+ // @ts-expect-error - string works here because the AST is proxified by magicast
59
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
60
+ handleErrorFunction.declaration.body.body.unshift(
61
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
62
+ recast.parse(HANDLE_ERROR_TEMPLATE_V2).program.body[0].body.body[0],
63
+ );
64
+ }
65
+
66
+ return true;
67
+ }
@@ -0,0 +1,91 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+
3
+ import * as recast from 'recast';
4
+ import * as path from 'path';
5
+
6
+ // @ts-expect-error - clack is ESM and TS complains about that. It works though
7
+ import clack from '@clack/prompts';
8
+ import chalk from 'chalk';
9
+
10
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
11
+ import { builders, generateCode, loadFile, writeFile } from 'magicast';
12
+
13
+ export async function instrumentRootRouteV1(
14
+ rootFileName: string,
15
+ ): Promise<void> {
16
+ try {
17
+ const rootRouteAst = await loadFile(
18
+ path.join(process.cwd(), 'app', rootFileName),
19
+ );
20
+
21
+ rootRouteAst.imports.$add({
22
+ from: '@sentry/remix',
23
+ imported: 'withSentry',
24
+ local: 'withSentry',
25
+ });
26
+
27
+ recast.visit(rootRouteAst.$ast, {
28
+ visitExportDefaultDeclaration(path) {
29
+ /* eslint-disable @typescript-eslint/no-unsafe-member-access */
30
+ if (path.value.declaration.type === 'FunctionDeclaration') {
31
+ // Move the function declaration just before the default export
32
+ path.insertBefore(path.value.declaration);
33
+
34
+ // Get the name of the function to be wrapped
35
+ const functionName: string = path.value.declaration.id.name as string;
36
+
37
+ // Create the wrapped function call
38
+ const functionCall = recast.types.builders.callExpression(
39
+ recast.types.builders.identifier('withSentry'),
40
+ [recast.types.builders.identifier(functionName)],
41
+ );
42
+
43
+ // Replace the default export with the wrapped function call
44
+ path.value.declaration = functionCall;
45
+ } else if (path.value.declaration.type === 'Identifier') {
46
+ const rootRouteExport = rootRouteAst.exports.default;
47
+
48
+ const expressionToWrap = generateCode(
49
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
50
+ rootRouteExport.$ast,
51
+ ).code;
52
+
53
+ rootRouteAst.exports.default = builders.raw(
54
+ `withSentry(${expressionToWrap})`,
55
+ );
56
+ } else {
57
+ clack.log.warn(
58
+ chalk.yellow(
59
+ `Couldn't instrument ${chalk.bold(
60
+ rootFileName,
61
+ )} automatically. Wrap your default export with: ${chalk.dim(
62
+ 'withSentry()',
63
+ )}\n`,
64
+ ),
65
+ );
66
+ }
67
+
68
+ this.traverse(path);
69
+ /* eslint-enable @typescript-eslint/no-unsafe-member-access */
70
+ },
71
+ });
72
+
73
+ await writeFile(
74
+ rootRouteAst.$ast,
75
+ path.join(process.cwd(), 'app', rootFileName),
76
+ );
77
+ } catch (e: unknown) {
78
+ // eslint-disable-next-line no-console
79
+ console.error(e);
80
+ clack.log.warn(
81
+ chalk.yellow(
82
+ `Something went wrong writing to ${chalk.bold(rootFileName)}`,
83
+ ),
84
+ );
85
+ clack.log.info(
86
+ `Please put the following code snippet into ${chalk.bold(
87
+ rootFileName,
88
+ )}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
89
+ );
90
+ }
91
+ }
@@ -0,0 +1,84 @@
1
+ /* eslint-disable @typescript-eslint/no-unsafe-assignment */
2
+
3
+ import * as recast from 'recast';
4
+ import * as path from 'path';
5
+
6
+ import type { ExportNamedDeclaration, Program } from '@babel/types';
7
+
8
+ // @ts-expect-error - magicast is ESM and TS complains about that. It works though
9
+ import { loadFile, writeFile } from 'magicast';
10
+
11
+ import { ERROR_BOUNDARY_TEMPLATE_V2 } from '../templates';
12
+
13
+ export async function instrumentRootRouteV2(
14
+ rootFileName: string,
15
+ ): Promise<void> {
16
+ const rootRouteAst = await loadFile(
17
+ path.join(process.cwd(), 'app', rootFileName),
18
+ );
19
+
20
+ const exportsAst = rootRouteAst.exports.$ast as Program;
21
+
22
+ const namedExports = exportsAst.body.filter(
23
+ (node) => node.type === 'ExportNamedDeclaration',
24
+ ) as ExportNamedDeclaration[];
25
+
26
+ let foundErrorBoundary = false;
27
+
28
+ namedExports.forEach((namedExport) => {
29
+ const declaration = namedExport.declaration;
30
+
31
+ if (!declaration) {
32
+ return;
33
+ }
34
+
35
+ if (declaration.type === 'FunctionDeclaration') {
36
+ if (declaration.id?.name === 'ErrorBoundary') {
37
+ foundErrorBoundary = true;
38
+ }
39
+ } else if (declaration.type === 'VariableDeclaration') {
40
+ const declarations = declaration.declarations;
41
+
42
+ declarations.forEach((declaration) => {
43
+ // @ts-expect-error - id should always have a name in this case
44
+ if (declaration.id?.name === 'ErrorBoundary') {
45
+ foundErrorBoundary = true;
46
+ }
47
+ });
48
+ }
49
+ });
50
+
51
+ if (!foundErrorBoundary) {
52
+ rootRouteAst.imports.$add({
53
+ from: '@sentry/remix',
54
+ imported: 'captureRemixErrorBoundaryError',
55
+ local: 'captureRemixErrorBoundaryError',
56
+ });
57
+
58
+ rootRouteAst.imports.$add({
59
+ from: '@remix-run/react',
60
+ imported: 'useRouteError',
61
+ local: 'useRouteError',
62
+ });
63
+
64
+ recast.visit(rootRouteAst.$ast, {
65
+ visitExportDefaultDeclaration(path) {
66
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
67
+ const implementation = recast.parse(ERROR_BOUNDARY_TEMPLATE_V2).program
68
+ .body[0];
69
+
70
+ path.insertBefore(
71
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
72
+ recast.types.builders.exportDeclaration(false, implementation),
73
+ );
74
+
75
+ this.traverse(path);
76
+ },
77
+ });
78
+ }
79
+
80
+ await writeFile(
81
+ rootRouteAst.$ast,
82
+ path.join(process.cwd(), 'app', rootFileName),
83
+ );
84
+ }
@@ -0,0 +1,137 @@
1
+ // @ts-expect-error - 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
+ addSentryCliRc,
7
+ askForProjectSelection,
8
+ askForSelfHosted,
9
+ askForWizardLogin,
10
+ confirmContinueEvenThoughNoGitRepo,
11
+ ensurePackageIsInstalled,
12
+ getPackageDotJson,
13
+ installPackage,
14
+ isUsingTypeScript,
15
+ printWelcome,
16
+ } from '../utils/clack-utils';
17
+ import { hasPackageInstalled } from '../utils/package-json';
18
+ import { WizardOptions } from '../utils/types';
19
+ import {
20
+ initializeSentryOnEntryClient,
21
+ initializeSentryOnEntryServer,
22
+ updateBuildScript,
23
+ instrumentRootRoute,
24
+ isRemixV2,
25
+ loadRemixConfig,
26
+ } from './sdk-setup';
27
+ import { debug } from '../utils/debug';
28
+ import { traceStep, withTelemetry } from '../telemetry';
29
+
30
+ export async function runRemixWizard(options: WizardOptions): Promise<void> {
31
+ return withTelemetry(
32
+ {
33
+ enabled: options.telemetryEnabled,
34
+ integration: 'remix',
35
+ },
36
+ () => runRemixWizardWithTelemetry(options),
37
+ );
38
+ }
39
+
40
+ async function runRemixWizardWithTelemetry(
41
+ options: WizardOptions,
42
+ ): Promise<void> {
43
+ printWelcome({
44
+ wizardName: 'Sentry Remix Wizard',
45
+ promoCode: options.promoCode,
46
+ telemetryEnabled: options.telemetryEnabled,
47
+ });
48
+
49
+ await confirmContinueEvenThoughNoGitRepo();
50
+
51
+ const remixConfig = await loadRemixConfig();
52
+ const packageJson = await getPackageDotJson();
53
+
54
+ // We expect `@remix-run/dev` to be installed for every Remix project
55
+ await ensurePackageIsInstalled(packageJson, '@remix-run/dev', 'Remix');
56
+
57
+ const { url: sentryUrl } = await askForSelfHosted(options.url);
58
+
59
+ const { projects, apiKeys } = await askForWizardLogin({
60
+ promoCode: options.promoCode,
61
+ url: sentryUrl,
62
+ platform: 'javascript-remix',
63
+ });
64
+
65
+ const selectedProject = await askForProjectSelection(projects);
66
+
67
+ await traceStep('Install Sentry SDK', () =>
68
+ installPackage({
69
+ packageName: '@sentry/remix',
70
+ alreadyInstalled: hasPackageInstalled('@sentry/remix', packageJson),
71
+ }),
72
+ );
73
+
74
+ const dsn = selectedProject.keys[0].dsn.public;
75
+
76
+ const isTS = isUsingTypeScript();
77
+ const isV2 = isRemixV2(remixConfig, packageJson);
78
+
79
+ await addSentryCliRc(
80
+ apiKeys.token,
81
+ selectedProject.organization.slug,
82
+ selectedProject.name,
83
+ );
84
+
85
+ await traceStep('Update build script for sourcemap uploads', async () => {
86
+ try {
87
+ await updateBuildScript();
88
+ } catch (e) {
89
+ clack.log
90
+ .warn(`Could not update build script to generate and upload sourcemaps.
91
+ Please update your build script manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/sourcemaps/`);
92
+ debug(e);
93
+ }
94
+ });
95
+
96
+ await traceStep('Instrument root route', async () => {
97
+ try {
98
+ await instrumentRootRoute(isV2, isTS);
99
+ } catch (e) {
100
+ clack.log.warn(`Could not instrument root route.
101
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
102
+ debug(e);
103
+ }
104
+ });
105
+
106
+ await traceStep('Initialize Sentry on client entry', async () => {
107
+ try {
108
+ await initializeSentryOnEntryClient(dsn, isTS);
109
+ } catch (e) {
110
+ clack.log.warn(`Could not initialize Sentry on client entry.
111
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
112
+ debug(e);
113
+ }
114
+ });
115
+
116
+ await traceStep('Initialize Sentry on server entry', async () => {
117
+ try {
118
+ await initializeSentryOnEntryServer(dsn, isTS, isV2);
119
+ } catch (e) {
120
+ clack.log.warn(`Could not initialize Sentry on server entry.
121
+ Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/`);
122
+ debug(e);
123
+ }
124
+ });
125
+
126
+ clack.outro(`
127
+ ${chalk.green(
128
+ 'Sentry has been successfully configured for your Remix project.',
129
+ )}
130
+
131
+ ${chalk.cyan('You can now deploy your project to see Sentry in action.')}
132
+
133
+ ${chalk.cyan(
134
+ `To learn more about how to use Sentry with Remix, visit our documentation:
135
+ https://docs.sentry.io/platforms/javascript/guides/remix/`,
136
+ )}`);
137
+ }