diff --git a/.changeset/silent-berries-cry.md b/.changeset/silent-berries-cry.md new file mode 100644 index 0000000000..5b28cd5695 --- /dev/null +++ b/.changeset/silent-berries-cry.md @@ -0,0 +1,5 @@ +--- +"@pnpm/license-scanner": patch +--- + +Output license information for a deterministic version when multiple versions of a single package are depended on diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a7f032cc0d..d353a3075d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5203,6 +5203,9 @@ importers: ramda: specifier: npm:@pnpm/ramda@0.28.1 version: /@pnpm/ramda@0.28.1 + semver: + specifier: ^7.5.4 + version: 7.5.4 devDependencies: '@pnpm/constants': specifier: workspace:* @@ -5213,6 +5216,9 @@ importers: '@types/ramda': specifier: 0.28.20 version: 0.28.20 + '@types/semver': + specifier: ^7.5.3 + version: 7.5.3 reviewing/list: dependencies: @@ -11385,6 +11391,7 @@ packages: /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + requiresBuild: true dev: false /error-ex@1.3.2: @@ -17899,6 +17906,7 @@ time: /@pnpm/which@3.0.1: '2023-05-14T22:08:27.551Z' /@reflink/reflink@0.1.16: '2024-01-02T17:41:22.200Z' /@types/byline@4.2.36: '2023-11-07T00:13:37.410Z' + /@types/semver@7.5.3: '2023-09-25T14:19:37.089Z' /@types/table@6.0.0: '2020-09-17T17:56:44.787Z' /@yarnpkg/core@4.0.2: '2023-11-14T09:21:22.875Z' /@yarnpkg/extensions@2.0.0: '2023-10-22T16:50:53.141Z' @@ -17911,3 +17919,4 @@ time: /fuse-native@2.2.6: '2020-06-03T19:26:36.838Z' /node-gyp@9.4.1: '2023-10-27T17:30:56.146Z' /safe-execa@0.1.2: '2022-07-18T01:09:17.517Z' + /semver@7.5.4: '2023-07-07T21:10:32.589Z' diff --git a/reviewing/license-scanner/package.json b/reviewing/license-scanner/package.json index b6039ff319..26ffabfd23 100644 --- a/reviewing/license-scanner/package.json +++ b/reviewing/license-scanner/package.json @@ -47,12 +47,14 @@ "load-json-file": "^6.2.0", "p-limit": "^3.1.0", "path-absolute": "^1.0.1", - "ramda": "npm:@pnpm/ramda@0.28.1" + "ramda": "npm:@pnpm/ramda@0.28.1", + "semver": "^7.5.4" }, "devDependencies": { "@pnpm/constants": "workspace:*", "@pnpm/license-scanner": "workspace:*", - "@types/ramda": "0.28.20" + "@types/ramda": "0.28.20", + "@types/semver": "^7.5.3" }, "funding": "https://opencollective.com/pnpm", "exports": { diff --git a/reviewing/license-scanner/src/licenses.ts b/reviewing/license-scanner/src/licenses.ts index 33ff4a6394..48409c7557 100644 --- a/reviewing/license-scanner/src/licenses.ts +++ b/reviewing/license-scanner/src/licenses.ts @@ -11,6 +11,7 @@ import { type LicenseNode, lockfileToLicenseNodeTree, } from './lockfileToLicenseNodeTree' +import { gt } from 'semver' export interface LicensePackage { belongsTo: DependenciesField @@ -101,7 +102,15 @@ export async function findDependencyLicenses (opts: { const dependenciesOfNode = getDependenciesFromLicenseNode(licenseNode) dependenciesOfNode.forEach((dependencyNode) => { - licensePackages.set(dependencyNode.name, dependencyNode) + const existingVersion = licensePackages.get(dependencyNode.name)?.version + // This just ensures that we use a deterministic version of each dependency, + // in the event that multiple versions are depended on. + if ( + existingVersion === undefined || + gt(dependencyNode.version, existingVersion) + ) { + licensePackages.set(dependencyNode.name, dependencyNode) + } }) } diff --git a/reviewing/license-scanner/test/licenses.spec.ts b/reviewing/license-scanner/test/licenses.spec.ts index f2a784ca58..9850d2343d 100644 --- a/reviewing/license-scanner/test/licenses.spec.ts +++ b/reviewing/license-scanner/test/licenses.spec.ts @@ -174,4 +174,103 @@ describe('licences', () => { }, ] as LicensePackage[]) }) + + test('findDependencyLicenses uses most updated version of each package', async () => { + const lockfile: Lockfile = { + importers: { + '.': { + dependencies: { + foo: '1.0.0', + bar: '1.0.1', + baz: '2.0.0', + }, + specifiers: { + foo: '^1.0.0', + bar: '^1.0.1', + baz: '^2.0.0', + }, + }, + }, + lockfileVersion: LOCKFILE_VERSION, + packages: { + '/bar@1.0.1': { + resolution: { + integrity: 'bar1-integrity', + }, + }, + '/bar@1.0.0': { + resolution: { + integrity: 'bar2-integrity', + }, + }, + '/baz@2.0.1': { + resolution: { + integrity: 'baz1-integrity', + }, + }, + '/baz@2.0.0': { + resolution: { + integrity: 'baz2-integrity', + }, + }, + '/foo@1.0.0': { + dependencies: { + bar: '1.0.0', + baz: '2.0.1', + }, + resolution: { + integrity: 'foo-integrity', + }, + }, + }, + } + + const licensePackages = await findDependencyLicenses({ + lockfileDir: '/opt/pnpm', + manifest: {} as ProjectManifest, + virtualStoreDir: '/.pnpm', + registries: {} as Registries, + wantedLockfile: lockfile, + storeDir: '/opt/.pnpm', + }) + + expect(licensePackages).toEqual([ + { + belongsTo: 'dependencies', + description: 'Package Description', + version: '1.0.1', + name: 'bar', + license: 'MIT', + licenseContents: undefined, + author: 'Package Author', + homepage: 'Homepage', + repository: 'Repository', + path: '/path/to/package/bar@1.0.1/node_modules', + }, + { + belongsTo: 'dependencies', + description: 'Package Description', + version: '2.0.1', + name: 'baz', + license: 'Unknown', + licenseContents: 'The MIT License', + author: 'Package Author', + homepage: 'Homepage', + repository: 'Repository', + path: '/path/to/package/baz@2.0.1/node_modules', + }, + { + belongsTo: 'dependencies', + description: 'Package Description', + version: '1.0.0', + name: 'foo', + license: 'Unknown', + licenseContents: 'The MIT License', + author: 'Package Author', + homepage: 'Homepage', + repository: 'Repository', + path: '/path/to/package/foo@1.0.0/node_modules', + }, + ] as LicensePackage[]) + }) }) diff --git a/reviewing/plugin-commands-licenses/test/__snapshots__/index.ts.snap b/reviewing/plugin-commands-licenses/test/__snapshots__/index.ts.snap index 856eb9f13a..7032e54ae8 100644 --- a/reviewing/plugin-commands-licenses/test/__snapshots__/index.ts.snap +++ b/reviewing/plugin-commands-licenses/test/__snapshots__/index.ts.snap @@ -64,7 +64,7 @@ exports[`pnpm licenses: should correctly read LICENSE file with executable file │ │ │ https://github.com/feross/safe-buffer │ ├──────────────────────────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ string_decoder │ MIT │ The string_decoder module from Node core │ -│ │ │ https://github.com/rvagg/string_decoder │ +│ │ │ https://github.com/nodejs/string_decoder │ ├──────────────────────────────┼─────────┼────────────────────────────────────────────────────────────────────────────────────────────────────────┤ │ string.fromcodepoint │ MIT │ Mathias Bynens │ │ │ │ A robust & optimized \`String.fromCodePoint\` polyfill, based on the ECMAScript 6 specification. │