@sentry/wizard 3.19.0 → 3.20.1
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 +13 -0
- package/dist/package.json +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +26 -19
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.js +1 -1
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/remix/codemods/express-server.d.ts +6 -0
- package/dist/src/remix/codemods/express-server.js +216 -0
- package/dist/src/remix/codemods/express-server.js.map +1 -0
- package/dist/src/remix/remix-wizard.js +25 -0
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/remix/sdk-setup.d.ts +1 -0
- package/dist/src/remix/sdk-setup.js +23 -1
- package/dist/src/remix/sdk-setup.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +24 -9
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/sveltekit/templates.js +1 -1
- package/dist/src/sveltekit/templates.js.map +1 -1
- package/dist/src/utils/clack-utils.d.ts +3 -2
- package/dist/src/utils/clack-utils.js +25 -3
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/package.json +1 -1
- package/src/nextjs/nextjs-wizard.ts +25 -20
- package/src/nextjs/templates.ts +6 -13
- package/src/remix/codemods/express-server.ts +197 -0
- package/src/remix/remix-wizard.ts +20 -0
- package/src/remix/sdk-setup.ts +17 -0
- package/src/sveltekit/sveltekit-wizard.ts +49 -33
- package/src/sveltekit/templates.ts +6 -13
- package/src/utils/clack-utils.ts +23 -2
|
@@ -0,0 +1,197 @@
|
|
|
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
|
+
import * as recast from 'recast';
|
|
5
|
+
import { visit } from 'ast-types';
|
|
6
|
+
import {
|
|
7
|
+
ASTNode,
|
|
8
|
+
ProxifiedImportItem,
|
|
9
|
+
generateCode,
|
|
10
|
+
loadFile,
|
|
11
|
+
writeFile,
|
|
12
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
13
|
+
} from 'magicast';
|
|
14
|
+
import type { Program } from '@babel/types';
|
|
15
|
+
import * as fs from 'fs';
|
|
16
|
+
|
|
17
|
+
import { getInitCallInsertionIndex, hasSentryContent } from '../utils';
|
|
18
|
+
import { findFile } from '../../utils/ast-utils';
|
|
19
|
+
|
|
20
|
+
// Find `loadViteServerBuild` or `unstable_loadViteServerBuild` call inside an arrow function
|
|
21
|
+
// and replace it with await loadViteServerBuild.
|
|
22
|
+
// For context, see: https://github.com/getsentry/sentry-javascript/issues/9500
|
|
23
|
+
export function updateViteBuildParameter(node: ASTNode) {
|
|
24
|
+
const hasViteConfig = findFile('vite.config');
|
|
25
|
+
|
|
26
|
+
if (!hasViteConfig) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
visit(node, {
|
|
31
|
+
visitArrowFunctionExpression(path) {
|
|
32
|
+
if (
|
|
33
|
+
path.value.body.type === 'CallExpression' &&
|
|
34
|
+
path.value.body.callee.type === 'Identifier' &&
|
|
35
|
+
(path.value.body.callee.name === 'unstable_loadViteServerBuild' ||
|
|
36
|
+
path.value.body.callee.name === 'loadViteServerBuild')
|
|
37
|
+
) {
|
|
38
|
+
// Replace the arrow function with a call to await loadViteServerBuild
|
|
39
|
+
path.replace(recast.types.builders.awaitExpression(path.value.body));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.traverse(path);
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try to find the Express server implementation that contains `createRequestHandler` from `@remix-run/express`
|
|
48
|
+
export async function findCustomExpressServerImplementation() {
|
|
49
|
+
const possiblePaths = [
|
|
50
|
+
'server',
|
|
51
|
+
'server/index',
|
|
52
|
+
'app/server',
|
|
53
|
+
'app/server/index',
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
for (const filePath of possiblePaths) {
|
|
57
|
+
const filename = findFile(filePath);
|
|
58
|
+
|
|
59
|
+
if (!filename) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const fileStat = fs.statSync(filename);
|
|
64
|
+
|
|
65
|
+
if (!fileStat.isFile()) {
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const fileMod = await loadFile(filename);
|
|
70
|
+
const createRequestHandlerImport = fileMod.imports.$items.find(
|
|
71
|
+
(imp) =>
|
|
72
|
+
imp.from === '@remix-run/express' &&
|
|
73
|
+
imp.imported === 'createRequestHandler',
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
if (createRequestHandlerImport) {
|
|
77
|
+
return filename;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Wrap createRequestHandler with `wrapExpressCreateRequestHandler` from `@sentry/remix`
|
|
85
|
+
export async function instrumentExpressCreateRequestHandler(
|
|
86
|
+
expressServerPath: string,
|
|
87
|
+
): Promise<boolean> {
|
|
88
|
+
const originalExpressServerMod = await loadFile(expressServerPath);
|
|
89
|
+
|
|
90
|
+
if (
|
|
91
|
+
hasSentryContent(
|
|
92
|
+
generateCode(originalExpressServerMod.$ast).code,
|
|
93
|
+
originalExpressServerMod.$code,
|
|
94
|
+
)
|
|
95
|
+
) {
|
|
96
|
+
clack.log.warn(
|
|
97
|
+
`Express server in ${chalk.cyan(
|
|
98
|
+
expressServerPath,
|
|
99
|
+
)} already has Sentry instrumentation. Skipping.`,
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
originalExpressServerMod.imports.$add({
|
|
106
|
+
from: '@sentry/remix',
|
|
107
|
+
imported: 'wrapExpressCreateRequestHandler',
|
|
108
|
+
local: 'wrapExpressCreateRequestHandler',
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const createRequestHandlerImport =
|
|
112
|
+
originalExpressServerMod.imports.$items.find(
|
|
113
|
+
(imp) =>
|
|
114
|
+
imp.from === '@remix-run/express' &&
|
|
115
|
+
imp.imported === 'createRequestHandler',
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
visit(originalExpressServerMod.$ast, {
|
|
119
|
+
visitIdentifier(path) {
|
|
120
|
+
if (
|
|
121
|
+
path.value.name === 'createRequestHandler' &&
|
|
122
|
+
path.parentPath.value.type === 'CallExpression'
|
|
123
|
+
) {
|
|
124
|
+
path.value.name = 'sentryCreateRequestHandler';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
this.traverse(path);
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Insert the const declaration right after the imports
|
|
132
|
+
// Where we want to insert the const declaration is the same as where we would want to insert the init call.
|
|
133
|
+
const insertionIndex = getInitCallInsertionIndex(
|
|
134
|
+
originalExpressServerMod.$ast as Program,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
const createRequestHandlerConst = wrapCreateRequestHandlerWithSentry(
|
|
138
|
+
createRequestHandlerImport,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!createRequestHandlerConst) {
|
|
142
|
+
// Todo: throw error
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
(originalExpressServerMod.$ast as Program).body.splice(
|
|
146
|
+
insertionIndex,
|
|
147
|
+
0,
|
|
148
|
+
// @ts-expect-error - string works here because the AST is proxified by magicast
|
|
149
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
150
|
+
createRequestHandlerConst,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Update the Vite build parameter to await loadViteServerBuild if everything goes well.
|
|
154
|
+
// This should be the last thing we do.
|
|
155
|
+
updateViteBuildParameter(originalExpressServerMod.$ast);
|
|
156
|
+
|
|
157
|
+
try {
|
|
158
|
+
await writeFile(originalExpressServerMod.$ast, expressServerPath);
|
|
159
|
+
|
|
160
|
+
clack.log.info(
|
|
161
|
+
`Successfully instrumented Express server in ${chalk.cyan(
|
|
162
|
+
expressServerPath,
|
|
163
|
+
)}.`,
|
|
164
|
+
);
|
|
165
|
+
} catch (e) {
|
|
166
|
+
clack.log.warn(
|
|
167
|
+
`Could not write to Express server in ${chalk.cyan(expressServerPath)}.`,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
throw e;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Wrap `createRequestHandler` with `wrapExpressCreateRequestHandler` and set const name to `sentryCreateRequestHandler`
|
|
177
|
+
export function wrapCreateRequestHandlerWithSentry(
|
|
178
|
+
createRequestHandlerImport: ProxifiedImportItem | undefined,
|
|
179
|
+
) {
|
|
180
|
+
if (!createRequestHandlerImport) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const createRequestHandler = createRequestHandlerImport.local;
|
|
185
|
+
|
|
186
|
+
const wrapCreateRequestHandler = recast.types.builders.callExpression(
|
|
187
|
+
recast.types.builders.identifier('wrapExpressCreateRequestHandler'),
|
|
188
|
+
[recast.types.builders.identifier(createRequestHandler)],
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return recast.types.builders.variableDeclaration('const', [
|
|
192
|
+
recast.types.builders.variableDeclarator(
|
|
193
|
+
recast.types.builders.identifier('sentryCreateRequestHandler'),
|
|
194
|
+
wrapCreateRequestHandler,
|
|
195
|
+
),
|
|
196
|
+
]);
|
|
197
|
+
}
|
|
@@ -23,6 +23,7 @@ import {
|
|
|
23
23
|
isRemixV2,
|
|
24
24
|
loadRemixConfig,
|
|
25
25
|
runRemixReveal,
|
|
26
|
+
instrumentExpressServer,
|
|
26
27
|
} from './sdk-setup';
|
|
27
28
|
import { debug } from '../utils/debug';
|
|
28
29
|
import { traceStep, withTelemetry } from '../telemetry';
|
|
@@ -152,6 +153,25 @@ async function runRemixWizardWithTelemetry(
|
|
|
152
153
|
}
|
|
153
154
|
});
|
|
154
155
|
|
|
156
|
+
await traceStep('Instrument custom Express server', async () => {
|
|
157
|
+
try {
|
|
158
|
+
const hasExpressAdapter = hasPackageInstalled(
|
|
159
|
+
'@remix-run/express',
|
|
160
|
+
packageJson,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
if (!hasExpressAdapter) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
await instrumentExpressServer();
|
|
168
|
+
} catch (e) {
|
|
169
|
+
clack.log.warn(`Could not instrument custom Express server.
|
|
170
|
+
Please do it manually using instructions from https://docs.sentry.io/platforms/javascript/guides/remix/manual-setup/#custom-express-server`);
|
|
171
|
+
debug(e);
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
155
175
|
clack.outro(`
|
|
156
176
|
${chalk.green(
|
|
157
177
|
'Sentry has been successfully configured for your Remix project.',
|
package/src/remix/sdk-setup.ts
CHANGED
|
@@ -22,6 +22,10 @@ import { getInitCallInsertionIndex, hasSentryContent } from './utils';
|
|
|
22
22
|
import { instrumentRootRouteV1 } from './codemods/root-v1';
|
|
23
23
|
import { instrumentRootRouteV2 } from './codemods/root-v2';
|
|
24
24
|
import { instrumentHandleError } from './codemods/handle-error';
|
|
25
|
+
import {
|
|
26
|
+
findCustomExpressServerImplementation,
|
|
27
|
+
instrumentExpressCreateRequestHandler,
|
|
28
|
+
} from './codemods/express-server';
|
|
25
29
|
import { getPackageDotJson } from '../utils/clack-utils';
|
|
26
30
|
|
|
27
31
|
export type PartialRemixConfig = {
|
|
@@ -347,3 +351,16 @@ export async function initializeSentryOnEntryServer(
|
|
|
347
351
|
)}.`,
|
|
348
352
|
);
|
|
349
353
|
}
|
|
354
|
+
|
|
355
|
+
export async function instrumentExpressServer() {
|
|
356
|
+
const expressServerPath = await findCustomExpressServerImplementation();
|
|
357
|
+
|
|
358
|
+
if (!expressServerPath) {
|
|
359
|
+
clack.log.warn(
|
|
360
|
+
`Could not find custom Express server implementation. Please instrument it manually.`,
|
|
361
|
+
);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
await instrumentExpressCreateRequestHandler(expressServerPath);
|
|
366
|
+
}
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
abort,
|
|
9
9
|
abortIfCancelled,
|
|
10
10
|
addSentryCliConfig,
|
|
11
|
+
askShouldCreateExamplePage,
|
|
11
12
|
confirmContinueIfNoOrDirtyGitRepo,
|
|
12
13
|
ensurePackageIsInstalled,
|
|
13
14
|
getOrAskForProjectData,
|
|
@@ -21,6 +22,7 @@ import { createExamplePage } from './sdk-example';
|
|
|
21
22
|
import { createOrMergeSvelteKitFiles, loadSvelteConfig } from './sdk-setup';
|
|
22
23
|
import { traceStep, withTelemetry } from '../telemetry';
|
|
23
24
|
import { getKitVersionBucket, getSvelteVersionBucket } from './utils';
|
|
25
|
+
import { NPM, detectPackageManger } from '../utils/package-manager';
|
|
24
26
|
|
|
25
27
|
export async function runSvelteKitWizard(
|
|
26
28
|
options: WizardOptions,
|
|
@@ -128,41 +130,55 @@ export async function runSvelteKitWizardWithTelemetry(
|
|
|
128
130
|
return;
|
|
129
131
|
}
|
|
130
132
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
133
|
+
const shouldCreateExamplePage = await askShouldCreateExamplePage(
|
|
134
|
+
'/sentry-example',
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (shouldCreateExamplePage) {
|
|
138
|
+
try {
|
|
139
|
+
await traceStep('create-example-page', () =>
|
|
140
|
+
createExamplePage(svelteConfig, {
|
|
141
|
+
selfHosted,
|
|
142
|
+
url: sentryUrl,
|
|
143
|
+
orgSlug: selectedProject.organization.slug,
|
|
144
|
+
projectId: selectedProject.id,
|
|
145
|
+
}),
|
|
146
|
+
);
|
|
147
|
+
} catch (e: unknown) {
|
|
148
|
+
clack.log.error('Error while creating an example page to test Sentry:');
|
|
149
|
+
clack.log.info(
|
|
150
|
+
chalk.dim(
|
|
151
|
+
typeof e === 'object' && e != null && 'toString' in e
|
|
152
|
+
? e.toString()
|
|
153
|
+
: typeof e === 'string'
|
|
154
|
+
? e
|
|
155
|
+
: 'Unknown error',
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
Sentry.captureException(
|
|
159
|
+
'Error while creating an example Svelte page to test Sentry',
|
|
160
|
+
);
|
|
161
|
+
await abort('Exiting Wizard');
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
156
164
|
}
|
|
157
165
|
|
|
158
|
-
clack.outro(
|
|
159
|
-
|
|
166
|
+
clack.outro(buildOutroMessage(shouldCreateExamplePage));
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function buildOutroMessage(shouldCreateExamplePage: boolean): string {
|
|
170
|
+
const packageManager = detectPackageManger() || NPM;
|
|
171
|
+
|
|
172
|
+
let msg = chalk.green('\nSuccessfully installed the Sentry SvelteKit SDK!');
|
|
173
|
+
|
|
174
|
+
if (shouldCreateExamplePage) {
|
|
175
|
+
msg += `\n\nYou can validate your setup by starting your dev environment (${chalk.cyan(
|
|
176
|
+
`\`${packageManager.runScriptCommand} dev\``,
|
|
177
|
+
)}) and visiting ${chalk.cyan('"/sentry-example"')}.`;
|
|
178
|
+
}
|
|
160
179
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
)}
|
|
180
|
+
msg += `\n\nCheck out the SDK documentation for further configuration:
|
|
181
|
+
https://docs.sentry.io/platforms/javascript/guides/sveltekit/`;
|
|
164
182
|
|
|
165
|
-
|
|
166
|
-
https://docs.sentry.io/platforms/javascript/guides/sveltekit/
|
|
167
|
-
`);
|
|
183
|
+
return msg;
|
|
168
184
|
}
|
|
@@ -62,23 +62,16 @@ Feel free to delete this file and the entire sentry route.
|
|
|
62
62
|
<script>
|
|
63
63
|
import * as Sentry from '@sentry/sveltekit';
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
name: 'Example Frontend
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
Sentry.configureScope((scope) => {
|
|
71
|
-
scope.setSpan(transaction);
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
try {
|
|
65
|
+
function getSentryData() {
|
|
66
|
+
Sentry.startSpan({
|
|
67
|
+
name: 'Example Frontend Span',
|
|
68
|
+
op: 'test',
|
|
69
|
+
}, async () => {
|
|
75
70
|
const res = await fetch('/sentry-example');
|
|
76
71
|
if (!res.ok) {
|
|
77
72
|
throw new Error('Sentry Example Frontend Error');
|
|
78
73
|
}
|
|
79
|
-
}
|
|
80
|
-
transaction.finish();
|
|
81
|
-
}
|
|
74
|
+
});
|
|
82
75
|
}
|
|
83
76
|
</script>
|
|
84
77
|
|
package/src/utils/clack-utils.ts
CHANGED
|
@@ -1126,7 +1126,7 @@ type CodeSnippetFormatter = (
|
|
|
1126
1126
|
* This is useful for printing the snippet to the console as part of copy/paste instructions.
|
|
1127
1127
|
*
|
|
1128
1128
|
* @param callback the callback that returns the formatted code snippet.
|
|
1129
|
-
* It exposes takes the helper functions for marking code as
|
|
1129
|
+
* It exposes takes the helper functions for marking code as unchanged, new or removed.
|
|
1130
1130
|
* These functions no-op if no special formatting should be applied
|
|
1131
1131
|
* and otherwise apply the appropriate formatting/coloring.
|
|
1132
1132
|
* (@see {@link CodeSnippetFormatter})
|
|
@@ -1161,7 +1161,7 @@ export function makeCodeSnippet(
|
|
|
1161
1161
|
* @param moreInformation (optional) the message to be printed after the file was created
|
|
1162
1162
|
* For example, this can be a link to more information about configuring the tool.
|
|
1163
1163
|
*
|
|
1164
|
-
* @returns true on
|
|
1164
|
+
* @returns true on success, false otherwise
|
|
1165
1165
|
*/
|
|
1166
1166
|
export async function createNewConfigFile(
|
|
1167
1167
|
filepath: string,
|
|
@@ -1194,3 +1194,24 @@ export async function createNewConfigFile(
|
|
|
1194
1194
|
|
|
1195
1195
|
return false;
|
|
1196
1196
|
}
|
|
1197
|
+
|
|
1198
|
+
export async function askShouldCreateExamplePage(
|
|
1199
|
+
customRoute?: string,
|
|
1200
|
+
): Promise<boolean> {
|
|
1201
|
+
const route = chalk.cyan(customRoute ?? '/sentry-example-page');
|
|
1202
|
+
return traceStep('ask-create-example-page', () =>
|
|
1203
|
+
abortIfCancelled(
|
|
1204
|
+
clack.select({
|
|
1205
|
+
message: `Do you want to create an example page ("${route}") to test your Sentry setup?`,
|
|
1206
|
+
options: [
|
|
1207
|
+
{
|
|
1208
|
+
value: true,
|
|
1209
|
+
label: 'Yes',
|
|
1210
|
+
hint: 'Recommended - Check your git status before committing!',
|
|
1211
|
+
},
|
|
1212
|
+
{ value: false, label: 'No' },
|
|
1213
|
+
],
|
|
1214
|
+
}),
|
|
1215
|
+
),
|
|
1216
|
+
);
|
|
1217
|
+
}
|