diff --git a/e2e-tests/tests/nuxt-3.test.ts b/e2e-tests/tests/nuxt-3.test.ts index 47091875..6e2dac34 100644 --- a/e2e-tests/tests/nuxt-3.test.ts +++ b/e2e-tests/tests/nuxt-3.test.ts @@ -44,13 +44,36 @@ async function runWizardOnNuxtProject(projectDir: string): Promise { 'Please select your package manager.', ); - const tracingOptionPrompted = + const nitropackOverridePrompted = packageManagerPrompted && (await wizardInstance.sendStdinAndWaitForOutput( // Selecting `yarn` as the package manager [KEYS.DOWN, KEYS.ENTER], + // Do you want to install version 2.9.7 of nitropack and add an override to package.json? + 'Do you want to add an override for nitropack version 2.9.7?', + { + timeout: 240_000, + }, + )); + + const nftOverridePrompted = + nitropackOverridePrompted && + (await wizardInstance.sendStdinAndWaitForOutput( + // Selecting `yes` to downgrade nitropack + KEYS.ENTER, + 'Do you want to add an override for @vercel/nft version ^0.27.4?', + // 'Do you want to install version', + { + timeout: 240_000, + }, + )); + + const tracingOptionPrompted = + nftOverridePrompted && + (await wizardInstance.sendStdinAndWaitForOutput( + KEYS.ENTER, // "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold. - 'to track the performance of your application?', + 'Do you want to enable', { timeout: 240_000, }, @@ -59,7 +82,7 @@ async function runWizardOnNuxtProject(projectDir: string): Promise { const replayOptionPrompted = tracingOptionPrompted && (await wizardInstance.sendStdinAndWaitForOutput( - [KEYS.ENTER], + KEYS.ENTER, // "Do you want to enable Sentry Session Replay", sometimes doesn't work as `Sentry Session Replay` can be printed in bold. 'to get a video-like reproduction of errors during a user session?', )); diff --git a/e2e-tests/tests/nuxt-4.test.ts b/e2e-tests/tests/nuxt-4.test.ts index 3c1e560b..d8a9e2e5 100644 --- a/e2e-tests/tests/nuxt-4.test.ts +++ b/e2e-tests/tests/nuxt-4.test.ts @@ -43,13 +43,36 @@ async function runWizardOnNuxtProject(projectDir: string): Promise { 'Please select your package manager.', ); - const tracingOptionPrompted = + const nitropackOverridePrompted = packageManagerPrompted && (await wizardInstance.sendStdinAndWaitForOutput( // Selecting `yarn` as the package manager [KEYS.DOWN, KEYS.ENTER], + // Do you want to install version 2.9.7 of nitropack and add an override to package.json? + 'Do you want to add an override for nitropack version 2.9.7?', + { + timeout: 240_000, + }, + )); + + const nftOverridePrompted = + nitropackOverridePrompted && + (await wizardInstance.sendStdinAndWaitForOutput( + // Selecting `yes` to downgrade nitropack + KEYS.ENTER, + 'Do you want to add an override for @vercel/nft version ^0.27.4?', + // 'Do you want to install version', + { + timeout: 240_000, + }, + )); + + const tracingOptionPrompted = + nftOverridePrompted && + (await wizardInstance.sendStdinAndWaitForOutput( + KEYS.ENTER, // "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold. - 'to track the performance of your application?', + 'Do you want to enable', { timeout: 240_000, }, diff --git a/e2e-tests/utils/index.ts b/e2e-tests/utils/index.ts index 2327d25d..fda8155e 100644 --- a/e2e-tests/utils/index.ts +++ b/e2e-tests/utils/index.ts @@ -243,7 +243,10 @@ export function createFile(filePath: string, content?: string) { * @param oldContent * @param newContent */ -export function modifyFile(filePath: string, replaceMap: Record) { +export function modifyFile( + filePath: string, + replaceMap: Record, +) { const fileContent = fs.readFileSync(filePath, 'utf-8'); let newFileContent = fileContent; diff --git a/package.json b/package.json index beb9539a..6068f524 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ "r2": "^2.0.1", "read-env": "^1.3.0", "recast": "^0.23.3", - "resolve": "^1.22.8", "semver": "^7.5.3", "xcode": "3.0.1", "xml-js": "^1.6.11", diff --git a/src/nuxt/nuxt-wizard.ts b/src/nuxt/nuxt-wizard.ts index 709a8adf..8823535d 100644 --- a/src/nuxt/nuxt-wizard.ts +++ b/src/nuxt/nuxt-wizard.ts @@ -1,7 +1,8 @@ // @ts-ignore - clack is ESM and TS complains about that. It works though import * as clack from '@clack/prompts'; import * as Sentry from '@sentry/node'; -import { gte, lt, lte, minVersion } from 'semver'; +import chalk from 'chalk'; +import { lt, minVersion } from 'semver'; import type { WizardOptions } from '../utils/types'; import { traceStep, withTelemetry } from '../telemetry'; import { @@ -14,6 +15,7 @@ import { ensurePackageIsInstalled, getOrAskForProjectData, getPackageDotJson, + getPackageManager, installPackage, printWelcome, runPrettierIfInstalled, @@ -23,7 +25,7 @@ import { addSDKModule, getNuxtConfig, createConfigFiles, - installExtraDepsIfNeeded, + addNuxtOverrides, } from './sdk-setup'; import { createExampleComponent, @@ -31,7 +33,6 @@ import { supportsExamplePage, } from './sdk-example'; import { isNuxtV4 } from './utils'; -import chalk from 'chalk'; export function runNuxtWizard(options: WizardOptions) { return withTelemetry( @@ -90,16 +91,19 @@ export async function runNuxtWizardWithTelemetry( const { authToken, selectedProject, selfHosted, sentryUrl } = await getOrAskForProjectData(options, 'javascript-nuxt'); + const packageManager = await getPackageManager(); + + await addNuxtOverrides(packageManager, minVer); + const sdkAlreadyInstalled = hasPackageInstalled('@sentry/nuxt', packageJson); Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled); await installPackage({ packageName: '@sentry/nuxt', alreadyInstalled: sdkAlreadyInstalled, + packageManager, }); - await installExtraDepsIfNeeded(minVer); - await addDotEnvSentryBuildPluginFile(authToken); const nuxtConfig = await traceStep('load-nuxt-config', getNuxtConfig); diff --git a/src/nuxt/sdk-setup.ts b/src/nuxt/sdk-setup.ts index 073df15b..075d04b1 100644 --- a/src/nuxt/sdk-setup.ts +++ b/src/nuxt/sdk-setup.ts @@ -17,15 +17,13 @@ import { import { abort, abortIfCancelled, - askShouldInstallPackage, + askShouldAddNuxtOverride, featureSelectionPrompt, - installPackage, isUsingTypeScript, } from '../utils/clack-utils'; import { traceStep } from '../telemetry'; -import { getInstalledPackageVersion } from '../utils/package-version'; -import { gte, lt, minVersion, SemVer } from 'semver'; -import { detectPackageManger, PackageManager } from '../utils/package-manager'; +import { lt, SemVer } from 'semver'; +import { PackageManager } from '../utils/package-manager'; const possibleNuxtConfig = [ 'nuxt.config.js', @@ -213,52 +211,38 @@ export async function createConfigFiles(dsn: string) { } } -export async function installExtraDepsIfNeeded(nuxtMinVer: SemVer | null) { - // We currently have some restrictions on the dependencies nuxt ships with - // and try to resolve these here for users if they agree. - // See: https://github.com/getsentry/sentry-javascript/issues/14514 - await installExtraDepIfNeeded('nitropack', '2.9.7', (minVer) => - gte(minVer, '2.10.0'), - ); - - await installExtraDepIfNeeded('@vercel/nft', '^0.27.4', (minVer) => - lt(minVer, '0.27.4'), - ); - - if (nuxtMinVer && lt(nuxtMinVer, '3.14.0')) { - await installExtraDepIfNeeded('ofetch', '^1.4.0', (minVer) => - lt(minVer, '1.4.0'), - ); - } -} - -export async function installExtraDepIfNeeded( - pkgName: string, - pkgVersion: string, - isNeeded: (minVer: SemVer) => boolean, +export async function addNuxtOverrides( + packageManager: PackageManager, + nuxtMinVer: SemVer | null, ) { - const installedVersion = - (await getInstalledPackageVersion(pkgName)) || '0.0.0'; - const minVer = minVersion(installedVersion); + const overrides = [ + { + pkgName: 'nitropack', + pkgVersion: '2.9.7', + }, + { + pkgName: '@vercel/nft', + pkgVersion: '^0.27.4', + }, + ...(nuxtMinVer && lt(nuxtMinVer, '3.14.0') + ? [{ pkgName: 'ofetch', pkgVersion: '^1.4.0' }] + : []), + ]; + + clack.log.warn( + `To ensure Sentry can properly instrument your code it needs to add version overrides for some Nuxt dependencies.\n\nFor more info see: ${chalk.cyan( + 'https://github.com/getsentry/sentry-javascript/issues/14514', + )}`, + ); - if (!minVer || isNeeded(minVer)) { - clack.log.warn( - `You have version ${chalk.cyan(installedVersion)} of ${chalk.cyan( - pkgName, - )} installed.\nCurrently, we do not properly support this version (see https://github.com/getsentry/sentry-javascript/issues/14514).`, + for (const { pkgName, pkgVersion } of overrides) { + const shouldAddOverride = await askShouldAddNuxtOverride( + pkgName, + pkgVersion, ); - const shouldInstall = await askShouldInstallPackage(pkgName, pkgVersion); - - if (shouldInstall) { - const { packageManager } = await installPackage({ - packageName: `${pkgName}@${pkgVersion}`, - alreadyInstalled: false, - askBeforeUpdating: false, - packageNameDisplayLabel: `${pkgName}@${pkgVersion}`, - }); - - await packageManager?.addOverride(pkgName, pkgVersion); + if (shouldAddOverride) { + await packageManager.addOverride(pkgName, pkgVersion); } } } diff --git a/src/utils/clack-utils.ts b/src/utils/clack-utils.ts index 9f9f672d..21806b9b 100644 --- a/src/utils/clack-utils.ts +++ b/src/utils/clack-utils.ts @@ -355,6 +355,7 @@ export async function installPackage({ alreadyInstalled, askBeforeUpdating = true, packageNameDisplayLabel, + packageManager, }: { /** The string that is passed to the package manager CLI as identifier to install (e.g. `@sentry/nextjs`, or `@sentry/nextjs@^8`) */ packageName: string; @@ -362,6 +363,7 @@ export async function installPackage({ askBeforeUpdating?: boolean; /** Overrides what is shown in the installation logs in place of the `packageName` option. Useful if the `packageName` is ugly (e.g. `@sentry/nextjs@^8`) */ packageNameDisplayLabel?: string; + packageManager?: PackageManager; }): Promise<{ packageManager?: PackageManager }> { return traceStep('install-package', async () => { if (alreadyInstalled && askBeforeUpdating) { @@ -380,18 +382,18 @@ export async function installPackage({ const sdkInstallSpinner = clack.spinner(); - const packageManager = await getPackageManager(); + const pkgManager = packageManager || (await getPackageManager()); sdkInstallSpinner.start( `${alreadyInstalled ? 'Updating' : 'Installing'} ${chalk.bold.cyan( packageNameDisplayLabel ?? packageName, - )} with ${chalk.bold(packageManager.label)}.`, + )} with ${chalk.bold(pkgManager.label)}.`, ); try { await new Promise((resolve, reject) => { childProcess.exec( - `${packageManager.installCommand} ${packageName} ${packageManager.flags}`, + `${pkgManager.installCommand} ${packageName} ${pkgManager.flags}`, (err, stdout, stderr) => { if (err) { // Write a log file so we can better troubleshoot issues @@ -430,10 +432,10 @@ export async function installPackage({ sdkInstallSpinner.stop( `${alreadyInstalled ? 'Updated' : 'Installed'} ${chalk.bold.cyan( packageNameDisplayLabel ?? packageName, - )} with ${chalk.bold(packageManager.label)}.`, + )} with ${chalk.bold(pkgManager.label)}.`, ); - return { packageManager }; + return { packageManager: pkgManager }; }); } @@ -1490,18 +1492,16 @@ export async function featureSelectionPrompt>( }); } -export async function askShouldInstallPackage( +export async function askShouldAddNuxtOverride( pkgName: string, pkgVersion: string, ): Promise { - return traceStep(`ask-install-package-${pkgName}`, () => + return traceStep(`ask-add-nuxt-overrides`, () => abortIfCancelled( clack.confirm({ - message: `Do you want to install version ${chalk.cyan( - pkgVersion, - )} of ${chalk.cyan(pkgName)} and add an override to ${chalk.cyan( - 'package.json', - )}?`, + message: `Do you want to add an override for ${chalk.cyan( + pkgName, + )} version ${chalk.cyan(pkgVersion)}?`, }), ), ); diff --git a/src/utils/package-version.ts b/src/utils/package-version.ts deleted file mode 100644 index 0419bea9..00000000 --- a/src/utils/package-version.ts +++ /dev/null @@ -1,46 +0,0 @@ -import resolve from 'resolve'; -import * as path from 'path'; -import * as fs from 'fs'; -// @ts-expect-error - clack is ESM and TS complains about that. It works though -import clack from '@clack/prompts'; -import chalk from 'chalk'; -import * as Sentry from '@sentry/node'; -import { abort } from './clack-utils'; -import { PNPM } from './package-manager'; - -/** - * Unlike `getPackageVersion`, this helper uses the `resolve` - * npm package to resolve the actually installed version of - * a package and is not limited to direct dependencies. - */ -export async function getInstalledPackageVersion(pkg: string) { - const isPnpm = PNPM.detect(); - try { - const pkgJson: { version: string } = JSON.parse( - fs - .readFileSync( - resolve.sync(`${pkg}/package.json`, { - basedir: isPnpm - ? path.join(process.cwd(), 'node_modules', '.pnpm') - : process.cwd(), - preserveSymlinks: isPnpm, - }), - ) - .toString(), - ); - return pkgJson.version; - } catch (e: unknown) { - clack.log.error(`Error while evaluating version of package ${pkg}.`); - clack.log.info( - chalk.dim( - typeof e === 'object' && e != null && 'toString' in e - ? e.toString() - : typeof e === 'string' - ? e - : 'Unknown error', - ), - ); - Sentry.captureException('Error while setting up the Nuxt SDK'); - await abort('Exiting Wizard'); - } -} diff --git a/yarn.lock b/yarn.lock index 44b2f6cd..70f41bee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2517,11 +2517,6 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -function-bind@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" - integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== - gensync@^1.0.0-beta.2: version "1.0.0-beta.2" resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" @@ -2694,13 +2689,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hasown@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" - integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== - dependencies: - function-bind "^1.1.2" - hosted-git-info@^2.1.4: version "2.8.9" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" @@ -2836,13 +2824,6 @@ is-core-module@^2.11.0: dependencies: has "^1.0.3" -is-core-module@^2.13.0: - version "2.15.1" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" - integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== - dependencies: - hasown "^2.0.2" - is-core-module@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.2.0.tgz#97037ef3d52224d85163f5597b2b63d9afed981a" @@ -4109,15 +4090,6 @@ resolve@^1.20.0: path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.22.8: - version "1.22.8" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" - integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== - dependencies: - is-core-module "^2.13.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"