diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts index a983e37223a9f96688e2b9b8fde61de4bcb1c202..c9e7e4baf127ab0476ca191c6c6cbd18876bd9e2 100644 --- a/lib/datasource/npm/get.spec.ts +++ b/lib/datasource/npm/get.spec.ts @@ -269,7 +269,45 @@ describe('datasource/npm/get', () => { `); }); - it('warns about repo directory override', async () => { + it('handles mixed sourceUrls in releases', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + httpMock + .scope('https://test.org') + .get('/vue') + .reply(200, { + name: 'vue', + repository: { + type: 'git', + url: 'https://github.com/vuejs/vue.git', + }, + versions: { + '2.0.0': { + repository: { + type: 'git', + url: 'https://github.com/vuejs/vue.git', + }, + }, + '3.0.0': { + repository: { + type: 'git', + url: 'https://github.com/vuejs/vue-next.git', + }, + }, + }, + 'dist-tags': { latest: '2.0.0' }, + }); + + const dep = await getDependency('vue'); + + expect(dep.sourceUrl).toBe('https://github.com/vuejs/vue.git'); + expect(dep.releases[0].sourceUrl).toBeUndefined(); + expect(dep.releases[1].sourceUrl).toEqual( + 'https://github.com/vuejs/vue-next.git' + ); + }); + + it('does not override sourceDirectory', async () => { setNpmrc('registry=https://test.org\n_authToken=XXX'); httpMock @@ -280,7 +318,7 @@ describe('datasource/npm/get', () => { repository: { type: 'git', url: 'https://github.com/neutrinojs/neutrino/tree/master/packages/react', - directory: 'path/to/directory', + directory: 'packages/foo', }, versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, @@ -289,7 +327,7 @@ describe('datasource/npm/get', () => { const dep = await getDependency('@neutrinojs/react'); expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino'); - expect(dep.sourceDirectory).toBe('packages/react'); + expect(dep.sourceDirectory).toBe('packages/foo'); expect(httpMock.getTrace()).toMatchInlineSnapshot(` Array [ diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts index 98ee0b7388b72e0a3edf42be5d31183dc7e88f68..d9de4aedc91b03b71a6549a48bab532e30452a07 100644 --- a/lib/datasource/npm/get.ts +++ b/lib/datasource/npm/get.ts @@ -21,6 +21,37 @@ export function resetCache(): void { resetMemCache(); } +interface PackageSource { + sourceUrl?: string; + sourceDirectory?: string; +} + +function getPackageSource(repository: any): PackageSource { + const res: PackageSource = {}; + if (repository) { + if (is.nonEmptyString(repository)) { + res.sourceUrl = repository; + } else if (is.nonEmptyString(repository.url)) { + res.sourceUrl = repository.url; + } + if (is.nonEmptyString(repository.directory)) { + res.sourceDirectory = repository.directory; + } + const sourceUrlCopy = `${res.sourceUrl}`; + const sourceUrlSplit: string[] = sourceUrlCopy.split('/'); + if (sourceUrlSplit.length > 7 && sourceUrlSplit[2] === 'github.com') { + // Massage the repository URL for non-compliant strings for github (see issue #4610) + // Remove the non-compliant segments of path, so the URL looks like "<scheme>://<domain>/<vendor>/<repo>" + // and add directory to the repository + res.sourceUrl = sourceUrlSplit.slice(0, 5).join('/'); + res.sourceDirectory ||= sourceUrlSplit + .slice(7, sourceUrlSplit.length) + .join('/'); + } + } + return res; +} + export async function getDependency( packageName: string ): Promise<NpmDependency | null> { @@ -70,49 +101,19 @@ export async function getDependency( res.repository = res.repository || latestVersion.repository; res.homepage = res.homepage || latestVersion.homepage; - // Determine repository URL - let sourceUrl: string; - - if (res.repository) { - if (is.string(res.repository)) { - sourceUrl = res.repository; - } else if (res.repository.url) { - sourceUrl = res.repository.url; - } - } + const { sourceUrl, sourceDirectory } = getPackageSource(res.repository); // Simplify response before caching and returning const dep: NpmDependency = { name: res.name, homepage: res.homepage, sourceUrl, + sourceDirectory, versions: {}, releases: null, 'dist-tags': res['dist-tags'], registryUrl, }; - if (res.repository?.directory) { - dep.sourceDirectory = res.repository.directory; - } - - // Massage the repository URL for non-compliant strings for github (see issue #4610) - // Remove the non-compliant segments of path, so the URL looks like "<scheme>://<domain>/<vendor>/<repo>" - // and add directory to the repository - const sourceUrlCopy = `${sourceUrl}`; - const sourceUrlSplit: string[] = sourceUrlCopy.split('/'); - - if (sourceUrlSplit.length > 7 && sourceUrlSplit[2] === 'github.com') { - if (dep.sourceDirectory) { - logger.debug( - { dependency: packageName }, - `Ambiguity: dependency has the repository URL path and repository/directory set at once; have to override repository/directory` - ); - } - dep.sourceUrl = sourceUrlSplit.slice(0, 5).join('/'); - dep.sourceDirectory = sourceUrlSplit - .slice(7, sourceUrlSplit.length) - .join('/'); - } if (latestVersion.deprecated) { dep.deprecationMessage = `On registry \`${registryUrl}\`, the "latest" version of dependency \`${packageName}\` has the following deprecation notice:\n\n\`${latestVersion.deprecated}\`\n\nMarking the latest version of an npm package as deprecated results in the entire package being considered deprecated, so contact the package author you think this is a mistake.`; @@ -131,6 +132,16 @@ export async function getDependency( if (res.versions[version].deprecated) { release.isDeprecated = true; } + const source = getPackageSource(res.versions[version].repository); + if (source.sourceUrl && source.sourceUrl !== dep.sourceUrl) { + release.sourceUrl = source.sourceUrl; + } + if ( + source.sourceDirectory && + source.sourceDirectory !== dep.sourceDirectory + ) { + release.sourceDirectory = source.sourceDirectory; + } return release; }); logger.trace({ dep }, 'dep'); diff --git a/lib/datasource/types.ts b/lib/datasource/types.ts index 098a17274259c01e8a9047b0266f384fe60b868d..1d271a8a9ad17ddd5d495363b7f6a39636ebb515 100644 --- a/lib/datasource/types.ts +++ b/lib/datasource/types.ts @@ -44,6 +44,8 @@ export interface Release { dependencies?: Record<string, string>; devDependencies?: Record<string, string>; registryUrl?: string; + sourceUrl?: string; + sourceDirectory?: string; } export interface ReleaseResult {