Skip to content

Commit

Permalink
feat(nuxt): Add deployment-platform flow with links to docs (#747)
Browse files Browse the repository at this point in the history
* feat(nuxt): Add deployment-platform flow with links to docs

* Add changelog entry

* Remove unused `MagicastError` import

* Update src/nuxt/nuxt-wizard.ts

Co-authored-by: Lukas Stracke <[email protected]>

* Add confirm step for import docs

---------

Co-authored-by: Lukas Stracke <[email protected]>
  • Loading branch information
andreiborza and Lms24 authored Dec 19, 2024
1 parent e6a1fb0 commit 9f89026
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 58 deletions.
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 @@ async function runWizardOnNuxtProject(projectDir: string): Promise<void> {
},
));

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
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 @@ async function runWizardOnNuxtProject(projectDir: string): Promise<void> {
},
));

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
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

0 comments on commit 9f89026

Please sign in to comment.