From 03c034fbb5a7fc508c05b5e6d83691fb2a2c996c Mon Sep 17 00:00:00 2001 From: Rhys Arkins Date: Mon, 17 Jun 2024 12:50:23 +0200 Subject: [PATCH] feat(dashboard): show deprecated dependency warnings (#29694) Co-authored-by: HonkingGoose <34918129+HonkingGoose@users.noreply.github.com> --- docs/usage/key-concepts/dashboard.md | 17 +++++++ .../repository/dependency-dashboard.spec.ts | 32 +++++++++++++ .../repository/dependency-dashboard.ts | 45 ++++++++++++++++++- 3 files changed, 93 insertions(+), 1 deletion(-) diff --git a/docs/usage/key-concepts/dashboard.md b/docs/usage/key-concepts/dashboard.md index a564d63dc9f41f..3199cb1359c721 100644 --- a/docs/usage/key-concepts/dashboard.md +++ b/docs/usage/key-concepts/dashboard.md @@ -49,6 +49,23 @@ To disable the Dependency Dashboard, add the preset `:disableDependencyDashboard This section explains some common use cases where having the Dependency Dashboard can help. +### Warnings for deprecated dependencies + +If Renovate finds: + +- packages flagged as deprecated on their registry, or +- packages that have a community-sourced replacement PR available + +Then Renovate adds a prominent warning about these packages near the top of the Dependency Dashboard. +Here is an example of how this can look: + +> The following dependencies are deprecated: + +| Datasource | Name | Replacement? | +| ---------- | ------------------- | --------------------------------------------------------------------------------- | +| npm | `airbnb-prop-types` | ![Available](https://img.shields.io/badge/available-green?style=flat-square) | +| npm | `left-pad` | ![Unavailable](https://img.shields.io/badge/unavailable-orange?style=flat-square) | + ### Visibility into rejected/deferred updates Renovate's Dependency Dashboard shows an overview of all updates that are still "to do". diff --git a/lib/workers/repository/dependency-dashboard.spec.ts b/lib/workers/repository/dependency-dashboard.spec.ts index 54be5280260931..d82ab52dd9443c 100644 --- a/lib/workers/repository/dependency-dashboard.spec.ts +++ b/lib/workers/repository/dependency-dashboard.spec.ts @@ -19,6 +19,7 @@ import { GitHubMaxPrBodyLen, massageMarkdown, } from '../../modules/platform/github'; +import { clone } from '../../util/clone'; import { regEx } from '../../util/regex'; import type { BranchConfig, BranchUpgradeConfig } from '../types'; import * as dependencyDashboard from './dependency-dashboard'; @@ -981,6 +982,37 @@ None detected // same with dry run await dryRun(branches, platform, 0, 1); }); + + it('shows deprecations', async () => { + const branches: BranchConfig[] = []; + const packageFilesWithDeprecations = clone(packageFiles); + packageFilesWithDeprecations.npm[0].deps[0].deprecationMessage = + 'some deprecation message'; + packageFilesWithDeprecations.npm[0].deps[2].updates.push({ + updateType: 'replacement', + newName: 'prop-types-tools', + newValue: '2.17.0', + branchName: 'renovate/airbnb-prop-types-replacement', + }); + PackageFiles.add('main', packageFilesWithDeprecations); + await dependencyDashboard.ensureDependencyDashboard( + config, + branches, + packageFilesWithDeprecations, + ); + expect(platform.ensureIssue).toHaveBeenCalledTimes(1); + expect(platform.ensureIssue.mock.calls[0][0].body).toInclude( + 'These dependencies are deprecated', + ); + expect(platform.ensureIssue.mock.calls[0][0].body).toInclude( + '| npm | `cookie-parser` | ![Unavailable]', + ); + expect(platform.ensureIssue.mock.calls[0][0].body).toInclude( + 'npm | `express-handlebars` | ![Available]', + ); + // same with dry run + await dryRun(branches, platform, 0, 1); + }); }); describe('multi base branch repo', () => { diff --git a/lib/workers/repository/dependency-dashboard.ts b/lib/workers/repository/dependency-dashboard.ts index 17d895c35b22ad..0baad236161d17 100644 --- a/lib/workers/repository/dependency-dashboard.ts +++ b/lib/workers/repository/dependency-dashboard.ts @@ -224,8 +224,32 @@ export async function ensureDependencyDashboard( return; } logger.debug('Ensuring Dependency Dashboard'); + + // Check packageFiles for any deprecations + let hasDeprecations = false; + const deprecatedPackages: Record> = {}; + logger.debug( + { packageFiles }, + 'Checking packageFiles for deprecated packages', + ); + for (const [manager, fileNames] of Object.entries(packageFiles)) { + for (const fileName of fileNames) { + for (const dep of fileName.deps) { + const name = dep.packageName ?? dep.depName; + const hasReplacement = !!dep.updates?.find( + (updates) => updates.updateType === 'replacement', + ); + if (name && (dep.deprecationMessage ?? hasReplacement)) { + hasDeprecations = true; + deprecatedPackages[manager] ??= {}; + deprecatedPackages[manager][name] ??= hasReplacement; + } + } + } + } + const hasBranches = is.nonEmptyArray(branches); - if (config.dependencyDashboardAutoclose && !hasBranches) { + if (config.dependencyDashboardAutoclose && !hasBranches && !hasDeprecations) { if (GlobalConfig.get('dryRun')) { logger.info( { title: config.dependencyDashboardTitle }, @@ -245,6 +269,25 @@ export async function ensureDependencyDashboard( issueBody = appendRepoProblems(config, issueBody); + if (hasDeprecations) { + issueBody += '> ⚠ **Warning**\n> \n'; + issueBody += 'These dependencies are deprecated:\n\n'; + issueBody += '| Datasource | Name | Replacement PR? |\n'; + issueBody += '|------------|------|--------------|\n'; + for (const manager of Object.keys(deprecatedPackages).sort()) { + const deps = deprecatedPackages[manager]; + for (const depName of Object.keys(deps).sort()) { + const hasReplacement = deps[depName]; + issueBody += `| ${manager} | \`${depName}\` | ${ + hasReplacement + ? '![Available](https://img.shields.io/badge/available-green?style=flat-square)' + : '![Unavailable](https://img.shields.io/badge/unavailable-orange?style=flat-square)' + } |\n`; + } + } + issueBody += '\n'; + } + const pendingApprovals = branches.filter( (branch) => branch.result === 'needs-approval', );