@sentry/wizard 3.1.0 → 3.2.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 +19 -0
- package/bin.ts +5 -1
- package/dist/bin.js +6 -1
- package/dist/bin.js.map +1 -1
- package/dist/lib/Constants.d.ts +2 -1
- package/dist/lib/Constants.js +5 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/lib/Helper/File.js +25 -2
- package/dist/lib/Helper/File.js.map +1 -1
- package/dist/lib/Helper/Git.d.ts +7 -0
- package/dist/lib/Helper/Git.js +94 -0
- package/dist/lib/Helper/Git.js.map +1 -0
- package/dist/lib/Helper/Logging.d.ts +1 -0
- package/dist/lib/Helper/Logging.js +9 -2
- package/dist/lib/Helper/Logging.js.map +1 -1
- package/dist/lib/Helper/MergeConfig.js +24 -1
- package/dist/lib/Helper/MergeConfig.js.map +1 -1
- package/dist/lib/Helper/Package.d.ts +9 -0
- package/dist/lib/Helper/Package.js +39 -2
- package/dist/lib/Helper/Package.js.map +1 -1
- package/dist/lib/Helper/PackageManager.d.ts +1 -1
- package/dist/lib/Helper/PackageManager.js +32 -11
- package/dist/lib/Helper/PackageManager.js.map +1 -1
- package/dist/lib/Helper/SentryCli.d.ts +11 -0
- package/dist/lib/Helper/SentryCli.js +141 -2
- package/dist/lib/Helper/SentryCli.js.map +1 -1
- package/dist/lib/Helper/Wizard.js +24 -1
- package/dist/lib/Helper/Wizard.js.map +1 -1
- package/dist/lib/Helper/__tests__/MergeConfig.js +25 -2
- package/dist/lib/Helper/__tests__/MergeConfig.js.map +1 -1
- package/dist/lib/Setup.js +25 -2
- package/dist/lib/Setup.js.map +1 -1
- package/dist/lib/Steps/ChooseIntegration.js +28 -1
- package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
- package/dist/lib/Steps/Initial.js +25 -2
- package/dist/lib/Steps/Initial.js.map +1 -1
- package/dist/lib/Steps/Integrations/BaseIntegration.js +24 -1
- package/dist/lib/Steps/Integrations/BaseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/Cordova.js +25 -2
- package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
- package/dist/lib/Steps/Integrations/Electron.js +26 -3
- package/dist/lib/Steps/Integrations/Electron.js.map +1 -1
- package/dist/lib/Steps/Integrations/MobileProject.js +24 -1
- package/dist/lib/Steps/Integrations/MobileProject.js.map +1 -1
- package/dist/lib/Steps/Integrations/NextJs.d.ts +5 -11
- package/dist/lib/Steps/Integrations/NextJs.js +14 -343
- package/dist/lib/Steps/Integrations/NextJs.js.map +1 -1
- package/dist/lib/Steps/Integrations/ReactNative.d.ts +1 -0
- package/dist/lib/Steps/Integrations/ReactNative.js +67 -6
- package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
- package/dist/lib/Steps/Integrations/SvelteKit.d.ts +13 -0
- package/dist/lib/Steps/Integrations/SvelteKit.js +95 -0
- package/dist/lib/Steps/Integrations/SvelteKit.js.map +1 -0
- package/dist/lib/Steps/Integrations/__tests__/ReactNative.js +28 -5
- package/dist/lib/Steps/Integrations/__tests__/ReactNative.js.map +1 -1
- package/dist/lib/Steps/PromptForParameters.js +24 -1
- package/dist/lib/Steps/PromptForParameters.js.map +1 -1
- package/dist/lib/Steps/SentryProjectSelector.js +25 -1
- package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
- package/dist/lib/__tests__/Setup.js +24 -1
- package/dist/lib/__tests__/Setup.js.map +1 -1
- package/dist/src/{nextjs-wizard.js → nextjs/nextjs-wizard.js} +113 -108
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -0
- package/dist/src/sveltekit/sdk-example.d.ts +10 -0
- package/dist/src/sveltekit/sdk-example.js +106 -0
- package/dist/src/sveltekit/sdk-example.js.map +1 -0
- package/dist/src/sveltekit/sdk-setup.d.ts +13 -0
- package/dist/src/sveltekit/sdk-setup.js +452 -0
- package/dist/src/sveltekit/sdk-setup.js.map +1 -0
- package/dist/src/sveltekit/sentry-cli-setup.d.ts +2 -0
- package/dist/src/sveltekit/sentry-cli-setup.js +71 -0
- package/dist/src/sveltekit/sentry-cli-setup.js.map +1 -0
- package/dist/src/sveltekit/sveltekit-wizard.d.ts +5 -0
- package/dist/src/sveltekit/sveltekit-wizard.js +147 -0
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -0
- package/dist/src/templates/nextjs-templates.js +2 -2
- package/dist/src/templates/nextjs-templates.js.map +1 -1
- package/dist/src/templates/sveltekit-templates.d.ts +12 -0
- package/dist/src/templates/sveltekit-templates.js +26 -0
- package/dist/src/templates/sveltekit-templates.js.map +1 -0
- package/dist/src/{clack-utils.d.ts → utils/clack-utils.d.ts} +7 -0
- package/dist/src/{clack-utils.js → utils/clack-utils.js} +127 -42
- package/dist/src/utils/clack-utils.js.map +1 -0
- package/lib/Constants.ts +5 -0
- package/lib/Helper/Git.ts +39 -0
- package/lib/Helper/Logging.ts +4 -0
- package/lib/Helper/Package.ts +17 -0
- package/lib/Helper/PackageManager.ts +4 -9
- package/lib/Helper/SentryCli.ts +74 -0
- package/lib/Steps/ChooseIntegration.ts +4 -0
- package/lib/Steps/Integrations/NextJs.ts +7 -397
- package/lib/Steps/Integrations/ReactNative.ts +49 -3
- package/lib/Steps/Integrations/SvelteKit.ts +29 -0
- package/lib/Steps/SentryProjectSelector.ts +1 -0
- package/package.json +2 -2
- package/src/{nextjs-wizard.ts → nextjs/nextjs-wizard.ts} +13 -44
- package/src/sveltekit/sdk-example.ts +56 -0
- package/src/sveltekit/sdk-setup.ts +431 -0
- package/src/sveltekit/sentry-cli-setup.ts +27 -0
- package/src/sveltekit/sveltekit-wizard.ts +116 -0
- package/src/templates/nextjs-templates.ts +2 -2
- package/src/templates/sveltekit-templates.ts +172 -0
- package/src/{clack-utils.ts → utils/clack-utils.ts} +73 -11
- package/dist/src/clack-utils.js.map +0 -1
- package/dist/src/nextjs-wizard.js.map +0 -1
- /package/dist/src/{nextjs-wizard.d.ts → nextjs/nextjs-wizard.d.ts} +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
4
|
+
import clack from '@clack/prompts';
|
|
5
|
+
|
|
6
|
+
import { PartialSvelteConfig } from './sdk-setup';
|
|
7
|
+
import {
|
|
8
|
+
getSentryExampleApiRoute,
|
|
9
|
+
getSentryExampleSveltePage,
|
|
10
|
+
} from '../templates/sveltekit-templates';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Creates example page and API route to test Sentry
|
|
14
|
+
*/
|
|
15
|
+
export async function createExamplePage(
|
|
16
|
+
svelteConfig: PartialSvelteConfig,
|
|
17
|
+
projectProps: {
|
|
18
|
+
selfHosted: boolean;
|
|
19
|
+
url: string;
|
|
20
|
+
orgSlug: string;
|
|
21
|
+
projectId: string;
|
|
22
|
+
},
|
|
23
|
+
): Promise<void> {
|
|
24
|
+
const routesDirectory = svelteConfig.kit?.files?.routes || 'src/routes';
|
|
25
|
+
const exampleRoutePath = path.resolve(
|
|
26
|
+
path.join(routesDirectory, 'sentry-example'),
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
if (!fs.existsSync(routesDirectory)) {
|
|
30
|
+
clack.log.warn(
|
|
31
|
+
`Couldn't find your routes directory. Creating it now: ${routesDirectory}`,
|
|
32
|
+
);
|
|
33
|
+
fs.mkdirSync(routesDirectory, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(exampleRoutePath)) {
|
|
37
|
+
fs.mkdirSync(exampleRoutePath);
|
|
38
|
+
} else {
|
|
39
|
+
clack.log.warn(
|
|
40
|
+
`It seems like a sentry example page already exists (${path.basename(
|
|
41
|
+
exampleRoutePath,
|
|
42
|
+
)}). Skipping creation of example route.`,
|
|
43
|
+
);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
await fs.promises.writeFile(
|
|
48
|
+
path.join(exampleRoutePath, '+page.svelte'),
|
|
49
|
+
getSentryExampleSveltePage(projectProps),
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
await fs.promises.writeFile(
|
|
53
|
+
path.join(exampleRoutePath, '+server.js'),
|
|
54
|
+
getSentryExampleApiRoute(),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import type { ExportNamedDeclaration, Program } from '@babel/types';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import * as url from 'url';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
|
|
7
|
+
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
8
|
+
import clack from '@clack/prompts';
|
|
9
|
+
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
10
|
+
import type { ProxifiedModule } from 'magicast';
|
|
11
|
+
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
12
|
+
import { builders, generateCode, loadFile, parseModule } from 'magicast';
|
|
13
|
+
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
14
|
+
import { addVitePlugin } from 'magicast/helpers';
|
|
15
|
+
import {
|
|
16
|
+
getClientHooksTemplate,
|
|
17
|
+
getServerHooksTemplate,
|
|
18
|
+
} from '../templates/sveltekit-templates';
|
|
19
|
+
|
|
20
|
+
const SVELTE_CONFIG_FILE = 'svelte.config.js';
|
|
21
|
+
|
|
22
|
+
export type PartialSvelteConfig = {
|
|
23
|
+
kit?: {
|
|
24
|
+
files?: {
|
|
25
|
+
hooks?: {
|
|
26
|
+
client?: string;
|
|
27
|
+
server?: string;
|
|
28
|
+
};
|
|
29
|
+
routes?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export async function createOrMergeSvelteKitFiles(
|
|
35
|
+
dsn: string,
|
|
36
|
+
svelteConfig: PartialSvelteConfig,
|
|
37
|
+
): Promise<void> {
|
|
38
|
+
const { clientHooksPath, serverHooksPath } = getHooksConfigDirs(svelteConfig);
|
|
39
|
+
|
|
40
|
+
// full file paths with correct file ending (or undefined if not found)
|
|
41
|
+
const originalClientHooksFile = findHooksFile(clientHooksPath);
|
|
42
|
+
const originalServerHooksFile = findHooksFile(serverHooksPath);
|
|
43
|
+
|
|
44
|
+
const viteConfig = findHooksFile(path.resolve(process.cwd(), 'vite.config'));
|
|
45
|
+
|
|
46
|
+
if (!originalClientHooksFile) {
|
|
47
|
+
clack.log.info('No client hooks file found, creating a new one.');
|
|
48
|
+
await createNewHooksFile(`${clientHooksPath}.js`, 'client', dsn);
|
|
49
|
+
}
|
|
50
|
+
if (!originalServerHooksFile) {
|
|
51
|
+
clack.log.info('No server hooks file found, creating a new one.');
|
|
52
|
+
await createNewHooksFile(`${serverHooksPath}.js`, 'client', dsn);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (originalClientHooksFile) {
|
|
56
|
+
await mergeHooksFile(originalClientHooksFile, 'client', dsn);
|
|
57
|
+
}
|
|
58
|
+
if (originalServerHooksFile) {
|
|
59
|
+
await mergeHooksFile(originalServerHooksFile, 'server', dsn);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (viteConfig) {
|
|
63
|
+
await modifyViteConfig(viteConfig);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Attempts to read the svelte.config.js file to find the location of the hooks files.
|
|
69
|
+
* If users specified a custom location, we'll use that. Otherwise, we'll use the default.
|
|
70
|
+
*/
|
|
71
|
+
function getHooksConfigDirs(svelteConfig: PartialSvelteConfig): {
|
|
72
|
+
clientHooksPath: string;
|
|
73
|
+
serverHooksPath: string;
|
|
74
|
+
} {
|
|
75
|
+
const relativeUserClientHooksPath = svelteConfig?.kit?.files?.hooks?.client;
|
|
76
|
+
const relativeUserServerHooksPath = svelteConfig?.kit?.files?.hooks?.server;
|
|
77
|
+
const userClientHooksPath =
|
|
78
|
+
relativeUserClientHooksPath &&
|
|
79
|
+
path.resolve(process.cwd(), relativeUserClientHooksPath);
|
|
80
|
+
const userServerHooksPath =
|
|
81
|
+
relativeUserServerHooksPath &&
|
|
82
|
+
path.resolve(process.cwd(), relativeUserServerHooksPath);
|
|
83
|
+
|
|
84
|
+
const defaulHooksDir = path.resolve(process.cwd(), 'src');
|
|
85
|
+
const defaultClientHooksPath = path.resolve(defaulHooksDir, 'hooks.client'); // file ending missing on purpose
|
|
86
|
+
const defaultServerHooksPath = path.resolve(defaulHooksDir, 'hooks.server'); // same here
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
clientHooksPath: userClientHooksPath || defaultClientHooksPath,
|
|
90
|
+
serverHooksPath: userServerHooksPath || defaultServerHooksPath,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Checks if a hooks file exists and returns the full path to the file with the correct file type.
|
|
96
|
+
*/
|
|
97
|
+
function findHooksFile(hooksFile: string): string | undefined {
|
|
98
|
+
const possibleFileTypes = ['.js', '.ts', '.mjs'];
|
|
99
|
+
return possibleFileTypes
|
|
100
|
+
.map((type) => `${hooksFile}${type}`)
|
|
101
|
+
.find((file) => fs.existsSync(file));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reads the template, replaces the dsn placeholder with the actual dsn and writes the file to @param hooksFileDest
|
|
106
|
+
*/
|
|
107
|
+
async function createNewHooksFile(
|
|
108
|
+
hooksFileDest: string,
|
|
109
|
+
hooktype: 'client' | 'server',
|
|
110
|
+
dsn: string,
|
|
111
|
+
): Promise<void> {
|
|
112
|
+
const filledTemplate =
|
|
113
|
+
hooktype === 'client'
|
|
114
|
+
? getClientHooksTemplate(dsn)
|
|
115
|
+
: getServerHooksTemplate(dsn);
|
|
116
|
+
|
|
117
|
+
await fs.promises.mkdir(path.dirname(hooksFileDest), { recursive: true });
|
|
118
|
+
await fs.promises.writeFile(hooksFileDest, filledTemplate);
|
|
119
|
+
|
|
120
|
+
clack.log.success(`Created ${hooksFileDest}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Merges the users' hooks file with Sentry-related code.
|
|
125
|
+
*
|
|
126
|
+
* Both hooks:
|
|
127
|
+
* - add import * as Sentry
|
|
128
|
+
* - add Sentry.init
|
|
129
|
+
* - add handleError hook wrapper
|
|
130
|
+
*
|
|
131
|
+
* Additionally in Server hook:
|
|
132
|
+
* - add handle hook handler
|
|
133
|
+
*/
|
|
134
|
+
async function mergeHooksFile(
|
|
135
|
+
hooksFile: string,
|
|
136
|
+
hookType: 'client' | 'server',
|
|
137
|
+
dsn: string,
|
|
138
|
+
): Promise<void> {
|
|
139
|
+
const originalHooksMod = await loadFile(hooksFile);
|
|
140
|
+
if (hasSentryContent(path.basename(hooksFile), originalHooksMod.$code)) {
|
|
141
|
+
// We don't want to mess with files that already have Sentry content.
|
|
142
|
+
// Let's just bail out at this point.
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
originalHooksMod.imports.$add({
|
|
147
|
+
from: '@sentry/sveltekit',
|
|
148
|
+
imported: '*',
|
|
149
|
+
local: 'Sentry',
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (hookType === 'client') {
|
|
153
|
+
insertClientInitCall(dsn, originalHooksMod);
|
|
154
|
+
} else {
|
|
155
|
+
insertServerInitCall(dsn, originalHooksMod);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
wrapHandleError(originalHooksMod);
|
|
159
|
+
|
|
160
|
+
if (hookType === 'server') {
|
|
161
|
+
wrapHandle(originalHooksMod);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const modifiedCode = originalHooksMod.generate().code;
|
|
165
|
+
|
|
166
|
+
await fs.promises.writeFile(hooksFile, modifiedCode);
|
|
167
|
+
|
|
168
|
+
clack.log.success(`Added Sentry code to ${hooksFile}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function insertClientInitCall(
|
|
172
|
+
dsn: string,
|
|
173
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
174
|
+
originalHooksMod: ProxifiedModule<any>,
|
|
175
|
+
): void {
|
|
176
|
+
const initCallComment = `
|
|
177
|
+
// If you don't want to use Session Replay, remove the \`Replay\` integration,
|
|
178
|
+
// \`replaysSessionSampleRate\` and \`replaysOnErrorSampleRate\` options.`;
|
|
179
|
+
|
|
180
|
+
// This assignment of any values is fine because we're just creating a function call in magicast
|
|
181
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
182
|
+
const initCall = builders.functionCall('Sentry.init', {
|
|
183
|
+
dsn,
|
|
184
|
+
tracesSampleRate: 1.0,
|
|
185
|
+
replaysSessionSampleRate: 0.1,
|
|
186
|
+
replaysOnErrorSampleRate: 1.0,
|
|
187
|
+
integrations: [builders.newExpression('Sentry.Replay')],
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
191
|
+
const initCallWithComment = builders.raw(
|
|
192
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
193
|
+
`${initCallComment}\n${generateCode(initCall).code}`,
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
const originalHooksModAST = originalHooksMod.$ast as Program;
|
|
197
|
+
|
|
198
|
+
const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
|
|
199
|
+
|
|
200
|
+
originalHooksModAST.body.splice(
|
|
201
|
+
initCallInsertionIndex,
|
|
202
|
+
0,
|
|
203
|
+
// @ts-ignore - string works here because the AST is proxified by magicast
|
|
204
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
205
|
+
generateCode(initCallWithComment).code,
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function insertServerInitCall(
|
|
210
|
+
dsn: string,
|
|
211
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
212
|
+
originalHooksMod: ProxifiedModule<any>,
|
|
213
|
+
): void {
|
|
214
|
+
// This assignment of any values is fine because we're just creating a function call in magicast
|
|
215
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
216
|
+
const initCall = builders.functionCall('Sentry.init', {
|
|
217
|
+
dsn,
|
|
218
|
+
tracesSampleRate: 1.0,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
const originalHooksModAST = originalHooksMod.$ast as Program;
|
|
222
|
+
|
|
223
|
+
const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
|
|
224
|
+
|
|
225
|
+
originalHooksModAST.body.splice(
|
|
226
|
+
initCallInsertionIndex,
|
|
227
|
+
0,
|
|
228
|
+
// @ts-ignore - string works here because the AST is proxified by magicast
|
|
229
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
230
|
+
generateCode(initCall).code,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
235
|
+
function wrapHandleError(mod: ProxifiedModule<any>): void {
|
|
236
|
+
const modAst = mod.exports.$ast as Program;
|
|
237
|
+
const namedExports = modAst.body.filter(
|
|
238
|
+
(node) => node.type === 'ExportNamedDeclaration',
|
|
239
|
+
) as ExportNamedDeclaration[];
|
|
240
|
+
|
|
241
|
+
let foundHandleError = false;
|
|
242
|
+
|
|
243
|
+
namedExports.forEach((modExport) => {
|
|
244
|
+
const declaration = modExport.declaration;
|
|
245
|
+
if (!declaration) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (declaration.type === 'FunctionDeclaration') {
|
|
249
|
+
if (!declaration.id || declaration.id.name !== 'handleError') {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
foundHandleError = true;
|
|
253
|
+
const userCode = generateCode(declaration).code;
|
|
254
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
255
|
+
mod.exports.handleError = builders.raw(
|
|
256
|
+
`Sentry.handleErrorWithSentry(${userCode.replace(
|
|
257
|
+
'handleError',
|
|
258
|
+
'_handleError',
|
|
259
|
+
)})`,
|
|
260
|
+
);
|
|
261
|
+
// because magicast doesn't overwrite the original function export, we need to remove it manually
|
|
262
|
+
modAst.body = modAst.body.filter((node) => node !== modExport);
|
|
263
|
+
} else if (declaration.type === 'VariableDeclaration') {
|
|
264
|
+
const declarations = declaration.declarations;
|
|
265
|
+
declarations.forEach((declaration) => {
|
|
266
|
+
// @ts-ignore - id should always have a name in this case
|
|
267
|
+
if (!declaration.id || declaration.id.name !== 'handleError') {
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
foundHandleError = true;
|
|
271
|
+
const userCode = declaration.init;
|
|
272
|
+
const stringifiedUserCode = userCode ? generateCode(userCode).code : '';
|
|
273
|
+
// @ts-ignore - we can just place a string here, magicast will convert it to a node
|
|
274
|
+
declaration.init = `Sentry.handleErrorWithSentry(${stringifiedUserCode})`;
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
if (!foundHandleError) {
|
|
280
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
281
|
+
mod.exports.handleError = builders.functionCall(
|
|
282
|
+
'Sentry.handleErrorWithSentry',
|
|
283
|
+
);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
288
|
+
function wrapHandle(mod: ProxifiedModule<any>): void {
|
|
289
|
+
const modAst = mod.exports.$ast as Program;
|
|
290
|
+
const namedExports = modAst.body.filter(
|
|
291
|
+
(node) => node.type === 'ExportNamedDeclaration',
|
|
292
|
+
) as ExportNamedDeclaration[];
|
|
293
|
+
|
|
294
|
+
let foundHandle = false;
|
|
295
|
+
|
|
296
|
+
namedExports.forEach((modExport) => {
|
|
297
|
+
const declaration = modExport.declaration;
|
|
298
|
+
if (!declaration) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (declaration.type === 'FunctionDeclaration') {
|
|
302
|
+
if (!declaration.id || declaration.id.name !== 'handle') {
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
foundHandle = true;
|
|
306
|
+
const userCode = generateCode(declaration).code;
|
|
307
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
308
|
+
mod.exports.handle = builders.raw(
|
|
309
|
+
`sequence(Sentry.sentryHandle(), ${userCode.replace(
|
|
310
|
+
'handle',
|
|
311
|
+
'_handle',
|
|
312
|
+
)})`,
|
|
313
|
+
);
|
|
314
|
+
// because of an issue with magicast, we need to remove the original export
|
|
315
|
+
modAst.body = modAst.body.filter((node) => node !== modExport);
|
|
316
|
+
} else if (declaration.type === 'VariableDeclaration') {
|
|
317
|
+
const declarations = declaration.declarations;
|
|
318
|
+
declarations.forEach((declaration) => {
|
|
319
|
+
// @ts-ignore - id should always have a name in this case
|
|
320
|
+
if (!declaration.id || declaration.id.name !== 'handle') {
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const userCode = declaration.init;
|
|
324
|
+
const stringifiedUserCode = userCode ? generateCode(userCode).code : '';
|
|
325
|
+
// @ts-ignore - we can just place a string here, magicast will convert it to a node
|
|
326
|
+
declaration.init = `sequence(Sentry.sentryHandle(), ${stringifiedUserCode})`;
|
|
327
|
+
foundHandle = true;
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
if (!foundHandle) {
|
|
333
|
+
// can't use builders.functionCall here because it doesn't yet
|
|
334
|
+
// support member expressions (Sentry.sentryHandle()) in args
|
|
335
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
336
|
+
mod.exports.handle = builders.raw('sequence(Sentry.sentryHandle())');
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
try {
|
|
340
|
+
mod.imports.$add({
|
|
341
|
+
from: '@sveltejs/kit/hooks',
|
|
342
|
+
imported: 'sequence',
|
|
343
|
+
local: 'sequence',
|
|
344
|
+
});
|
|
345
|
+
} catch (_) {
|
|
346
|
+
// It's possible sequence is already imported. in this case, magicast throws but that's fine.
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/** Checks if the Sentry SvelteKit SDK is already mentioned in the file */
|
|
351
|
+
function hasSentryContent(fileName: string, fileContent: string): boolean {
|
|
352
|
+
if (fileContent.includes('@sentry/sveltekit')) {
|
|
353
|
+
clack.log.warn(
|
|
354
|
+
`File ${chalk.cyan(path.basename(fileName))} already contains Sentry code.
|
|
355
|
+
Skipping adding Sentry functionality to ${chalk.cyan(
|
|
356
|
+
path.basename(fileName),
|
|
357
|
+
)}.`,
|
|
358
|
+
);
|
|
359
|
+
return true;
|
|
360
|
+
}
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
export async function loadSvelteConfig(): Promise<PartialSvelteConfig> {
|
|
365
|
+
const configFilePath = path.join(process.cwd(), SVELTE_CONFIG_FILE);
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
if (!fs.existsSync(configFilePath)) {
|
|
369
|
+
return {};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const configUrl = url.pathToFileURL(configFilePath).href;
|
|
373
|
+
const svelteConfigModule = (await import(configUrl)) as {
|
|
374
|
+
default: PartialSvelteConfig;
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
return svelteConfigModule?.default || {};
|
|
378
|
+
} catch (e: unknown) {
|
|
379
|
+
clack.log.error(`Couldn't load ${SVELTE_CONFIG_FILE}.
|
|
380
|
+
Please make sure, you're running this wizard with Node 16 or newer`);
|
|
381
|
+
clack.log.info(
|
|
382
|
+
chalk.dim(
|
|
383
|
+
typeof e === 'object' && e != null && 'toString' in e
|
|
384
|
+
? e.toString()
|
|
385
|
+
: typeof e === 'string'
|
|
386
|
+
? e
|
|
387
|
+
: 'Unknown error',
|
|
388
|
+
),
|
|
389
|
+
);
|
|
390
|
+
|
|
391
|
+
return {};
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async function modifyViteConfig(viteConfigPath: string): Promise<void> {
|
|
396
|
+
const viteConfigContent = (
|
|
397
|
+
await fs.promises.readFile(viteConfigPath, 'utf-8')
|
|
398
|
+
).toString();
|
|
399
|
+
|
|
400
|
+
if (hasSentryContent(viteConfigPath, viteConfigContent)) {
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const viteModule = parseModule(viteConfigContent);
|
|
405
|
+
|
|
406
|
+
addVitePlugin(viteModule, {
|
|
407
|
+
imported: 'sentrySvelteKit',
|
|
408
|
+
from: '@sentry/sveltekit',
|
|
409
|
+
constructor: 'sentrySvelteKit',
|
|
410
|
+
index: 0,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
const code = generateCode(viteModule.$ast).code;
|
|
414
|
+
await fs.promises.writeFile(viteConfigPath, code);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* We want to insert the init call on top of the file but after all import statements
|
|
419
|
+
*/
|
|
420
|
+
function getInitCallInsertionIndex(originalHooksModAST: Program): number {
|
|
421
|
+
// We need to deep-copy here because reverse mutates in place
|
|
422
|
+
const copiedBodyNodes = [...originalHooksModAST.body];
|
|
423
|
+
const lastImportDeclaration = copiedBodyNodes
|
|
424
|
+
.reverse()
|
|
425
|
+
.find((node) => node.type === 'ImportDeclaration');
|
|
426
|
+
|
|
427
|
+
const initCallInsertionIndex = lastImportDeclaration
|
|
428
|
+
? originalHooksModAST.body.indexOf(lastImportDeclaration) + 1
|
|
429
|
+
: 0;
|
|
430
|
+
return initCallInsertionIndex;
|
|
431
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Args } from '../../lib/Constants';
|
|
2
|
+
import { SentryCli } from '../../lib/Helper/SentryCli';
|
|
3
|
+
import { SentryProjectData } from '../utils/clack-utils';
|
|
4
|
+
|
|
5
|
+
export async function setupCLIConfig(
|
|
6
|
+
authToken: string,
|
|
7
|
+
selectedProject: SentryProjectData,
|
|
8
|
+
sentryUrl: string,
|
|
9
|
+
): Promise<void> {
|
|
10
|
+
const cli = new SentryCli({ url: sentryUrl } as Args);
|
|
11
|
+
|
|
12
|
+
const answers = {
|
|
13
|
+
config: {
|
|
14
|
+
organization: {
|
|
15
|
+
slug: selectedProject.organization.slug,
|
|
16
|
+
},
|
|
17
|
+
project: {
|
|
18
|
+
slug: selectedProject.slug,
|
|
19
|
+
},
|
|
20
|
+
auth: {
|
|
21
|
+
token: authToken,
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
const props = cli.convertAnswersToProperties(answers);
|
|
26
|
+
await cli.createSentryCliConfig(props);
|
|
27
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// @ts-ignore - 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
|
+
abortIfCancelled,
|
|
7
|
+
askForSelfHosted,
|
|
8
|
+
askForWizardLogin,
|
|
9
|
+
confirmContinueEvenThoughNoGitRepo,
|
|
10
|
+
ensurePackageIsInstalled,
|
|
11
|
+
getPackageDotJson,
|
|
12
|
+
hasPackageInstalled,
|
|
13
|
+
installPackage,
|
|
14
|
+
printWelcome,
|
|
15
|
+
SentryProjectData,
|
|
16
|
+
} from '../utils/clack-utils';
|
|
17
|
+
import { createExamplePage } from './sdk-example';
|
|
18
|
+
import { createOrMergeSvelteKitFiles, loadSvelteConfig } from './sdk-setup';
|
|
19
|
+
|
|
20
|
+
import { setupCLIConfig } from './sentry-cli-setup';
|
|
21
|
+
|
|
22
|
+
interface SvelteKitWizardOptions {
|
|
23
|
+
promoCode?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export async function runSvelteKitWizard(
|
|
27
|
+
options: SvelteKitWizardOptions,
|
|
28
|
+
): Promise<void> {
|
|
29
|
+
printWelcome({
|
|
30
|
+
wizardName: 'Sentry SvelteKit Wizard',
|
|
31
|
+
promoCode: options.promoCode,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
await confirmContinueEvenThoughNoGitRepo();
|
|
35
|
+
|
|
36
|
+
const packageJson = await getPackageDotJson();
|
|
37
|
+
await ensurePackageIsInstalled(packageJson, '@sveltejs/kit', 'Sveltekit');
|
|
38
|
+
|
|
39
|
+
const { url: sentryUrl, selfHosted } = await askForSelfHosted();
|
|
40
|
+
|
|
41
|
+
const { projects, apiKeys } = await askForWizardLogin({
|
|
42
|
+
promoCode: options.promoCode,
|
|
43
|
+
url: sentryUrl,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const selectedProject: SentryProjectData | symbol = await clack.select({
|
|
47
|
+
message: 'Select your Sentry project.',
|
|
48
|
+
options: projects.map((project) => {
|
|
49
|
+
return {
|
|
50
|
+
value: project,
|
|
51
|
+
label: `${project.organization.slug}/${project.slug}`,
|
|
52
|
+
};
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
abortIfCancelled(selectedProject);
|
|
57
|
+
|
|
58
|
+
await installPackage({
|
|
59
|
+
packageName: '@sentry/sveltekit',
|
|
60
|
+
alreadyInstalled: hasPackageInstalled('@sentry/sveltekit', packageJson),
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
await setupCLIConfig(apiKeys.token, selectedProject, sentryUrl);
|
|
64
|
+
|
|
65
|
+
const dsn = selectedProject.keys[0].dsn.public;
|
|
66
|
+
|
|
67
|
+
const svelteConfig = await loadSvelteConfig();
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
await createOrMergeSvelteKitFiles(dsn, svelteConfig);
|
|
71
|
+
} catch (e: unknown) {
|
|
72
|
+
clack.log.error('Error while setting up the SvelteKit SDK:');
|
|
73
|
+
clack.log.info(
|
|
74
|
+
chalk.dim(
|
|
75
|
+
typeof e === 'object' && e != null && 'toString' in e
|
|
76
|
+
? e.toString()
|
|
77
|
+
: typeof e === 'string'
|
|
78
|
+
? e
|
|
79
|
+
: 'Unknown error',
|
|
80
|
+
),
|
|
81
|
+
);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await createExamplePage(svelteConfig, {
|
|
87
|
+
selfHosted,
|
|
88
|
+
url: sentryUrl,
|
|
89
|
+
orgSlug: selectedProject.organization.slug,
|
|
90
|
+
projectId: selectedProject.id,
|
|
91
|
+
});
|
|
92
|
+
} catch (e: unknown) {
|
|
93
|
+
clack.log.error('Error while creating an example page to test Sentry:');
|
|
94
|
+
clack.log.info(
|
|
95
|
+
chalk.dim(
|
|
96
|
+
typeof e === 'object' && e != null && 'toString' in e
|
|
97
|
+
? e.toString()
|
|
98
|
+
: typeof e === 'string'
|
|
99
|
+
? e
|
|
100
|
+
: 'Unknown error',
|
|
101
|
+
),
|
|
102
|
+
);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
clack.outro(`
|
|
107
|
+
${chalk.green('Successfully installed the Sentry SvelteKit SDK!')}
|
|
108
|
+
|
|
109
|
+
${chalk.cyan(
|
|
110
|
+
'You can validate your setup by starting your dev environment (`npm run dev`) and visiting "/sentry-example".',
|
|
111
|
+
)}
|
|
112
|
+
|
|
113
|
+
Check out the SDK documentation for further configuration:
|
|
114
|
+
https://docs.sentry.io/platforms/javascript/guides/sveltekit/
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
@@ -59,7 +59,7 @@ export function getNextjsConfigCjsAppendix(
|
|
|
59
59
|
): string {
|
|
60
60
|
return `
|
|
61
61
|
|
|
62
|
-
//
|
|
62
|
+
// Injected content via Sentry wizard below
|
|
63
63
|
|
|
64
64
|
const { withSentryConfig } = require("@sentry/nextjs");
|
|
65
65
|
|
|
@@ -104,7 +104,7 @@ export function getSentryConfigContents(
|
|
|
104
104
|
} else if (config === 'edge') {
|
|
105
105
|
primer = `// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
|
|
106
106
|
// The config you add here will be used whenever one of the edge features is loaded.
|
|
107
|
-
// Note that this config is unrelated to the
|
|
107
|
+
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
|
|
108
108
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/`;
|
|
109
109
|
}
|
|
110
110
|
|