@sentry/wizard 3.12.0 → 3.14.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 +19 -0
- package/dist/lib/Steps/ChooseIntegration.js +1 -0
- package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
- package/dist/package.json +2 -2
- package/dist/src/android/android-wizard.js +8 -8
- package/dist/src/android/android-wizard.js.map +1 -1
- package/dist/src/apple/apple-wizard.js +1 -1
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.d.ts +1 -0
- package/dist/src/nextjs/nextjs-wizard.js +257 -163
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +1 -1
- package/dist/src/nextjs/templates.js +2 -2
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/nextjs/utils.d.ts +1 -0
- package/dist/src/nextjs/utils.js +25 -0
- package/dist/src/nextjs/utils.js.map +1 -0
- package/dist/src/remix/remix-wizard.js +13 -11
- package/dist/src/remix/remix-wizard.js.map +1 -1
- package/dist/src/remix/sdk-setup.d.ts +5 -1
- package/dist/src/remix/sdk-setup.js +13 -6
- package/dist/src/remix/sdk-setup.js.map +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js +1 -1
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/sourcemaps/tools/sentry-cli.d.ts +9 -0
- package/dist/src/sourcemaps/tools/sentry-cli.js +26 -22
- package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
- package/dist/src/sourcemaps/tools/tsc.d.ts +6 -0
- package/dist/src/sourcemaps/tools/tsc.js +98 -17
- package/dist/src/sourcemaps/tools/tsc.js.map +1 -1
- package/dist/src/sourcemaps/tools/vite.js +3 -13
- package/dist/src/sourcemaps/tools/vite.js.map +1 -1
- package/dist/src/sourcemaps/tools/webpack.js +3 -13
- package/dist/src/sourcemaps/tools/webpack.js.map +1 -1
- package/dist/src/sveltekit/sdk-setup.js +122 -48
- package/dist/src/sveltekit/sdk-setup.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.d.ts +1 -0
- package/dist/src/sveltekit/sveltekit-wizard.js +113 -42
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/sveltekit/utils.d.ts +2 -0
- package/dist/src/sveltekit/utils.js +48 -0
- package/dist/src/sveltekit/utils.js.map +1 -0
- package/dist/src/telemetry.d.ts +1 -0
- package/dist/src/telemetry.js +27 -12
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +70 -0
- package/dist/src/utils/ast-utils.js +152 -1
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack-utils.d.ts +49 -7
- package/dist/src/utils/clack-utils.js +238 -168
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/src/utils/package-manager.d.ts +5 -0
- package/dist/src/utils/package-manager.js +23 -14
- package/dist/src/utils/package-manager.js.map +1 -1
- package/dist/test/sourcemaps/tools/sentry-cli.test.d.ts +1 -0
- package/dist/test/sourcemaps/tools/sentry-cli.test.js +112 -0
- package/dist/test/sourcemaps/tools/sentry-cli.test.js.map +1 -0
- package/dist/test/sourcemaps/tools/tsc.test.d.ts +1 -0
- package/dist/test/sourcemaps/tools/tsc.test.js +121 -0
- package/dist/test/sourcemaps/tools/tsc.test.js.map +1 -0
- package/dist/test/utils/ast-utils.test.js +157 -26
- package/dist/test/utils/ast-utils.test.js.map +1 -1
- package/lib/Steps/ChooseIntegration.ts +1 -0
- package/package.json +2 -2
- package/src/android/android-wizard.ts +12 -10
- package/src/apple/apple-wizard.ts +2 -2
- package/src/nextjs/nextjs-wizard.ts +277 -198
- package/src/nextjs/templates.ts +3 -2
- package/src/nextjs/utils.ts +21 -0
- package/src/remix/remix-wizard.ts +15 -20
- package/src/remix/sdk-setup.ts +20 -5
- package/src/sourcemaps/sourcemaps-wizard.ts +2 -2
- package/src/sourcemaps/tools/sentry-cli.ts +16 -9
- package/src/sourcemaps/tools/tsc.ts +133 -28
- package/src/sourcemaps/tools/vite.ts +15 -39
- package/src/sourcemaps/tools/webpack.ts +16 -39
- package/src/sveltekit/sdk-setup.ts +109 -37
- package/src/sveltekit/sveltekit-wizard.ts +86 -21
- package/src/sveltekit/utils.ts +50 -0
- package/src/telemetry.ts +22 -11
- package/src/utils/ast-utils.ts +180 -0
- package/src/utils/clack-utils.ts +238 -149
- package/src/utils/package-manager.ts +24 -12
- package/test/sourcemaps/tools/sentry-cli.test.ts +51 -0
- package/test/sourcemaps/tools/tsc.test.ts +181 -0
- package/test/utils/ast-utils.test.ts +233 -32
|
@@ -4,6 +4,8 @@ import * as path from 'path';
|
|
|
4
4
|
import * as url from 'url';
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
|
|
7
|
+
import * as Sentry from '@sentry/node';
|
|
8
|
+
|
|
7
9
|
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
8
10
|
import clack from '@clack/prompts';
|
|
9
11
|
// @ts-ignore - magicast is ESM and TS complains about that. It works though
|
|
@@ -20,6 +22,7 @@ import { findFile, hasSentryContent } from '../utils/ast-utils';
|
|
|
20
22
|
import * as recast from 'recast';
|
|
21
23
|
import x = recast.types;
|
|
22
24
|
import t = x.namedTypes;
|
|
25
|
+
import { traceStep } from '../telemetry';
|
|
23
26
|
|
|
24
27
|
const SVELTE_CONFIG_FILE = 'svelte.config.js';
|
|
25
28
|
|
|
@@ -59,19 +62,25 @@ export async function createOrMergeSvelteKitFiles(
|
|
|
59
62
|
|
|
60
63
|
const { dsn } = projectInfo;
|
|
61
64
|
|
|
65
|
+
Sentry.setTag(
|
|
66
|
+
'client-hooks-file-strategy',
|
|
67
|
+
originalClientHooksFile ? 'merge' : 'create',
|
|
68
|
+
);
|
|
62
69
|
if (!originalClientHooksFile) {
|
|
63
70
|
clack.log.info('No client hooks file found, creating a new one.');
|
|
64
71
|
await createNewHooksFile(`${clientHooksPath}.${fileEnding}`, 'client', dsn);
|
|
72
|
+
} else {
|
|
73
|
+
await mergeHooksFile(originalClientHooksFile, 'client', dsn);
|
|
65
74
|
}
|
|
75
|
+
|
|
76
|
+
Sentry.setTag(
|
|
77
|
+
'server-hooks-file-strategy',
|
|
78
|
+
originalServerHooksFile ? 'merge' : 'create',
|
|
79
|
+
);
|
|
66
80
|
if (!originalServerHooksFile) {
|
|
67
81
|
clack.log.info('No server hooks file found, creating a new one.');
|
|
68
82
|
await createNewHooksFile(`${serverHooksPath}.${fileEnding}`, 'server', dsn);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
if (originalClientHooksFile) {
|
|
72
|
-
await mergeHooksFile(originalClientHooksFile, 'client', dsn);
|
|
73
|
-
}
|
|
74
|
-
if (originalServerHooksFile) {
|
|
83
|
+
} else {
|
|
75
84
|
await mergeHooksFile(originalServerHooksFile, 'server', dsn);
|
|
76
85
|
}
|
|
77
86
|
|
|
@@ -124,6 +133,7 @@ async function createNewHooksFile(
|
|
|
124
133
|
await fs.promises.writeFile(hooksFileDest, filledTemplate);
|
|
125
134
|
|
|
126
135
|
clack.log.success(`Created ${hooksFileDest}`);
|
|
136
|
+
Sentry.setTag(`created-${hooktype}-hooks`, 'success');
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
/**
|
|
@@ -143,6 +153,9 @@ async function mergeHooksFile(
|
|
|
143
153
|
dsn: string,
|
|
144
154
|
): Promise<void> {
|
|
145
155
|
const originalHooksMod = await loadFile(hooksFile);
|
|
156
|
+
|
|
157
|
+
const file: 'server-hooks' | 'client-hooks' = `${hookType}-hooks`;
|
|
158
|
+
|
|
146
159
|
if (hasSentryContent(originalHooksMod.$ast as t.Program)) {
|
|
147
160
|
// We don't want to mess with files that already have Sentry content.
|
|
148
161
|
// Let's just bail out at this point.
|
|
@@ -152,32 +165,59 @@ async function mergeHooksFile(
|
|
|
152
165
|
)} already contains Sentry code.
|
|
153
166
|
Skipping adding Sentry functionality to.`,
|
|
154
167
|
);
|
|
168
|
+
Sentry.setTag(`modified-${file}`, 'fail');
|
|
169
|
+
Sentry.setTag(`${file}-fail-reason`, 'has-sentry-content');
|
|
155
170
|
return;
|
|
156
171
|
}
|
|
157
172
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
173
|
+
await modifyAndRecordFail(
|
|
174
|
+
() =>
|
|
175
|
+
originalHooksMod.imports.$add({
|
|
176
|
+
from: '@sentry/sveltekit',
|
|
177
|
+
imported: '*',
|
|
178
|
+
local: 'Sentry',
|
|
179
|
+
}),
|
|
180
|
+
'import-injection',
|
|
181
|
+
file,
|
|
182
|
+
);
|
|
163
183
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
await modifyAndRecordFail(
|
|
185
|
+
() => {
|
|
186
|
+
if (hookType === 'client') {
|
|
187
|
+
insertClientInitCall(dsn, originalHooksMod);
|
|
188
|
+
} else {
|
|
189
|
+
insertServerInitCall(dsn, originalHooksMod);
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
'init-call-injection',
|
|
193
|
+
file,
|
|
194
|
+
);
|
|
169
195
|
|
|
170
|
-
|
|
196
|
+
await modifyAndRecordFail(
|
|
197
|
+
() => wrapHandleError(originalHooksMod),
|
|
198
|
+
'wrap-handle-error',
|
|
199
|
+
file,
|
|
200
|
+
);
|
|
171
201
|
|
|
172
202
|
if (hookType === 'server') {
|
|
173
|
-
|
|
203
|
+
await modifyAndRecordFail(
|
|
204
|
+
() => wrapHandle(originalHooksMod),
|
|
205
|
+
'wrap-handle',
|
|
206
|
+
'server-hooks',
|
|
207
|
+
);
|
|
174
208
|
}
|
|
175
209
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
210
|
+
await modifyAndRecordFail(
|
|
211
|
+
async () => {
|
|
212
|
+
const modifiedCode = originalHooksMod.generate().code;
|
|
213
|
+
await fs.promises.writeFile(hooksFile, modifiedCode);
|
|
214
|
+
},
|
|
215
|
+
'write-file',
|
|
216
|
+
file,
|
|
217
|
+
);
|
|
179
218
|
|
|
180
219
|
clack.log.success(`Added Sentry code to ${hooksFile}`);
|
|
220
|
+
Sentry.setTag(`modified-${hookType}-hooks`, 'success');
|
|
181
221
|
}
|
|
182
222
|
|
|
183
223
|
function insertClientInitCall(
|
|
@@ -410,32 +450,45 @@ async function modifyViteConfig(
|
|
|
410
450
|
)} already contains Sentry code.
|
|
411
451
|
Skipping adding Sentry functionality to.`,
|
|
412
452
|
);
|
|
453
|
+
Sentry.setTag(`modified-vite-cfg`, 'fail');
|
|
454
|
+
Sentry.setTag(`vite-cfg-fail-reason`, 'has-sentry-content');
|
|
413
455
|
return;
|
|
414
456
|
}
|
|
415
457
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
458
|
+
await modifyAndRecordFail(
|
|
459
|
+
() =>
|
|
460
|
+
addVitePlugin(viteModule, {
|
|
461
|
+
imported: 'sentrySvelteKit',
|
|
462
|
+
from: '@sentry/sveltekit',
|
|
463
|
+
constructor: 'sentrySvelteKit',
|
|
464
|
+
options: {
|
|
465
|
+
sourceMapsUploadOptions: {
|
|
466
|
+
org,
|
|
467
|
+
project,
|
|
468
|
+
...(selfHosted && { url }),
|
|
469
|
+
},
|
|
470
|
+
},
|
|
471
|
+
index: 0,
|
|
472
|
+
}),
|
|
473
|
+
'add-vite-plugin',
|
|
474
|
+
'vite-cfg',
|
|
475
|
+
);
|
|
431
476
|
|
|
432
|
-
await
|
|
477
|
+
await modifyAndRecordFail(
|
|
478
|
+
async () => {
|
|
479
|
+
const code = generateCode(viteModule.$ast).code;
|
|
480
|
+
await fs.promises.writeFile(viteConfigPath, code);
|
|
481
|
+
},
|
|
482
|
+
'write-file',
|
|
483
|
+
'vite-cfg',
|
|
484
|
+
);
|
|
433
485
|
} catch (e) {
|
|
434
486
|
debug(e);
|
|
435
487
|
await showFallbackViteCopyPasteSnippet(
|
|
436
488
|
viteConfigPath,
|
|
437
489
|
getViteConfigCodeSnippet(org, project, selfHosted, url),
|
|
438
490
|
);
|
|
491
|
+
Sentry.captureException('Sveltekit Vite Config Modification Fail');
|
|
439
492
|
}
|
|
440
493
|
}
|
|
441
494
|
|
|
@@ -512,3 +565,22 @@ function getInitCallInsertionIndex(originalHooksModAST: Program): number {
|
|
|
512
565
|
: 0;
|
|
513
566
|
return initCallInsertionIndex;
|
|
514
567
|
}
|
|
568
|
+
|
|
569
|
+
/**
|
|
570
|
+
* Applies the @param modifyCallback and records Sentry tags if the call failed.
|
|
571
|
+
* In case of a failure, a tag is set with @param reason as a fail reason
|
|
572
|
+
* and the error is rethrown.
|
|
573
|
+
*/
|
|
574
|
+
async function modifyAndRecordFail<T>(
|
|
575
|
+
modifyCallback: () => T | Promise<T>,
|
|
576
|
+
reason: string,
|
|
577
|
+
fileType: 'server-hooks' | 'client-hooks' | 'vite-cfg',
|
|
578
|
+
): Promise<void> {
|
|
579
|
+
try {
|
|
580
|
+
await traceStep(`${fileType}-${reason}`, modifyCallback);
|
|
581
|
+
} catch (e) {
|
|
582
|
+
Sentry.setTag(`modified-${fileType}`, 'fail');
|
|
583
|
+
Sentry.setTag(`${fileType}-mod-fail-reason`, reason);
|
|
584
|
+
throw e;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
@@ -1,57 +1,116 @@
|
|
|
1
1
|
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
2
|
-
import clack from '@clack/prompts';
|
|
2
|
+
import * as clack from '@clack/prompts';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
|
|
5
|
+
import * as Sentry from '@sentry/node';
|
|
6
|
+
|
|
5
7
|
import {
|
|
6
8
|
abort,
|
|
9
|
+
abortIfCancelled,
|
|
7
10
|
addSentryCliConfig,
|
|
8
|
-
|
|
11
|
+
confirmContinueIfNoOrDirtyGitRepo,
|
|
9
12
|
ensurePackageIsInstalled,
|
|
10
13
|
getOrAskForProjectData,
|
|
11
14
|
getPackageDotJson,
|
|
12
15
|
installPackage,
|
|
13
16
|
printWelcome,
|
|
14
17
|
} from '../utils/clack-utils';
|
|
15
|
-
import { hasPackageInstalled } from '../utils/package-json';
|
|
18
|
+
import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
|
|
16
19
|
import { WizardOptions } from '../utils/types';
|
|
17
20
|
import { createExamplePage } from './sdk-example';
|
|
18
21
|
import { createOrMergeSvelteKitFiles, loadSvelteConfig } from './sdk-setup';
|
|
22
|
+
import { traceStep, withTelemetry } from '../telemetry';
|
|
23
|
+
import { getKitVersionBucket, getSvelteVersionBucket } from './utils';
|
|
19
24
|
|
|
20
25
|
export async function runSvelteKitWizard(
|
|
21
26
|
options: WizardOptions,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
return withTelemetry(
|
|
29
|
+
{
|
|
30
|
+
enabled: options.telemetryEnabled,
|
|
31
|
+
integration: 'sveltekit',
|
|
32
|
+
},
|
|
33
|
+
() => runSvelteKitWizardWithTelemetry(options),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function runSvelteKitWizardWithTelemetry(
|
|
38
|
+
options: WizardOptions,
|
|
22
39
|
): Promise<void> {
|
|
23
40
|
printWelcome({
|
|
24
41
|
wizardName: 'Sentry SvelteKit Wizard',
|
|
25
42
|
promoCode: options.promoCode,
|
|
43
|
+
telemetryEnabled: options.telemetryEnabled,
|
|
26
44
|
});
|
|
27
45
|
|
|
28
|
-
await
|
|
46
|
+
await confirmContinueIfNoOrDirtyGitRepo();
|
|
29
47
|
|
|
30
48
|
const packageJson = await getPackageDotJson();
|
|
49
|
+
|
|
31
50
|
await ensurePackageIsInstalled(packageJson, '@sveltejs/kit', 'Sveltekit');
|
|
32
51
|
|
|
52
|
+
const kitVersion = getPackageVersion('@sveltejs/kit', packageJson);
|
|
53
|
+
const kitVersionBucket = getKitVersionBucket(kitVersion);
|
|
54
|
+
Sentry.setTag('sveltekit-version', kitVersionBucket);
|
|
55
|
+
|
|
56
|
+
if (kitVersionBucket === '0.x') {
|
|
57
|
+
clack.log.warn(
|
|
58
|
+
"It seems you're using a SvelteKit version <1.0.0 which is not supported by Sentry.\nWe recommend upgrading to the latest 1.x version before you continue.",
|
|
59
|
+
);
|
|
60
|
+
const shouldContinue = await abortIfCancelled(
|
|
61
|
+
clack.select({
|
|
62
|
+
message: 'Do you want to continue anyway?',
|
|
63
|
+
options: [
|
|
64
|
+
{
|
|
65
|
+
label: 'Yes, continue',
|
|
66
|
+
hint: 'The SDK might not work correctly',
|
|
67
|
+
value: true,
|
|
68
|
+
},
|
|
69
|
+
{ label: "No, I'll upgrade first", value: false },
|
|
70
|
+
],
|
|
71
|
+
}),
|
|
72
|
+
);
|
|
73
|
+
if (!shouldContinue) {
|
|
74
|
+
await abort('Exiting Wizard', 0);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
Sentry.setTag(
|
|
80
|
+
'svelte-version',
|
|
81
|
+
getSvelteVersionBucket(getPackageVersion('svelte', packageJson)),
|
|
82
|
+
);
|
|
83
|
+
|
|
33
84
|
const { selectedProject, selfHosted, sentryUrl, authToken } =
|
|
34
85
|
await getOrAskForProjectData(options, 'javascript-sveltekit');
|
|
35
86
|
|
|
87
|
+
const sdkAlreadyInstalled = hasPackageInstalled(
|
|
88
|
+
'@sentry/sveltekit',
|
|
89
|
+
packageJson,
|
|
90
|
+
);
|
|
91
|
+
Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled);
|
|
92
|
+
|
|
36
93
|
await installPackage({
|
|
37
94
|
packageName: '@sentry/sveltekit',
|
|
38
|
-
alreadyInstalled:
|
|
95
|
+
alreadyInstalled: sdkAlreadyInstalled,
|
|
39
96
|
});
|
|
40
97
|
|
|
41
98
|
await addSentryCliConfig(authToken);
|
|
42
99
|
|
|
43
|
-
const svelteConfig = await loadSvelteConfig
|
|
100
|
+
const svelteConfig = await traceStep('load-svelte-config', loadSvelteConfig);
|
|
44
101
|
|
|
45
102
|
try {
|
|
46
|
-
await
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
103
|
+
await traceStep('configure-sdk', () =>
|
|
104
|
+
createOrMergeSvelteKitFiles(
|
|
105
|
+
{
|
|
106
|
+
dsn: selectedProject.keys[0].dsn.public,
|
|
107
|
+
org: selectedProject.organization.slug,
|
|
108
|
+
project: selectedProject.slug,
|
|
109
|
+
selfHosted,
|
|
110
|
+
url: sentryUrl,
|
|
111
|
+
},
|
|
112
|
+
svelteConfig,
|
|
113
|
+
),
|
|
55
114
|
);
|
|
56
115
|
} catch (e: unknown) {
|
|
57
116
|
clack.log.error('Error while setting up the SvelteKit SDK:');
|
|
@@ -64,17 +123,20 @@ export async function runSvelteKitWizard(
|
|
|
64
123
|
: 'Unknown error',
|
|
65
124
|
),
|
|
66
125
|
);
|
|
126
|
+
Sentry.captureException('Error while setting up the SvelteKit SDK');
|
|
67
127
|
await abort('Exiting Wizard');
|
|
68
128
|
return;
|
|
69
129
|
}
|
|
70
130
|
|
|
71
131
|
try {
|
|
72
|
-
await
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
132
|
+
await traceStep('create-example-page', () =>
|
|
133
|
+
createExamplePage(svelteConfig, {
|
|
134
|
+
selfHosted,
|
|
135
|
+
url: sentryUrl,
|
|
136
|
+
orgSlug: selectedProject.organization.slug,
|
|
137
|
+
projectId: selectedProject.id,
|
|
138
|
+
}),
|
|
139
|
+
);
|
|
78
140
|
} catch (e: unknown) {
|
|
79
141
|
clack.log.error('Error while creating an example page to test Sentry:');
|
|
80
142
|
clack.log.info(
|
|
@@ -86,6 +148,9 @@ export async function runSvelteKitWizard(
|
|
|
86
148
|
: 'Unknown error',
|
|
87
149
|
),
|
|
88
150
|
);
|
|
151
|
+
Sentry.captureException(
|
|
152
|
+
'Error while creating an example Svelte page to test Sentry',
|
|
153
|
+
);
|
|
89
154
|
await abort('Exiting Wizard');
|
|
90
155
|
return;
|
|
91
156
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { lt, minVersion } from 'semver';
|
|
2
|
+
|
|
3
|
+
export function getKitVersionBucket(
|
|
4
|
+
version: string | undefined,
|
|
5
|
+
): 'none' | 'invalid' | '0.x' | '>=1.0.0 <1.24.0' | '>=1.24.0' {
|
|
6
|
+
if (!version) {
|
|
7
|
+
return 'none';
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const minVer = minVersion(version);
|
|
11
|
+
if (!minVer) {
|
|
12
|
+
return 'invalid';
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (lt(minVer, '1.0.0')) {
|
|
16
|
+
return '0.x';
|
|
17
|
+
} else if (lt(minVer, '1.24.0')) {
|
|
18
|
+
return '>=1.0.0 <1.24.0';
|
|
19
|
+
} else {
|
|
20
|
+
// This is the version when the client-side invalidation fix was released
|
|
21
|
+
// https://github.com/sveltejs/kit/releases/tag/%40sveltejs%2Fkit%401.24.0
|
|
22
|
+
// https://github.com/sveltejs/kit/pull/10576
|
|
23
|
+
return '>=1.24.0';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getSvelteVersionBucket(
|
|
28
|
+
version: string | undefined,
|
|
29
|
+
): 'none' | 'invalid' | '<3.0.0' | '3.x' | '4.x' | '>4.x' {
|
|
30
|
+
if (!version) {
|
|
31
|
+
return 'none';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const minVer = minVersion(version);
|
|
35
|
+
if (!minVer) {
|
|
36
|
+
return 'invalid';
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (lt(minVer, '3.0.0')) {
|
|
40
|
+
return '<3.0.0';
|
|
41
|
+
}
|
|
42
|
+
if (lt(minVer, '4.0.0')) {
|
|
43
|
+
return '3.x';
|
|
44
|
+
}
|
|
45
|
+
if (lt(minVer, '5.0.0')) {
|
|
46
|
+
return '4.x';
|
|
47
|
+
}
|
|
48
|
+
// Svelte 5 isn't released yet but it's being worked on
|
|
49
|
+
return '>4.x';
|
|
50
|
+
}
|
package/src/telemetry.ts
CHANGED
|
@@ -6,7 +6,8 @@ import {
|
|
|
6
6
|
makeNodeTransport,
|
|
7
7
|
NodeClient,
|
|
8
8
|
runWithAsyncContext,
|
|
9
|
-
|
|
9
|
+
setTag,
|
|
10
|
+
startSpan,
|
|
10
11
|
} from '@sentry/node';
|
|
11
12
|
import packageJson from '../package.json';
|
|
12
13
|
|
|
@@ -24,24 +25,29 @@ export async function withTelemetry<F>(
|
|
|
24
25
|
|
|
25
26
|
makeMain(sentryHub);
|
|
26
27
|
|
|
27
|
-
const transaction = sentryHub.startTransaction({
|
|
28
|
-
name: 'sentry-wizard-execution',
|
|
29
|
-
status: 'ok',
|
|
30
|
-
op: 'wizard.flow',
|
|
31
|
-
});
|
|
32
|
-
sentryHub.getScope().setSpan(transaction);
|
|
33
28
|
const sentrySession = sentryHub.startSession();
|
|
34
29
|
sentryHub.captureSession();
|
|
35
30
|
|
|
36
31
|
try {
|
|
37
|
-
return await
|
|
32
|
+
return await startSpan(
|
|
33
|
+
{
|
|
34
|
+
name: 'sentry-wizard-execution',
|
|
35
|
+
status: 'ok',
|
|
36
|
+
op: 'wizard.flow',
|
|
37
|
+
},
|
|
38
|
+
async () => {
|
|
39
|
+
updateProgress('start');
|
|
40
|
+
const res = await runWithAsyncContext(callback);
|
|
41
|
+
updateProgress('finished');
|
|
42
|
+
|
|
43
|
+
return res;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
38
46
|
} catch (e) {
|
|
39
47
|
sentryHub.captureException('Error during wizard execution.');
|
|
40
|
-
transaction.setStatus('internal_error');
|
|
41
48
|
sentrySession.status = 'crashed';
|
|
42
49
|
throw e;
|
|
43
50
|
} finally {
|
|
44
|
-
transaction.finish();
|
|
45
51
|
sentryHub.endSession();
|
|
46
52
|
await sentryClient.flush(3000);
|
|
47
53
|
}
|
|
@@ -92,5 +98,10 @@ function createSentryInstance(enabled: boolean, integration: string) {
|
|
|
92
98
|
}
|
|
93
99
|
|
|
94
100
|
export function traceStep<T>(step: string, callback: () => T): T {
|
|
95
|
-
|
|
101
|
+
updateProgress(step);
|
|
102
|
+
return startSpan({ name: step, op: 'wizard.step' }, () => callback());
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function updateProgress(step: string) {
|
|
106
|
+
setTag('progress', step);
|
|
96
107
|
}
|
package/src/utils/ast-utils.ts
CHANGED
|
@@ -4,6 +4,8 @@ import * as recast from 'recast';
|
|
|
4
4
|
import x = recast.types;
|
|
5
5
|
import t = x.namedTypes;
|
|
6
6
|
|
|
7
|
+
const b = recast.types.builders;
|
|
8
|
+
|
|
7
9
|
/**
|
|
8
10
|
* Checks if a file where we don't know its concrete file type yet exists
|
|
9
11
|
* and returns the full path to the file with the correct file type.
|
|
@@ -36,3 +38,181 @@ export function hasSentryContent(program: t.Program): boolean {
|
|
|
36
38
|
|
|
37
39
|
return !!foundSentry;
|
|
38
40
|
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Searches for a property of an ObjectExpression by name
|
|
44
|
+
*
|
|
45
|
+
* @param object the ObjectExpression to search in
|
|
46
|
+
* @param name the name of the property to search for
|
|
47
|
+
*
|
|
48
|
+
* @returns the property if it exists
|
|
49
|
+
*/
|
|
50
|
+
export function getObjectProperty(
|
|
51
|
+
object: t.ObjectExpression,
|
|
52
|
+
name: string,
|
|
53
|
+
): t.Property | undefined {
|
|
54
|
+
return object.properties.find((p): p is t.Property => {
|
|
55
|
+
const isObjectProp = p.type === 'Property' || p.type === 'ObjectProperty';
|
|
56
|
+
|
|
57
|
+
if (!isObjectProp) {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const hasMatchingLiteralKey =
|
|
62
|
+
isObjectProp &&
|
|
63
|
+
(p.key.type === 'Literal' || p.key.type === 'StringLiteral') &&
|
|
64
|
+
p.key.value === name;
|
|
65
|
+
|
|
66
|
+
if (hasMatchingLiteralKey) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// has matching identifier key
|
|
71
|
+
return isObjectProp && p.key.type === 'Identifier' && p.key.name === name;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Attempts to find a property of an ObjectExpression by name. If it doesn't exist,
|
|
77
|
+
* the property will be added to the ObjectExpression with the provided default value.
|
|
78
|
+
*
|
|
79
|
+
* @param object the parent object expression to search in
|
|
80
|
+
* @param name the name of the property to search for
|
|
81
|
+
* @param defaultValue the default value to set if the property doesn't exist
|
|
82
|
+
*
|
|
83
|
+
* @returns the
|
|
84
|
+
*/
|
|
85
|
+
export function getOrSetObjectProperty(
|
|
86
|
+
object: t.ObjectExpression,
|
|
87
|
+
name: string,
|
|
88
|
+
defaultValue:
|
|
89
|
+
| t.Literal
|
|
90
|
+
| t.BooleanLiteral
|
|
91
|
+
| t.StringLiteral
|
|
92
|
+
| t.ObjectExpression,
|
|
93
|
+
): t.Property {
|
|
94
|
+
const existingProperty = getObjectProperty(object, name);
|
|
95
|
+
|
|
96
|
+
if (existingProperty) {
|
|
97
|
+
return existingProperty;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const newProperty = b.property.from({
|
|
101
|
+
kind: 'init',
|
|
102
|
+
key: b.stringLiteral(name),
|
|
103
|
+
value: defaultValue,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
object.properties.push(newProperty);
|
|
107
|
+
|
|
108
|
+
return newProperty;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Sets a property of an ObjectExpression if it exists, otherwise adds it
|
|
113
|
+
* to the ObjectExpression. Optionally, a comment can be added to the
|
|
114
|
+
* property.
|
|
115
|
+
*
|
|
116
|
+
* @param object the ObjectExpression to set the property on
|
|
117
|
+
* @param name the name of the property to set
|
|
118
|
+
* @param value the value of the property to set
|
|
119
|
+
* @param comment (optional) a comment to add to the property
|
|
120
|
+
*/
|
|
121
|
+
export function setOrUpdateObjectProperty(
|
|
122
|
+
object: t.ObjectExpression,
|
|
123
|
+
name: string,
|
|
124
|
+
value: t.Literal | t.BooleanLiteral | t.StringLiteral | t.ObjectExpression,
|
|
125
|
+
comment?: string,
|
|
126
|
+
) {
|
|
127
|
+
const newComments =
|
|
128
|
+
comment &&
|
|
129
|
+
comment.split('\n').map((c) => b.commentLine(` ${c}`, true, false));
|
|
130
|
+
|
|
131
|
+
const existingProperty = getObjectProperty(object, name);
|
|
132
|
+
|
|
133
|
+
if (existingProperty) {
|
|
134
|
+
existingProperty.value = value;
|
|
135
|
+
if (newComments) {
|
|
136
|
+
existingProperty.comments = [
|
|
137
|
+
...(existingProperty?.comments || []),
|
|
138
|
+
...newComments,
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
object.properties.push(
|
|
143
|
+
b.objectProperty.from({
|
|
144
|
+
key: b.stringLiteral(name),
|
|
145
|
+
value,
|
|
146
|
+
...(newComments && {
|
|
147
|
+
comments: newComments,
|
|
148
|
+
}),
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
type JsonCParseResult =
|
|
155
|
+
| {
|
|
156
|
+
jsonObject: t.ObjectExpression;
|
|
157
|
+
ast: t.Program;
|
|
158
|
+
}
|
|
159
|
+
| {
|
|
160
|
+
jsonObject: undefined;
|
|
161
|
+
ast: undefined;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Parses a JSON string with (potential) comments (JSON-C) and returns the JS AST
|
|
166
|
+
* that can be walked and modified with recast like a normal JS AST.
|
|
167
|
+
*
|
|
168
|
+
* This is done by wrapping the JSON-C string in parentheses, thereby making it
|
|
169
|
+
* a JS `Program` with an `ExpressionStatement` as its body. The expression is then
|
|
170
|
+
* extracted from the AST and returned alongside the AST.
|
|
171
|
+
*
|
|
172
|
+
* To preserve as much original formatting as possible, the returned `ast`
|
|
173
|
+
* property should be passed to {@link `printJsonC`} to get the JSON-C string back.
|
|
174
|
+
*
|
|
175
|
+
* If the input is not valid JSON-C, the result will be undefined.
|
|
176
|
+
*
|
|
177
|
+
* @see {@link JsonCParseResult}
|
|
178
|
+
*
|
|
179
|
+
* @param jsonString a JSON-C string
|
|
180
|
+
*
|
|
181
|
+
* @returns a {@link JsonCParseResult}, containing either the JSON-C object and the AST or undefined in both cases
|
|
182
|
+
*/
|
|
183
|
+
export function parseJsonC(jsonString: string): JsonCParseResult {
|
|
184
|
+
try {
|
|
185
|
+
const jsTsConfig = `(${jsonString})`;
|
|
186
|
+
// no idea why recast returns any here, this is dumb :/
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
188
|
+
const ast = recast.parse(jsTsConfig.toString()).program as t.Program;
|
|
189
|
+
|
|
190
|
+
const jsonObject =
|
|
191
|
+
(ast.body[0].type === 'ExpressionStatement' &&
|
|
192
|
+
ast.body[0].expression.type === 'ObjectExpression' &&
|
|
193
|
+
ast.body[0].expression) ||
|
|
194
|
+
undefined;
|
|
195
|
+
|
|
196
|
+
if (jsonObject) {
|
|
197
|
+
return { jsonObject, ast };
|
|
198
|
+
}
|
|
199
|
+
} catch {
|
|
200
|
+
/* empty */
|
|
201
|
+
}
|
|
202
|
+
return { jsonObject: undefined, ast: undefined };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Takes the AST of a parsed JSON-C "program" and returns the JSON-C string without
|
|
207
|
+
* any of the temporary JS wrapper code that was previously applied.
|
|
208
|
+
*
|
|
209
|
+
* Only use this in conjunction with {@link `parseJsonC`}
|
|
210
|
+
*
|
|
211
|
+
* @param ast the `ast` returned from {@link `parseJsonC`}
|
|
212
|
+
*
|
|
213
|
+
* @returns the JSON-C string
|
|
214
|
+
*/
|
|
215
|
+
export function printJsonC(ast: t.Program): string {
|
|
216
|
+
const js = recast.print(ast).code;
|
|
217
|
+
return js.substring(1, js.length - 1);
|
|
218
|
+
}
|