@sentry/wizard 3.9.2 → 3.11.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 +58 -6
- package/dist/lib/Constants.d.ts +2 -0
- package/dist/lib/Constants.js +10 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/lib/Steps/ChooseIntegration.js +15 -4
- package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/Android.d.ts +9 -0
- package/dist/lib/Steps/Integrations/Android.js +86 -0
- package/dist/lib/Steps/Integrations/Android.js.map +1 -0
- package/dist/lib/Steps/Integrations/Cordova.js +5 -1
- package/dist/lib/Steps/Integrations/Cordova.js.map +1 -1
- package/dist/lib/Steps/Integrations/ReactNative.js +3 -3
- package/dist/lib/Steps/Integrations/ReactNative.js.map +1 -1
- package/dist/lib/Steps/Integrations/Remix.d.ts +12 -0
- package/dist/lib/Steps/Integrations/Remix.js +98 -0
- package/dist/lib/Steps/Integrations/Remix.js.map +1 -0
- package/dist/lib/Steps/PromptForParameters.js +36 -3
- package/dist/lib/Steps/PromptForParameters.js.map +1 -1
- package/dist/lib/Steps/SentryProjectSelector.js +1 -1
- package/dist/lib/Steps/SentryProjectSelector.js.map +1 -1
- package/dist/package.json +4 -3
- package/dist/src/android/android-wizard.d.ts +2 -0
- package/dist/src/android/android-wizard.js +217 -0
- package/dist/src/android/android-wizard.js.map +1 -0
- package/dist/src/android/code-tools.d.ts +39 -0
- package/dist/src/android/code-tools.js +161 -0
- package/dist/src/android/code-tools.js.map +1 -0
- package/dist/src/android/gradle.d.ts +62 -0
- package/dist/src/android/gradle.js +281 -0
- package/dist/src/android/gradle.js.map +1 -0
- package/dist/src/android/manifest.d.ts +57 -0
- package/dist/src/android/manifest.js +183 -0
- package/dist/src/android/manifest.js.map +1 -0
- package/dist/src/android/templates.d.ts +11 -0
- package/dist/src/android/templates.js +34 -0
- package/dist/src/android/templates.js.map +1 -0
- package/dist/src/apple/apple-wizard.js +123 -64
- package/dist/src/apple/apple-wizard.js.map +1 -1
- package/dist/src/apple/cocoapod.js +4 -3
- package/dist/src/apple/cocoapod.js.map +1 -1
- package/dist/src/apple/code-tools.d.ts +1 -1
- package/dist/src/apple/code-tools.js +43 -19
- package/dist/src/apple/code-tools.js.map +1 -1
- package/dist/src/apple/fastlane.d.ts +1 -1
- package/dist/src/apple/fastlane.js +12 -6
- package/dist/src/apple/fastlane.js.map +1 -1
- package/dist/src/apple/templates.d.ts +2 -2
- package/dist/src/apple/templates.js +4 -4
- package/dist/src/apple/templates.js.map +1 -1
- package/dist/src/apple/xcode-manager.d.ts +19 -3
- package/dist/src/apple/xcode-manager.js +126 -24
- package/dist/src/apple/xcode-manager.js.map +1 -1
- package/dist/src/nextjs/nextjs-wizard.js +49 -11
- package/dist/src/nextjs/nextjs-wizard.js.map +1 -1
- package/dist/src/nextjs/templates.d.ts +2 -0
- package/dist/src/nextjs/templates.js +6 -2
- package/dist/src/nextjs/templates.js.map +1 -1
- package/dist/src/remix/codemods/handle-error.d.ts +2 -0
- package/dist/src/remix/codemods/handle-error.js +70 -0
- package/dist/src/remix/codemods/handle-error.js.map +1 -0
- package/dist/src/remix/codemods/root-v1.d.ts +1 -0
- package/dist/src/remix/codemods/root-v1.js +133 -0
- package/dist/src/remix/codemods/root-v1.js.map +1 -0
- package/dist/src/remix/codemods/root-v2.d.ts +1 -0
- package/dist/src/remix/codemods/root-v2.js +134 -0
- package/dist/src/remix/codemods/root-v2.js.map +1 -0
- package/dist/src/remix/remix-wizard.d.ts +2 -0
- package/dist/src/remix/remix-wizard.js +196 -0
- package/dist/src/remix/remix-wizard.js.map +1 -0
- package/dist/src/remix/sdk-setup.d.ts +18 -0
- package/dist/src/remix/sdk-setup.js +293 -0
- package/dist/src/remix/sdk-setup.js.map +1 -0
- package/dist/src/remix/templates.d.ts +2 -0
- package/dist/src/remix/templates.js +6 -0
- package/dist/src/remix/templates.js.map +1 -0
- package/dist/src/remix/utils.d.ts +6 -0
- package/dist/src/remix/utils.js +55 -0
- package/dist/src/remix/utils.js.map +1 -0
- package/dist/src/sourcemaps/sourcemaps-wizard.js +49 -25
- package/dist/src/sourcemaps/sourcemaps-wizard.js.map +1 -1
- package/dist/src/sourcemaps/tools/nextjs.js +1 -1
- package/dist/src/sourcemaps/tools/nextjs.js.map +1 -1
- package/dist/src/sourcemaps/tools/remix.d.ts +3 -0
- package/dist/src/sourcemaps/tools/remix.js +125 -0
- package/dist/src/sourcemaps/tools/remix.js.map +1 -0
- package/dist/src/sourcemaps/tools/sentry-cli.js +19 -16
- package/dist/src/sourcemaps/tools/sentry-cli.js.map +1 -1
- package/dist/src/sourcemaps/tools/vite.d.ts +2 -1
- package/dist/src/sourcemaps/tools/vite.js +99 -12
- package/dist/src/sourcemaps/tools/vite.js.map +1 -1
- package/dist/src/sourcemaps/utils/detect-tool.d.ts +1 -1
- package/dist/src/sourcemaps/utils/detect-tool.js +1 -0
- package/dist/src/sourcemaps/utils/detect-tool.js.map +1 -1
- package/dist/src/sveltekit/sdk-setup.js +3 -3
- package/dist/src/sveltekit/sdk-setup.js.map +1 -1
- package/dist/src/sveltekit/sveltekit-wizard.js +34 -44
- package/dist/src/sveltekit/sveltekit-wizard.js.map +1 -1
- package/dist/src/telemetry.js +1 -0
- package/dist/src/telemetry.js.map +1 -1
- package/dist/src/utils/ast-utils.d.ts +2 -2
- package/dist/src/utils/ast-utils.js +7 -7
- package/dist/src/utils/ast-utils.js.map +1 -1
- package/dist/src/utils/clack-utils.d.ts +23 -28
- package/dist/src/utils/clack-utils.js +287 -244
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/dist/src/utils/package-manager.d.ts +10 -0
- package/dist/{lib/Helper/PackageManager.js → src/utils/package-manager.js} +42 -74
- package/dist/src/utils/package-manager.js.map +1 -0
- package/dist/src/utils/release-registry.d.ts +1 -0
- package/dist/src/utils/release-registry.js +68 -0
- package/dist/src/utils/release-registry.js.map +1 -0
- package/dist/src/utils/sentrycli-utils.d.ts +4 -0
- package/dist/src/utils/sentrycli-utils.js +41 -0
- package/dist/src/utils/sentrycli-utils.js.map +1 -0
- package/dist/test/sourcemaps/tools/vite.test.d.ts +1 -0
- package/dist/test/sourcemaps/tools/vite.test.js +132 -0
- package/dist/test/sourcemaps/tools/vite.test.js.map +1 -0
- package/lib/Constants.ts +10 -0
- package/lib/Steps/ChooseIntegration.ts +14 -3
- package/lib/Steps/Integrations/Android.ts +23 -0
- package/lib/Steps/Integrations/Cordova.ts +5 -1
- package/lib/Steps/Integrations/ReactNative.ts +9 -3
- package/lib/Steps/Integrations/Remix.ts +32 -0
- package/lib/Steps/PromptForParameters.ts +48 -3
- package/lib/Steps/SentryProjectSelector.ts +3 -1
- package/package.json +4 -3
- package/src/android/android-wizard.ts +196 -0
- package/src/android/code-tools.ts +156 -0
- package/src/android/gradle.ts +245 -0
- package/src/android/manifest.ts +180 -0
- package/src/android/templates.ts +88 -0
- package/src/apple/apple-wizard.ts +113 -35
- package/src/apple/cocoapod.ts +6 -3
- package/src/apple/code-tools.ts +46 -18
- package/src/apple/fastlane.ts +6 -12
- package/src/apple/templates.ts +2 -8
- package/src/apple/xcode-manager.ts +167 -25
- package/src/nextjs/nextjs-wizard.ts +72 -8
- package/src/nextjs/templates.ts +16 -2
- package/src/remix/codemods/handle-error.ts +67 -0
- package/src/remix/codemods/root-v1.ts +91 -0
- package/src/remix/codemods/root-v2.ts +84 -0
- package/src/remix/remix-wizard.ts +132 -0
- package/src/remix/sdk-setup.ts +300 -0
- package/src/remix/templates.ts +15 -0
- package/src/remix/utils.ts +41 -0
- package/src/sourcemaps/sourcemaps-wizard.ts +28 -5
- package/src/sourcemaps/tools/nextjs.ts +2 -2
- package/src/sourcemaps/tools/remix.ts +90 -0
- package/src/sourcemaps/tools/sentry-cli.ts +8 -7
- package/src/sourcemaps/tools/vite.ts +136 -6
- package/src/sourcemaps/utils/detect-tool.ts +4 -1
- package/src/sveltekit/sdk-setup.ts +4 -4
- package/src/sveltekit/sveltekit-wizard.ts +5 -14
- package/src/telemetry.ts +2 -0
- package/src/utils/ast-utils.ts +7 -5
- package/src/utils/clack-utils.ts +366 -258
- package/src/utils/package-manager.ts +61 -0
- package/src/utils/release-registry.ts +19 -0
- package/src/utils/sentrycli-utils.ts +22 -0
- package/test/sourcemaps/tools/vite.test.ts +149 -0
- package/dist/lib/Helper/PackageManager.d.ts +0 -22
- package/dist/lib/Helper/PackageManager.js.map +0 -1
- package/dist/src/utils/vendor/clack-custom-select.d.ts +0 -21
- package/dist/src/utils/vendor/clack-custom-select.js +0 -137
- package/dist/src/utils/vendor/clack-custom-select.js.map +0 -1
- package/lib/Helper/PackageManager.ts +0 -59
- package/src/utils/vendor/clack-custom-select.ts +0 -160
|
@@ -2,17 +2,30 @@
|
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
4
4
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
5
|
-
|
|
5
|
+
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
6
|
+
import clack from '@clack/prompts';
|
|
6
7
|
import * as fs from 'fs';
|
|
7
8
|
import { SentryProjectData } from '../utils/types';
|
|
8
9
|
import * as templates from './templates';
|
|
10
|
+
import * as path from 'path';
|
|
9
11
|
const xcode = require('xcode');
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
interface ProjetFile {
|
|
14
|
+
key: string;
|
|
15
|
+
path: string;
|
|
16
|
+
}
|
|
12
17
|
|
|
13
|
-
function setDebugInformationFormat(proj: any): void {
|
|
18
|
+
function setDebugInformationFormat(proj: any, targetName: string): void {
|
|
14
19
|
const xcObjects = proj.hash.project.objects;
|
|
15
|
-
const
|
|
20
|
+
const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter(
|
|
21
|
+
(key) => {
|
|
22
|
+
return (
|
|
23
|
+
!key.endsWith('_comment') &&
|
|
24
|
+
xcObjects.PBXNativeTarget[key].name === targetName
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
)[0];
|
|
28
|
+
const target = xcObjects.PBXNativeTarget[targetKey];
|
|
16
29
|
|
|
17
30
|
xcObjects.XCConfigurationList[
|
|
18
31
|
target.buildConfigurationList
|
|
@@ -23,7 +36,7 @@ function setDebugInformationFormat(proj: any): void {
|
|
|
23
36
|
});
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
function addSentrySPM(proj: any): void {
|
|
39
|
+
function addSentrySPM(proj: any, targetName: string): void {
|
|
27
40
|
const xcObjects = proj.hash.project.objects;
|
|
28
41
|
|
|
29
42
|
const sentryFrameworkUUID = proj.generateUuid() as string;
|
|
@@ -61,7 +74,16 @@ function addSentrySPM(proj: any): void {
|
|
|
61
74
|
}
|
|
62
75
|
}
|
|
63
76
|
|
|
64
|
-
const
|
|
77
|
+
const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter(
|
|
78
|
+
(key) => {
|
|
79
|
+
return (
|
|
80
|
+
!key.endsWith('_comment') &&
|
|
81
|
+
xcObjects.PBXNativeTarget[key].name === targetName
|
|
82
|
+
);
|
|
83
|
+
},
|
|
84
|
+
)[0];
|
|
85
|
+
const target = xcObjects.PBXNativeTarget[targetKey];
|
|
86
|
+
|
|
65
87
|
if (!target.packageProductDependencies) {
|
|
66
88
|
target.packageProductDependencies = [];
|
|
67
89
|
}
|
|
@@ -106,15 +128,25 @@ function addSentrySPM(proj: any): void {
|
|
|
106
128
|
};
|
|
107
129
|
xcObjects.XCSwiftPackageProductDependency[sentrySPMUUID + '_comment'] =
|
|
108
130
|
'Sentry';
|
|
131
|
+
|
|
132
|
+
clack.log.step('Added Sentry SPM dependency to your project');
|
|
109
133
|
}
|
|
110
134
|
|
|
111
135
|
function addUploadSymbolsScript(
|
|
112
136
|
xcodeProject: any,
|
|
113
137
|
sentryProject: SentryProjectData,
|
|
114
|
-
|
|
138
|
+
targetName: string,
|
|
115
139
|
uploadSource = true,
|
|
116
140
|
): void {
|
|
117
141
|
const xcObjects = xcodeProject.hash.project.objects;
|
|
142
|
+
const targetKey: string = Object.keys(xcObjects.PBXNativeTarget || {}).filter(
|
|
143
|
+
(key) => {
|
|
144
|
+
return (
|
|
145
|
+
!key.endsWith('_comment') &&
|
|
146
|
+
xcObjects.PBXNativeTarget[key].name === targetName
|
|
147
|
+
);
|
|
148
|
+
},
|
|
149
|
+
)[0];
|
|
118
150
|
|
|
119
151
|
for (const scriptKey in xcObjects.PBXShellScriptBuildPhase || {}) {
|
|
120
152
|
if (!scriptKey.endsWith('_comment')) {
|
|
@@ -132,7 +164,7 @@ function addUploadSymbolsScript(
|
|
|
132
164
|
[],
|
|
133
165
|
'PBXShellScriptBuildPhase',
|
|
134
166
|
'Upload Debug Symbols to Sentry',
|
|
135
|
-
|
|
167
|
+
targetKey,
|
|
136
168
|
{
|
|
137
169
|
inputFileListPaths: [],
|
|
138
170
|
outputFileListPaths: [],
|
|
@@ -141,29 +173,139 @@ function addUploadSymbolsScript(
|
|
|
141
173
|
shellScript: templates.getRunScriptTemplate(
|
|
142
174
|
sentryProject.organization.slug,
|
|
143
175
|
sentryProject.slug,
|
|
144
|
-
apiKeys.token,
|
|
145
176
|
uploadSource,
|
|
146
177
|
),
|
|
147
178
|
},
|
|
148
179
|
);
|
|
180
|
+
clack.log.step(`Added Sentry upload script to "${targetName}" build phase`);
|
|
149
181
|
}
|
|
150
182
|
|
|
151
|
-
export
|
|
152
|
-
projectPath: string
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
183
|
+
export class XcodeProject {
|
|
184
|
+
projectPath: string;
|
|
185
|
+
project: any;
|
|
186
|
+
objects: any;
|
|
187
|
+
files: ProjetFile[] | undefined;
|
|
188
|
+
|
|
189
|
+
public constructor(projectPath: string) {
|
|
190
|
+
this.projectPath = projectPath;
|
|
191
|
+
this.project = xcode.project(projectPath);
|
|
192
|
+
this.project.parseSync();
|
|
193
|
+
this.objects = this.project.hash.project.objects;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
public getAllTargets(): string[] {
|
|
197
|
+
return Object.keys(this.objects.PBXNativeTarget || {})
|
|
198
|
+
.filter((key) => {
|
|
199
|
+
return (
|
|
200
|
+
!key.endsWith('_comment') &&
|
|
201
|
+
this.objects.PBXNativeTarget[key].productType.startsWith(
|
|
202
|
+
'"com.apple.product-type.application',
|
|
203
|
+
)
|
|
204
|
+
);
|
|
205
|
+
})
|
|
206
|
+
.map((key) => {
|
|
207
|
+
return this.objects.PBXNativeTarget[key].name as string;
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public updateXcodeProject(
|
|
212
|
+
sentryProject: SentryProjectData,
|
|
213
|
+
target: string,
|
|
214
|
+
apiKeys: { token: string },
|
|
215
|
+
addSPMReference: boolean,
|
|
216
|
+
uploadSource = true,
|
|
217
|
+
): void {
|
|
218
|
+
addUploadSymbolsScript(this.project, sentryProject, target, uploadSource);
|
|
219
|
+
if (uploadSource) {
|
|
220
|
+
setDebugInformationFormat(this.project, target);
|
|
221
|
+
}
|
|
222
|
+
if (addSPMReference) {
|
|
223
|
+
addSentrySPM(this.project, target);
|
|
224
|
+
}
|
|
225
|
+
const newContent = this.project.writeSync();
|
|
226
|
+
fs.writeFileSync(this.projectPath, newContent);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
public filesForTarget(target: string): string[] | undefined {
|
|
230
|
+
const files = this.projectFiles();
|
|
231
|
+
const fileDictionary: any = {};
|
|
232
|
+
files.forEach((file) => {
|
|
233
|
+
fileDictionary[file.key] = file.path;
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const nativeTarget = Object.keys(this.objects.PBXNativeTarget || {}).filter(
|
|
237
|
+
(key) => {
|
|
238
|
+
return (
|
|
239
|
+
!key.endsWith('_comment') &&
|
|
240
|
+
this.objects.PBXNativeTarget[key].name === target
|
|
241
|
+
);
|
|
242
|
+
},
|
|
243
|
+
)[0];
|
|
244
|
+
|
|
245
|
+
if (nativeTarget === undefined) {
|
|
246
|
+
return undefined;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const buildPhaseKey = this.objects.PBXNativeTarget[
|
|
250
|
+
nativeTarget
|
|
251
|
+
].buildPhases.filter((phase: any) => {
|
|
252
|
+
return this.objects.PBXSourcesBuildPhase[phase.value] !== undefined;
|
|
253
|
+
})[0];
|
|
254
|
+
|
|
255
|
+
if (buildPhaseKey === undefined) {
|
|
256
|
+
return undefined;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const buildPhases = this.objects.PBXSourcesBuildPhase[buildPhaseKey.value];
|
|
260
|
+
if (buildPhases === undefined) {
|
|
261
|
+
return undefined;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const baseDir = path.dirname(path.dirname(this.projectPath));
|
|
265
|
+
|
|
266
|
+
return buildPhases.files
|
|
267
|
+
.map((file: any) => {
|
|
268
|
+
const buildFile = fileDictionary[
|
|
269
|
+
this.objects.PBXBuildFile[file.value].fileRef
|
|
270
|
+
] as string;
|
|
271
|
+
if (!buildFile) {
|
|
272
|
+
return '';
|
|
273
|
+
}
|
|
274
|
+
return path.join(baseDir, buildFile);
|
|
275
|
+
})
|
|
276
|
+
.filter((f: string) => f.length > 0) as string[];
|
|
163
277
|
}
|
|
164
|
-
|
|
165
|
-
|
|
278
|
+
|
|
279
|
+
projectFiles(): ProjetFile[] {
|
|
280
|
+
if (this.files === undefined) {
|
|
281
|
+
const proj = this.project.getFirstProject();
|
|
282
|
+
const mainGroupKey = proj.firstProject.mainGroup;
|
|
283
|
+
const mainGroup = this.objects.PBXGroup[mainGroupKey];
|
|
284
|
+
this.files = this.buildGroup(mainGroup);
|
|
285
|
+
}
|
|
286
|
+
return this.files;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
buildGroup(group: any, path = ''): ProjetFile[] {
|
|
290
|
+
const result: ProjetFile[] = [];
|
|
291
|
+
for (const child of group.children) {
|
|
292
|
+
if (this.objects.PBXFileReference[child.value]) {
|
|
293
|
+
const fileReference = this.objects.PBXFileReference[child.value];
|
|
294
|
+
result.push({
|
|
295
|
+
key: child.value,
|
|
296
|
+
path: `${path}${fileReference.path.replace(/"/g, '')}`,
|
|
297
|
+
});
|
|
298
|
+
} else if (this.objects.PBXGroup[child.value]) {
|
|
299
|
+
const groupReference = this.objects.PBXGroup[child.value];
|
|
300
|
+
const groupChildren = this.buildGroup(
|
|
301
|
+
groupReference,
|
|
302
|
+
groupReference.path
|
|
303
|
+
? `${path}${groupReference.path.replace(/"/g, '')}/`
|
|
304
|
+
: path,
|
|
305
|
+
);
|
|
306
|
+
result.push(...groupChildren);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
166
310
|
}
|
|
167
|
-
const newContent = proj.writeSync();
|
|
168
|
-
fs.writeFileSync(projectPath, newContent);
|
|
169
311
|
}
|
|
@@ -10,7 +10,7 @@ import * as path from 'path';
|
|
|
10
10
|
import {
|
|
11
11
|
abort,
|
|
12
12
|
abortIfCancelled,
|
|
13
|
-
|
|
13
|
+
addSentryCliConfig,
|
|
14
14
|
confirmContinueEvenThoughNoGitRepo,
|
|
15
15
|
ensurePackageIsInstalled,
|
|
16
16
|
getOrAskForProjectData,
|
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
getNextjsWebpackPluginOptionsTemplate,
|
|
29
29
|
getSentryConfigContents,
|
|
30
30
|
getSentryExampleApiRoute,
|
|
31
|
+
getSentryExampleAppDirApiRoute,
|
|
31
32
|
getSentryExamplePageContents,
|
|
32
33
|
} from './templates';
|
|
33
34
|
|
|
@@ -44,7 +45,7 @@ export async function runNextjsWizard(options: WizardOptions): Promise<void> {
|
|
|
44
45
|
await ensurePackageIsInstalled(packageJson, 'next', 'Next.js');
|
|
45
46
|
|
|
46
47
|
const { selectedProject, authToken, selfHosted, sentryUrl } =
|
|
47
|
-
await getOrAskForProjectData(options);
|
|
48
|
+
await getOrAskForProjectData(options, 'javascript-nextjs');
|
|
48
49
|
|
|
49
50
|
await installPackage({
|
|
50
51
|
packageName: '@sentry/nextjs',
|
|
@@ -279,8 +280,11 @@ export async function runNextjsWizard(options: WizardOptions): Promise<void> {
|
|
|
279
280
|
}
|
|
280
281
|
}
|
|
281
282
|
|
|
283
|
+
const srcDir = path.join(process.cwd(), 'src');
|
|
282
284
|
const maybePagesDirPath = path.join(process.cwd(), 'pages');
|
|
283
|
-
const maybeSrcPagesDirPath = path.join(
|
|
285
|
+
const maybeSrcPagesDirPath = path.join(srcDir, 'pages');
|
|
286
|
+
const maybeAppDirPath = path.join(process.cwd(), 'app');
|
|
287
|
+
const maybeSrcAppDirPath = path.join(srcDir, 'app');
|
|
284
288
|
|
|
285
289
|
let pagesLocation =
|
|
286
290
|
fs.existsSync(maybePagesDirPath) &&
|
|
@@ -291,23 +295,83 @@ export async function runNextjsWizard(options: WizardOptions): Promise<void> {
|
|
|
291
295
|
? ['src', 'pages']
|
|
292
296
|
: undefined;
|
|
293
297
|
|
|
294
|
-
|
|
295
|
-
|
|
298
|
+
const appLocation =
|
|
299
|
+
fs.existsSync(maybeAppDirPath) &&
|
|
300
|
+
fs.lstatSync(maybeAppDirPath).isDirectory()
|
|
301
|
+
? ['app']
|
|
302
|
+
: fs.existsSync(maybeSrcAppDirPath) &&
|
|
303
|
+
fs.lstatSync(maybeSrcAppDirPath).isDirectory()
|
|
304
|
+
? ['src', 'app']
|
|
305
|
+
: undefined;
|
|
306
|
+
|
|
307
|
+
if (!pagesLocation && !appLocation) {
|
|
308
|
+
pagesLocation =
|
|
309
|
+
fs.existsSync(srcDir) && fs.lstatSync(srcDir).isDirectory()
|
|
310
|
+
? ['src', 'pages']
|
|
311
|
+
: ['pages'];
|
|
296
312
|
fs.mkdirSync(path.join(process.cwd(), ...pagesLocation), {
|
|
297
313
|
recursive: true,
|
|
298
314
|
});
|
|
299
315
|
}
|
|
300
316
|
|
|
301
|
-
if (
|
|
317
|
+
if (appLocation) {
|
|
318
|
+
const examplePageContents = getSentryExamplePageContents({
|
|
319
|
+
selfHosted,
|
|
320
|
+
orgSlug: selectedProject.organization.slug,
|
|
321
|
+
projectId: selectedProject.id,
|
|
322
|
+
url: sentryUrl,
|
|
323
|
+
useClient: true,
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
await fs.promises.writeFile(
|
|
327
|
+
path.join(
|
|
328
|
+
process.cwd(),
|
|
329
|
+
...appLocation,
|
|
330
|
+
'sentry-example-page',
|
|
331
|
+
'page.jsx',
|
|
332
|
+
),
|
|
333
|
+
examplePageContents,
|
|
334
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
clack.log.success(
|
|
338
|
+
`Created ${chalk.bold(
|
|
339
|
+
path.join(...appLocation, 'sentry-example-page', 'page.jsx'),
|
|
340
|
+
)}.`,
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
fs.mkdirSync(path.join(process.cwd(), ...appLocation, 'api'), {
|
|
344
|
+
recursive: true,
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
await fs.promises.writeFile(
|
|
348
|
+
path.join(
|
|
349
|
+
process.cwd(),
|
|
350
|
+
...appLocation,
|
|
351
|
+
'api',
|
|
352
|
+
'sentry-example-api',
|
|
353
|
+
'route.js',
|
|
354
|
+
),
|
|
355
|
+
getSentryExampleAppDirApiRoute(),
|
|
356
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
clack.log.success(
|
|
360
|
+
`Created ${chalk.bold(
|
|
361
|
+
path.join(...appLocation, 'api', 'sentry-example-api', 'route.js'),
|
|
362
|
+
)}.`,
|
|
363
|
+
);
|
|
364
|
+
} else if (pagesLocation) {
|
|
302
365
|
const examplePageContents = getSentryExamplePageContents({
|
|
303
366
|
selfHosted,
|
|
304
367
|
orgSlug: selectedProject.organization.slug,
|
|
305
368
|
projectId: selectedProject.id,
|
|
306
369
|
url: sentryUrl,
|
|
370
|
+
useClient: false,
|
|
307
371
|
});
|
|
308
372
|
|
|
309
373
|
await fs.promises.writeFile(
|
|
310
|
-
path.join(process.cwd(), ...pagesLocation, 'sentry-example-page.
|
|
374
|
+
path.join(process.cwd(), ...pagesLocation, 'sentry-example-page.jsx'),
|
|
311
375
|
examplePageContents,
|
|
312
376
|
{ encoding: 'utf8', flag: 'w' },
|
|
313
377
|
);
|
|
@@ -340,7 +404,7 @@ export async function runNextjsWizard(options: WizardOptions): Promise<void> {
|
|
|
340
404
|
);
|
|
341
405
|
}
|
|
342
406
|
|
|
343
|
-
await
|
|
407
|
+
await addSentryCliConfig(authToken);
|
|
344
408
|
|
|
345
409
|
const mightBeUsingVercel = fs.existsSync(
|
|
346
410
|
path.join(process.cwd(), 'vercel.json'),
|
package/src/nextjs/templates.ts
CHANGED
|
@@ -150,15 +150,18 @@ export function getSentryExamplePageContents(options: {
|
|
|
150
150
|
url: string;
|
|
151
151
|
orgSlug: string;
|
|
152
152
|
projectId: string;
|
|
153
|
+
useClient: boolean;
|
|
153
154
|
}): string {
|
|
154
155
|
const issuesPageLink = options.selfHosted
|
|
155
156
|
? `${options.url}organizations/${options.orgSlug}/issues/?project=${options.projectId}`
|
|
156
157
|
: `https://${options.orgSlug}.sentry.io/issues/?project=${options.projectId}`;
|
|
157
158
|
|
|
158
|
-
return
|
|
159
|
+
return `${
|
|
160
|
+
options.useClient ? '"use client";\n\n' : ''
|
|
161
|
+
}import Head from "next/head";
|
|
159
162
|
import * as Sentry from "@sentry/nextjs";
|
|
160
163
|
|
|
161
|
-
export default function
|
|
164
|
+
export default function Page() {
|
|
162
165
|
return (
|
|
163
166
|
<div>
|
|
164
167
|
<Head>
|
|
@@ -250,3 +253,14 @@ export default function handler(_req, res) {
|
|
|
250
253
|
}
|
|
251
254
|
`;
|
|
252
255
|
}
|
|
256
|
+
|
|
257
|
+
export function getSentryExampleAppDirApiRoute() {
|
|
258
|
+
return `import { NextResponse } from "next/server";
|
|
259
|
+
|
|
260
|
+
// A faulty API route to test Sentry's error monitoring
|
|
261
|
+
export function GET() {
|
|
262
|
+
throw new Error("Sentry Example API Route Error");
|
|
263
|
+
return NextResponse.json({ data: "Testing Sentry Error..." });
|
|
264
|
+
}
|
|
265
|
+
`;
|
|
266
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
|
|
3
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
4
|
+
import type { ProxifiedModule } from 'magicast';
|
|
5
|
+
import type { Program } from '@babel/types';
|
|
6
|
+
|
|
7
|
+
import * as recast from 'recast';
|
|
8
|
+
|
|
9
|
+
import { HANDLE_ERROR_TEMPLATE_V2 } from '../templates';
|
|
10
|
+
import { getInitCallInsertionIndex, hasSentryContent } from '../utils';
|
|
11
|
+
|
|
12
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
13
|
+
import clack from '@clack/prompts';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
|
|
16
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
17
|
+
import { generateCode } from 'magicast';
|
|
18
|
+
|
|
19
|
+
export function instrumentHandleError(
|
|
20
|
+
originalEntryServerMod: ProxifiedModule<any>,
|
|
21
|
+
serverEntryFilename: string,
|
|
22
|
+
): boolean {
|
|
23
|
+
const originalEntryServerModAST = originalEntryServerMod.$ast as Program;
|
|
24
|
+
|
|
25
|
+
const handleErrorFunction = originalEntryServerModAST.body.find(
|
|
26
|
+
(node) =>
|
|
27
|
+
node.type === 'ExportNamedDeclaration' &&
|
|
28
|
+
node.declaration?.type === 'FunctionDeclaration' &&
|
|
29
|
+
node.declaration.id?.name === 'handleError',
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
if (!handleErrorFunction) {
|
|
33
|
+
clack.log.warn(
|
|
34
|
+
`Could not find function ${chalk.cyan('handleError')} in ${chalk.cyan(
|
|
35
|
+
serverEntryFilename,
|
|
36
|
+
)}. Creating one for you.`,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
40
|
+
const implementation = recast.parse(HANDLE_ERROR_TEMPLATE_V2).program
|
|
41
|
+
.body[0];
|
|
42
|
+
|
|
43
|
+
originalEntryServerModAST.body.splice(
|
|
44
|
+
getInitCallInsertionIndex(originalEntryServerModAST),
|
|
45
|
+
0,
|
|
46
|
+
// @ts-expect-error - string works here because the AST is proxified by magicast
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
48
|
+
recast.types.builders.exportNamedDeclaration(implementation),
|
|
49
|
+
);
|
|
50
|
+
} else if (
|
|
51
|
+
hasSentryContent(
|
|
52
|
+
generateCode(handleErrorFunction).code,
|
|
53
|
+
originalEntryServerMod.$code,
|
|
54
|
+
)
|
|
55
|
+
) {
|
|
56
|
+
return false;
|
|
57
|
+
} else {
|
|
58
|
+
// @ts-expect-error - string works here because the AST is proxified by magicast
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
|
|
60
|
+
handleErrorFunction.declaration.body.body.unshift(
|
|
61
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
62
|
+
recast.parse(HANDLE_ERROR_TEMPLATE_V2).program.body[0].body.body[0],
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
|
|
3
|
+
import * as recast from 'recast';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
7
|
+
import clack from '@clack/prompts';
|
|
8
|
+
import chalk from 'chalk';
|
|
9
|
+
|
|
10
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
11
|
+
import { builders, generateCode, loadFile, writeFile } from 'magicast';
|
|
12
|
+
|
|
13
|
+
export async function instrumentRootRouteV1(
|
|
14
|
+
rootFileName: string,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
try {
|
|
17
|
+
const rootRouteAst = await loadFile(
|
|
18
|
+
path.join(process.cwd(), 'app', rootFileName),
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
rootRouteAst.imports.$add({
|
|
22
|
+
from: '@sentry/remix',
|
|
23
|
+
imported: 'withSentry',
|
|
24
|
+
local: 'withSentry',
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
recast.visit(rootRouteAst.$ast, {
|
|
28
|
+
visitExportDefaultDeclaration(path) {
|
|
29
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
30
|
+
if (path.value.declaration.type === 'FunctionDeclaration') {
|
|
31
|
+
// Move the function declaration just before the default export
|
|
32
|
+
path.insertBefore(path.value.declaration);
|
|
33
|
+
|
|
34
|
+
// Get the name of the function to be wrapped
|
|
35
|
+
const functionName: string = path.value.declaration.id.name as string;
|
|
36
|
+
|
|
37
|
+
// Create the wrapped function call
|
|
38
|
+
const functionCall = recast.types.builders.callExpression(
|
|
39
|
+
recast.types.builders.identifier('withSentry'),
|
|
40
|
+
[recast.types.builders.identifier(functionName)],
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
// Replace the default export with the wrapped function call
|
|
44
|
+
path.value.declaration = functionCall;
|
|
45
|
+
} else if (path.value.declaration.type === 'Identifier') {
|
|
46
|
+
const rootRouteExport = rootRouteAst.exports.default;
|
|
47
|
+
|
|
48
|
+
const expressionToWrap = generateCode(
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
50
|
+
rootRouteExport.$ast,
|
|
51
|
+
).code;
|
|
52
|
+
|
|
53
|
+
rootRouteAst.exports.default = builders.raw(
|
|
54
|
+
`withSentry(${expressionToWrap})`,
|
|
55
|
+
);
|
|
56
|
+
} else {
|
|
57
|
+
clack.log.warn(
|
|
58
|
+
chalk.yellow(
|
|
59
|
+
`Couldn't instrument ${chalk.bold(
|
|
60
|
+
rootFileName,
|
|
61
|
+
)} automatically. Wrap your default export with: ${chalk.dim(
|
|
62
|
+
'withSentry()',
|
|
63
|
+
)}\n`,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.traverse(path);
|
|
69
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await writeFile(
|
|
74
|
+
rootRouteAst.$ast,
|
|
75
|
+
path.join(process.cwd(), 'app', rootFileName),
|
|
76
|
+
);
|
|
77
|
+
} catch (e: unknown) {
|
|
78
|
+
// eslint-disable-next-line no-console
|
|
79
|
+
console.error(e);
|
|
80
|
+
clack.log.warn(
|
|
81
|
+
chalk.yellow(
|
|
82
|
+
`Something went wrong writing to ${chalk.bold(rootFileName)}`,
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
clack.log.info(
|
|
86
|
+
`Please put the following code snippet into ${chalk.bold(
|
|
87
|
+
rootFileName,
|
|
88
|
+
)}: ${chalk.dim('You probably have to clean it up a bit.')}\n`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
|
|
3
|
+
import * as recast from 'recast';
|
|
4
|
+
import * as path from 'path';
|
|
5
|
+
|
|
6
|
+
import type { ExportNamedDeclaration, Program } from '@babel/types';
|
|
7
|
+
|
|
8
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
9
|
+
import { loadFile, writeFile } from 'magicast';
|
|
10
|
+
|
|
11
|
+
import { ERROR_BOUNDARY_TEMPLATE_V2 } from '../templates';
|
|
12
|
+
|
|
13
|
+
export async function instrumentRootRouteV2(
|
|
14
|
+
rootFileName: string,
|
|
15
|
+
): Promise<void> {
|
|
16
|
+
const rootRouteAst = await loadFile(
|
|
17
|
+
path.join(process.cwd(), 'app', rootFileName),
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
const exportsAst = rootRouteAst.exports.$ast as Program;
|
|
21
|
+
|
|
22
|
+
const namedExports = exportsAst.body.filter(
|
|
23
|
+
(node) => node.type === 'ExportNamedDeclaration',
|
|
24
|
+
) as ExportNamedDeclaration[];
|
|
25
|
+
|
|
26
|
+
let foundErrorBoundary = false;
|
|
27
|
+
|
|
28
|
+
namedExports.forEach((namedExport) => {
|
|
29
|
+
const declaration = namedExport.declaration;
|
|
30
|
+
|
|
31
|
+
if (!declaration) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (declaration.type === 'FunctionDeclaration') {
|
|
36
|
+
if (declaration.id?.name === 'ErrorBoundary') {
|
|
37
|
+
foundErrorBoundary = true;
|
|
38
|
+
}
|
|
39
|
+
} else if (declaration.type === 'VariableDeclaration') {
|
|
40
|
+
const declarations = declaration.declarations;
|
|
41
|
+
|
|
42
|
+
declarations.forEach((declaration) => {
|
|
43
|
+
// @ts-expect-error - id should always have a name in this case
|
|
44
|
+
if (declaration.id?.name === 'ErrorBoundary') {
|
|
45
|
+
foundErrorBoundary = true;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
if (!foundErrorBoundary) {
|
|
52
|
+
rootRouteAst.imports.$add({
|
|
53
|
+
from: '@sentry/remix',
|
|
54
|
+
imported: 'captureRemixErrorBoundaryError',
|
|
55
|
+
local: 'captureRemixErrorBoundaryError',
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
rootRouteAst.imports.$add({
|
|
59
|
+
from: '@remix-run/react',
|
|
60
|
+
imported: 'useRouteError',
|
|
61
|
+
local: 'useRouteError',
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
recast.visit(rootRouteAst.$ast, {
|
|
65
|
+
visitExportDefaultDeclaration(path) {
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
67
|
+
const implementation = recast.parse(ERROR_BOUNDARY_TEMPLATE_V2).program
|
|
68
|
+
.body[0];
|
|
69
|
+
|
|
70
|
+
path.insertBefore(
|
|
71
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
72
|
+
recast.types.builders.exportDeclaration(false, implementation),
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
this.traverse(path);
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await writeFile(
|
|
81
|
+
rootRouteAst.$ast,
|
|
82
|
+
path.join(process.cwd(), 'app', rootFileName),
|
|
83
|
+
);
|
|
84
|
+
}
|