Skip to content

Commit

Permalink
feat(nuxt): Add downgrade path to nitro 2.9.7
Browse files Browse the repository at this point in the history
Also adds automatic resolutions for @vercel/nft and ofetch.
See: getsentry/sentry-javascript#14514
  • Loading branch information
andreiborza committed Nov 28, 2024
1 parent 8af901c commit 7874d60
Show file tree
Hide file tree
Showing 8 changed files with 253 additions and 2 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@clack/prompts": "0.7.0",
"@sentry/cli": "^1.77.3",
"@sentry/node": "^7.119.2",
"@types/resolve": "^1.20.6",
"axios": "1.7.4",
"chalk": "^2.4.1",
"glob": "^8.1.0",
Expand All @@ -37,6 +38,7 @@
"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",
Expand Down
11 changes: 9 additions & 2 deletions src/nuxt/nuxt-wizard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @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 { lt, minVersion } from 'semver';
import { gte, lt, lte, minVersion } from 'semver';

Check failure on line 4 in src/nuxt/nuxt-wizard.ts

View workflow job for this annotation

GitHub Actions / Lint

'gte' is defined but never used

Check failure on line 4 in src/nuxt/nuxt-wizard.ts

View workflow job for this annotation

GitHub Actions / Lint

'lte' is defined but never used
import type { WizardOptions } from '../utils/types';
import { traceStep, withTelemetry } from '../telemetry';
import {
Expand All @@ -19,7 +19,12 @@ import {
runPrettierIfInstalled,
} from '../utils/clack-utils';
import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
import { addSDKModule, getNuxtConfig, createConfigFiles } from './sdk-setup';
import {
addSDKModule,
getNuxtConfig,
createConfigFiles,
installExtraDepsIfNeeded,
} from './sdk-setup';
import {
createExampleComponent,
createExamplePage,
Expand Down Expand Up @@ -93,6 +98,8 @@ export async function runNuxtWizardWithTelemetry(
alreadyInstalled: sdkAlreadyInstalled,
});

await installExtraDepsIfNeeded(minVer);

await addDotEnvSentryBuildPluginFile(authToken);

const nuxtConfig = await traceStep('load-nuxt-config', getNuxtConfig);
Expand Down
55 changes: 55 additions & 0 deletions src/nuxt/sdk-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import {
import {
abort,
abortIfCancelled,
askShouldInstallPackage,
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';

Check failure on line 28 in src/nuxt/sdk-setup.ts

View workflow job for this annotation

GitHub Actions / Lint

'detectPackageManger' is defined but never used

Check failure on line 28 in src/nuxt/sdk-setup.ts

View workflow job for this annotation

GitHub Actions / Lint

'PackageManager' is defined but never used

const possibleNuxtConfig = [
'nuxt.config.js',
Expand Down Expand Up @@ -207,3 +212,53 @@ 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,
) {
const installedVersion =
(await getInstalledPackageVersion(pkgName)) || '0.0.0';
const minVer = minVersion(installedVersion);

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).`,
);

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);
}
}
}
37 changes: 37 additions & 0 deletions src/utils/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,26 @@ export async function getPackageDotJson(): Promise<PackageDotJson> {
return packageJson || {};
}

export async function updatePackageDotJson(
packageDotJson: PackageDotJson,
): Promise<void> {
try {
await fs.promises.writeFile(
path.join(process.cwd(), 'package.json'),
// TODO: maybe figure out the original indentation
JSON.stringify(packageDotJson, null, 2),
{
encoding: 'utf8',
flag: 'w',
},
);
} catch {
clack.log.error(`Unable to update your ${chalk.cyan('package.json')}.`);

await abort();
}
}

export async function getPackageManager(): Promise<PackageManager> {
const detectedPackageManager = detectPackageManger();

Expand Down Expand Up @@ -1469,3 +1489,20 @@ export async function featureSelectionPrompt<F extends ReadonlyArray<Feature>>(
return selectedFeatures as { [key in F[number]['id']]: boolean };
});
}

export async function askShouldInstallPackage(
pkgName: string,
pkgVersion: string,
): Promise<boolean> {
return traceStep(`ask-install-package-${pkgName}`, () =>
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',
)}?`,
}),
),
);
}
5 changes: 5 additions & 0 deletions src/utils/package-json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ export type PackageDotJson = {
scripts?: Record<string, string | undefined>;
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
resolutions?: Record<string, string>;
overrides?: Record<string, string>;
pnpm?: {
overrides?: Record<string, string>;
};
};

type NpmPackage = {
Expand Down
66 changes: 66 additions & 0 deletions src/utils/package-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as path from 'path';

import * as Sentry from '@sentry/node';
import { traceStep } from '../telemetry';
import { getPackageDotJson, updatePackageDotJson } from './clack-utils';

export interface PackageManager {
name: string;
Expand All @@ -15,6 +16,7 @@ export interface PackageManager {
runScriptCommand: string;
flags: string;
detect: () => boolean;
addOverride: (pkgName: string, pkgVersion: string) => Promise<void>;
}

export const BUN: PackageManager = {
Expand All @@ -26,6 +28,18 @@ export const BUN: PackageManager = {
runScriptCommand: 'bun run',
flags: '',
detect: () => fs.existsSync(path.join(process.cwd(), BUN.lockFile)),
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
const overrides = packageDotJson.overrides || {};

await updatePackageDotJson({
...packageDotJson,
overrides: {
...overrides,
[pkgName]: pkgVersion,
},
});
},
};
export const YARN_V1: PackageManager = {
name: 'yarn',
Expand All @@ -45,6 +59,18 @@ export const YARN_V1: PackageManager = {
return false;
}
},
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
const resolutions = packageDotJson.resolutions || {};

await updatePackageDotJson({
...packageDotJson,
resolutions: {
...resolutions,
[pkgName]: pkgVersion,
},
});
},
};
/** YARN V2/3/4 */
export const YARN_V2: PackageManager = {
Expand All @@ -65,6 +91,18 @@ export const YARN_V2: PackageManager = {
return false;
}
},
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
const resolutions = packageDotJson.resolutions || {};

await updatePackageDotJson({
...packageDotJson,
resolutions: {
...resolutions,
[pkgName]: pkgVersion,
},
});
},
};
export const PNPM: PackageManager = {
name: 'pnpm',
Expand All @@ -75,6 +113,22 @@ export const PNPM: PackageManager = {
runScriptCommand: 'pnpm',
flags: '--ignore-workspace-root-check',
detect: () => fs.existsSync(path.join(process.cwd(), PNPM.lockFile)),
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
const pnpm = packageDotJson.pnpm || {};
const overrides = pnpm.overrides || {};

await updatePackageDotJson({
...packageDotJson,
pnpm: {
...pnpm,
overrides: {
...overrides,
[pkgName]: pkgVersion,
},
},
});
},
};
export const NPM: PackageManager = {
name: 'npm',
Expand All @@ -85,6 +139,18 @@ export const NPM: PackageManager = {
runScriptCommand: 'npm run',
flags: '',
detect: () => fs.existsSync(path.join(process.cwd(), NPM.lockFile)),
addOverride: async (pkgName, pkgVersion): Promise<void> => {
const packageDotJson = await getPackageDotJson();
const overrides = packageDotJson.overrides || {};

await updatePackageDotJson({
...packageDotJson,
overrides: {
...overrides,
[pkgName]: pkgVersion,
},
});
},
};

export const packageManagers = [BUN, YARN_V1, YARN_V2, PNPM, NPM];
Expand Down
46 changes: 46 additions & 0 deletions src/utils/package-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
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');
}
}
Loading

0 comments on commit 7874d60

Please sign in to comment.