Skip to content

Commit

Permalink
feat(nuxt): Add example page/component creation and final messaging (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
andreiborza authored Nov 22, 2024
1 parent e1b29d9 commit be94c4b
Show file tree
Hide file tree
Showing 6 changed files with 433 additions and 9 deletions.
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

0 comments on commit be94c4b

Please sign in to comment.