@sentry/wizard 3.11.0 → 3.12.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.
- package/CHANGELOG.md +7 -0
- package/dist/package.json +1 -1
- package/dist/src/android/android-wizard.js +8 -0
- package/dist/src/android/android-wizard.js.map +1 -1
- package/dist/src/android/code-tools.d.ts +8 -0
- package/dist/src/android/code-tools.js +20 -8
- package/dist/src/android/code-tools.js.map +1 -1
- package/dist/src/android/gradle.js +6 -1
- package/dist/src/android/gradle.js.map +1 -1
- package/dist/src/sourcemaps/tools/vite.js +36 -111
- package/dist/src/sourcemaps/tools/vite.js.map +1 -1
- package/dist/src/sourcemaps/tools/webpack.d.ts +6 -1
- package/dist/src/sourcemaps/tools/webpack.js +290 -25
- package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
- package/dist/src/sveltekit/sdk-setup.js +2 -2
- package/dist/src/sveltekit/sdk-setup.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +7 -3
- package/dist/src/utils/ast-utils.js +20 -5
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack-utils.d.ts +52 -0
- package/dist/src/utils/clack-utils.js +169 -12
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/test/android/code-tools.test.d.ts +1 -0
- package/dist/test/android/code-tools.test.js +34 -0
- package/dist/test/android/code-tools.test.js.map +1 -0
- package/dist/test/sourcemaps/tools/webpack.test.d.ts +1 -0
- package/dist/test/sourcemaps/tools/webpack.test.js +179 -0
- package/dist/test/sourcemaps/tools/webpack.test.js.map +1 -0
- package/dist/test/utils/ast-utils.test.js +42 -7
- package/dist/test/utils/ast-utils.test.js.map +1 -1
- package/dist/test/utils/clack-utils.test.d.ts +1 -0
- package/dist/test/utils/clack-utils.test.js +200 -0
- package/dist/test/utils/clack-utils.test.js.map +1 -0
- package/package.json +1 -1
- package/src/android/android-wizard.ts +8 -0
- package/src/android/code-tools.ts +21 -7
- package/src/android/gradle.ts +6 -1
- package/src/sourcemaps/tools/vite.ts +22 -88
- package/src/sourcemaps/tools/webpack.ts +369 -30
- package/src/sveltekit/sdk-setup.ts +6 -2
- package/src/utils/ast-utils.ts +23 -7
- package/src/utils/clack-utils.ts +150 -2
- package/test/android/code-tools.test.ts +49 -0
- package/test/sourcemaps/tools/webpack.test.ts +303 -0
- package/test/utils/ast-utils.test.ts +28 -9
- package/test/utils/clack-utils.test.ts +142 -0
|
@@ -15,8 +15,11 @@ import chalk from 'chalk';
|
|
|
15
15
|
import {
|
|
16
16
|
abortIfCancelled,
|
|
17
17
|
addDotEnvSentryBuildPluginFile,
|
|
18
|
+
askForToolConfigPath,
|
|
19
|
+
createNewConfigFile,
|
|
18
20
|
getPackageDotJson,
|
|
19
21
|
installPackage,
|
|
22
|
+
showCopyPasteInstructions,
|
|
20
23
|
} from '../../utils/clack-utils';
|
|
21
24
|
import { hasPackageInstalled } from '../../utils/package-json';
|
|
22
25
|
|
|
@@ -91,58 +94,43 @@ export const configureVitePlugin: SourceMapUploadToolConfigurationFunction =
|
|
|
91
94
|
});
|
|
92
95
|
|
|
93
96
|
const viteConfigPath =
|
|
94
|
-
findFile(path.resolve(process.cwd(), 'vite.config'))
|
|
95
|
-
(await
|
|
97
|
+
findFile(path.resolve(process.cwd(), 'vite.config')) ??
|
|
98
|
+
(await askForToolConfigPath('Vite', 'vite.config.js'));
|
|
96
99
|
|
|
97
100
|
let successfullyAdded = false;
|
|
98
101
|
if (viteConfigPath) {
|
|
99
102
|
successfullyAdded = await addVitePluginToConfig(viteConfigPath, options);
|
|
100
103
|
} else {
|
|
101
|
-
successfullyAdded = await
|
|
104
|
+
successfullyAdded = await createNewConfigFile(
|
|
105
|
+
path.join(process.cwd(), 'vite.config.js'),
|
|
106
|
+
getViteConfigSnippet(options, false),
|
|
107
|
+
'More information about vite configs: https://vitejs.dev/config/',
|
|
108
|
+
);
|
|
109
|
+
Sentry.setTag(
|
|
110
|
+
'created-new-config',
|
|
111
|
+
successfullyAdded ? 'success' : 'fail',
|
|
112
|
+
);
|
|
102
113
|
}
|
|
103
114
|
|
|
104
115
|
if (successfullyAdded) {
|
|
116
|
+
clack.log.info(
|
|
117
|
+
`We recommend checking the ${
|
|
118
|
+
viteConfigPath ? 'modified' : 'added'
|
|
119
|
+
} file after the wizard finished to ensure it works with your build setup.`,
|
|
120
|
+
);
|
|
121
|
+
|
|
105
122
|
Sentry.setTag('ast-mod', 'success');
|
|
106
123
|
} else {
|
|
107
124
|
Sentry.setTag('ast-mod', 'fail');
|
|
108
125
|
await showCopyPasteInstructions(
|
|
109
126
|
path.basename(viteConfigPath || 'vite.config.js'),
|
|
110
|
-
options,
|
|
127
|
+
getViteConfigSnippet(options, true),
|
|
111
128
|
);
|
|
112
129
|
}
|
|
113
130
|
|
|
114
131
|
await addDotEnvSentryBuildPluginFile(options.authToken);
|
|
115
132
|
};
|
|
116
133
|
|
|
117
|
-
async function createNewViteConfig(
|
|
118
|
-
options: SourceMapUploadToolConfigurationOptions,
|
|
119
|
-
): Promise<boolean> {
|
|
120
|
-
try {
|
|
121
|
-
await fs.promises.writeFile(
|
|
122
|
-
'vite.config.js',
|
|
123
|
-
getViteConfigSnippet(options, false),
|
|
124
|
-
);
|
|
125
|
-
Sentry.setTag('created-new-config', 'success');
|
|
126
|
-
return true;
|
|
127
|
-
} catch (e) {
|
|
128
|
-
debug(e);
|
|
129
|
-
Sentry.setTag('created-new-config', 'fail');
|
|
130
|
-
clack.log.warn(
|
|
131
|
-
`Could not create a new ${chalk.cyan(
|
|
132
|
-
'vite.config.js',
|
|
133
|
-
)} file. Please create one manually and follow the instructions below.`,
|
|
134
|
-
);
|
|
135
|
-
|
|
136
|
-
clack.log.info(
|
|
137
|
-
chalk.gray(
|
|
138
|
-
'More information about vite configs: https://vitejs.dev/config/',
|
|
139
|
-
),
|
|
140
|
-
);
|
|
141
|
-
|
|
142
|
-
return false;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
134
|
export async function addVitePluginToConfig(
|
|
147
135
|
viteConfigPath: string,
|
|
148
136
|
options: SourceMapUploadToolConfigurationOptions,
|
|
@@ -156,7 +144,7 @@ export async function addVitePluginToConfig(
|
|
|
156
144
|
|
|
157
145
|
const mod = parseModule(viteConfigContent);
|
|
158
146
|
|
|
159
|
-
if (hasSentryContent(mod)) {
|
|
147
|
+
if (hasSentryContent(mod.$ast as t.Program)) {
|
|
160
148
|
const shouldContinue = await abortIfCancelled(
|
|
161
149
|
clack.select({
|
|
162
150
|
message: `${prettyViteConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,
|
|
@@ -215,60 +203,6 @@ export async function addVitePluginToConfig(
|
|
|
215
203
|
}
|
|
216
204
|
}
|
|
217
205
|
|
|
218
|
-
async function showCopyPasteInstructions(
|
|
219
|
-
viteConfigFilename: string,
|
|
220
|
-
options: SourceMapUploadToolConfigurationOptions,
|
|
221
|
-
) {
|
|
222
|
-
clack.log.step(
|
|
223
|
-
`Add the following code to your ${chalk.cyan(viteConfigFilename)} file:`,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
// Intentionally logging directly to console here so that the code can be copied/pasted directly
|
|
227
|
-
// eslint-disable-next-line no-console
|
|
228
|
-
console.log(`\n${getViteConfigSnippet(options, true)}`);
|
|
229
|
-
|
|
230
|
-
await abortIfCancelled(
|
|
231
|
-
clack.select({
|
|
232
|
-
message: 'Did you copy the snippet above?',
|
|
233
|
-
options: [{ label: 'Yes, continue!', value: true }],
|
|
234
|
-
initialValue: true,
|
|
235
|
-
}),
|
|
236
|
-
);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
async function askForViteConfigPath(): Promise<string | undefined> {
|
|
240
|
-
const hasViteConfig = await abortIfCancelled(
|
|
241
|
-
clack.confirm({
|
|
242
|
-
message: `Do you have a vite config file (e.g. ${chalk.cyan(
|
|
243
|
-
'vite.config.js',
|
|
244
|
-
)}?`,
|
|
245
|
-
initialValue: true,
|
|
246
|
-
}),
|
|
247
|
-
);
|
|
248
|
-
|
|
249
|
-
if (!hasViteConfig) {
|
|
250
|
-
return undefined;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return await abortIfCancelled(
|
|
254
|
-
clack.text({
|
|
255
|
-
message: 'Please enter the path to your vite config file:',
|
|
256
|
-
placeholder: `.${path.sep}vite.config.js`,
|
|
257
|
-
validate: (value) => {
|
|
258
|
-
if (!value) {
|
|
259
|
-
return 'Please enter a path.';
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
fs.accessSync(value);
|
|
264
|
-
} catch {
|
|
265
|
-
return 'Could not access the file at this path.';
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
}),
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
|
|
272
206
|
function enableSourcemapGeneration(program: t.Program): boolean {
|
|
273
207
|
const configObj = getViteConfigObject(program);
|
|
274
208
|
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
import * as path from 'path';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
|
|
1
4
|
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
2
|
-
import
|
|
5
|
+
import * as clack from '@clack/prompts';
|
|
3
6
|
import chalk from 'chalk';
|
|
7
|
+
|
|
8
|
+
import * as recast from 'recast';
|
|
9
|
+
import x = recast.types;
|
|
10
|
+
import t = x.namedTypes;
|
|
11
|
+
|
|
12
|
+
import * as Sentry from '@sentry/node';
|
|
13
|
+
|
|
4
14
|
import {
|
|
5
15
|
abortIfCancelled,
|
|
6
16
|
addDotEnvSentryBuildPluginFile,
|
|
17
|
+
askForToolConfigPath,
|
|
18
|
+
createNewConfigFile,
|
|
7
19
|
getPackageDotJson,
|
|
8
20
|
installPackage,
|
|
21
|
+
showCopyPasteInstructions,
|
|
9
22
|
} from '../../utils/clack-utils';
|
|
10
23
|
import { hasPackageInstalled } from '../../utils/package-json';
|
|
11
24
|
|
|
@@ -14,28 +27,57 @@ import {
|
|
|
14
27
|
SourceMapUploadToolConfigurationOptions,
|
|
15
28
|
} from './types';
|
|
16
29
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
${chalk.greenBright(
|
|
20
|
-
'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");',
|
|
21
|
-
)}
|
|
30
|
+
import { findFile, hasSentryContent } from '../../utils/ast-utils';
|
|
31
|
+
import { debug } from '../../utils/debug';
|
|
22
32
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
33
|
+
const getCodeSnippet = (
|
|
34
|
+
options: SourceMapUploadToolConfigurationOptions,
|
|
35
|
+
colors: boolean,
|
|
36
|
+
) => {
|
|
37
|
+
const rawImportStmt =
|
|
38
|
+
'const { sentryWebpackPlugin } = require("@sentry/webpack-plugin");';
|
|
39
|
+
const rawGenerateSourceMapsOption =
|
|
40
|
+
'devtool: "source-map", // Source map generation must be turned on';
|
|
41
|
+
const rawSentryWebpackPluginFunction = `sentryWebpackPlugin({
|
|
30
42
|
authToken: process.env.SENTRY_AUTH_TOKEN,
|
|
31
43
|
org: "${options.orgSlug}",
|
|
32
44
|
project: "${options.projectSlug}",${
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
})
|
|
36
|
-
|
|
45
|
+
options.selfHosted ? `\n url: "${options.url}",` : ''
|
|
46
|
+
}
|
|
47
|
+
})`;
|
|
48
|
+
|
|
49
|
+
const importStmt = colors ? chalk.greenBright(rawImportStmt) : rawImportStmt;
|
|
50
|
+
const generateSourceMapsOption = colors
|
|
51
|
+
? chalk.greenBright(rawGenerateSourceMapsOption)
|
|
52
|
+
: rawGenerateSourceMapsOption;
|
|
53
|
+
const sentryWebpackPluginFunction = colors
|
|
54
|
+
? chalk.greenBright(rawSentryWebpackPluginFunction)
|
|
55
|
+
: rawSentryWebpackPluginFunction;
|
|
56
|
+
|
|
57
|
+
const code = getWebpackConfigContent(
|
|
58
|
+
importStmt,
|
|
59
|
+
generateSourceMapsOption,
|
|
60
|
+
sentryWebpackPluginFunction,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
return colors ? chalk.gray(code) : code;
|
|
37
64
|
};
|
|
38
|
-
|
|
65
|
+
|
|
66
|
+
const getWebpackConfigContent = (
|
|
67
|
+
importStmt: string,
|
|
68
|
+
generateSourceMapsOption: string,
|
|
69
|
+
sentryWebpackPluginFunction: string,
|
|
70
|
+
) => `${importStmt}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
// ... other options
|
|
74
|
+
${generateSourceMapsOption},
|
|
75
|
+
plugins: [
|
|
76
|
+
// Put the Sentry Webpack plugin after all other plugins
|
|
77
|
+
${sentryWebpackPluginFunction},
|
|
78
|
+
],
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
39
81
|
|
|
40
82
|
export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
|
|
41
83
|
async (options) => {
|
|
@@ -47,21 +89,318 @@ export const configureWebPackPlugin: SourceMapUploadToolConfigurationFunction =
|
|
|
47
89
|
),
|
|
48
90
|
});
|
|
49
91
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
92
|
+
const webpackConfigPath =
|
|
93
|
+
findFile(path.resolve(process.cwd(), 'webpack.config')) ??
|
|
94
|
+
(await askForToolConfigPath('Webpack', 'webpack.config.js'));
|
|
95
|
+
|
|
96
|
+
let successfullyAdded = false;
|
|
97
|
+
if (webpackConfigPath) {
|
|
98
|
+
successfullyAdded = await modifyWebpackConfig(webpackConfigPath, options);
|
|
99
|
+
} else {
|
|
100
|
+
successfullyAdded = await createNewConfigFile(
|
|
101
|
+
path.join(process.cwd(), 'webpack.config.js'),
|
|
102
|
+
getCodeSnippet(options, false),
|
|
103
|
+
'More information about Webpack configs: https://vitejs.dev/config/',
|
|
104
|
+
);
|
|
105
|
+
Sentry.setTag(
|
|
106
|
+
'created-new-config',
|
|
107
|
+
successfullyAdded ? 'success' : 'fail',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (successfullyAdded) {
|
|
112
|
+
clack.log.info(
|
|
113
|
+
`We recommend checking the ${
|
|
114
|
+
webpackConfigPath ? 'modified' : 'added'
|
|
115
|
+
} file after the wizard finished to ensure it works with your build setup.`,
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
Sentry.setTag('ast-mod', 'success');
|
|
119
|
+
} else {
|
|
120
|
+
Sentry.setTag('ast-mod', 'fail');
|
|
121
|
+
await showCopyPasteInstructions(
|
|
122
|
+
path.basename(webpackConfigPath || 'webpack.config.js'),
|
|
123
|
+
getCodeSnippet(options, true),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
await addDotEnvSentryBuildPluginFile(options.authToken);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Modifies a webpack config file to enable source map generation and add the Sentry webpack plugin
|
|
132
|
+
* exported only for testing
|
|
133
|
+
*/
|
|
134
|
+
export async function modifyWebpackConfig(
|
|
135
|
+
webpackConfigPath: string,
|
|
136
|
+
options: SourceMapUploadToolConfigurationOptions,
|
|
137
|
+
): Promise<boolean> {
|
|
138
|
+
try {
|
|
139
|
+
const webpackConfig = await fs.promises.readFile(webpackConfigPath, {
|
|
140
|
+
encoding: 'utf-8',
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
const prettyConfigFilename = chalk.cyan(path.basename(webpackConfigPath));
|
|
144
|
+
|
|
145
|
+
// no idea why recast returns any here, this is dumb :/
|
|
146
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
147
|
+
const program = recast.parse(webpackConfig.toString()).program as t.Program;
|
|
148
|
+
|
|
149
|
+
if (!(await shouldModifyWebpackConfig(program, prettyConfigFilename))) {
|
|
150
|
+
// Sentry tag is set in shouldModifyWebpackConfig
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const exportStmt = getCjsModuleExports(program);
|
|
155
|
+
if (!exportStmt) {
|
|
156
|
+
// We only care about CJS at the moment since it's probably the most widely used format for webpack configs.
|
|
157
|
+
debug(`Could not find module.exports = {...} in ${webpackConfigPath}.`);
|
|
158
|
+
Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const configObject = getWebpackConfigObject(exportStmt, program);
|
|
163
|
+
|
|
164
|
+
if (!configObject) {
|
|
165
|
+
debug(`Couldn't find config object in ${webpackConfigPath}`);
|
|
166
|
+
Sentry.setTag('ast-mod-fail-reason', 'config-object-not-found');
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
53
169
|
|
|
54
|
-
|
|
55
|
-
// eslint-disable-next-line no-console
|
|
56
|
-
console.log(getCodeSnippet(options));
|
|
170
|
+
const enabledSourcemaps = enableSourcemapsGeneration(configObject);
|
|
57
171
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
172
|
+
if (enabledSourcemaps) {
|
|
173
|
+
clack.log.success(
|
|
174
|
+
`Enabled source map generation in ${prettyConfigFilename}.`,
|
|
175
|
+
);
|
|
176
|
+
} else {
|
|
177
|
+
clack.log.warn(
|
|
178
|
+
`Couldn't enable source maps generation in ${prettyConfigFilename} Please follow the instructions below.`,
|
|
179
|
+
);
|
|
180
|
+
Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
|
|
181
|
+
return false;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const addedPlugin = addSentryWebpackPlugin(program, configObject, options);
|
|
185
|
+
if (addedPlugin) {
|
|
186
|
+
clack.log.success(
|
|
187
|
+
`Added Sentry webpack plugin to ${prettyConfigFilename}.`,
|
|
188
|
+
);
|
|
189
|
+
} else {
|
|
190
|
+
clack.log.warn(
|
|
191
|
+
`Couldn't add Sentry webpack plugin to ${prettyConfigFilename}. Please follow the instructions below.`,
|
|
192
|
+
);
|
|
193
|
+
Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const code = recast.print(program).code;
|
|
198
|
+
await fs.promises.writeFile(webpackConfigPath, code);
|
|
199
|
+
|
|
200
|
+
return true;
|
|
201
|
+
} catch (e) {
|
|
202
|
+
Sentry.setTag('ast-mod-fail-reason', 'insertion-fail');
|
|
203
|
+
debug(e);
|
|
204
|
+
return false;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async function shouldModifyWebpackConfig(
|
|
209
|
+
program: t.Program,
|
|
210
|
+
prettyConfigFilename: string,
|
|
211
|
+
) {
|
|
212
|
+
if (hasSentryContent(program)) {
|
|
213
|
+
const shouldContinue = await abortIfCancelled(
|
|
214
|
+
clack.select({
|
|
215
|
+
message: `Seems like ${prettyConfigFilename} already contains Sentry-related code. Should the wizard modify it anyway?`,
|
|
216
|
+
options: [
|
|
217
|
+
{
|
|
218
|
+
label: 'Yes, add the Sentry Webpack plugin',
|
|
219
|
+
value: true,
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
label: 'No, show me instructions to manually add the plugin',
|
|
223
|
+
value: false,
|
|
224
|
+
},
|
|
225
|
+
],
|
|
62
226
|
initialValue: true,
|
|
63
227
|
}),
|
|
64
228
|
);
|
|
65
229
|
|
|
66
|
-
|
|
67
|
-
|
|
230
|
+
if (!shouldContinue) {
|
|
231
|
+
Sentry.setTag('ast-mod-fail-reason', 'has-sentry-content');
|
|
232
|
+
return false;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return true;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function addSentryWebpackPlugin(
|
|
240
|
+
program: t.Program,
|
|
241
|
+
configObj: t.ObjectExpression,
|
|
242
|
+
options: SourceMapUploadToolConfigurationOptions,
|
|
243
|
+
) {
|
|
244
|
+
const b = addSentryWebpackPluginImport(program);
|
|
245
|
+
|
|
246
|
+
const sentryPluginCall = b.callExpression(
|
|
247
|
+
b.identifier('sentryWebpackPlugin'),
|
|
248
|
+
[
|
|
249
|
+
b.objectExpression([
|
|
250
|
+
b.objectProperty(
|
|
251
|
+
b.identifier('authToken'),
|
|
252
|
+
b.identifier('process.env.SENTRY_AUTH_TOKEN'),
|
|
253
|
+
),
|
|
254
|
+
b.objectProperty(b.identifier('org'), b.stringLiteral(options.orgSlug)),
|
|
255
|
+
b.objectProperty(
|
|
256
|
+
b.identifier('project'),
|
|
257
|
+
b.stringLiteral(options.projectSlug),
|
|
258
|
+
),
|
|
259
|
+
...(options.selfHosted
|
|
260
|
+
? [
|
|
261
|
+
b.objectProperty(
|
|
262
|
+
b.identifier('url'),
|
|
263
|
+
b.stringLiteral(options.url),
|
|
264
|
+
),
|
|
265
|
+
]
|
|
266
|
+
: []),
|
|
267
|
+
]),
|
|
268
|
+
],
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
const pluginsProp = configObj.properties.find(
|
|
272
|
+
(p): p is t.Property =>
|
|
273
|
+
p.type === 'Property' &&
|
|
274
|
+
p.key.type === 'Identifier' &&
|
|
275
|
+
p.key.name === 'plugins',
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
if (pluginsProp) {
|
|
279
|
+
if (pluginsProp.value.type === 'ArrayExpression') {
|
|
280
|
+
pluginsProp.value.elements.push(sentryPluginCall);
|
|
281
|
+
} else {
|
|
282
|
+
pluginsProp.value = b.arrayExpression([sentryPluginCall]);
|
|
283
|
+
}
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
configObj.properties.push(
|
|
288
|
+
b.objectProperty(
|
|
289
|
+
b.identifier('plugins'),
|
|
290
|
+
b.arrayExpression([sentryPluginCall]),
|
|
291
|
+
),
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
return true;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function addSentryWebpackPluginImport(program: t.Program) {
|
|
298
|
+
const b = recast.types.builders;
|
|
299
|
+
|
|
300
|
+
const sentryPluginRequireStmt = b.variableDeclaration('const', [
|
|
301
|
+
b.variableDeclarator(
|
|
302
|
+
b.objectPattern([
|
|
303
|
+
b.objectProperty.from({
|
|
304
|
+
key: b.identifier('sentryWebpackPlugin'),
|
|
305
|
+
value: b.identifier('sentryWebpackPlugin'),
|
|
306
|
+
shorthand: true,
|
|
307
|
+
}),
|
|
308
|
+
]),
|
|
309
|
+
b.callExpression(b.identifier('require'), [
|
|
310
|
+
b.stringLiteral('@sentry/webpack-plugin'),
|
|
311
|
+
]),
|
|
312
|
+
),
|
|
313
|
+
]);
|
|
314
|
+
|
|
315
|
+
program.body.unshift(sentryPluginRequireStmt);
|
|
316
|
+
return b;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function enableSourcemapsGeneration(configObj: t.ObjectExpression): boolean {
|
|
320
|
+
const b = recast.types.builders;
|
|
321
|
+
|
|
322
|
+
const devtoolProp = configObj.properties.find(
|
|
323
|
+
(p): p is t.Property =>
|
|
324
|
+
p.type === 'Property' &&
|
|
325
|
+
p.key.type === 'Identifier' &&
|
|
326
|
+
p.key.name === 'devtool',
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (devtoolProp) {
|
|
330
|
+
// devtool can have quite a lot of source maps values.
|
|
331
|
+
// see: https://webpack.js.org/configuration/devtool/#devtool
|
|
332
|
+
// For Sentry to work best, we should set it to "source-map" or "hidden-source-map"
|
|
333
|
+
// Heuristic:
|
|
334
|
+
// - all values that contain "hidden" will be set to "hidden-source-map"
|
|
335
|
+
// - all other values will be set to "source-map"
|
|
336
|
+
if (
|
|
337
|
+
(devtoolProp.value.type === 'Literal' ||
|
|
338
|
+
devtoolProp.value.type === 'StringLiteral') &&
|
|
339
|
+
devtoolProp.value.value?.toString().startsWith('hidden-')
|
|
340
|
+
) {
|
|
341
|
+
devtoolProp.value = b.stringLiteral('hidden-source-map');
|
|
342
|
+
} else {
|
|
343
|
+
devtoolProp.value = b.stringLiteral('source-map');
|
|
344
|
+
}
|
|
345
|
+
return true;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
configObj.properties.push(
|
|
349
|
+
b.objectProperty(b.identifier('devtool'), b.stringLiteral('source-map')),
|
|
350
|
+
);
|
|
351
|
+
|
|
352
|
+
return true;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function getWebpackConfigObject(
|
|
356
|
+
moduleExports: t.AssignmentExpression,
|
|
357
|
+
program: t.Program,
|
|
358
|
+
): t.ObjectExpression | undefined {
|
|
359
|
+
const rhs = moduleExports.right;
|
|
360
|
+
if (rhs.type === 'ObjectExpression') {
|
|
361
|
+
return rhs;
|
|
362
|
+
}
|
|
363
|
+
if (rhs.type === 'Identifier') {
|
|
364
|
+
const configId = rhs.name;
|
|
365
|
+
|
|
366
|
+
const configDeclaration = program.body.find(
|
|
367
|
+
(s): s is t.VariableDeclaration =>
|
|
368
|
+
s.type === 'VariableDeclaration' &&
|
|
369
|
+
!!s.declarations.find(
|
|
370
|
+
(d) =>
|
|
371
|
+
d.type === 'VariableDeclarator' &&
|
|
372
|
+
d.id.type === 'Identifier' &&
|
|
373
|
+
d.id.name === configId,
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
const declarator = configDeclaration?.declarations.find(
|
|
378
|
+
(d): d is t.VariableDeclarator =>
|
|
379
|
+
d.type === 'VariableDeclarator' &&
|
|
380
|
+
d.id.type === 'Identifier' &&
|
|
381
|
+
d.id.name === configId,
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
return declarator?.init?.type === 'ObjectExpression'
|
|
385
|
+
? declarator.init
|
|
386
|
+
: undefined;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
return undefined;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function getCjsModuleExports(
|
|
393
|
+
program: t.Program,
|
|
394
|
+
): t.AssignmentExpression | undefined {
|
|
395
|
+
const moduleExports = program.body.find(
|
|
396
|
+
(s): s is t.ExpressionStatement =>
|
|
397
|
+
s.type === 'ExpressionStatement' &&
|
|
398
|
+
s.expression.type === 'AssignmentExpression' &&
|
|
399
|
+
s.expression.left.type === 'MemberExpression' &&
|
|
400
|
+
s.expression.left.object.type === 'Identifier' &&
|
|
401
|
+
s.expression.left.object.name === 'module' &&
|
|
402
|
+
s.expression.left.property.type === 'Identifier' &&
|
|
403
|
+
s.expression.left.property.name === 'exports',
|
|
404
|
+
);
|
|
405
|
+
return moduleExports?.expression as t.AssignmentExpression;
|
|
406
|
+
}
|
|
@@ -17,6 +17,10 @@ import { abortIfCancelled, isUsingTypeScript } from '../utils/clack-utils';
|
|
|
17
17
|
import { debug } from '../utils/debug';
|
|
18
18
|
import { findFile, hasSentryContent } from '../utils/ast-utils';
|
|
19
19
|
|
|
20
|
+
import * as recast from 'recast';
|
|
21
|
+
import x = recast.types;
|
|
22
|
+
import t = x.namedTypes;
|
|
23
|
+
|
|
20
24
|
const SVELTE_CONFIG_FILE = 'svelte.config.js';
|
|
21
25
|
|
|
22
26
|
export type PartialSvelteConfig = {
|
|
@@ -139,7 +143,7 @@ async function mergeHooksFile(
|
|
|
139
143
|
dsn: string,
|
|
140
144
|
): Promise<void> {
|
|
141
145
|
const originalHooksMod = await loadFile(hooksFile);
|
|
142
|
-
if (hasSentryContent(originalHooksMod)) {
|
|
146
|
+
if (hasSentryContent(originalHooksMod.$ast as t.Program)) {
|
|
143
147
|
// We don't want to mess with files that already have Sentry content.
|
|
144
148
|
// Let's just bail out at this point.
|
|
145
149
|
clack.log.warn(
|
|
@@ -399,7 +403,7 @@ async function modifyViteConfig(
|
|
|
399
403
|
try {
|
|
400
404
|
const viteModule = parseModule(viteConfigContent);
|
|
401
405
|
|
|
402
|
-
if (hasSentryContent(viteModule)) {
|
|
406
|
+
if (hasSentryContent(viteModule.$ast as t.Program)) {
|
|
403
407
|
clack.log.warn(
|
|
404
408
|
`File ${chalk.cyan(
|
|
405
409
|
path.basename(viteConfigPath),
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import * as fs from 'fs';
|
|
2
|
-
|
|
3
|
-
import
|
|
2
|
+
|
|
3
|
+
import * as recast from 'recast';
|
|
4
|
+
import x = recast.types;
|
|
5
|
+
import t = x.namedTypes;
|
|
4
6
|
|
|
5
7
|
/**
|
|
6
8
|
* Checks if a file where we don't know its concrete file type yet exists
|
|
@@ -8,15 +10,29 @@ import { ProxifiedModule } from 'magicast';
|
|
|
8
10
|
*/
|
|
9
11
|
export function findFile(
|
|
10
12
|
filePath: string,
|
|
11
|
-
fileTypes: string[] = ['.js', '.ts', '.mjs'],
|
|
13
|
+
fileTypes: string[] = ['.js', '.ts', '.mjs', '.cjs'],
|
|
12
14
|
): string | undefined {
|
|
13
15
|
return fileTypes
|
|
14
16
|
.map((type) => `${filePath}${type}`)
|
|
15
17
|
.find((file) => fs.existsSync(file));
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
/**
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
/**
|
|
21
|
+
* checks for require('@sentry/*') syntax
|
|
22
|
+
*/
|
|
23
|
+
export function hasSentryContent(program: t.Program): boolean {
|
|
24
|
+
let foundSentry: boolean | undefined = false;
|
|
25
|
+
recast.visit(program, {
|
|
26
|
+
visitStringLiteral(path) {
|
|
27
|
+
foundSentry = foundSentry || path.node.value.startsWith('@sentry/');
|
|
28
|
+
this.traverse(path);
|
|
29
|
+
},
|
|
30
|
+
visitLiteral(path) {
|
|
31
|
+
foundSentry =
|
|
32
|
+
foundSentry || path.node.value?.toString().startsWith('@sentry/');
|
|
33
|
+
this.traverse(path);
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
return !!foundSentry;
|
|
22
38
|
}
|