From 4865e2ff071924b4b159358c918f9b2d5353fe48 Mon Sep 17 00:00:00 2001 From: Yann Braga Date: Wed, 28 Feb 2024 08:44:59 +0100 Subject: [PATCH] Doctor: Add dynamic check for incompatible storybook packages --- .../fixes/incompatible-addons.test.ts | 106 ------------ .../automigrate/fixes/incompatible-addons.ts | 34 ---- .../helpers/getMigrationSummary.ts | 10 -- code/lib/cli/src/automigrate/index.ts | 5 +- .../getIncompatibleStorybookPackages.test.ts | 122 ++++++++++++++ .../getIncompatibleStorybookPackages.ts | 155 ++++++++++++++++++ code/lib/cli/src/doctor/index.ts | 68 ++++---- code/lib/core-server/src/build-dev.ts | 2 +- .../src/utils/warnOnIncompatibleAddons.ts | 35 ++-- 9 files changed, 338 insertions(+), 199 deletions(-) delete mode 100644 code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts delete mode 100644 code/lib/cli/src/automigrate/fixes/incompatible-addons.ts create mode 100644 code/lib/cli/src/doctor/getIncompatibleStorybookPackages.test.ts create mode 100644 code/lib/cli/src/doctor/getIncompatibleStorybookPackages.ts diff --git a/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts b/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts deleted file mode 100644 index d23f369da214..000000000000 --- a/code/lib/cli/src/automigrate/fixes/incompatible-addons.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -import { describe, afterEach, it, expect, vi } from 'vitest'; - -import type { StorybookConfig } from '@storybook/types'; -import { incompatibleAddons } from './incompatible-addons'; -import type { JsPackageManager } from '@storybook/core-common'; - -const check = async ({ - packageManager, - main: mainConfig = {}, - storybookVersion = '7.0.0', -}: { - packageManager: Partial; - main?: Partial & Record; - storybookVersion?: string; -}) => { - return incompatibleAddons.check({ - packageManager: packageManager as any, - configDir: '', - mainConfig: mainConfig as any, - storybookVersion, - }); -}; - -describe('incompatible-addons fix', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('should show incompatible addons registered in main.js', async () => { - await expect( - check({ - packageManager: { - getPackageVersion(packageName, basePath) { - switch (packageName) { - case '@storybook/addon-essentials': - return Promise.resolve('7.0.0'); - case '@storybook/addon-info': - return Promise.resolve('5.3.21'); - default: - return Promise.resolve(null); - } - }, - getAllDependencies: async () => ({}), - }, - main: { addons: ['@storybook/essentials', '@storybook/addon-info'] }, - }) - ).resolves.toEqual({ - incompatibleAddonList: [ - { - name: '@storybook/addon-info', - version: '5.3.21', - }, - ], - }); - }); - - it('should show incompatible addons from package.json', async () => { - await expect( - check({ - packageManager: { - getPackageVersion(packageName, basePath) { - switch (packageName) { - case '@storybook/addon-essentials': - return Promise.resolve('7.0.0'); - case '@storybook/addon-info': - return Promise.resolve('5.3.21'); - default: - return Promise.resolve(null); - } - }, - getAllDependencies: async () => ({ - '@storybook/addon-essentials': '7.0.0', - '@storybook/addon-info': '5.3.21', - }), - }, - main: { addons: [] }, - }) - ).resolves.toEqual({ - incompatibleAddonList: [ - { - name: '@storybook/addon-info', - version: '5.3.21', - }, - ], - }); - }); - - it('no-op when there are no incompatible addons', async () => { - await expect( - check({ - packageManager: { - getPackageVersion(packageName, basePath) { - switch (packageName) { - case '@storybook/addon-essentials': - return Promise.resolve('7.0.0'); - default: - return Promise.resolve(null); - } - }, - getAllDependencies: async () => ({}), - }, - main: { addons: ['@storybook/essentials'] }, - }) - ).resolves.toBeNull(); - }); -}); diff --git a/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts b/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts deleted file mode 100644 index fdf1c9678763..000000000000 --- a/code/lib/cli/src/automigrate/fixes/incompatible-addons.ts +++ /dev/null @@ -1,34 +0,0 @@ -import chalk from 'chalk'; -import dedent from 'ts-dedent'; -import type { Fix } from '../types'; -import { getIncompatibleAddons } from '../../doctor/getIncompatibleAddons'; - -interface IncompatibleAddonsOptions { - incompatibleAddonList: { name: string; version: string }[]; -} - -export const incompatibleAddons: Fix = { - id: 'incompatible-addons', - promptType: 'manual', - versionRange: ['*', '*'], - - async check({ mainConfig, packageManager }) { - const incompatibleAddonList = await getIncompatibleAddons(mainConfig, packageManager); - - return incompatibleAddonList.length > 0 ? { incompatibleAddonList } : null; - }, - prompt({ incompatibleAddonList }) { - return dedent` - ${chalk.bold( - 'Attention' - )}: We've detected that you're using the following addons in versions which are known to be incompatible with Storybook 8: - - ${incompatibleAddonList - .map(({ name, version }) => `- ${chalk.cyan(`${name}@${version}`)}`) - .join('\n')} - - Please be aware they might not work in Storybook 8. Reach out to their maintainers for updates and check the following Github issue for more information: - ${chalk.yellow('https://github.com/storybookjs/storybook/issues/26031')} - `; - }, -}; diff --git a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts index 1dcd0a0658c7..58179e9115f3 100644 --- a/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts +++ b/code/lib/cli/src/automigrate/helpers/getMigrationSummary.ts @@ -4,7 +4,6 @@ import dedent from 'ts-dedent'; import type { InstallationMetadata } from '@storybook/core-common'; import type { FixSummary } from '../types'; import { FixStatus } from '../types'; -import { getDuplicatedDepsWarnings } from '../../doctor/getDuplicatedDepsWarnings'; export const messageDivider = '\n\n'; const segmentDivider = '\n\n─────────────────────────────────────────────────\n\n'; @@ -53,7 +52,6 @@ export function getMigrationSummary({ fixResults, fixSummary, logFile, - installationMetadata, }: { fixResults: Record; fixSummary: FixSummary; @@ -75,14 +73,6 @@ export function getMigrationSummary({ And reach out on Discord if you need help: ${chalk.yellow('https://discord.gg/storybook')} `); - const duplicatedDepsMessage = installationMetadata - ? getDuplicatedDepsWarnings(installationMetadata) - : getDuplicatedDepsWarnings(); - - if (duplicatedDepsMessage) { - messages.push(duplicatedDepsMessage.join(messageDivider)); - } - const hasNoFixes = Object.values(fixResults).every((r) => r === FixStatus.UNNECESSARY); const hasFailures = Object.values(fixResults).some( (r) => r === FixStatus.FAILED || r === FixStatus.CHECK_FAILED diff --git a/code/lib/cli/src/automigrate/index.ts b/code/lib/cli/src/automigrate/index.ts index 92d21e2ec6c2..b0864a7071c6 100644 --- a/code/lib/cli/src/automigrate/index.ts +++ b/code/lib/cli/src/automigrate/index.ts @@ -27,6 +27,7 @@ import { FixStatus, allFixes } from './fixes'; import { cleanLog } from './helpers/cleanLog'; import { getMigrationSummary } from './helpers/getMigrationSummary'; import { getStorybookData } from './helpers/mainConfigFile'; +import { doctor } from '../doctor'; const logger = console; const LOG_FILE_NAME = 'migration-storybook.log'; @@ -83,7 +84,7 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { throw new Error('Could not determine main config path'); } - return automigrate({ + await automigrate({ ...options, packageManager, storybookVersion, @@ -92,6 +93,8 @@ export const doAutomigrate = async (options: AutofixOptionsFromCLI) => { configDir, isUpgrade: false, }); + + await doctor({ configDir, packageManager: options.packageManager }); }; export const automigrate = async ({ diff --git a/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.test.ts b/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.test.ts new file mode 100644 index 000000000000..e354497930f1 --- /dev/null +++ b/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.test.ts @@ -0,0 +1,122 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { + getIncompatibleStorybookPackages, + getIncompatiblePackagesSummary, +} from './getIncompatibleStorybookPackages'; +import pkgUp from 'read-pkg-up'; +import type { JsPackageManager } from '@storybook/core-common'; + +vi.mock('chalk', () => { + return { + default: { + yellow: (str: string) => str, + cyan: (str: string) => str, + }, + }; +}); + +vi.mock('read-pkg-up', () => ({ + default: vi.fn(), +})); + +const packageManagerMock = { + getAllDependencies: () => + Promise.resolve({ + '@storybook/addon-essentials': '7.0.0', + }), + latestVersion: vi.fn(() => Promise.resolve('8.0.0')), +} as Partial; + +describe('getIncompatibleStorybookPackages', () => { + beforeEach(() => { + vi.resetAllMocks(); + }); + + it('returns an array of incompatible packages', async () => { + vi.mocked(pkgUp).mockResolvedValueOnce({ + packageJson: { + name: '@storybook/addon-essentials', + version: '7.0.0', + dependencies: { + '@storybook/core-common': '7.0.0', + }, + }, + path: '', + }); + + vi.mocked(packageManagerMock.latestVersion)?.mockResolvedValueOnce('8.0.0'); + + const result = await getIncompatibleStorybookPackages({ + currentStorybookVersion: '8.0.0', + packageManager: packageManagerMock as JsPackageManager, + }); + + expect(packageManagerMock.latestVersion).toHaveBeenCalled(); + expect(result).toEqual([ + { + packageName: '@storybook/addon-essentials', + packageVersion: '7.0.0', + hasIncompatibleDependencies: true, + homepage: undefined, + availableUpdate: true, + latestVersionOfPackage: '8.0.0', + }, + ]); + }); + + it('returns an array of incompatible packages without upgrade check', async () => { + vi.mocked(pkgUp).mockResolvedValueOnce({ + packageJson: { + name: '@storybook/addon-essentials', + version: '7.0.0', + dependencies: { + '@storybook/core-common': '7.0.0', + }, + }, + path: '', + }); + + const result = await getIncompatibleStorybookPackages({ + currentStorybookVersion: '8.0.0', + packageManager: packageManagerMock as JsPackageManager, + skipUpgradeCheck: true, + }); + + expect(packageManagerMock.latestVersion).not.toHaveBeenCalled(); + + expect(result).toEqual([ + { + packageName: '@storybook/addon-essentials', + packageVersion: '7.0.0', + hasIncompatibleDependencies: true, + homepage: undefined, + availableUpdate: false, + latestVersionOfPackage: undefined, + }, + ]); + }); +}); + +describe('getIncompatiblePackagesSummary', () => { + it('generates a summary message for incompatible packages', () => { + const analysedPackages = [ + { + packageName: 'storybook-react', + packageVersion: '1.0.0', + hasIncompatibleDependencies: true, + latestVersionOfPackage: '2.0.0', + availableUpdate: true, + }, + ]; + const summary = getIncompatiblePackagesSummary(analysedPackages, '7.0.0'); + expect(summary).toMatchInlineSnapshot(` + "The following addons are likely incompatible with Storybook 7.0.0: + - storybook-react@1.0.0 (2.0.0 available!) + + + Please consider updating your packages or contacting the maintainers for compatibility details. + For more on Storybook 8 compatibility, see the linked Github issue: + https://github.com/storybookjs/storybook/issues/26031" + `); + }); +}); diff --git a/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.ts b/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.ts new file mode 100644 index 000000000000..e07ca9ec3489 --- /dev/null +++ b/code/lib/cli/src/doctor/getIncompatibleStorybookPackages.ts @@ -0,0 +1,155 @@ +/* eslint-disable local-rules/no-uncategorized-errors */ +import chalk from 'chalk'; +import semver from 'semver'; +import readPkgUp from 'read-pkg-up'; +import type { JsPackageManager } from '@storybook/core-common'; +import { JsPackageManagerFactory, versions as storybookCorePackages } from '@storybook/core-common'; + +type AnalysedPackage = { + packageName: string; + packageVersion?: string; + homepage?: string; + hasIncompatibleDependencies?: boolean; + latestVersionOfPackage?: string; + availableUpdate?: boolean; +}; + +export const getIncompatibleStorybookPackages = async ({ + currentStorybookVersion, + packageManager = JsPackageManagerFactory.getPackageManager(), + skipUpgradeCheck = false, + skipErrors = false, +}: { + currentStorybookVersion: string; + packageManager?: JsPackageManager; + skipUpgradeCheck?: boolean; + skipErrors?: boolean; +}): Promise => { + const allDeps = await packageManager.getAllDependencies(); + const storybookLikeDeps = Object.keys(allDeps).filter((dep) => dep.includes('storybook')); + + if (storybookLikeDeps.length === 0) { + throw new Error('No storybook dependencies found in the package.json'); + } + + const isPackageIncompatible = (installedVersion: string) => { + const dependencyMajor = semver.coerce(installedVersion)!.major; + const storybookMajor = semver.coerce(currentStorybookVersion)!.major; + return dependencyMajor !== storybookMajor; + }; + + const checkCompatibility = async (dependency: string): Promise => { + try { + const resolvedPath = require.resolve(dependency); + const result = await readPkgUp({ cwd: resolvedPath }); + + if (!result?.packageJson) { + throw new Error(`No package.json found for ${dependency}`); + } + + const { + packageJson: { version: versionSpecifier, name, dependencies, peerDependencies, homepage }, + } = result; + const coercedVersion = new semver.SemVer(versionSpecifier); + const packageVersion = coercedVersion.version; + + const hasIncompatibleDependencies = !!Object.entries({ + ...dependencies, + ...peerDependencies, + }) + .filter(([dep]) => Object.keys(storybookCorePackages).includes(dep)) + .find(([, version]) => { + // prevent issues with "tag" based versions e.g. "latest" or "next" instead of actual numbers + return version && semver.validRange(version) && isPackageIncompatible(version); + }); + + let latestVersionOfPackage; + + if (!skipUpgradeCheck) { + try { + const isStorybookPreRelease = currentStorybookVersion.includes('-'); + // if the user is on a pre-release, we try to get the existing prereleases of all packages + if (isStorybookPreRelease) { + // this is mostly a guess that makes it work for external addons which use the next/latest release strategy + const constraint = currentStorybookVersion.includes('-') + ? `^${coercedVersion.major + 1}.0.0-alpha.0` + : `^${coercedVersion.major + 1}.0.0`; + + latestVersionOfPackage = await packageManager.latestVersion(name, constraint); + } else { + latestVersionOfPackage = await packageManager.latestVersion(name); + } + } catch (err) { + // things might not work when defining the prerelease constraint, so we fall back to "latest" + latestVersionOfPackage = await packageManager.latestVersion(name); + } + } + + return { + packageName: name, + packageVersion, + homepage, + hasIncompatibleDependencies, + latestVersionOfPackage, + availableUpdate: !!( + latestVersionOfPackage && semver.gt(latestVersionOfPackage, packageVersion) + ), + }; + } catch (err) { + // For the reviewers: When running sb doctor, this error message is only shown in the log file. + // Do we want it? maybe not? it's currently under a flag because this is also used in storybook dev and we do not want to show errors there + // We can choose to silently fail, but this has proven quite useful as some of our addons + // have faulty package.json files: @storybook/addon-onboarding, @storybook/addon-coverage + if (!skipErrors) { + console.error( + `Error checking compatibility for ${dependency}, please report an issue:\n`, + err + ); + } + return { packageName: dependency }; + } + }; + + return Promise.all(storybookLikeDeps.map((dep) => checkCompatibility(dep))); +}; + +export const getIncompatiblePackagesSummary = ( + dependencyAnalysis: AnalysedPackage[], + currentVersion: string +) => { + const summaryMessage: string[] = []; + + const incompatiblePackages = dependencyAnalysis.filter( + (dep) => dep.hasIncompatibleDependencies + ) as AnalysedPackage[]; + + if (incompatiblePackages.length > 0) { + summaryMessage.push( + `The following addons are likely incompatible with Storybook ${currentVersion}:` + ); + incompatiblePackages.forEach( + ({ + packageName: addonName, + packageVersion: addonVersion, + homepage, + availableUpdate, + latestVersionOfPackage, + }) => { + const packageDescription = `${chalk.cyan(addonName)}@${chalk.cyan(addonVersion)}`; + const updateMessage = availableUpdate ? ` (${latestVersionOfPackage} available!)` : ''; + const packageRepo = homepage ? `\n Repo: ${chalk.yellow(homepage)}` : ''; + + summaryMessage.push(`- ${packageDescription}${updateMessage}${packageRepo}`); + } + ); + + summaryMessage.push( + '\n', + 'Please consider updating your packages or contacting the maintainers for compatibility details.', + 'For more on Storybook 8 compatibility, see the linked Github issue:', + chalk.yellow('https://github.com/storybookjs/storybook/issues/26031') + ); + } + + return summaryMessage.join('\n'); +}; diff --git a/code/lib/cli/src/doctor/index.ts b/code/lib/cli/src/doctor/index.ts index 9aaeede8bc0f..efd1b74642c5 100644 --- a/code/lib/cli/src/doctor/index.ts +++ b/code/lib/cli/src/doctor/index.ts @@ -9,10 +9,11 @@ import { JsPackageManagerFactory } from '@storybook/core-common'; import type { PackageManagerName } from '@storybook/core-common'; import { getStorybookData } from '../automigrate/helpers/mainConfigFile'; import { cleanLog } from '../automigrate/helpers/cleanLog'; -import { incompatibleAddons } from '../automigrate/fixes/incompatible-addons'; -import { getDuplicatedDepsWarnings } from './getDuplicatedDepsWarnings'; -import { getIncompatibleAddons } from './getIncompatibleAddons'; import { getMismatchingVersionsWarnings } from './getMismatchingVersionsWarning'; +import { + getIncompatiblePackagesSummary, + getIncompatibleStorybookPackages, +} from './getIncompatibleStorybookPackages'; const logger = console; const LOG_FILE_NAME = 'doctor-storybook.log'; @@ -45,14 +46,24 @@ type DoctorOptions = { packageManager?: PackageManagerName; }; +const logDiagnostic = (title: string, message: string) => { + logger.info( + boxen(message, { + borderStyle: 'round', + padding: 1, + title, + borderColor: '#F1618C', + }) + ); +}; + export const doctor = async ({ configDir: userSpecifiedConfigDir, packageManager: pkgMgr, }: DoctorOptions = {}) => { augmentLogsToFile(); - const diagnosticMessages: string[] = []; - logger.info('🩺 checking the health of your Storybook..'); + logger.info('🩺 The doctor is checking the health of your Storybook..'); const packageManager = JsPackageManagerFactory.getPackageManager({ force: pkgMgr }); let storybookVersion; @@ -89,9 +100,17 @@ export const doctor = async ({ throw new Error('mainConfig is undefined'); } - const incompatibleAddonList = await getIncompatibleAddons(mainConfig); - if (incompatibleAddonList.length > 0) { - diagnosticMessages.push(incompatibleAddons.prompt({ incompatibleAddonList })); + const allDependencies = (await packageManager.getAllDependencies()) as Record; + + const incompatibleStorybookPackagesList = await getIncompatibleStorybookPackages({ + currentStorybookVersion: storybookVersion, + }); + const incompatiblePackagesMessage = getIncompatiblePackagesSummary( + incompatibleStorybookPackagesList, + storybookVersion + ); + if (incompatiblePackagesMessage) { + logDiagnostic('Incompatible packages found', incompatiblePackagesMessage); } const installationMetadata = await packageManager.findInstallations([ @@ -99,36 +118,29 @@ export const doctor = async ({ 'storybook', ]); - const allDependencies = (await packageManager.getAllDependencies()) as Record; const mismatchingVersionMessage = getMismatchingVersionsWarnings( installationMetadata, allDependencies ); if (mismatchingVersionMessage) { - diagnosticMessages.push(mismatchingVersionMessage); - } else { - const list = installationMetadata - ? getDuplicatedDepsWarnings(installationMetadata) - : getDuplicatedDepsWarnings(); - if (list) { - diagnosticMessages.push(list?.join('\n')); - } + logDiagnostic('Diagnostics', [mismatchingVersionMessage].join('\n\n-------\n\n')); } + // CHECK: Temporarily disable multiple versions warning as the incompatible packages mostly covers this + // else { + // const list = installationMetadata + // ? getDuplicatedDepsWarnings(installationMetadata) + // : getDuplicatedDepsWarnings(); + // if (list) { + // diagnosticMessages.push(list?.join('\n')); + // } + // } logger.info(); - const finalMessages = diagnosticMessages.filter(Boolean); + const foundIssues = incompatiblePackagesMessage || mismatchingVersionMessage; - if (finalMessages.length > 0) { - finalMessages.push(`You can find the full logs in ${chalk.cyan(LOG_FILE_PATH)}`); + if (foundIssues) { + logger.info(`You can find the full logs in ${chalk.cyan(LOG_FILE_PATH)}`); - logger.info( - boxen(finalMessages.join('\n\n-------\n\n'), { - borderStyle: 'round', - padding: 1, - title: 'Diagnostics', - borderColor: 'red', - }) - ); await move(TEMP_LOG_FILE_PATH, join(process.cwd(), LOG_FILE_NAME), { overwrite: true }); } else { logger.info('🥳 Your Storybook project looks good!'); diff --git a/code/lib/core-server/src/build-dev.ts b/code/lib/core-server/src/build-dev.ts index b55b5e22b8a7..de47bcd7a575 100644 --- a/code/lib/core-server/src/build-dev.ts +++ b/code/lib/core-server/src/build-dev.ts @@ -89,7 +89,7 @@ export async function buildDevStandalone( frameworkName = frameworkName || 'custom'; try { - await warnOnIncompatibleAddons(config); + await warnOnIncompatibleAddons(packageJson.version); } catch (e) { console.warn('Storybook failed to check addon compatibility', e); } diff --git a/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts b/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts index fd4aaac39286..6d7359a21edb 100644 --- a/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts +++ b/code/lib/core-server/src/utils/warnOnIncompatibleAddons.ts @@ -1,25 +1,22 @@ -import type { StorybookConfig } from '@storybook/types'; import { logger } from '@storybook/node-logger'; -import chalk from 'chalk'; -import dedent from 'ts-dedent'; +import { + getIncompatibleStorybookPackages, + getIncompatiblePackagesSummary, +} from '../../../cli/src/doctor/getIncompatibleStorybookPackages'; -import { getIncompatibleAddons } from '../../../cli/src/doctor/getIncompatibleAddons'; +export const warnOnIncompatibleAddons = async (currentStorybookVersion: string) => { + const incompatiblePackagesList = await getIncompatibleStorybookPackages({ + skipUpgradeCheck: true, + skipErrors: true, + currentStorybookVersion, + }); -export const warnOnIncompatibleAddons = async (config: StorybookConfig) => { - const incompatibleAddons = await getIncompatibleAddons(config); + const incompatiblePackagesMessage = await getIncompatiblePackagesSummary( + incompatiblePackagesList, + currentStorybookVersion + ); - if (incompatibleAddons.length > 0) { - logger.warn(dedent` - ${chalk.bold( - chalk.red('Attention') - )}: We've detected that you're using the following addons in versions which are known to be incompatible with Storybook 7: - - ${incompatibleAddons - .map(({ name, version }) => `- ${chalk.cyan(`${name}@${version}`)}`) - .join('\n')} - - Please be aware they might not work in Storybook 7. Reach out to their maintainers for updates and check the following Github issue for more information: - ${chalk.yellow('https://github.com/storybookjs/storybook/issues/20529')} - `); + if (incompatiblePackagesMessage) { + logger.warn(incompatiblePackagesMessage); } };