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 example page/component creation and final messaging #717

Merged
merged 1 commit into from
Nov 22, 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
81 changes: 74 additions & 7 deletions src/nuxt/nuxt-wizard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,25 @@ import {
abort,
abortIfCancelled,
addDotEnvSentryBuildPluginFile,
askShouldCreateExampleComponent,
askShouldCreateExamplePage,
confirmContinueIfNoOrDirtyGitRepo,
ensurePackageIsInstalled,
getOrAskForProjectData,
getPackageDotJson,
installPackage,
printWelcome,
runPrettierIfInstalled,
} from '../utils/clack-utils';
import { getPackageVersion, hasPackageInstalled } from '../utils/package-json';
import { addSDKModule, getNuxtConfig, createConfigFiles } from './sdk-setup';
import {
createExampleComponent,
createExamplePage,
supportsExamplePage,
} from './sdk-example';
import { isNuxtV4 } from './utils';
import chalk from 'chalk';

export function runNuxtWizard(options: WizardOptions) {
return withTelemetry(
Expand Down Expand Up @@ -47,7 +57,7 @@ export async function runNuxtWizardWithTelemetry(
const nuxtVersion = getPackageVersion('nuxt', packageJson);
Sentry.setTag('nuxt-version', nuxtVersion);

const minVer = minVersion(nuxtVersion || 'none');
const minVer = minVersion(nuxtVersion || '0.0.0');

if (!nuxtVersion || !minVer || lt(minVer, '3.13.2')) {
clack.log.warn(
Expand Down Expand Up @@ -87,13 +97,70 @@ export async function runNuxtWizardWithTelemetry(

const nuxtConfig = await traceStep('load-nuxt-config', getNuxtConfig);

await traceStep('configure-sdk', async () => {
await addSDKModule(nuxtConfig, {
org: selectedProject.organization.slug,
project: selectedProject.slug,
url: selfHosted ? sentryUrl : undefined,
});
const projectData = {
org: selectedProject.organization.slug,
project: selectedProject.slug,
projectId: selectedProject.id,
url: sentryUrl,
selfHosted,
};

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

let shouldCreateExamplePage = false;
let shouldCreateExampleButton = false;

const isV4 = await isNuxtV4(nuxtConfig, nuxtVersion);
const canCreateExamplePage = await supportsExamplePage(isV4);
Sentry.setTag('supports-example-page-creation', canCreateExamplePage);

if (canCreateExamplePage) {
shouldCreateExamplePage = await askShouldCreateExamplePage();

if (shouldCreateExamplePage) {
await traceStep('create-example-page', async () =>
createExamplePage(isV4, projectData),
);
}
} else {
shouldCreateExampleButton = await askShouldCreateExampleComponent();

if (shouldCreateExampleButton) {
await traceStep('create-example-component', async () =>
createExampleComponent(isV4),
);
}
}

await runPrettierIfInstalled();

clack.outro(
buildOutroMessage(shouldCreateExamplePage, shouldCreateExampleButton),
);
}

function buildOutroMessage(
shouldCreateExamplePage: boolean,
shouldCreateExampleButton: boolean,
): string {
let msg = chalk.green('\nSuccessfully installed the Sentry Nuxt SDK!');

if (shouldCreateExamplePage) {
msg += `\n\nYou can validate your setup by visiting ${chalk.cyan(
'"/sentry-example-page"',
)}.`;
}
if (shouldCreateExampleButton) {
msg += `\n\nYou can validate your setup by adding the ${chalk.cyan(
'`SentryExampleButton`',
)} 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/`;

return msg;
}
135 changes: 135 additions & 0 deletions src/nuxt/sdk-example.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import * as fs from 'fs';
import * as path from 'path';
// @ts-expect-error - clack is ESM and TS complains about that. It works though
import clack from '@clack/prompts';
import {
getIndexRouteTemplate,
getSentryExampleApiTemplate,
getSentryExamplePageTemplate,
getSentryErrorButtonTemplate,
} from './templates';
import { abort, isUsingTypeScript } from '../utils/clack-utils';
import chalk from 'chalk';
import * as Sentry from '@sentry/node';

function getSrcDirectory(isNuxtV4: boolean) {
// In nuxt v4, the src directory is `app/` unless
// users already had a `pages` directory
return isNuxtV4 && !fs.existsSync(path.resolve('pages')) ? 'app' : '.';
}

export async function supportsExamplePage(isNuxtV4: boolean) {
// We currently only support creating an example page
// if users can reliably access it without having to
// add code changes themselves.
//
// If users have an `app.vue` layout without the
// needed component to render routes (<NuxtPage/>),
// we bail out of creating an example page altogether.
const src = getSrcDirectory(isNuxtV4);
const app = path.join(src, 'app.vue');

// If there's no `app.vue` layout, nuxt automatically renders
// the routes.
if (!fs.existsSync(path.resolve(app))) {
return true;
}

const content = await fs.promises.readFile(path.resolve(app), 'utf8');
return !!content.match(/<NuxtPage/g);
}

export async function createExamplePage(
isNuxtV4: boolean,
options: {
org: string;
project: string;
projectId: string;
url: string;
},
) {
try {
const src = getSrcDirectory(isNuxtV4);
const pages = path.join(src, 'pages');

if (!fs.existsSync(path.resolve(pages))) {
fs.mkdirSync(path.resolve(pages), { recursive: true });

const indexPage = path.join(pages, 'index.vue');

await fs.promises.writeFile(
path.resolve(indexPage),
getIndexRouteTemplate(),
);

clack.log.success(`Created ${chalk.cyan(indexPage)}.`);
}

const examplePage = path.join(pages, 'sentry-example-page.vue');

if (fs.existsSync(path.resolve(examplePage))) {
clack.log.warn(
`It seems like a sentry example page already exists. Skipping creation of example page.`,
);
return;
}

await fs.promises.writeFile(
path.resolve(examplePage),
getSentryExamplePageTemplate(options),
);

clack.log.success(`Created ${chalk.cyan(examplePage)}.`);

const api = path.join('server', 'api');

if (!fs.existsSync(path.resolve(api))) {
fs.mkdirSync(path.resolve(api), { recursive: true });
}

const exampleApi = path.join(
api,
isUsingTypeScript() ? 'sentry-example-api.ts' : 'sentry-example-api.js',
);

await fs.promises.writeFile(
path.resolve(exampleApi),
getSentryExampleApiTemplate(),
);

clack.log.success(`Created ${chalk.cyan(exampleApi)}.`);
} catch (e: unknown) {
clack.log.error('Error while creating an example page to test Sentry:');
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 creating an example Nuxt page to test Sentry',
);
await abort('Exiting Wizard');
}
}

export async function createExampleComponent(isNuxtV4: boolean) {
const src = getSrcDirectory(isNuxtV4);
const components = path.join(src, 'components');

if (!fs.existsSync(path.resolve(components))) {
fs.mkdirSync(path.resolve(components), { recursive: true });
}

const exampleComponent = path.join(components, 'SentryErrorButton.vue');

await fs.promises.writeFile(
path.resolve(exampleComponent),
getSentryErrorButtonTemplate(),
);

clack.log.success(`Created ${chalk.cyan(exampleComponent)}.`);
}
4 changes: 2 additions & 2 deletions src/nuxt/sdk-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export async function getNuxtConfig(): Promise<string> {

export async function addSDKModule(
config: string,
options: { org: string; project: string; url?: string },
options: { org: string; project: string; url: string; selfHosted: boolean },
): Promise<void> {
clack.log.info('Adding Sentry Nuxt Module to Nuxt config.');

Expand All @@ -66,7 +66,7 @@ export async function addSDKModule(
sourceMapsUploadOptions: {
org: options.org,
project: options.project,
...(options.url && { url: options.url }),
...(options.selfHosted && { url: options.url }),
},
});
addNuxtModule(mod, '@sentry/nuxt/module', 'sourcemap', { client: true });
Expand Down
Loading
Loading