Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(nuxt): Add deployment-platform flow with links to docs #747

Merged
merged 5 commits into from
Dec 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
- feat(nuxt): Add `import-in-the-middle` install step when using pnpm ([#727](https://github.com/getsentry/sentry-wizard/pull/727))
- fix(nuxt): Remove unused parameter in sentry-example-api template ([#734](https://github.com/getsentry/sentry-wizard/pull/734))
- fix(nuxt): Remove option to downgrade override nitropack ([#744](https://github.com/getsentry/sentry-wizard/pull/744))
- feat(nuxt): Add deployment-platform flow with links to docs ([#747](https://github.com/getsentry/sentry-wizard/pull/747))

## 3.36.0

Expand Down
12 changes: 11 additions & 1 deletion e2e-tests/tests/nuxt-3.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,18 @@
},
));

const tracingOptionPrompted =
const deploymentPlatformPrompted =
nftOverridePrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
KEYS.ENTER,
'Please select your deployment platform.',
{
timeout: 240_000,
},
));

const tracingOptionPrompted =
deploymentPlatformPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
KEYS.ENTER,
// "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold.
Expand Down Expand Up @@ -94,27 +104,27 @@
function testNuxtProjectSetup(projectDir: string) {
const integration = Integration.nuxt;

test('package.json is updated correctly', () => {

Check warning on line 107 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkPackageJson(projectDir, integration);
});

test('.env-sentry-build-plugin is created and contains the auth token', () => {

Check warning on line 111 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkEnvBuildPlugin(projectDir);
});

test('config files created', () => {

Check warning on line 115 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkFileExists(`${projectDir}/sentry.server.config.ts`);
checkFileExists(`${projectDir}/sentry.client.config.ts`);
});

test('example page exists', () => {

Check warning on line 120 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkFileExists(`${projectDir}/pages/sentry-example-page.vue`);
checkFileExists(`${projectDir}/server/api/sentry-example-api.ts`);
});
}

function testNuxtProjectConfigs(projectDir: string) {
test('nuxt config contains sentry module', () => {

Check warning on line 127 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkFileContents(path.resolve(projectDir, 'nuxt.config.ts'), [
"modules: ['@sentry/nuxt/module'],",
'sentry: {',
Expand All @@ -129,7 +139,7 @@
]);
});

test('sentry.client.config.ts contents', () => {

Check warning on line 142 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkFileContents(path.resolve(projectDir, 'sentry.client.config.ts'), [
'import * as Sentry from "@sentry/nuxt";',
'Sentry.init({',
Expand All @@ -153,7 +163,7 @@
]);
});

test('sentry.server.config.ts contents', () => {

Check warning on line 166 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkFileContents(path.resolve(projectDir, 'sentry.server.config.ts'), [
'import * as Sentry from "@sentry/nuxt";',
'Sentry.init({',
Expand All @@ -169,11 +179,11 @@
}

function testNuxtProjectBuildsAndRuns(projectDir: string) {
test('builds successfully', async () => {

Check warning on line 182 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
await checkIfBuilds(projectDir, 'preview this build');
});

test('runs on prod mode correctly', async () => {

Check warning on line 186 in e2e-tests/tests/nuxt-3.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
await checkIfRunsOnProdMode(projectDir, 'Listening on');
});
}
12 changes: 11 additions & 1 deletion e2e-tests/tests/nuxt-4.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,18 @@
},
));

const tracingOptionPrompted =
const deploymentPlatformPrompted =
nftOverridePrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
KEYS.ENTER,
'Please select your deployment platform.',
{
timeout: 240_000,
},
));

const tracingOptionPrompted =
deploymentPlatformPrompted &&
(await wizardInstance.sendStdinAndWaitForOutput(
KEYS.ENTER,
// "Do you want to enable Tracing", sometimes doesn't work as `Tracing` can be printed in bold.
Expand Down Expand Up @@ -93,7 +103,7 @@
function testNuxtProjectSetup(projectDir: string) {
const integration = Integration.nuxt;

test('package.json is updated correctly', () => {

Check warning on line 106 in e2e-tests/tests/nuxt-4.test.ts

View workflow job for this annotation

GitHub Actions / Lint

Test has no assertions
checkPackageJson(projectDir, integration);
});

Expand Down
13 changes: 10 additions & 3 deletions src/nuxt/nuxt-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
getNuxtConfig,
createConfigFiles,
addNuxtOverrides,
askDeploymentPlatform,
confirmReadImportDocs,
} from './sdk-setup';
import {
createExampleComponent,
Expand Down Expand Up @@ -116,8 +118,10 @@ export async function runNuxtWizardWithTelemetry(
selfHosted,
};

const deploymentPlatform = await askDeploymentPlatform();

await traceStep('configure-sdk', async () => {
await addSDKModule(nuxtConfig, projectData);
await addSDKModule(nuxtConfig, projectData, deploymentPlatform);
await createConfigFiles(selectedProject.keys[0].dsn.public);
});

Expand Down Expand Up @@ -148,6 +152,8 @@ export async function runNuxtWizardWithTelemetry(

await runPrettierIfInstalled();

await confirmReadImportDocs(deploymentPlatform);

clack.outro(
buildOutroMessage(shouldCreateExamplePage, shouldCreateExampleButton),
);
Expand All @@ -170,8 +176,9 @@ function buildOutroMessage(
)} component to a page and triggering it.`;
}

msg += `\n\nCheck out the SDK documentation for further configuration:
https://docs.sentry.io/platforms/javascript/guides/nuxt/`;
msg += `\n\nCheck out the SDK documentation for further configuration: ${chalk.underline(
'https://docs.sentry.io/platforms/javascript/guides/nuxt/',
)}`;

return msg;
}
129 changes: 97 additions & 32 deletions src/nuxt/sdk-setup.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// @ts-expect-error - clack is ESM and TS complains about that. It works though
import clack from '@clack/prompts';
import * as clack from '@clack/prompts';
import * as Sentry from '@sentry/node';
import chalk from 'chalk';
import fs from 'fs';
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
import { loadFile, generateCode, MagicastError } from 'magicast';
import { loadFile, generateCode } from 'magicast';
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
import { addNuxtModule } from 'magicast/helpers';
import path from 'path';
Expand All @@ -15,18 +15,19 @@ import {
getSentryConfigContents,
} from './templates';
import {
abort,
abortIfCancelled,
askShouldAddPackageOverride,
askShouldInstallPackage,
featureSelectionPrompt,
installPackage,
isUsingTypeScript,
opn,
} from '../utils/clack-utils';
import { traceStep } from '../telemetry';
import { lt, SemVer } from 'semver';
import { PackageManager, PNPM } from '../utils/package-manager';
import { hasPackageInstalled, PackageDotJson } from '../utils/package-json';
import { deploymentPlatforms, DeploymentPlatform } from './types';

const possibleNuxtConfig = [
'nuxt.config.js',
Expand Down Expand Up @@ -60,10 +61,40 @@ export async function getNuxtConfig(): Promise<string> {
return path.join(process.cwd(), configFile);
}

export async function askDeploymentPlatform(): Promise<
DeploymentPlatform | symbol
> {
return await abortIfCancelled(
clack.select({
message: 'Please select your deployment platform.',
options: deploymentPlatforms.map((platform) => ({
value: platform,
label: `${platform.charAt(0).toUpperCase()}${platform.slice(1)}`,
})),
}),
);
}

export async function addSDKModule(
config: string,
options: { org: string; project: string; url: string; selfHosted: boolean },
deploymentPlatform: DeploymentPlatform | symbol,
): Promise<void> {
const shouldTopLevelImport =
deploymentPlatform === 'vercel' || deploymentPlatform === 'netlify';

if (shouldTopLevelImport) {
clack.log.warn(
`Sentry needs to be initialized before the application starts. ${chalk.cyan(
`${deploymentPlatform
.charAt(0)
.toUpperCase()}${deploymentPlatform.slice(1)}`,
)} does not support this yet.\n\nWe will inject the Sentry server-side config at the top of your Nuxt server entry file instead.\n\nThis comes with some restrictions, for more info see:\n\n${chalk.underline(
'https://docs.sentry.io/platforms/javascript/guides/nuxt/install/top-level-import/',
)} `,
);
}

try {
const mod = await loadFile(config);

Expand All @@ -73,6 +104,9 @@ export async function addSDKModule(
project: options.project,
...(options.selfHosted && { url: options.url }),
},
...(shouldTopLevelImport && {
autoInjectServerSentry: 'top-level-import',
}),
});
addNuxtModule(mod, '@sentry/nuxt/module', 'sourcemap', {
client: 'hidden',
Expand All @@ -86,33 +120,31 @@ export async function addSDKModule(
`Added Sentry Nuxt Module to ${chalk.cyan(path.basename(config))}.`,
);
} catch (e: unknown) {
// Cases where users spread options are not covered by magicast,
// so we fall back to showing how to configure the nuxt config
// manually.
if (e instanceof MagicastError) {
clack.log.warn(
`Automatic configuration of ${chalk.cyan(
path.basename(config),
)} failed, please add the following settings:`,
);
// eslint-disable-next-line no-console
console.log(`\n\n${getNuxtModuleFallbackTemplate(options)}\n\n`);
} else {
clack.log.error(
'Error while adding the Sentry Nuxt Module to the Nuxt config.',
);
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');
}
clack.log.error(
'Error while adding the Sentry Nuxt Module to the Nuxt config.',
);
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 Module in nuxt config',
);

clack.log.warn(
`Please add the following settings to ${chalk.cyan(
path.basename(config),
)}:`,
);
// eslint-disable-next-line no-console
console.log(
`\n\n${getNuxtModuleFallbackTemplate(options, shouldTopLevelImport)}\n\n`,
);
}
}

Expand Down Expand Up @@ -234,11 +266,11 @@ export async function addNuxtOverrides(
clack.log.warn(
`To ensure Sentry can properly instrument your code it needs to add version overrides for some Nuxt dependencies${
isPNPM ? ` and install ${chalk.cyan('import-in-the-middle')}.` : '.'
}\n\nFor more info see: ${chalk.cyan(
}\n\nFor more info see: ${chalk.underline(
'https://github.com/getsentry/sentry-javascript/issues/14514',
)}${
isPNPM
? `\n\nand ${chalk.cyan(
? `\n\nand ${chalk.underline(
'https://docs.sentry.io/platforms/javascript/guides/nuxt/troubleshooting/#pnpm-dev-cannot-find-package-import-in-the-middle',
)}`
: ''
Expand Down Expand Up @@ -278,3 +310,36 @@ export async function addNuxtOverrides(
}
}
}

export async function confirmReadImportDocs(
deploymentPlatform: DeploymentPlatform | symbol,
) {
const canImportSentryServerConfigFile =
deploymentPlatform !== 'vercel' && deploymentPlatform !== 'netlify';

if (!canImportSentryServerConfigFile) {
// Nothing to do, users have been set up with automatic top-level-import instead
return;
}

const docsUrl =
'https://docs.sentry.io/platforms/javascript/guides/nuxt/install/cli-import/#initializing-sentry-with---import';

clack.log.info(
`After building your Nuxt app, you need to ${chalk.bold(
'--import',
)} the Sentry server config file when running your app.\n\nFor more info, see:\n\n${chalk.underline(
docsUrl,
)}`,
);

const shouldOpenDocs = await abortIfCancelled(
clack.confirm({ message: 'Do you want to open the docs?' }),
);

if (shouldOpenDocs) {
opn(docsUrl, { wait: false }).catch(() => {
// opn throws in environments that don't have a browser (e.g. remote shells) so we just noop here
});
}
}
21 changes: 14 additions & 7 deletions src/nuxt/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,27 @@ export default defineNuxtConfig({
`;
}

export function getNuxtModuleFallbackTemplate(options: {
org: string;
project: string;
url: string;
selfHosted: boolean;
}): string {
export function getNuxtModuleFallbackTemplate(
options: {
org: string;
project: string;
url: string;
selfHosted: boolean;
},
shouldTopLevelImport: boolean,
): string {
return ` modules: ["@sentry/nuxt/module"],
sentry: {
sourceMapsUploadOptions: {
org: "${options.org}",
project: "${options.project}",${
options.selfHosted ? `\n url: "${options.url}",` : ''
}
},
},${
shouldTopLevelImport
? `\n autoInjectServerSentry: "top-level-import",`
: ''
}
},
sourcemap: { client: "hidden" },`;
}
Expand Down
8 changes: 8 additions & 0 deletions src/nuxt/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const deploymentPlatforms = [
'vercel',
'netlify',
'other',
'none',
] as const;

export type DeploymentPlatform = (typeof deploymentPlatforms)[number];
24 changes: 17 additions & 7 deletions src/nuxt/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// @ts-ignore - clack is ESM and TS complains about that. It works though
import * as clack from '@clack/prompts';
import { gte, minVersion } from 'semver';
// @ts-expect-error - magicast is ESM and TS complains about that. It works though
import { loadFile } from 'magicast';
import { abortIfCancelled } from '../utils/clack-utils';

export async function isNuxtV4(
nuxtConfig: string,
Expand All @@ -18,14 +21,21 @@ export async function isNuxtV4(
// At the time of writing, nuxt 4 is not on its own
// major yet. We must read the `compatibilityVersion`
// from the nuxt config.
const mod = await loadFile(nuxtConfig);
const config =
mod.exports.default.$type === 'function-call'
? mod.exports.default.$args[0]
: mod.exports.default;
try {
const mod = await loadFile(nuxtConfig);
const config =
mod.exports.default.$type === 'function-call'
? mod.exports.default.$args[0]
: mod.exports.default;

if (config && config.future && config.future.compatibilityVersion === 4) {
return true;
if (config && config.future && config.future.compatibilityVersion === 4) {
return true;
}
} catch {
// If we cannot parse their config, just ask.
return await abortIfCancelled(
clack.confirm({ message: 'Are you using Nuxt version 4?' }),
);
}

return false;
Expand Down
2 changes: 1 addition & 1 deletion src/utils/clack-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import {
import { debug } from './debug';
import { fulfillsVersionRange } from './semver';

const opn = require('opn') as (
export const opn = require('opn') as (
url: string,
options?: {
wait?: boolean;
Expand Down
Loading
Loading