@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
package/src/utils/clack-utils.ts
CHANGED
|
@@ -5,6 +5,7 @@ import chalk from 'chalk';
|
|
|
5
5
|
import * as childProcess from 'child_process';
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
8
9
|
import { setInterval } from 'timers';
|
|
9
10
|
import { URL } from 'url';
|
|
10
11
|
import * as Sentry from '@sentry/node';
|
|
@@ -125,41 +126,95 @@ export function printWelcome(options: {
|
|
|
125
126
|
|
|
126
127
|
let welcomeText =
|
|
127
128
|
options.message ||
|
|
128
|
-
|
|
129
|
+
`The ${options.wizardName} will help you set up Sentry for your application.\nThank you for using Sentry :)`;
|
|
129
130
|
|
|
130
131
|
if (options.promoCode) {
|
|
131
|
-
welcomeText
|
|
132
|
+
welcomeText = `${welcomeText}\n\nUsing promo-code: ${options.promoCode}`;
|
|
132
133
|
}
|
|
133
134
|
|
|
134
135
|
if (wizardPackage.version) {
|
|
135
|
-
welcomeText
|
|
136
|
+
welcomeText = `${welcomeText}\n\nVersion: ${wizardPackage.version}`;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
if (options.telemetryEnabled) {
|
|
139
|
-
welcomeText
|
|
140
|
+
welcomeText = `${welcomeText}
|
|
141
|
+
|
|
142
|
+
This wizard sends telemetry data and crash reports to Sentry. This helps us improve the Wizard.
|
|
143
|
+
You can turn this off at any time by running ${chalk.cyanBright(
|
|
144
|
+
'sentry-wizard --disable-telemetry',
|
|
145
|
+
)}.`;
|
|
140
146
|
}
|
|
141
147
|
|
|
142
148
|
clack.note(welcomeText);
|
|
143
149
|
}
|
|
144
150
|
|
|
145
|
-
export async function
|
|
151
|
+
export async function confirmContinueIfNoOrDirtyGitRepo(): Promise<void> {
|
|
152
|
+
return traceStep('check-git-status', async () => {
|
|
153
|
+
if (!isInGitRepo()) {
|
|
154
|
+
const continueWithoutGit = await abortIfCancelled(
|
|
155
|
+
clack.confirm({
|
|
156
|
+
message:
|
|
157
|
+
'You are not inside a git repository. The wizard will create and update files. Do you want to continue anyway?',
|
|
158
|
+
}),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
Sentry.setTag('continue-without-git', continueWithoutGit);
|
|
162
|
+
|
|
163
|
+
if (!continueWithoutGit) {
|
|
164
|
+
await abort(undefined, 0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const uncommittedOrUntrackedFiles = getUncommittedOrUntrackedFiles();
|
|
169
|
+
if (uncommittedOrUntrackedFiles.length) {
|
|
170
|
+
clack.log.warn(
|
|
171
|
+
`You have uncommitted or untracked files in your repo:
|
|
172
|
+
|
|
173
|
+
${uncommittedOrUntrackedFiles.join('\n')}
|
|
174
|
+
|
|
175
|
+
The wizard will create and update files.`,
|
|
176
|
+
);
|
|
177
|
+
const continueWithDirtyRepo = await abortIfCancelled(
|
|
178
|
+
clack.confirm({
|
|
179
|
+
message: 'Do you want to continue anyway?',
|
|
180
|
+
}),
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
Sentry.setTag('continue-with-dirty-repo', continueWithDirtyRepo);
|
|
184
|
+
|
|
185
|
+
if (!continueWithDirtyRepo) {
|
|
186
|
+
await abort(undefined, 0);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function isInGitRepo() {
|
|
146
193
|
try {
|
|
147
194
|
childProcess.execSync('git rev-parse --is-inside-work-tree', {
|
|
148
195
|
stdio: 'ignore',
|
|
149
196
|
});
|
|
197
|
+
return true;
|
|
150
198
|
} catch {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
'You are not inside a git repository. The wizard will create and update files. Do you still want to continue?',
|
|
155
|
-
}),
|
|
156
|
-
);
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
157
202
|
|
|
158
|
-
|
|
203
|
+
function getUncommittedOrUntrackedFiles(): string[] {
|
|
204
|
+
try {
|
|
205
|
+
const gitStatus = childProcess
|
|
206
|
+
.execSync('git status --porcelain=v1')
|
|
207
|
+
.toString();
|
|
159
208
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
209
|
+
const files = gitStatus
|
|
210
|
+
.split(os.EOL)
|
|
211
|
+
.map((line) => line.trim())
|
|
212
|
+
.filter(Boolean)
|
|
213
|
+
.map((f) => `- ${f.split(/\s+/)[1]}`);
|
|
214
|
+
|
|
215
|
+
return files;
|
|
216
|
+
} catch {
|
|
217
|
+
return [];
|
|
163
218
|
}
|
|
164
219
|
}
|
|
165
220
|
|
|
@@ -202,161 +257,127 @@ export async function installPackage({
|
|
|
202
257
|
alreadyInstalled: boolean;
|
|
203
258
|
askBeforeUpdating?: boolean;
|
|
204
259
|
}): Promise<void> {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
260
|
+
return traceStep('install-package', async () => {
|
|
261
|
+
if (alreadyInstalled && askBeforeUpdating) {
|
|
262
|
+
const shouldUpdatePackage = await abortIfCancelled(
|
|
263
|
+
clack.confirm({
|
|
264
|
+
message: `The ${chalk.bold.cyan(
|
|
265
|
+
packageName,
|
|
266
|
+
)} package is already installed. Do you want to update it to the latest version?`,
|
|
267
|
+
}),
|
|
268
|
+
);
|
|
213
269
|
|
|
214
|
-
|
|
215
|
-
|
|
270
|
+
if (!shouldUpdatePackage) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
216
273
|
}
|
|
217
|
-
}
|
|
218
274
|
|
|
219
|
-
|
|
275
|
+
const sdkInstallSpinner = clack.spinner();
|
|
220
276
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
sdkInstallSpinner.start(
|
|
224
|
-
`${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan(
|
|
225
|
-
packageName,
|
|
226
|
-
)} with ${chalk.bold(packageManager.label)}.`,
|
|
227
|
-
);
|
|
277
|
+
const packageManager = await getPackageManager();
|
|
228
278
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
clack.log.error(
|
|
234
|
-
`${chalk.red(
|
|
235
|
-
'Encountered the following error during installation:',
|
|
236
|
-
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
237
|
-
)}\n\n${e}\n\n${chalk.dim(
|
|
238
|
-
'If you think this issue is caused by the Sentry wizard, let us know here:\nhttps://github.com/getsentry/sentry-wizard/issues',
|
|
239
|
-
)}`,
|
|
279
|
+
sdkInstallSpinner.start(
|
|
280
|
+
`${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan(
|
|
281
|
+
packageName,
|
|
282
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
240
283
|
);
|
|
241
|
-
await abort();
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
sdkInstallSpinner.stop(
|
|
245
|
-
`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan(
|
|
246
|
-
packageName,
|
|
247
|
-
)} with ${chalk.bold(packageManager.label)}.`,
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
284
|
|
|
251
|
-
async function addOrgAndProjectToSentryCliRc(
|
|
252
|
-
org: string,
|
|
253
|
-
project: string,
|
|
254
|
-
setupConfig: CliSetupConfig,
|
|
255
|
-
): Promise<void> {
|
|
256
|
-
const configContents = fs.readFileSync(
|
|
257
|
-
path.join(process.cwd(), setupConfig.filename),
|
|
258
|
-
'utf8',
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
if (setupConfig.likelyAlreadyHasOrgAndProject(configContents)) {
|
|
262
|
-
clack.log.warn(
|
|
263
|
-
`${chalk.bold(
|
|
264
|
-
setupConfig.filename,
|
|
265
|
-
)} already has org and project. Will not add them.`,
|
|
266
|
-
);
|
|
267
|
-
} else {
|
|
268
285
|
try {
|
|
269
|
-
await
|
|
270
|
-
path.join(process.cwd(), setupConfig.filename),
|
|
271
|
-
`\n${setupConfig.orgAndProjContent(org, project)}\n`,
|
|
272
|
-
);
|
|
286
|
+
await installPackageWithPackageManager(packageManager, packageName);
|
|
273
287
|
} catch (e) {
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
288
|
+
sdkInstallSpinner.stop('Installation failed.');
|
|
289
|
+
clack.log.error(
|
|
290
|
+
`${chalk.red(
|
|
291
|
+
'Encountered the following error during installation:',
|
|
292
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
293
|
+
)}\n\n${e}\n\n${chalk.dim(
|
|
294
|
+
'If you think this issue is caused by the Sentry wizard, let us know here:\nhttps://github.com/getsentry/sentry-wizard/issues',
|
|
295
|
+
)}`,
|
|
278
296
|
);
|
|
297
|
+
await abort();
|
|
279
298
|
}
|
|
280
|
-
|
|
299
|
+
|
|
300
|
+
sdkInstallSpinner.stop(
|
|
301
|
+
`${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan(
|
|
302
|
+
packageName,
|
|
303
|
+
)} with ${chalk.bold(packageManager.label)}.`,
|
|
304
|
+
);
|
|
305
|
+
});
|
|
281
306
|
}
|
|
282
307
|
|
|
283
308
|
export async function addSentryCliConfig(
|
|
284
309
|
authToken: string,
|
|
285
310
|
setupConfig: CliSetupConfig = sourceMapsCliSetupConfig,
|
|
286
|
-
orgSlug?: string,
|
|
287
|
-
projectSlug?: string,
|
|
288
311
|
): Promise<void> {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
);
|
|
292
|
-
if (configExists) {
|
|
293
|
-
const configContents = fs.readFileSync(
|
|
312
|
+
return traceStep('add-sentry-cli-config', async () => {
|
|
313
|
+
const configExists = fs.existsSync(
|
|
294
314
|
path.join(process.cwd(), setupConfig.filename),
|
|
295
|
-
'utf8',
|
|
296
315
|
);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
setupConfig.filename,
|
|
302
|
-
)} already has auth token. Will not add one.`,
|
|
316
|
+
if (configExists) {
|
|
317
|
+
const configContents = fs.readFileSync(
|
|
318
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
319
|
+
'utf8',
|
|
303
320
|
);
|
|
321
|
+
|
|
322
|
+
if (setupConfig.likelyAlreadyHasAuthToken(configContents)) {
|
|
323
|
+
clack.log.warn(
|
|
324
|
+
`${chalk.bold(
|
|
325
|
+
setupConfig.filename,
|
|
326
|
+
)} already has auth token. Will not add one.`,
|
|
327
|
+
);
|
|
328
|
+
} else {
|
|
329
|
+
try {
|
|
330
|
+
await fs.promises.writeFile(
|
|
331
|
+
path.join(process.cwd(), setupConfig.filename),
|
|
332
|
+
`${configContents}\n${setupConfig.tokenContent(authToken)}\n`,
|
|
333
|
+
{ encoding: 'utf8', flag: 'w' },
|
|
334
|
+
);
|
|
335
|
+
clack.log.success(
|
|
336
|
+
chalk.greenBright(
|
|
337
|
+
`Added auth token to ${chalk.bold(
|
|
338
|
+
setupConfig.filename,
|
|
339
|
+
)} for you to test uploading ${setupConfig.name} locally.`,
|
|
340
|
+
),
|
|
341
|
+
);
|
|
342
|
+
} catch {
|
|
343
|
+
clack.log.warning(
|
|
344
|
+
`Failed to add auth token to ${chalk.bold(
|
|
345
|
+
setupConfig.filename,
|
|
346
|
+
)}. Uploading ${
|
|
347
|
+
setupConfig.name
|
|
348
|
+
} during build will likely not work locally.`,
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
304
352
|
} else {
|
|
305
353
|
try {
|
|
306
354
|
await fs.promises.writeFile(
|
|
307
355
|
path.join(process.cwd(), setupConfig.filename),
|
|
308
|
-
`${
|
|
356
|
+
`${setupConfig.tokenContent(authToken)}\n`,
|
|
309
357
|
{ encoding: 'utf8', flag: 'w' },
|
|
310
358
|
);
|
|
311
359
|
clack.log.success(
|
|
312
360
|
chalk.greenBright(
|
|
313
|
-
`
|
|
361
|
+
`Created ${chalk.bold(
|
|
314
362
|
setupConfig.filename,
|
|
315
|
-
)} for you to test uploading ${
|
|
363
|
+
)} with auth token for you to test uploading ${
|
|
364
|
+
setupConfig.name
|
|
365
|
+
} locally.`,
|
|
316
366
|
),
|
|
317
367
|
);
|
|
318
368
|
} catch {
|
|
319
369
|
clack.log.warning(
|
|
320
|
-
`Failed to
|
|
370
|
+
`Failed to create ${chalk.bold(
|
|
321
371
|
setupConfig.filename,
|
|
322
|
-
)}. Uploading ${
|
|
372
|
+
)} with auth token. Uploading ${
|
|
323
373
|
setupConfig.name
|
|
324
374
|
} during build will likely not work locally.`,
|
|
325
375
|
);
|
|
326
376
|
}
|
|
327
377
|
}
|
|
328
|
-
} else {
|
|
329
|
-
try {
|
|
330
|
-
await fs.promises.writeFile(
|
|
331
|
-
path.join(process.cwd(), setupConfig.filename),
|
|
332
|
-
`${setupConfig.tokenContent(authToken)}\n`,
|
|
333
|
-
{ encoding: 'utf8', flag: 'w' },
|
|
334
|
-
);
|
|
335
|
-
clack.log.success(
|
|
336
|
-
chalk.greenBright(
|
|
337
|
-
`Created ${chalk.bold(
|
|
338
|
-
setupConfig.filename,
|
|
339
|
-
)} with auth token for you to test uploading ${
|
|
340
|
-
setupConfig.name
|
|
341
|
-
} locally.`,
|
|
342
|
-
),
|
|
343
|
-
);
|
|
344
|
-
} catch {
|
|
345
|
-
clack.log.warning(
|
|
346
|
-
`Failed to create ${chalk.bold(
|
|
347
|
-
setupConfig.filename,
|
|
348
|
-
)} with auth token. Uploading ${
|
|
349
|
-
setupConfig.name
|
|
350
|
-
} during build will likely not work locally.`,
|
|
351
|
-
);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
378
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
await addAuthTokenFileToGitIgnore(setupConfig.filename);
|
|
379
|
+
await addAuthTokenFileToGitIgnore(setupConfig.filename);
|
|
380
|
+
});
|
|
360
381
|
}
|
|
361
382
|
|
|
362
383
|
export async function addDotEnvSentryBuildPluginFile(
|
|
@@ -451,23 +472,40 @@ async function addAuthTokenFileToGitIgnore(filename: string): Promise<void> {
|
|
|
451
472
|
}
|
|
452
473
|
}
|
|
453
474
|
|
|
475
|
+
/**
|
|
476
|
+
* Checks if @param packageId is listed as a dependency in @param packageJson.
|
|
477
|
+
* If not, it will ask users if they want to continue without the package.
|
|
478
|
+
*
|
|
479
|
+
* Use this function to check if e.g. a the framework of the SDK is installed
|
|
480
|
+
*
|
|
481
|
+
* @param packageJson the package.json object
|
|
482
|
+
* @param packageId the npm name of the package
|
|
483
|
+
* @param packageName a human readable name of the package
|
|
484
|
+
*/
|
|
454
485
|
export async function ensurePackageIsInstalled(
|
|
455
486
|
packageJson: PackageDotJson,
|
|
456
487
|
packageId: string,
|
|
457
488
|
packageName: string,
|
|
458
|
-
) {
|
|
459
|
-
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
initialValue: false,
|
|
464
|
-
}),
|
|
465
|
-
);
|
|
489
|
+
): Promise<void> {
|
|
490
|
+
return traceStep('ensure-package-installed', async () => {
|
|
491
|
+
const installed = hasPackageInstalled(packageId, packageJson);
|
|
492
|
+
|
|
493
|
+
Sentry.setTag(`${packageName.toLowerCase()}-installed`, installed);
|
|
466
494
|
|
|
467
|
-
if (!
|
|
468
|
-
|
|
495
|
+
if (!installed) {
|
|
496
|
+
Sentry.setTag(`${packageName.toLowerCase()}-installed`, false);
|
|
497
|
+
const continueWithoutPackage = await abortIfCancelled(
|
|
498
|
+
clack.confirm({
|
|
499
|
+
message: `${packageName} does not seem to be installed. Do you still want to continue?`,
|
|
500
|
+
initialValue: false,
|
|
501
|
+
}),
|
|
502
|
+
);
|
|
503
|
+
|
|
504
|
+
if (!continueWithoutPackage) {
|
|
505
|
+
await abort(undefined, 0);
|
|
506
|
+
}
|
|
469
507
|
}
|
|
470
|
-
}
|
|
508
|
+
});
|
|
471
509
|
}
|
|
472
510
|
|
|
473
511
|
export async function getPackageDotJson(): Promise<PackageDotJson> {
|
|
@@ -487,7 +525,9 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
|
|
|
487
525
|
packageJson = JSON.parse(packageJsonFileContents);
|
|
488
526
|
} catch {
|
|
489
527
|
clack.log.error(
|
|
490
|
-
|
|
528
|
+
`Unable to parse your ${chalk.cyan(
|
|
529
|
+
'package.json',
|
|
530
|
+
)}. Make sure it has a valid format!`,
|
|
491
531
|
);
|
|
492
532
|
|
|
493
533
|
await abort();
|
|
@@ -826,7 +866,7 @@ export async function askForToolConfigPath(
|
|
|
826
866
|
clack.confirm({
|
|
827
867
|
message: `Do you have a ${toolName} config file (e.g. ${chalk.cyan(
|
|
828
868
|
configFileName,
|
|
829
|
-
)}?`,
|
|
869
|
+
)})?`,
|
|
830
870
|
initialValue: true,
|
|
831
871
|
}),
|
|
832
872
|
);
|
|
@@ -866,27 +906,35 @@ export async function askForToolConfigPath(
|
|
|
866
906
|
*
|
|
867
907
|
* @param filename the name of the file to which the code snippet should be applied.
|
|
868
908
|
* If a path is provided, only the filename will be used.
|
|
869
|
-
* @param codeSnippet the snippet to be printed.
|
|
870
|
-
* Make sure to follow the diff-like format of highlighting lines that require changes
|
|
871
|
-
* and showing unchanged lines in gray.
|
|
872
909
|
*
|
|
873
|
-
*
|
|
910
|
+
* @param codeSnippet the snippet to be printed. Use {@link makeCodeSnippet} to create the
|
|
911
|
+
* diff-like format for visually highlighting unchanged or modified lines of code.
|
|
912
|
+
*
|
|
913
|
+
* @param hint (optional) a hint to be printed after the main instruction to add
|
|
914
|
+
* the code from @param codeSnippet to their @param filename.
|
|
915
|
+
*
|
|
916
|
+
* More guidelines on copy/paste instructions:
|
|
917
|
+
* @see {@link https://develop.sentry.dev/sdk/setup-wizards/#copy--paste-snippets}
|
|
918
|
+
*
|
|
874
919
|
* TODO: refactor copy paste instructions across different wizards to use this function.
|
|
875
920
|
* this might require adding a custom message parameter to the function
|
|
876
921
|
*/
|
|
877
922
|
export async function showCopyPasteInstructions(
|
|
878
923
|
filename: string,
|
|
879
924
|
codeSnippet: string,
|
|
925
|
+
hint?: string,
|
|
880
926
|
): Promise<void> {
|
|
881
927
|
clack.log.step(
|
|
882
928
|
`Add the following code to your ${chalk.cyan(
|
|
883
929
|
path.basename(filename),
|
|
884
|
-
)} file
|
|
930
|
+
)} file:${hint ? chalk.dim(` (${chalk.dim(hint)})`) : ''}`,
|
|
885
931
|
);
|
|
886
932
|
|
|
933
|
+
// Padding the code snippet to be printed with a \n at the beginning and end
|
|
934
|
+
// This makes it easier to distinguish the snippet from the rest of the output
|
|
887
935
|
// Intentionally logging directly to console here so that the code can be copied/pasted directly
|
|
888
936
|
// eslint-disable-next-line no-console
|
|
889
|
-
console.log(`\n${codeSnippet}`);
|
|
937
|
+
console.log(`\n${codeSnippet}\n`);
|
|
890
938
|
|
|
891
939
|
await abortIfCancelled(
|
|
892
940
|
clack.select({
|
|
@@ -897,6 +945,47 @@ export async function showCopyPasteInstructions(
|
|
|
897
945
|
);
|
|
898
946
|
}
|
|
899
947
|
|
|
948
|
+
/**
|
|
949
|
+
* Callback that exposes formatting helpers for a code snippet.
|
|
950
|
+
* @param unchanged - Formats text as old code.
|
|
951
|
+
* @param plus - Formats text as new code.
|
|
952
|
+
* @param minus - Formats text as removed code.
|
|
953
|
+
*/
|
|
954
|
+
type CodeSnippetFormatter = (
|
|
955
|
+
unchanged: (txt: string) => string,
|
|
956
|
+
plus: (txt: string) => string,
|
|
957
|
+
minus: (txt: string) => string,
|
|
958
|
+
) => string;
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* Crafts a code snippet that can be used to e.g.
|
|
962
|
+
* - print copy/paste instructions to the console
|
|
963
|
+
* - create a new config file.
|
|
964
|
+
*
|
|
965
|
+
* @param colors set this to true if you want the final snippet to be colored.
|
|
966
|
+
* This is useful for printing the snippet to the console as part of copy/paste instructions.
|
|
967
|
+
*
|
|
968
|
+
* @param callback the callback that returns the formatted code snippet.
|
|
969
|
+
* It exposes takes the helper functions for marking code as unchaned, new or removed.
|
|
970
|
+
* These functions no-op if no special formatting should be applied
|
|
971
|
+
* and otherwise apply the appropriate formatting/coloring.
|
|
972
|
+
* (@see {@link CodeSnippetFormatter})
|
|
973
|
+
*
|
|
974
|
+
* @see {@link showCopyPasteInstructions} for the helper with which to display the snippet in the console.
|
|
975
|
+
*
|
|
976
|
+
* @returns a string containing the final, formatted code snippet.
|
|
977
|
+
*/
|
|
978
|
+
export function makeCodeSnippet(
|
|
979
|
+
colors: boolean,
|
|
980
|
+
callback: CodeSnippetFormatter,
|
|
981
|
+
): string {
|
|
982
|
+
const unchanged = (txt: string) => (colors ? chalk.grey(txt) : txt);
|
|
983
|
+
const plus = (txt: string) => (colors ? chalk.greenBright(txt) : txt);
|
|
984
|
+
const minus = (txt: string) => (colors ? chalk.redBright(txt) : txt);
|
|
985
|
+
|
|
986
|
+
return callback(unchanged, plus, minus);
|
|
987
|
+
}
|
|
988
|
+
|
|
900
989
|
/**
|
|
901
990
|
* Creates a new config file with the given @param filepath and @param codeSnippet.
|
|
902
991
|
*
|
|
@@ -4,53 +4,65 @@ import * as fs from 'fs';
|
|
|
4
4
|
import * as path from 'path';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
|
|
7
|
+
import * as Sentry from '@sentry/node';
|
|
8
|
+
import { traceStep } from '../telemetry';
|
|
9
|
+
|
|
7
10
|
export interface PackageManager {
|
|
8
11
|
name: string;
|
|
9
12
|
label: string;
|
|
10
13
|
lockFile: string;
|
|
11
14
|
installCommand: string;
|
|
12
15
|
buildCommand: string;
|
|
16
|
+
/* The command that the package manager uses to run a script from package.json */
|
|
17
|
+
runScriptCommand: string;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
const
|
|
20
|
+
export const BUN: PackageManager = {
|
|
16
21
|
name: 'bun',
|
|
17
22
|
label: 'Bun',
|
|
18
23
|
lockFile: 'bun.lockb',
|
|
19
24
|
installCommand: 'bun add',
|
|
20
|
-
buildCommand: 'bun build',
|
|
25
|
+
buildCommand: 'bun run build',
|
|
26
|
+
runScriptCommand: 'bun run',
|
|
21
27
|
};
|
|
22
|
-
const
|
|
28
|
+
export const YARN: PackageManager = {
|
|
23
29
|
name: 'yarn',
|
|
24
30
|
label: 'Yarn',
|
|
25
31
|
lockFile: 'yarn.lock',
|
|
26
32
|
installCommand: 'yarn add',
|
|
27
33
|
buildCommand: 'yarn build',
|
|
34
|
+
runScriptCommand: 'yarn',
|
|
28
35
|
};
|
|
29
|
-
const
|
|
36
|
+
export const PNPM: PackageManager = {
|
|
30
37
|
name: 'pnpm',
|
|
31
38
|
label: 'PNPM',
|
|
32
39
|
lockFile: 'pnpm-lock.yaml',
|
|
33
40
|
installCommand: 'pnpm add',
|
|
34
41
|
buildCommand: 'pnpm build',
|
|
42
|
+
runScriptCommand: 'pnpm',
|
|
35
43
|
};
|
|
36
|
-
const
|
|
44
|
+
export const NPM: PackageManager = {
|
|
37
45
|
name: 'npm',
|
|
38
46
|
label: 'NPM',
|
|
39
47
|
lockFile: 'package-lock.json',
|
|
40
48
|
installCommand: 'npm add',
|
|
41
49
|
buildCommand: 'npm run build',
|
|
50
|
+
runScriptCommand: 'npm run',
|
|
42
51
|
};
|
|
43
52
|
|
|
44
|
-
export const packageManagers = [
|
|
53
|
+
export const packageManagers = [BUN, YARN, PNPM, NPM];
|
|
45
54
|
|
|
46
55
|
export function detectPackageManger(): PackageManager | null {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
return traceStep('detect-package-manager', () => {
|
|
57
|
+
for (const packageManager of packageManagers) {
|
|
58
|
+
if (fs.existsSync(path.join(process.cwd(), packageManager.lockFile))) {
|
|
59
|
+
Sentry.setTag('package-manager', packageManager.name);
|
|
60
|
+
return packageManager;
|
|
61
|
+
}
|
|
50
62
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
63
|
+
Sentry.setTag('package-manager', 'not-detected');
|
|
64
|
+
return null;
|
|
65
|
+
});
|
|
54
66
|
}
|
|
55
67
|
|
|
56
68
|
export async function installPackageWithPackageManager(
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
|
|
3
|
+
import { addSentryCommandToBuildCommand } from '../../../src/sourcemaps/tools/sentry-cli';
|
|
4
|
+
|
|
5
|
+
import * as packageManagerHelpers from '../../../src/utils/package-manager';
|
|
6
|
+
|
|
7
|
+
const writeFileSpy = jest
|
|
8
|
+
.spyOn(fs.promises, 'writeFile')
|
|
9
|
+
.mockImplementation(() => Promise.resolve());
|
|
10
|
+
|
|
11
|
+
jest.mock('@clack/prompts', () => {
|
|
12
|
+
return {
|
|
13
|
+
log: {
|
|
14
|
+
info: jest.fn(),
|
|
15
|
+
success: jest.fn(),
|
|
16
|
+
},
|
|
17
|
+
confirm: jest.fn().mockResolvedValue(true),
|
|
18
|
+
isCancel: jest.fn().mockReturnValue(false),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe('addSentryCommandToBuildCommand', () => {
|
|
23
|
+
afterEach(() => {
|
|
24
|
+
jest.clearAllMocks();
|
|
25
|
+
});
|
|
26
|
+
it.each([
|
|
27
|
+
[
|
|
28
|
+
packageManagerHelpers.NPM,
|
|
29
|
+
packageManagerHelpers.PNPM,
|
|
30
|
+
packageManagerHelpers.YARN,
|
|
31
|
+
packageManagerHelpers.BUN,
|
|
32
|
+
],
|
|
33
|
+
])('adds the cli command to the script command (%s)', async (_, pacMan) => {
|
|
34
|
+
jest
|
|
35
|
+
.spyOn(packageManagerHelpers, 'detectPackageManger')
|
|
36
|
+
.mockReturnValue(pacMan);
|
|
37
|
+
const packageJson = {
|
|
38
|
+
scripts: {
|
|
39
|
+
build: 'tsc',
|
|
40
|
+
},
|
|
41
|
+
version: '1.0.0',
|
|
42
|
+
};
|
|
43
|
+
await addSentryCommandToBuildCommand(packageJson);
|
|
44
|
+
expect(writeFileSpy).toHaveBeenCalledWith(
|
|
45
|
+
expect.stringContaining('package.json'),
|
|
46
|
+
expect.stringContaining(
|
|
47
|
+
`tsc && ${pacMan.runScriptCommand} sentry:sourcemaps`,
|
|
48
|
+
),
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|