@sentry/wizard 3.9.1 → 3.10.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 +16 -0
- package/dist/lib/Constants.d.ts +1 -0
- package/dist/lib/Constants.js +5 -0
- package/dist/lib/Constants.js.map +1 -1
- package/dist/lib/Steps/ChooseIntegration.js +7 -0
- package/dist/lib/Steps/ChooseIntegration.js.map +1 -1
- package/dist/lib/Steps/Integrations/Cordova.js +5 -1
- package/dist/lib/Steps/Integrations/Cordova.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/package.json +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 +206 -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 +23 -12
- package/dist/src/sourcemaps/sourcemaps-wizard.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 +6 -3
- package/dist/src/sourcemaps/tools/sentry-cli.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/utils/clack-utils.d.ts +3 -2
- package/dist/src/utils/clack-utils.js +39 -2
- package/dist/src/utils/clack-utils.js.map +1 -1
- package/lib/Constants.ts +5 -0
- package/lib/Steps/ChooseIntegration.ts +7 -0
- package/lib/Steps/Integrations/Cordova.ts +5 -1
- package/lib/Steps/Integrations/Remix.ts +32 -0
- package/package.json +1 -1
- 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 +137 -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 +9 -0
- package/src/sourcemaps/tools/remix.ts +90 -0
- package/src/sourcemaps/tools/sentry-cli.ts +5 -3
- package/src/sourcemaps/utils/detect-tool.ts +3 -1
- package/src/utils/clack-utils.ts +56 -2
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
2
|
+
|
|
3
|
+
import type { Program } from '@babel/types';
|
|
4
|
+
|
|
5
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
6
|
+
import type { ProxifiedModule } from 'magicast';
|
|
7
|
+
|
|
8
|
+
import * as fs from 'fs';
|
|
9
|
+
import * as path from 'path';
|
|
10
|
+
import * as url from 'url';
|
|
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
|
+
import { parse } from 'semver';
|
|
16
|
+
|
|
17
|
+
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
|
|
18
|
+
import { builders, generateCode, loadFile, writeFile } from 'magicast';
|
|
19
|
+
import { PackageDotJson, getPackageVersion } from '../utils/package-json';
|
|
20
|
+
import { getInitCallInsertionIndex, hasSentryContent } from './utils';
|
|
21
|
+
import { instrumentRootRouteV1 } from './codemods/root-v1';
|
|
22
|
+
import { instrumentRootRouteV2 } from './codemods/root-v2';
|
|
23
|
+
import { instrumentHandleError } from './codemods/handle-error';
|
|
24
|
+
|
|
25
|
+
export type PartialRemixConfig = {
|
|
26
|
+
unstable_dev?: boolean;
|
|
27
|
+
future?: {
|
|
28
|
+
v2_dev?: boolean;
|
|
29
|
+
v2_errorBoundary?: boolean;
|
|
30
|
+
v2_headers?: boolean;
|
|
31
|
+
v2_meta?: boolean;
|
|
32
|
+
v2_normalizeFormMethod?: boolean;
|
|
33
|
+
v2_routeConvention?: boolean;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const REMIX_CONFIG_FILE = 'remix.config.js';
|
|
38
|
+
|
|
39
|
+
function insertClientInitCall(
|
|
40
|
+
dsn: string,
|
|
41
|
+
originalHooksMod: ProxifiedModule<any>,
|
|
42
|
+
): void {
|
|
43
|
+
const initCall = builders.functionCall('Sentry.init', {
|
|
44
|
+
dsn,
|
|
45
|
+
tracesSampleRate: 1.0,
|
|
46
|
+
replaysSessionSampleRate: 0.1,
|
|
47
|
+
replaysOnErrorSampleRate: 1.0,
|
|
48
|
+
integrations: [
|
|
49
|
+
builders.newExpression('Sentry.BrowserTracing', {
|
|
50
|
+
routingInstrumentation: builders.functionCall(
|
|
51
|
+
'Sentry.remixRouterInstrumentation',
|
|
52
|
+
builders.raw('useEffect'),
|
|
53
|
+
builders.raw('useLocation'),
|
|
54
|
+
builders.raw('useMatches'),
|
|
55
|
+
),
|
|
56
|
+
}),
|
|
57
|
+
builders.newExpression('Sentry.Replay'),
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const originalHooksModAST = originalHooksMod.$ast as Program;
|
|
62
|
+
const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
|
|
63
|
+
|
|
64
|
+
originalHooksModAST.body.splice(
|
|
65
|
+
initCallInsertionIndex,
|
|
66
|
+
0,
|
|
67
|
+
// @ts-expect-error - string works here because the AST is proxified by magicast
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
69
|
+
generateCode(initCall).code,
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function insertServerInitCall(
|
|
74
|
+
dsn: string,
|
|
75
|
+
originalHooksMod: ProxifiedModule<any>,
|
|
76
|
+
) {
|
|
77
|
+
const initCall = builders.functionCall('Sentry.init', {
|
|
78
|
+
dsn,
|
|
79
|
+
tracesSampleRate: 1.0,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const originalHooksModAST = originalHooksMod.$ast as Program;
|
|
83
|
+
|
|
84
|
+
const initCallInsertionIndex = getInitCallInsertionIndex(originalHooksModAST);
|
|
85
|
+
|
|
86
|
+
originalHooksModAST.body.splice(
|
|
87
|
+
initCallInsertionIndex,
|
|
88
|
+
0,
|
|
89
|
+
// @ts-expect-error - string works here because the AST is proxified by magicast
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
91
|
+
generateCode(initCall).code,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function isRemixV2(
|
|
96
|
+
remixConfig: PartialRemixConfig,
|
|
97
|
+
packageJson: PackageDotJson,
|
|
98
|
+
): boolean {
|
|
99
|
+
const remixVersion = getPackageVersion('@remix-run/react', packageJson);
|
|
100
|
+
const remixVersionMajor = remixVersion && parse(remixVersion)?.major;
|
|
101
|
+
const isV2Remix = remixVersionMajor && remixVersionMajor >= 2;
|
|
102
|
+
|
|
103
|
+
return isV2Remix || remixConfig?.future?.v2_errorBoundary || false;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function loadRemixConfig(): Promise<PartialRemixConfig> {
|
|
107
|
+
const configFilePath = path.join(process.cwd(), REMIX_CONFIG_FILE);
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
if (!fs.existsSync(configFilePath)) {
|
|
111
|
+
return {};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const configUrl = url.pathToFileURL(configFilePath).href;
|
|
115
|
+
const remixConfigModule = (await import(configUrl)) as {
|
|
116
|
+
default: PartialRemixConfig;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
return remixConfigModule?.default || {};
|
|
120
|
+
} catch (e: unknown) {
|
|
121
|
+
clack.log.error(`Couldn't load ${REMIX_CONFIG_FILE}.`);
|
|
122
|
+
clack.log.info(
|
|
123
|
+
chalk.dim(
|
|
124
|
+
typeof e === 'object' && e != null && 'toString' in e
|
|
125
|
+
? e.toString()
|
|
126
|
+
: typeof e === 'string'
|
|
127
|
+
? e
|
|
128
|
+
: 'Unknown error',
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
return {};
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export async function instrumentRootRoute(
|
|
137
|
+
isV2?: boolean,
|
|
138
|
+
isTS?: boolean,
|
|
139
|
+
): Promise<void> {
|
|
140
|
+
const rootFilename = `root.${isTS ? 'tsx' : 'jsx'}`;
|
|
141
|
+
|
|
142
|
+
if (isV2) {
|
|
143
|
+
await instrumentRootRouteV2(rootFilename);
|
|
144
|
+
} else {
|
|
145
|
+
await instrumentRootRouteV1(rootFilename);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
clack.log.success(
|
|
149
|
+
`Successfully instrumented root route ${chalk.cyan(rootFilename)}.`,
|
|
150
|
+
);
|
|
151
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function updateBuildScript(): Promise<void> {
|
|
155
|
+
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
156
|
+
// Add sourcemaps option to build script
|
|
157
|
+
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
158
|
+
const packageJsonString = (
|
|
159
|
+
await fs.promises.readFile(packageJsonPath)
|
|
160
|
+
).toString();
|
|
161
|
+
const packageJson = JSON.parse(packageJsonString);
|
|
162
|
+
|
|
163
|
+
if (!packageJson.scripts) {
|
|
164
|
+
packageJson.scripts = {};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (!packageJson.scripts.build) {
|
|
168
|
+
packageJson.scripts.build =
|
|
169
|
+
'remix build --sourcemap && sentry-upload-sourcemaps';
|
|
170
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
171
|
+
} else if (packageJson.scripts.build.includes('remix build')) {
|
|
172
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
|
|
173
|
+
packageJson.scripts.build = packageJson.scripts.build.replace(
|
|
174
|
+
'remix build',
|
|
175
|
+
'remix build --sourcemap && sentry-upload-sourcemaps',
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
await fs.promises.writeFile(
|
|
180
|
+
packageJsonPath,
|
|
181
|
+
JSON.stringify(packageJson, null, 2),
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
clack.log.success(
|
|
185
|
+
`Successfully updated ${chalk.cyan('build')} script in ${chalk.cyan(
|
|
186
|
+
'package.json',
|
|
187
|
+
)} to generate and upload sourcemaps.`,
|
|
188
|
+
);
|
|
189
|
+
/* eslint-enable @typescript-eslint/no-unsafe-member-access */
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function initializeSentryOnEntryClient(
|
|
193
|
+
dsn: string,
|
|
194
|
+
isTS: boolean,
|
|
195
|
+
): Promise<void> {
|
|
196
|
+
const clientEntryFilename = `entry.client.${isTS ? 'tsx' : 'jsx'}`;
|
|
197
|
+
|
|
198
|
+
const originalEntryClient = path.join(
|
|
199
|
+
process.cwd(),
|
|
200
|
+
'app',
|
|
201
|
+
clientEntryFilename,
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const originalEntryClientMod = await loadFile(originalEntryClient);
|
|
205
|
+
|
|
206
|
+
if (hasSentryContent(originalEntryClient, originalEntryClientMod.$code)) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
originalEntryClientMod.imports.$add({
|
|
211
|
+
from: '@sentry/remix',
|
|
212
|
+
imported: '*',
|
|
213
|
+
local: 'Sentry',
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
originalEntryClientMod.imports.$add({
|
|
217
|
+
from: 'react',
|
|
218
|
+
imported: 'useEffect',
|
|
219
|
+
local: 'useEffect',
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
originalEntryClientMod.imports.$add({
|
|
223
|
+
from: '@remix-run/react',
|
|
224
|
+
imported: 'useLocation',
|
|
225
|
+
local: 'useLocation',
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
originalEntryClientMod.imports.$add({
|
|
229
|
+
from: '@remix-run/react',
|
|
230
|
+
imported: 'useMatches',
|
|
231
|
+
local: 'useMatches',
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
insertClientInitCall(dsn, originalEntryClientMod);
|
|
235
|
+
|
|
236
|
+
await writeFile(
|
|
237
|
+
originalEntryClientMod.$ast,
|
|
238
|
+
path.join(process.cwd(), 'app', clientEntryFilename),
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
clack.log.success(
|
|
242
|
+
`Successfully initialized Sentry on client entry point ${chalk.cyan(
|
|
243
|
+
clientEntryFilename,
|
|
244
|
+
)}`,
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function initializeSentryOnEntryServer(
|
|
249
|
+
dsn: string,
|
|
250
|
+
isV2: boolean,
|
|
251
|
+
isTS: boolean,
|
|
252
|
+
): Promise<void> {
|
|
253
|
+
const serverEntryFilename = `entry.server.${isTS ? 'tsx' : 'jsx'}`;
|
|
254
|
+
|
|
255
|
+
const originalEntryServer = path.join(
|
|
256
|
+
process.cwd(),
|
|
257
|
+
'app',
|
|
258
|
+
serverEntryFilename,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const originalEntryServerMod = await loadFile(originalEntryServer);
|
|
262
|
+
|
|
263
|
+
if (hasSentryContent(originalEntryServer, originalEntryServerMod.$code)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
originalEntryServerMod.imports.$add({
|
|
268
|
+
from: '@sentry/remix',
|
|
269
|
+
imported: '*',
|
|
270
|
+
local: 'Sentry',
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
insertServerInitCall(dsn, originalEntryServerMod);
|
|
274
|
+
|
|
275
|
+
if (isV2) {
|
|
276
|
+
const handleErrorInstrumented = instrumentHandleError(
|
|
277
|
+
originalEntryServerMod,
|
|
278
|
+
serverEntryFilename,
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
if (handleErrorInstrumented) {
|
|
282
|
+
clack.log.success(
|
|
283
|
+
`Instrumented ${chalk.cyan('handleError')} in ${chalk.cyan(
|
|
284
|
+
`${serverEntryFilename}`,
|
|
285
|
+
)}`,
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
await writeFile(
|
|
291
|
+
originalEntryServerMod.$ast,
|
|
292
|
+
path.join(process.cwd(), 'app', serverEntryFilename),
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
clack.log.success(
|
|
296
|
+
`Successfully initialized Sentry on server entry point ${chalk.cyan(
|
|
297
|
+
serverEntryFilename,
|
|
298
|
+
)}.`,
|
|
299
|
+
);
|
|
300
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export const ERROR_BOUNDARY_TEMPLATE_V2 = `const ErrorBoundary = () => {
|
|
2
|
+
const error = useRouteError();
|
|
3
|
+
captureRemixErrorBoundaryError(error);
|
|
4
|
+
return <div>Something went wrong</div>;
|
|
5
|
+
};
|
|
6
|
+
`;
|
|
7
|
+
|
|
8
|
+
export const HANDLE_ERROR_TEMPLATE_V2 = `function handleError(error) {
|
|
9
|
+
if (error instanceof Error) {
|
|
10
|
+
Sentry.captureRemixErrorBoundaryError(error);
|
|
11
|
+
} else {
|
|
12
|
+
Sentry.captureException(error);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
`;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Program } from '@babel/types';
|
|
2
|
+
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
// @ts-expect-error - clack is ESM and TS complains about that. It works though
|
|
6
|
+
import clack from '@clack/prompts';
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
|
|
9
|
+
// Copied from sveltekit wizard
|
|
10
|
+
export function hasSentryContent(
|
|
11
|
+
fileName: string,
|
|
12
|
+
fileContent: string,
|
|
13
|
+
): boolean {
|
|
14
|
+
const includesContent = fileContent.includes('@sentry/remix');
|
|
15
|
+
|
|
16
|
+
if (includesContent) {
|
|
17
|
+
clack.log.warn(
|
|
18
|
+
`File ${chalk.cyan(path.basename(fileName))} already contains Sentry code.
|
|
19
|
+
Skipping adding Sentry functionality to ${chalk.cyan(
|
|
20
|
+
path.basename(fileName),
|
|
21
|
+
)}.`,
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return includesContent;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* We want to insert the init call on top of the file but after all import statements
|
|
30
|
+
*/
|
|
31
|
+
export function getInitCallInsertionIndex(
|
|
32
|
+
originalHooksModAST: Program,
|
|
33
|
+
): number {
|
|
34
|
+
for (let x = originalHooksModAST.body.length - 1; x >= 0; x--) {
|
|
35
|
+
if (originalHooksModAST.body[x].type === 'ImportDeclaration') {
|
|
36
|
+
return x + 1;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return 0;
|
|
41
|
+
}
|
|
@@ -29,6 +29,7 @@ import { checkIfMoreSuitableWizardExistsAndAskForRedirect } from './utils/other-
|
|
|
29
29
|
import { configureAngularSourcemapGenerationFlow } from './tools/angular';
|
|
30
30
|
import { detectUsedTool, SupportedTools } from './utils/detect-tool';
|
|
31
31
|
import { configureNextJsSourceMapsUpload } from './tools/nextjs';
|
|
32
|
+
import { configureRemixSourceMapsUpload } from './tools/remix';
|
|
32
33
|
|
|
33
34
|
export async function runSourcemapsWizard(
|
|
34
35
|
options: WizardOptions,
|
|
@@ -133,6 +134,11 @@ async function askForUsedBundlerTool(): Promise<SupportedTools> {
|
|
|
133
134
|
value: 'nextjs',
|
|
134
135
|
hint: 'Select this option if you want to set up source maps in a NextJS project.',
|
|
135
136
|
},
|
|
137
|
+
{
|
|
138
|
+
label: 'Remix',
|
|
139
|
+
value: 'remix',
|
|
140
|
+
hint: 'Select this option if you want to set up source maps in a Remix project.',
|
|
141
|
+
},
|
|
136
142
|
{
|
|
137
143
|
label: 'Webpack',
|
|
138
144
|
value: 'webpack',
|
|
@@ -204,6 +210,9 @@ async function startToolSetupFlow(
|
|
|
204
210
|
case 'nextjs':
|
|
205
211
|
await configureNextJsSourceMapsUpload(options, wizardOptions);
|
|
206
212
|
break;
|
|
213
|
+
case 'remix':
|
|
214
|
+
await configureRemixSourceMapsUpload(options, wizardOptions);
|
|
215
|
+
break;
|
|
207
216
|
default:
|
|
208
217
|
await configureSentryCLI(options);
|
|
209
218
|
break;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// @ts-ignore - clack is ESM and TS complains about that. It works though
|
|
2
|
+
import * as clack from '@clack/prompts';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { runRemixWizard } from '../../remix/remix-wizard';
|
|
5
|
+
import { traceStep } from '../../telemetry';
|
|
6
|
+
import { abortIfCancelled } from '../../utils/clack-utils';
|
|
7
|
+
import { WizardOptions } from '../../utils/types';
|
|
8
|
+
import { SourceMapUploadToolConfigurationOptions } from './types';
|
|
9
|
+
|
|
10
|
+
import * as Sentry from '@sentry/node';
|
|
11
|
+
|
|
12
|
+
export const configureRemixSourceMapsUpload = async (
|
|
13
|
+
options: SourceMapUploadToolConfigurationOptions,
|
|
14
|
+
wizardOptions: WizardOptions,
|
|
15
|
+
) => {
|
|
16
|
+
clack.log
|
|
17
|
+
.info(`Source Maps upload for Remix is configured automatically by default if you run the Sentry Wizard for Remix.
|
|
18
|
+
But don't worry, we can redirect you to the wizard now!
|
|
19
|
+
In case you already tried the wizard, we can also show you how to configure your ${chalk.cyan(
|
|
20
|
+
'remix.config.js',
|
|
21
|
+
)} file manually instead.`);
|
|
22
|
+
|
|
23
|
+
const shouldRedirect: boolean = await abortIfCancelled(
|
|
24
|
+
clack.select({
|
|
25
|
+
message: 'Do you want to run the Sentry Wizard for Remix now?',
|
|
26
|
+
options: [
|
|
27
|
+
{
|
|
28
|
+
label: 'Yes, run the wizard!',
|
|
29
|
+
value: true,
|
|
30
|
+
hint: 'The wizard can also configure your SDK setup',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
label: 'No, show me how to configure it manually',
|
|
34
|
+
value: false,
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
}),
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
Sentry.setTag('redirect-remix-wizard', shouldRedirect);
|
|
41
|
+
|
|
42
|
+
if (shouldRedirect) {
|
|
43
|
+
await traceStep('run-remix-wizard', () => runRemixWizard(wizardOptions));
|
|
44
|
+
clack.intro('Sentry Source Maps Upload Configuration Wizard');
|
|
45
|
+
clack.log.info(
|
|
46
|
+
"Welcome back to the Source Maps wizard - we're almost done ;)",
|
|
47
|
+
);
|
|
48
|
+
} else {
|
|
49
|
+
clack.log.step(
|
|
50
|
+
`Build your app with ${chalk.cyan(
|
|
51
|
+
'remix build --sourcemap',
|
|
52
|
+
)}, then upload your source maps using ${chalk.cyan(
|
|
53
|
+
'sentry-upload-sourcemaps',
|
|
54
|
+
)} cli tool.`,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
clack.log.step(
|
|
58
|
+
`You can add ${chalk.cyan(
|
|
59
|
+
'sentry-upload-sourcemaps',
|
|
60
|
+
)} to your build script in ${chalk.cyan('package.json')} like this:`,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// Intentially logging directly to console here so that the code can be copied/pasted directly
|
|
64
|
+
// eslint-disable-next-line no-console
|
|
65
|
+
console.log(codeSnippet);
|
|
66
|
+
|
|
67
|
+
clack.log.step(`or run it manually after building your app.
|
|
68
|
+
|
|
69
|
+
To see all available options for ${chalk.cyan(
|
|
70
|
+
'sentry-upload-sourcemaps',
|
|
71
|
+
)}, run ${chalk.cyan('sentry-upload-sourcemaps --help')}
|
|
72
|
+
`);
|
|
73
|
+
|
|
74
|
+
await abortIfCancelled(
|
|
75
|
+
clack.select({
|
|
76
|
+
message: 'Did you finish configuring your build and prod scripts?',
|
|
77
|
+
options: [{ label: 'Yes, continue!', value: true }],
|
|
78
|
+
initialValue: true,
|
|
79
|
+
}),
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const codeSnippet = chalk.gray(`
|
|
85
|
+
"scripts": {
|
|
86
|
+
${chalk.greenBright(
|
|
87
|
+
'"build": "remix build --sourcemap && sentry-upload-sourcemaps"',
|
|
88
|
+
)};
|
|
89
|
+
}
|
|
90
|
+
`);
|
|
@@ -211,8 +211,9 @@ async function addSentryCommandToBuildCommand(
|
|
|
211
211
|
// Often, 'build' is the prod build command, so we favour it.
|
|
212
212
|
// If it's not there, commands that include 'build' might be the prod build command.
|
|
213
213
|
let buildCommand =
|
|
214
|
-
packageDotJson.scripts.build
|
|
215
|
-
|
|
214
|
+
typeof packageDotJson.scripts.build === 'string'
|
|
215
|
+
? 'build'
|
|
216
|
+
: allNpmScripts.find((s) => s.toLocaleLowerCase().includes('build'));
|
|
216
217
|
|
|
217
218
|
const isProdBuildCommand =
|
|
218
219
|
!!buildCommand &&
|
|
@@ -252,7 +253,8 @@ Please add it manually to your prod build command.`,
|
|
|
252
253
|
|
|
253
254
|
packageDotJson.scripts[
|
|
254
255
|
buildCommand
|
|
255
|
-
|
|
256
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
257
|
+
] = `${packageDotJson.scripts[buildCommand]} && ${pacMan} run ${SENTRY_NPM_SCRIPT_NAME}`;
|
|
256
258
|
|
|
257
259
|
await fs.promises.writeFile(
|
|
258
260
|
path.join(process.cwd(), 'package.json'),
|
|
@@ -10,7 +10,8 @@ export type SupportedTools =
|
|
|
10
10
|
| 'sentry-cli'
|
|
11
11
|
| 'create-react-app'
|
|
12
12
|
| 'angular'
|
|
13
|
-
| 'nextjs'
|
|
13
|
+
| 'nextjs'
|
|
14
|
+
| 'remix';
|
|
14
15
|
|
|
15
16
|
// A map of package names pointing to the tool slug.
|
|
16
17
|
// The order is important, because we want to detect the most specific tool first.
|
|
@@ -25,6 +26,7 @@ export const TOOL_PACKAGE_MAP: Record<string, SupportedTools> = {
|
|
|
25
26
|
esbuild: 'esbuild',
|
|
26
27
|
rollup: 'rollup',
|
|
27
28
|
typescript: 'tsc',
|
|
29
|
+
remix: 'remix',
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
export async function detectUsedTool(): Promise<SupportedTools> {
|
package/src/utils/clack-utils.ts
CHANGED
|
@@ -66,6 +66,7 @@ export function printWelcome(options: {
|
|
|
66
66
|
wizardName: string;
|
|
67
67
|
promoCode?: string;
|
|
68
68
|
message?: string;
|
|
69
|
+
telemetryEnabled?: boolean;
|
|
69
70
|
}): void {
|
|
70
71
|
let wizardPackage: { version?: string } = {};
|
|
71
72
|
|
|
@@ -96,6 +97,10 @@ export function printWelcome(options: {
|
|
|
96
97
|
welcomeText += `\n\nVersion: ${wizardPackage.version}`;
|
|
97
98
|
}
|
|
98
99
|
|
|
100
|
+
if (options.telemetryEnabled) {
|
|
101
|
+
welcomeText += `\n\nYou are using the Sentry Wizard with telemetry enabled. This helps us improve the Wizard.\nYou can disable it at any time by running \`sentry-wizard --disable-telemetry\`.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
99
104
|
clack.note(welcomeText);
|
|
100
105
|
}
|
|
101
106
|
|
|
@@ -132,7 +137,11 @@ export async function askToInstallSentryCLI(): Promise<boolean> {
|
|
|
132
137
|
export async function askForWizardLogin(options: {
|
|
133
138
|
url: string;
|
|
134
139
|
promoCode?: string;
|
|
135
|
-
platform?:
|
|
140
|
+
platform?:
|
|
141
|
+
| 'javascript-nextjs'
|
|
142
|
+
| 'javascript-remix'
|
|
143
|
+
| 'javascript-sveltekit'
|
|
144
|
+
| 'apple-ios';
|
|
136
145
|
}): Promise<WizardProjectData> {
|
|
137
146
|
Sentry.setTag('has-promo-code', !!options.promoCode);
|
|
138
147
|
|
|
@@ -408,7 +417,48 @@ export async function askForSelfHosted(urlFromArgs?: string): Promise<{
|
|
|
408
417
|
return { url: validUrl, selfHosted: true };
|
|
409
418
|
}
|
|
410
419
|
|
|
411
|
-
|
|
420
|
+
async function addOrgAndProjectToSentryCliRc(
|
|
421
|
+
org: string,
|
|
422
|
+
project: string,
|
|
423
|
+
): Promise<void> {
|
|
424
|
+
const clircContents = fs.readFileSync(
|
|
425
|
+
path.join(process.cwd(), SENTRY_CLI_RC_FILE),
|
|
426
|
+
'utf8',
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
const likelyAlreadyHasOrgAndProject = !!(
|
|
430
|
+
clircContents.includes('[defaults]') &&
|
|
431
|
+
clircContents.match(/org=./g) &&
|
|
432
|
+
clircContents.match(/project=./g)
|
|
433
|
+
);
|
|
434
|
+
|
|
435
|
+
if (likelyAlreadyHasOrgAndProject) {
|
|
436
|
+
clack.log.warn(
|
|
437
|
+
`${chalk.bold(
|
|
438
|
+
SENTRY_CLI_RC_FILE,
|
|
439
|
+
)} already has org and project. Will not add them.`,
|
|
440
|
+
);
|
|
441
|
+
} else {
|
|
442
|
+
try {
|
|
443
|
+
await fs.promises.appendFile(
|
|
444
|
+
path.join(process.cwd(), SENTRY_CLI_RC_FILE),
|
|
445
|
+
`\n[defaults]\norg=${org}\nproject=${project}\n`,
|
|
446
|
+
);
|
|
447
|
+
} catch (e) {
|
|
448
|
+
clack.log.warn(
|
|
449
|
+
`${chalk.bold(
|
|
450
|
+
SENTRY_CLI_RC_FILE,
|
|
451
|
+
)} could not be updated with org and project.`,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
export async function addSentryCliRc(
|
|
458
|
+
authToken: string,
|
|
459
|
+
orgSlug?: string,
|
|
460
|
+
projectSlug?: string,
|
|
461
|
+
): Promise<void> {
|
|
412
462
|
const clircExists = fs.existsSync(
|
|
413
463
|
path.join(process.cwd(), SENTRY_CLI_RC_FILE),
|
|
414
464
|
);
|
|
@@ -469,6 +519,10 @@ export async function addSentryCliRc(authToken: string): Promise<void> {
|
|
|
469
519
|
}
|
|
470
520
|
}
|
|
471
521
|
|
|
522
|
+
if (orgSlug && projectSlug) {
|
|
523
|
+
await addOrgAndProjectToSentryCliRc(orgSlug, projectSlug);
|
|
524
|
+
}
|
|
525
|
+
|
|
472
526
|
await addAuthTokenFileToGitIgnore(SENTRY_CLI_RC_FILE);
|
|
473
527
|
}
|
|
474
528
|
|