From 4aa5cdc721f9248cd1d27537eaa9528c38769751 Mon Sep 17 00:00:00 2001 From: Oleg Krivtsov <olegkrivtsov@gmail.com> Date: Thu, 23 Sep 2021 13:48:46 +0700 Subject: [PATCH] feat(datasource/npm): massage non compliant npm repo strings (#11776) --- lib/datasource/npm/get.spec.ts | 117 +++++++++++++++++++++++++++++++++ lib/datasource/npm/get.ts | 21 ++++++ 2 files changed, 138 insertions(+) diff --git a/lib/datasource/npm/get.spec.ts b/lib/datasource/npm/get.spec.ts index 9486d34075..a983e37223 100644 --- a/lib/datasource/npm/get.spec.ts +++ b/lib/datasource/npm/get.spec.ts @@ -230,4 +230,121 @@ describe('datasource/npm/get', () => { expect(httpMock.getTrace()).toMatchSnapshot(); }); + + it('massages non-compliant repository urls', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + httpMock + .scope('https://test.org') + .get('/@neutrinojs%2Freact') + .reply(200, { + name: '@neutrinojs/react', + repository: { + type: 'git', + url: 'https://github.com/neutrinojs/neutrino/tree/master/packages/react', + }, + versions: { '1.0.0': {} }, + 'dist-tags': { latest: '1.0.0' }, + }); + + const dep = await getDependency('@neutrinojs/react'); + + expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino'); + expect(dep.sourceDirectory).toBe('packages/react'); + + expect(httpMock.getTrace()).toMatchInlineSnapshot(` + Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer XXX", + "host": "test.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://test.org/@neutrinojs%2Freact", + }, + ] + `); + }); + + it('warns about repo directory override', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + httpMock + .scope('https://test.org') + .get('/@neutrinojs%2Freact') + .reply(200, { + name: '@neutrinojs/react', + repository: { + type: 'git', + url: 'https://github.com/neutrinojs/neutrino/tree/master/packages/react', + directory: 'path/to/directory', + }, + versions: { '1.0.0': {} }, + 'dist-tags': { latest: '1.0.0' }, + }); + + const dep = await getDependency('@neutrinojs/react'); + + expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino'); + expect(dep.sourceDirectory).toBe('packages/react'); + + expect(httpMock.getTrace()).toMatchInlineSnapshot(` + Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer XXX", + "host": "test.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://test.org/@neutrinojs%2Freact", + }, + ] + `); + }); + + it('does not massage non-github non-compliant repository urls', async () => { + setNpmrc('registry=https://test.org\n_authToken=XXX'); + + httpMock + .scope('https://test.org') + .get('/@neutrinojs%2Freact') + .reply(200, { + name: '@neutrinojs/react', + repository: { + type: 'git', + url: 'https://bitbucket.org/neutrinojs/neutrino/tree/master/packages/react', + }, + versions: { '1.0.0': {} }, + 'dist-tags': { latest: '1.0.0' }, + }); + + const dep = await getDependency('@neutrinojs/react'); + + expect(dep.sourceUrl).toBe( + 'https://bitbucket.org/neutrinojs/neutrino/tree/master/packages/react' + ); + expect(dep.sourceDirectory).toBeUndefined(); + + expect(httpMock.getTrace()).toMatchInlineSnapshot(` + Array [ + Object { + "headers": Object { + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "authorization": "Bearer XXX", + "host": "test.org", + "user-agent": "RenovateBot/0.0.0-semantic-release (https://github.com/renovatebot/renovate)", + }, + "method": "GET", + "url": "https://test.org/@neutrinojs%2Freact", + }, + ] + `); + }); }); diff --git a/lib/datasource/npm/get.ts b/lib/datasource/npm/get.ts index cfbb1764e4..98ee0b7388 100644 --- a/lib/datasource/npm/get.ts +++ b/lib/datasource/npm/get.ts @@ -80,6 +80,7 @@ export async function getDependency( sourceUrl = res.repository.url; } } + // Simplify response before caching and returning const dep: NpmDependency = { name: res.name, @@ -93,6 +94,26 @@ export async function getDependency( 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.`; dep.deprecationSource = id; -- GitLab