From 93acb013fa04c64061e9141d0526158543a335d6 Mon Sep 17 00:00:00 2001 From: Onur Temizkan Date: Wed, 18 Dec 2024 13:43:04 +0000 Subject: [PATCH] Address review comments. --- CHANGELOG.md | 2 +- e2e-tests/tests/angular-17.test.ts | 2 +- e2e-tests/tests/angular-19.test.ts | 3 +- src/angular/angular-wizard.ts | 69 ++++++++++++++++++++--------- src/angular/codemods/app-config.ts | 20 +++++---- src/angular/codemods/main.ts | 2 +- src/angular/codemods/sourcemaps.ts | 16 ++++--- src/angular/sdk-setup.ts | 22 ++++----- src/sourcemaps/sourcemaps-wizard.ts | 3 +- src/sourcemaps/tools/angular.ts | 4 +- src/sourcemaps/tools/sentry-cli.ts | 6 ++- 11 files changed, 95 insertions(+), 54 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 513ad8d3..37350e54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,9 @@ ## Unreleased -- feat: Add Angular Wizard ([#741](https://github.com/getsentry/sentry-wizard/pull/741)) - 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)) +- feat: Add Angular Wizard ([#741](https://github.com/getsentry/sentry-wizard/pull/741)) ## 3.36.0 diff --git a/e2e-tests/tests/angular-17.test.ts b/e2e-tests/tests/angular-17.test.ts index fe561c50..7022645f 100644 --- a/e2e-tests/tests/angular-17.test.ts +++ b/e2e-tests/tests/angular-17.test.ts @@ -42,7 +42,7 @@ async function runWizardOnAngularProject(projectDir: string, integration: Integr const sourcemapsConfigured = sourcemapsPrompted && (await wizardInstance.sendStdinAndWaitForOutput( ["./dist", KEYS.ENTER], - 'Verify that your build tool is generating source maps.', + 'Added a sentry:sourcemaps script to your package.json.', ), { optional: true, }); diff --git a/e2e-tests/tests/angular-19.test.ts b/e2e-tests/tests/angular-19.test.ts index 9665801f..d7a4b041 100644 --- a/e2e-tests/tests/angular-19.test.ts +++ b/e2e-tests/tests/angular-19.test.ts @@ -3,7 +3,6 @@ import { Integration } from "../../lib/Constants"; import { checkFileContents, checkFileExists, checkIfBuilds, checkIfRunsOnDevMode, checkIfRunsOnProdMode, checkPackageJson, cleanupGit, KEYS, revertLocalChanges, startWizardInstance } from "../utils"; import * as path from 'path'; import { TEST_ARGS } from "../utils"; -import { option } from "yargs"; async function runWizardOnAngularProject(projectDir: string, integration: Integration) { const wizardInstance = startWizardInstance(integration, projectDir); @@ -43,7 +42,7 @@ async function runWizardOnAngularProject(projectDir: string, integration: Integr const sourcemapsConfigured = sourcemapsPrompted && (await wizardInstance.sendStdinAndWaitForOutput( ["./dist", KEYS.ENTER], - 'Verify that your build tool is generating source maps.', + 'Added a sentry:sourcemaps script to your package.json.', ), { optional: true, }); diff --git a/src/angular/angular-wizard.ts b/src/angular/angular-wizard.ts index 8c60b9d1..cee034fd 100644 --- a/src/angular/angular-wizard.ts +++ b/src/angular/angular-wizard.ts @@ -7,6 +7,7 @@ import chalk from 'chalk'; import type { WizardOptions } from '../utils/types'; import { traceStep, withTelemetry } from '../telemetry'; import { + abortIfCancelled, confirmContinueIfNoOrDirtyGitRepo, ensurePackageIsInstalled, featureSelectionPrompt, @@ -14,12 +15,17 @@ import { getPackageDotJson, installPackage, printWelcome, + runPrettierIfInstalled, } from '../utils/clack-utils'; import { getPackageVersion, hasPackageInstalled } from '../utils/package-json'; -import { gte, minVersion } from 'semver'; -import { initalizeSentryOnAppModule, updateAppConfig } from './sdk-setup'; +import { gte, minVersion, SemVer } from 'semver'; +import { + initalizeSentryOnApplicationEntry, + updateAppConfig, +} from './sdk-setup'; import { addSourcemapEntryToAngularJSON } from './codemods/sourcemaps'; import { runSourcemapsWizard } from '../sourcemaps/sourcemaps-wizard'; +import * as Sentry from '@sentry/node'; const MIN_SUPPORTED_ANGULAR_VERSION = '14.0.0'; @@ -38,7 +44,7 @@ async function runAngularWizardWithTelemetry( options: WizardOptions, ): Promise { printWelcome({ - wizardName: 'Sentry Remix Wizard', + wizardName: 'Sentry Angular Wizard', promoCode: options.promoCode, telemetryEnabled: options.telemetryEnabled, }); @@ -49,24 +55,28 @@ async function runAngularWizardWithTelemetry( await ensurePackageIsInstalled(packageJson, '@angular/core', 'Angular'); - const installedAngularVersion = getPackageVersion( - '@angular/core', - packageJson, - ); + let installedAngularVersion = getPackageVersion('@angular/core', packageJson); if (!installedAngularVersion) { clack.log.warn('Could not determine installed Angular version.'); - return; + installedAngularVersion = await abortIfCancelled( + clack.text({ + message: 'Please enter the installed Angular version:', + validate(value) { + if (!value) { + return 'Please enter the installed Angular version.'; + } + + if (!minVersion(value)) { + return `Invalid Angular version provided: ${value}`; + } + }, + }), + ); } - const installedMinVersion = minVersion(installedAngularVersion); - - if (!installedMinVersion) { - clack.log.warn('Could not determine minimum Angular version.'); - - return; - } + const installedMinVersion = minVersion(installedAngularVersion) as SemVer; const isSupportedAngularVersion = gte( installedMinVersion, @@ -77,6 +87,11 @@ async function runAngularWizardWithTelemetry( clack.log.warn( `Angular version ${MIN_SUPPORTED_ANGULAR_VERSION} or higher is required.`, ); + clack.log.warn( + `Please refer to Sentry's version compatibility table for more information: ${chalk.underline( + 'https://docs.sentry.io/platforms/javascript/guides/angular/#angular-version-compatibility', + )}`, + ); return; } @@ -84,10 +99,17 @@ async function runAngularWizardWithTelemetry( const { selectedProject, authToken, sentryUrl, selfHosted } = await getOrAskForProjectData(options, 'javascript-angular'); + const sdkAlreadyInstalled = hasPackageInstalled( + '@sentry/angular', + packageJson, + ); + + Sentry.setTag('sdk-already-installed', sdkAlreadyInstalled); + await installPackage({ packageName: '@sentry/angular@^8', packageNameDisplayLabel: '@sentry/angular', - alreadyInstalled: hasPackageInstalled('@sentry/angular', packageJson), + alreadyInstalled: sdkAlreadyInstalled, }); const dsn = selectedProject.keys[0].dsn.public; @@ -109,16 +131,19 @@ async function runAngularWizardWithTelemetry( }, ] as const); - await traceStep('Inject Sentry to Angular app config', async () => { - await initalizeSentryOnAppModule(dsn, selectedFeatures); - }); + await traceStep( + 'Initialize Sentry on Angular application entry point', + async () => { + await initalizeSentryOnApplicationEntry(dsn, selectedFeatures); + }, + ); await traceStep('Update Angular project configuration', async () => { await updateAppConfig(installedMinVersion, selectedFeatures.performance); }); await traceStep('Setup for sourcemap uploads', async () => { - addSourcemapEntryToAngularJSON(); + await addSourcemapEntryToAngularJSON(); if (!options.preSelectedProject) { options.preSelectedProject = { @@ -148,6 +173,10 @@ async function runAngularWizardWithTelemetry( await runSourcemapsWizard(options, 'angular'); }); + await traceStep('Run Prettier', async () => { + await runPrettierIfInstalled(); + }); + clack.log.success( 'Sentry has been successfully configured for your Angular project', ); diff --git a/src/angular/codemods/app-config.ts b/src/angular/codemods/app-config.ts index d7264777..c8fcabb9 100644 --- a/src/angular/codemods/app-config.ts +++ b/src/angular/codemods/app-config.ts @@ -25,10 +25,7 @@ export function updateAppConfigMod( function addSentryImport(originalAppConfigMod: ProxifiedModule): void { const imports = originalAppConfigMod.imports; const hasSentryImport = imports.$items.some( - (item) => - item.from === '@sentry/angular' && - item.imported === '*' && - item.local === 'Sentry', + (item) => item.from === '@sentry/angular', ); if (!hasSentryImport) { @@ -45,7 +42,7 @@ function addErrorHandlerImport( ): void { const imports = originalAppConfigMod.imports; const hasErrorHandler = imports.$items.some( - (item) => item.local === 'ErrorHandler', + (item) => item.local === 'ErrorHandler' && item.from === '@angular/core', ); if (!hasErrorHandler) { @@ -59,7 +56,9 @@ function addErrorHandlerImport( function addRouterImport(originalAppConfigMod: ProxifiedModule): void { const imports = originalAppConfigMod.imports; - const hasRouter = imports.$items.some((item) => item.local === 'Router'); + const hasRouter = imports.$items.some( + (item) => item.local === 'Router' && item.from === '@angular/router', + ); if (!hasRouter) { imports.$add({ @@ -76,7 +75,8 @@ function addMissingImportsV19( const imports = originalAppConfigMod.imports; const hasProvideAppInitializer = imports.$items.some( - (item) => item.local === 'provideAppInitializer', + (item) => + item.local === 'provideAppInitializer' && item.from === '@angular/core', ); if (!hasProvideAppInitializer) { @@ -87,7 +87,9 @@ function addMissingImportsV19( }); } - const hasInject = imports.$items.some((item) => item.local === 'inject'); + const hasInject = imports.$items.some( + (item) => item.local === 'inject' && item.from === '@angular/core', + ); if (!hasInject) { imports.$add({ @@ -102,7 +104,7 @@ function addAppInitializer(originalAppConfigMod: ProxifiedModule): void { const imports = originalAppConfigMod.imports; const hasAppInitializer = imports.$items.some( - (item) => item.local === 'APP_INITIALIZER', + (item) => item.local === 'APP_INITIALIZER' && item.from === '@angular/core', ); if (!hasAppInitializer) { diff --git a/src/angular/codemods/main.ts b/src/angular/codemods/main.ts index ff750bcf..896d6ad9 100644 --- a/src/angular/codemods/main.ts +++ b/src/angular/codemods/main.ts @@ -5,7 +5,7 @@ import type { Program } from '@babel/types'; // @ts-expect-error - magicast is ESM and TS complains about that. It works though import { builders, generateCode, type ProxifiedModule } from 'magicast'; -export function updateAppModuleMod( +export function updateAppEntryMod( originalAppModuleMod: ProxifiedModule, dsn: string, selectedFeatures: { diff --git a/src/angular/codemods/sourcemaps.ts b/src/angular/codemods/sourcemaps.ts index 34607107..22450ca0 100644 --- a/src/angular/codemods/sourcemaps.ts +++ b/src/angular/codemods/sourcemaps.ts @@ -1,23 +1,25 @@ /* eslint-disable @typescript-eslint/no-unsafe-member-access */ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ + +// @ts-ignore - clack is ESM and TS complains about that. It works though +import * as clack from '@clack/prompts'; import * as path from 'path'; import * as fs from 'fs'; +import { configureAngularSourcemapGenerationFlow } from '../../sourcemaps/tools/angular'; -export function addSourcemapEntryToAngularJSON(): void { +export async function addSourcemapEntryToAngularJSON(): Promise { const angularJsonPath = path.join(process.cwd(), 'angular.json'); - const angularJSONFile = fs.readFileSync(angularJsonPath, 'utf-8'); - const angularJson = JSON.parse(angularJSONFile); if (!angularJson) { - throw new Error('Could not find in angular.json in your project'); + await configureAngularSourcemapGenerationFlow(); } const projects = Object.keys(angularJson.projects as Record); if (!projects.length) { - throw new Error('Could not find any projects in angular.json'); + await configureAngularSourcemapGenerationFlow(); } // Emit sourcemaps from all projects in angular.json @@ -42,4 +44,8 @@ export function addSourcemapEntryToAngularJSON(): void { } fs.writeFileSync(angularJsonPath, JSON.stringify(angularJson, null, 2)); + + clack.log.info( + 'Added sourcemap configuration to angular.json for all projects', + ); } diff --git a/src/angular/sdk-setup.ts b/src/angular/sdk-setup.ts index 32c21b1f..9146648f 100644 --- a/src/angular/sdk-setup.ts +++ b/src/angular/sdk-setup.ts @@ -1,14 +1,14 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ // @ts-expect-error - magicast is ESM and TS complains about that. It works though -import { loadFile, ProxifiedModule, writeFile } from 'magicast'; +import { loadFile, type ProxifiedModule, writeFile } from 'magicast'; 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 chalk from 'chalk'; -import { updateAppModuleMod } from './codemods/main'; +import { updateAppEntryMod } from './codemods/main'; import { updateAppConfigMod } from './codemods/app-config'; import type { SemVer } from 'semver'; @@ -33,33 +33,33 @@ Skipping adding Sentry functionality to ${chalk.cyan( return includesContent; } -export async function initalizeSentryOnAppModule( +export async function initalizeSentryOnApplicationEntry( dsn: string, selectedFeatures: { performance: boolean; replay: boolean; }, ): Promise { - const appModuleFilename = 'main.ts'; - const appModulePath = path.join(process.cwd(), 'src', appModuleFilename); + const appEntryFilename = 'main.ts'; + const appEntryPath = path.join(process.cwd(), 'src', appEntryFilename); - const originalAppModule = await loadFile(appModulePath); + const originalAppEntry = await loadFile(appEntryPath); - if (hasSentryContent(appModulePath, originalAppModule.$code)) { + if (hasSentryContent(appEntryPath, originalAppEntry.$code)) { return; } - const updatedAppModuleMod = updateAppModuleMod( - originalAppModule, + const updatedAppEntryMod = updateAppEntryMod( + originalAppEntry, dsn, selectedFeatures, ); - await writeFile(updatedAppModuleMod.$ast, appModulePath); + await writeFile(updatedAppEntryMod.$ast, appEntryPath); clack.log.success( `Successfully initialized Sentry on your app module ${chalk.cyan( - appModuleFilename, + appEntryFilename, )}`, ); } diff --git a/src/sourcemaps/sourcemaps-wizard.ts b/src/sourcemaps/sourcemaps-wizard.ts index 691b8303..353b4fdc 100644 --- a/src/sourcemaps/sourcemaps-wizard.ts +++ b/src/sourcemaps/sourcemaps-wizard.ts @@ -232,7 +232,8 @@ async function startToolSetupFlow( // Angular wizard handles the angular.json setup itself !preSelectedTool || preSelectedTool !== 'angular' ? configureAngularSourcemapGenerationFlow - : undefined, + : // Not leaving this as undefined to avoid default. This is expected to be a no-op. + async () => Promise.resolve(), ); break; case 'nextjs': diff --git a/src/sourcemaps/tools/angular.ts b/src/sourcemaps/tools/angular.ts index ab31e93f..fc8c674b 100644 --- a/src/sourcemaps/tools/angular.ts +++ b/src/sourcemaps/tools/angular.ts @@ -3,7 +3,7 @@ import clack from '@clack/prompts'; import chalk from 'chalk'; import { abortIfCancelled } from '../../utils/clack-utils'; -const angularJsonTemplate = chalk.gray(`{ +export const angularJsonTemplate = chalk.gray(`{ "projects": { "your-project": { "architect": { @@ -22,7 +22,7 @@ export async function configureAngularSourcemapGenerationFlow(): Promise { `Enable generating source maps in your ${chalk.bold('angular.json')} file:`, ); - // Intentially logging directly to console here so that the code can be copied/pasted directly + // Intentionally logging directly to console here so that the code can be copied/pasted directly // eslint-disable-next-line no-console console.log(angularJsonTemplate); diff --git a/src/sourcemaps/tools/sentry-cli.ts b/src/sourcemaps/tools/sentry-cli.ts index 64990457..60a748fe 100644 --- a/src/sourcemaps/tools/sentry-cli.ts +++ b/src/sourcemaps/tools/sentry-cli.ts @@ -23,6 +23,7 @@ let addedToBuildCommand = false; export async function configureSentryCLI( options: SourceMapUploadToolConfigurationOptions, configureSourcemapGenerationFlow: () => Promise = defaultConfigureSourcemapGenerationFlow, + skipValidation = false, ): Promise { const packageDotJson = await getPackageDotJson(); @@ -33,6 +34,7 @@ export async function configureSentryCLI( let validPath = false; let relativeArtifactPath; + do { const rawArtifactPath = await abortIfCancelled( clack.text({ @@ -76,7 +78,9 @@ export async function configureSentryCLI( .split(path.sep) .join(path.posix.sep); - await configureSourcemapGenerationFlow(); + if (!skipValidation) { + await configureSourcemapGenerationFlow(); + } await createAndAddNpmScript(options, relativePosixArtifactPath);