From daa03dfdc6eb18ab27189e934199bfdb41257ae3 Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 7 Jan 2026 17:18:23 +0000 Subject: [PATCH 1/7] Initial refactor to use lockfile --- src/analyze/dependencies.ts | 162 ++++++++++++++++++++++++++++++++++++ src/analyze/report.ts | 8 +- 2 files changed, 168 insertions(+), 2 deletions(-) diff --git a/src/analyze/dependencies.ts b/src/analyze/dependencies.ts index 4e05926..fad545f 100644 --- a/src/analyze/dependencies.ts +++ b/src/analyze/dependencies.ts @@ -9,6 +9,7 @@ import type { } from '../types.js'; import type {FileSystem} from '../file-system.js'; import {normalizePath} from '../utils/path.js'; +import {ParsedLockFile, traverse, VisitorFn} from 'lockparse'; interface DependencyNode { name: string; @@ -329,3 +330,164 @@ export async function runDependencyAnalysis( return {stats, messages}; } + +type ResolvedDependency = { + name: string; + version: string; + //key: string; // lockfile identity + parents: string[]; +}; + +async function getInitialStats(context: AnalysisContext): Promise { + const lockfile = context.lockfile; + + if (!lockfile) { + throw new Error('No package-lock.json found.'); + } + + const installSize = await context.fs.getInstallSize(); + const root = lockfile.root; + const stats: Stats = { + name: root.name, + version: root.version, + installSize, + dependencyCount: { + production: root.dependencies.length, + development: root.devDependencies.length, + esm: 0, + cjs: 0, + duplicate: 0 + } + }; + return stats; +} + +function exportOutput( + stats: Stats, + duplicateDependencies: Map, + cjsDependencies: number, + esmDependencies: number +) { + const messages: Message[] = []; + stats.dependencyCount.cjs = cjsDependencies; + stats.dependencyCount.esm = esmDependencies; + + if (duplicateDependencies.size > 0) { + stats.dependencyCount.duplicate = duplicateDependencies.size; + + for (const duplicate of duplicateDependencies.values()) { + const severityColor = colors.green; + + let message = `${severityColor('[duplicate dependency]')} ${colors.bold(duplicate[0].name)} has ${duplicate.length} installed versions:`; + + for (const version of duplicate) { + for (const parent of version.parents) { + message += `\n ${colors.gray(version.version)} via ${colors.gray(parent)}`; + } + } + message += '\n'; + + // if (duplicate.suggestions && duplicate.suggestions.length > 0) { + // message += '\nSuggestions:'; + // for (const suggestion of duplicate.suggestions) { + // message += ` ${colors.blue('šŸ’”')} ${colors.gray(suggestion)}`; + // } + // } + + messages.push({ + message, + severity: 'warning', + score: 0 + }); + } + } + + return {stats, messages}; +} + +function resolveDependencies( + lockfile: ParsedLockFile +): Map { + const resolvedDependencies: Map = new Map(); + for (const pkg of lockfile.packages) { + const entry: ResolvedDependency = { + name: pkg.name, + version: pkg.version, + parents: [] + }; + if (!resolvedDependencies.has(pkg.name)) { + resolvedDependencies.set(pkg.name, [entry]); + } else { + const packageEntries = resolvedDependencies.get(pkg.name); + if (!packageEntries?.some((x) => x.version === pkg.version)) { + packageEntries?.push(entry); + } + } + } + return resolvedDependencies; +} + +async function computeParents( + lockfile: ParsedLockFile, + duplicateDependencies: Map +) { + const visitorFn: VisitorFn = (node, parent, path) => { + if (!duplicateDependencies.has(node.name) || !path) { + return; + } + const resolvedDependencies = duplicateDependencies.get(node.name); + if (!resolvedDependencies) { + return; + } + + //get the correct version + const version = resolvedDependencies.find( + (x) => x.version === node.version + ); + if (!version) { + return; + } + if (!parent) { + return; + } + + const parentPath = `${parent.name}@${parent.version}`; + if (version.parents.includes(parentPath)) { + return; + } + + version.parents.push(parentPath); + }; + const visitor = { + dependency: visitorFn, + devDependency: visitorFn, + optionalDependency: visitorFn + }; + + traverse(lockfile.root, visitor); +} + +export async function runDependencyAnalysisNEW( + context: AnalysisContext +): Promise { + const lockfile = context.lockfile; + + if (!lockfile) { + throw new Error('No package-lock.json found.'); + } + + const stats: Stats = await getInitialStats(context); + + const resolvedDependencies = resolveDependencies(lockfile); + const duplicateDependencies: Map = new Map(); + for (const [packageName, dependencies] of resolvedDependencies) { + if (dependencies.length <= 1) { + continue; + } + duplicateDependencies.set(packageName, dependencies); + } + + await computeParents(lockfile, duplicateDependencies); + + return exportOutput(stats, duplicateDependencies, 0, 0); +} diff --git a/src/analyze/report.ts b/src/analyze/report.ts index 2f70b87..5e81cd6 100644 --- a/src/analyze/report.ts +++ b/src/analyze/report.ts @@ -12,7 +12,10 @@ import type { } from '../types.js'; import {runPublint} from './publint.js'; import {runReplacements} from './replacements.js'; -import {runDependencyAnalysis} from './dependencies.js'; +import { + runDependencyAnalysis, + runDependencyAnalysisNEW +} from './dependencies.js'; import {runPlugins} from '../plugin-runner.js'; import {getPackageJson, detectLockfile} from '../utils/package-json.js'; import {parse as parseLockfile} from 'lockparse'; @@ -20,7 +23,8 @@ import {parse as parseLockfile} from 'lockparse'; const plugins: ReportPlugin[] = [ runPublint, runReplacements, - runDependencyAnalysis + runDependencyAnalysis, + runDependencyAnalysisNEW ]; async function computeInfo(fileSystem: FileSystem) { From 2bfb62fd7182870f89d2a8574cbb4d302f8ba3d4 Mon Sep 17 00:00:00 2001 From: laurathackray Date: Fri, 9 Jan 2026 12:11:28 +0000 Subject: [PATCH 2/7] refactor and add suggestions --- src/analyze/dependencies.ts | 104 +++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 36 deletions(-) diff --git a/src/analyze/dependencies.ts b/src/analyze/dependencies.ts index fad545f..1003137 100644 --- a/src/analyze/dependencies.ts +++ b/src/analyze/dependencies.ts @@ -334,7 +334,6 @@ export async function runDependencyAnalysis( type ResolvedDependency = { name: string; version: string; - //key: string; // lockfile identity parents: string[]; }; @@ -354,8 +353,8 @@ async function getInitialStats(context: AnalysisContext): Promise { dependencyCount: { production: root.dependencies.length, development: root.devDependencies.length, - esm: 0, cjs: 0, + esm: 0, duplicate: 0 } }; @@ -364,47 +363,73 @@ async function getInitialStats(context: AnalysisContext): Promise { function exportOutput( stats: Stats, - duplicateDependencies: Map, - cjsDependencies: number, - esmDependencies: number + duplicateDependencies: Map ) { const messages: Message[] = []; - stats.dependencyCount.cjs = cjsDependencies; - stats.dependencyCount.esm = esmDependencies; - - if (duplicateDependencies.size > 0) { - stats.dependencyCount.duplicate = duplicateDependencies.size; + if (duplicateDependencies.size === 0) { + return {stats, messages}; + } - for (const duplicate of duplicateDependencies.values()) { - const severityColor = colors.green; + stats.dependencyCount.duplicate = duplicateDependencies.size; - let message = `${severityColor('[duplicate dependency]')} ${colors.bold(duplicate[0].name)} has ${duplicate.length} installed versions:`; + for (const duplicate of duplicateDependencies.values()) { + const severityColor = colors.green; + let message = `${severityColor('[duplicate dependency]')} ${colors.bold(duplicate[0].name)} has ${duplicate.length} installed versions:`; - for (const version of duplicate) { - for (const parent of version.parents) { - message += `\n ${colors.gray(version.version)} via ${colors.gray(parent)}`; - } - } - message += '\n'; + for (const version of duplicate) { + message += `\n${colors.yellow(version.version)} via the following ${version.parents.length} package(s) ${colors.blue(version.parents.join(', '))}`; + } - // if (duplicate.suggestions && duplicate.suggestions.length > 0) { - // message += '\nSuggestions:'; - // for (const suggestion of duplicate.suggestions) { - // message += ` ${colors.blue('šŸ’”')} ${colors.gray(suggestion)}`; - // } - // } + const suggestions = generateSuggestionsForDuplicate(duplicate); - messages.push({ - message, - severity: 'warning', - score: 0 - }); + if (suggestions.length > 0) { + message += '\nšŸ’” Suggestions'; + for (const suggestion of suggestions) { + message += `${colors.gray(suggestion)}`; + } } + message += '\n'; + messages.push({ + message, + severity: 'warning', + score: 0 + }); } return {stats, messages}; } +/** + * Generates suggestions for resolving duplicates + */ +function generateSuggestionsForDuplicate( + resolvedDependencies: ResolvedDependency[] +): string[] { + const suggestions: string[] = []; + + // Find the package version with the most parents + const mostCommonVersion = resolvedDependencies.sort( + (a, b) => b.parents.length - a.parents.length + )[0]; + + if (mostCommonVersion?.parents.length > 1) { + suggestions.push( + `\n- Consider standardizing on version ${mostCommonVersion.version} as this version is the most commonly used.` + ); + } + + // Suggest checking for newer versions of consuming packages + suggestions.push( + `\n- Consider upgrading consuming packages as this may resolve this duplicate version.` + ); + + return suggestions; +} + +/** + * Computes a map of package names to their versions + * @param lockfile + */ function resolveDependencies( lockfile: ParsedLockFile ): Map { @@ -427,12 +452,17 @@ function resolveDependencies( return resolvedDependencies; } +/** + * Compute all the parent packages that use each duplicate dependency + * @param lockfile + * @param duplicateDependencies + */ async function computeParents( lockfile: ParsedLockFile, duplicateDependencies: Map ) { - const visitorFn: VisitorFn = (node, parent, path) => { - if (!duplicateDependencies.has(node.name) || !path) { + const visitorFn: VisitorFn = (node, parent, _path) => { + if (!duplicateDependencies.has(node.name) || !parent) { return; } const resolvedDependencies = duplicateDependencies.get(node.name); @@ -447,9 +477,6 @@ async function computeParents( if (!version) { return; } - if (!parent) { - return; - } const parentPath = `${parent.name}@${parent.version}`; if (version.parents.includes(parentPath)) { @@ -467,6 +494,11 @@ async function computeParents( traverse(lockfile.root, visitor); } +/** + * Find duplicate packages and output suggested fixes + * @param context + * @returns + */ export async function runDependencyAnalysisNEW( context: AnalysisContext ): Promise { @@ -489,5 +521,5 @@ export async function runDependencyAnalysisNEW( await computeParents(lockfile, duplicateDependencies); - return exportOutput(stats, duplicateDependencies, 0, 0); + return exportOutput(stats, duplicateDependencies); } From f786bd68a4eb1c4fe74854526b5b24d1f9359526 Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 14 Jan 2026 10:11:14 +0000 Subject: [PATCH 3/7] move new code into its own file --- src/analyze/dependencies.ts | 194 -------------------------- src/analyze/duplicate-dependencies.ts | 162 +++++++++++++++++++++ src/analyze/report.ts | 8 +- 3 files changed, 165 insertions(+), 199 deletions(-) create mode 100644 src/analyze/duplicate-dependencies.ts diff --git a/src/analyze/dependencies.ts b/src/analyze/dependencies.ts index 1003137..4e05926 100644 --- a/src/analyze/dependencies.ts +++ b/src/analyze/dependencies.ts @@ -9,7 +9,6 @@ import type { } from '../types.js'; import type {FileSystem} from '../file-system.js'; import {normalizePath} from '../utils/path.js'; -import {ParsedLockFile, traverse, VisitorFn} from 'lockparse'; interface DependencyNode { name: string; @@ -330,196 +329,3 @@ export async function runDependencyAnalysis( return {stats, messages}; } - -type ResolvedDependency = { - name: string; - version: string; - parents: string[]; -}; - -async function getInitialStats(context: AnalysisContext): Promise { - const lockfile = context.lockfile; - - if (!lockfile) { - throw new Error('No package-lock.json found.'); - } - - const installSize = await context.fs.getInstallSize(); - const root = lockfile.root; - const stats: Stats = { - name: root.name, - version: root.version, - installSize, - dependencyCount: { - production: root.dependencies.length, - development: root.devDependencies.length, - cjs: 0, - esm: 0, - duplicate: 0 - } - }; - return stats; -} - -function exportOutput( - stats: Stats, - duplicateDependencies: Map -) { - const messages: Message[] = []; - if (duplicateDependencies.size === 0) { - return {stats, messages}; - } - - stats.dependencyCount.duplicate = duplicateDependencies.size; - - for (const duplicate of duplicateDependencies.values()) { - const severityColor = colors.green; - let message = `${severityColor('[duplicate dependency]')} ${colors.bold(duplicate[0].name)} has ${duplicate.length} installed versions:`; - - for (const version of duplicate) { - message += `\n${colors.yellow(version.version)} via the following ${version.parents.length} package(s) ${colors.blue(version.parents.join(', '))}`; - } - - const suggestions = generateSuggestionsForDuplicate(duplicate); - - if (suggestions.length > 0) { - message += '\nšŸ’” Suggestions'; - for (const suggestion of suggestions) { - message += `${colors.gray(suggestion)}`; - } - } - message += '\n'; - messages.push({ - message, - severity: 'warning', - score: 0 - }); - } - - return {stats, messages}; -} - -/** - * Generates suggestions for resolving duplicates - */ -function generateSuggestionsForDuplicate( - resolvedDependencies: ResolvedDependency[] -): string[] { - const suggestions: string[] = []; - - // Find the package version with the most parents - const mostCommonVersion = resolvedDependencies.sort( - (a, b) => b.parents.length - a.parents.length - )[0]; - - if (mostCommonVersion?.parents.length > 1) { - suggestions.push( - `\n- Consider standardizing on version ${mostCommonVersion.version} as this version is the most commonly used.` - ); - } - - // Suggest checking for newer versions of consuming packages - suggestions.push( - `\n- Consider upgrading consuming packages as this may resolve this duplicate version.` - ); - - return suggestions; -} - -/** - * Computes a map of package names to their versions - * @param lockfile - */ -function resolveDependencies( - lockfile: ParsedLockFile -): Map { - const resolvedDependencies: Map = new Map(); - for (const pkg of lockfile.packages) { - const entry: ResolvedDependency = { - name: pkg.name, - version: pkg.version, - parents: [] - }; - if (!resolvedDependencies.has(pkg.name)) { - resolvedDependencies.set(pkg.name, [entry]); - } else { - const packageEntries = resolvedDependencies.get(pkg.name); - if (!packageEntries?.some((x) => x.version === pkg.version)) { - packageEntries?.push(entry); - } - } - } - return resolvedDependencies; -} - -/** - * Compute all the parent packages that use each duplicate dependency - * @param lockfile - * @param duplicateDependencies - */ -async function computeParents( - lockfile: ParsedLockFile, - duplicateDependencies: Map -) { - const visitorFn: VisitorFn = (node, parent, _path) => { - if (!duplicateDependencies.has(node.name) || !parent) { - return; - } - const resolvedDependencies = duplicateDependencies.get(node.name); - if (!resolvedDependencies) { - return; - } - - //get the correct version - const version = resolvedDependencies.find( - (x) => x.version === node.version - ); - if (!version) { - return; - } - - const parentPath = `${parent.name}@${parent.version}`; - if (version.parents.includes(parentPath)) { - return; - } - - version.parents.push(parentPath); - }; - const visitor = { - dependency: visitorFn, - devDependency: visitorFn, - optionalDependency: visitorFn - }; - - traverse(lockfile.root, visitor); -} - -/** - * Find duplicate packages and output suggested fixes - * @param context - * @returns - */ -export async function runDependencyAnalysisNEW( - context: AnalysisContext -): Promise { - const lockfile = context.lockfile; - - if (!lockfile) { - throw new Error('No package-lock.json found.'); - } - - const stats: Stats = await getInitialStats(context); - - const resolvedDependencies = resolveDependencies(lockfile); - const duplicateDependencies: Map = new Map(); - for (const [packageName, dependencies] of resolvedDependencies) { - if (dependencies.length <= 1) { - continue; - } - duplicateDependencies.set(packageName, dependencies); - } - - await computeParents(lockfile, duplicateDependencies); - - return exportOutput(stats, duplicateDependencies); -} diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts new file mode 100644 index 0000000..4165b48 --- /dev/null +++ b/src/analyze/duplicate-dependencies.ts @@ -0,0 +1,162 @@ +import colors from 'picocolors'; +import {ParsedLockFile, traverse, VisitorFn} from 'lockparse'; +import {AnalysisContext, Message, ReportPluginResult} from '../types.js'; + +type Version = { + name: string; + version: string; + parents: string[]; +}; + +/** + * Outputs packages with duplicate versions and suggest possible fixes + * @param context + */ +export async function runDuplicateDependencyAnalysis( + context: AnalysisContext +): Promise { + const lockfile = context.lockfile; + + if (!lockfile) { + throw new Error('No package-lock.json found.'); + } + + const duplicateDependencies = resolveDuplicateDependencies(lockfile); + await computeParents(lockfile, duplicateDependencies); + return exportOutput(duplicateDependencies); +} + +/** + * Computes a map of package names to their unique versions and then finds the packages with multiple versions + * @param lockfile + */ +function resolveDuplicateDependencies( + lockfile: ParsedLockFile +): Map { + const resolvedDependencies: Map = new Map(); + for (const pkg of lockfile.packages) { + const entry: Version = { + name: pkg.name, + version: pkg.version, + parents: [] + }; + if (!resolvedDependencies.has(pkg.name)) { + resolvedDependencies.set(pkg.name, [entry]); + } else { + const packageEntries = resolvedDependencies.get(pkg.name); + if (!packageEntries?.some((x) => x.version === pkg.version)) { + packageEntries?.push(entry); + } + } + } + + // find all the packages that have more than one version + const duplicateDependencies: Map = new Map(); + for (const [packageName, versions] of resolvedDependencies) { + if (versions.length <= 1) { + continue; + } + duplicateDependencies.set(packageName, versions); + } + return duplicateDependencies; +} + +/** + * Compute all the parent packages that use each duplicate dependency + * @param lockfile + * @param duplicateDependencies + */ +async function computeParents( + lockfile: ParsedLockFile, + duplicateDependencies: Map +) { + const visitorFn: VisitorFn = (node, parent, _path) => { + if (!duplicateDependencies.has(node.name) || !parent) { + return; + } + const resolvedVersions = duplicateDependencies.get(node.name); + if (!resolvedVersions) { + return; + } + + //get the correct version + const version = resolvedVersions.find((x) => x.version === node.version); + if (!version) { + return; + } + + const parentPath = `${parent.name}@${parent.version}`; + if (version.parents.includes(parentPath)) { + return; + } + + version.parents.push(parentPath); + }; + const visitor = { + dependency: visitorFn, + devDependency: visitorFn, + optionalDependency: visitorFn + }; + + traverse(lockfile.root, visitor); +} + +function exportOutput(duplicateDependencies: Map) { + const messages: Message[] = []; + if (duplicateDependencies.size === 0) { + return {messages}; + } + + for (const [packageName, duplicate] of duplicateDependencies) { + const severityColor = colors.green; + let message = `${severityColor('[duplicate dependency]')} ${colors.bold(packageName)} has ${duplicate.length} installed versions:`; + + for (const version of duplicate) { + message += `\n${colors.yellow(version.version)} via the following ${version.parents.length} package(s) ${colors.blue(version.parents.join(', '))}`; + } + + const suggestions = generateSuggestionsForDuplicate(duplicate); + + if (suggestions.length > 0) { + message += '\nšŸ’” Suggestions'; + for (const suggestion of suggestions) { + message += `${colors.gray(suggestion)}`; + } + } + message += '\n'; + messages.push({ + message, + severity: 'warning', + score: 0 + }); + } + + return {messages}; +} + +/** + * Generates suggestions for resolving duplicates + */ +function generateSuggestionsForDuplicate( + resolvedVersions: Version[] +): string[] { + const suggestions: string[] = []; + + // Find the package version with the most parents + const mostCommonVersion = resolvedVersions.sort( + (a, b) => b.parents.length - a.parents.length + )[0]; + + if (mostCommonVersion?.parents.length > 1) { + suggestions.push( + `\n- Consider standardizing on version ${mostCommonVersion.version} as this version is the most commonly used.` + ); + } + + // Suggest checking for newer versions of consuming packages + suggestions.push( + `\n- Consider upgrading consuming packages as this may resolve this duplicate version.` + ); + + return suggestions; +} diff --git a/src/analyze/report.ts b/src/analyze/report.ts index 5e81cd6..fb8670d 100644 --- a/src/analyze/report.ts +++ b/src/analyze/report.ts @@ -12,19 +12,17 @@ import type { } from '../types.js'; import {runPublint} from './publint.js'; import {runReplacements} from './replacements.js'; -import { - runDependencyAnalysis, - runDependencyAnalysisNEW -} from './dependencies.js'; +import {runDependencyAnalysis} from './dependencies.js'; import {runPlugins} from '../plugin-runner.js'; import {getPackageJson, detectLockfile} from '../utils/package-json.js'; import {parse as parseLockfile} from 'lockparse'; +import {runDuplicateDependencyAnalysis} from './duplicate-dependencies.js'; const plugins: ReportPlugin[] = [ runPublint, runReplacements, runDependencyAnalysis, - runDependencyAnalysisNEW + runDuplicateDependencyAnalysis ]; async function computeInfo(fileSystem: FileSystem) { From e203aad28fed5e43ac03ba2f2c3d5e4250c451c5 Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 14 Jan 2026 10:12:49 +0000 Subject: [PATCH 4/7] remove unused property --- src/analyze/duplicate-dependencies.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index 4165b48..0223ec8 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -3,7 +3,6 @@ import {ParsedLockFile, traverse, VisitorFn} from 'lockparse'; import {AnalysisContext, Message, ReportPluginResult} from '../types.js'; type Version = { - name: string; version: string; parents: string[]; }; @@ -36,7 +35,6 @@ function resolveDuplicateDependencies( const resolvedDependencies: Map = new Map(); for (const pkg of lockfile.packages) { const entry: Version = { - name: pkg.name, version: pkg.version, parents: [] }; From 5ce694ff23e04eea334df19b89b5959b172aa39e Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 14 Jan 2026 10:21:57 +0000 Subject: [PATCH 5/7] tidy up --- src/analyze/duplicate-dependencies.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index 0223ec8..5f73323 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -26,7 +26,8 @@ export async function runDuplicateDependencyAnalysis( } /** - * Computes a map of package names to their unique versions and then finds the packages with multiple versions + * Computes a map of package names to their unique versions using the lock file + * It returns just the packages with multiple versions * @param lockfile */ function resolveDuplicateDependencies( @@ -42,8 +43,11 @@ function resolveDuplicateDependencies( resolvedDependencies.set(pkg.name, [entry]); } else { const packageEntries = resolvedDependencies.get(pkg.name); - if (!packageEntries?.some((x) => x.version === pkg.version)) { - packageEntries?.push(entry); + if ( + packageEntries && + !packageEntries.some((x) => x.version === pkg.version) + ) { + packageEntries.push(entry); } } } @@ -77,7 +81,7 @@ async function computeParents( return; } - //get the correct version + // get the correct version const version = resolvedVersions.find((x) => x.version === node.version); if (!version) { return; From b3ff44dc28dd6eb6356f7b7d53ecb6a83528077a Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 14 Jan 2026 10:39:25 +0000 Subject: [PATCH 6/7] bit more tidying --- src/analyze/duplicate-dependencies.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index 5f73323..c2d1f15 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -2,10 +2,10 @@ import colors from 'picocolors'; import {ParsedLockFile, traverse, VisitorFn} from 'lockparse'; import {AnalysisContext, Message, ReportPluginResult} from '../types.js'; -type Version = { +interface Version { version: string; parents: string[]; -}; +} /** * Outputs packages with duplicate versions and suggest possible fixes From 5d9f6660aa614d577371b4444dad6c7691f3759b Mon Sep 17 00:00:00 2001 From: laurathackray Date: Wed, 14 Jan 2026 10:40:19 +0000 Subject: [PATCH 7/7] make error more general --- src/analyze/duplicate-dependencies.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/analyze/duplicate-dependencies.ts b/src/analyze/duplicate-dependencies.ts index c2d1f15..1343b34 100644 --- a/src/analyze/duplicate-dependencies.ts +++ b/src/analyze/duplicate-dependencies.ts @@ -17,7 +17,7 @@ export async function runDuplicateDependencyAnalysis( const lockfile = context.lockfile; if (!lockfile) { - throw new Error('No package-lock.json found.'); + throw new Error('No lock file found.'); } const duplicateDependencies = resolveDuplicateDependencies(lockfile);