diff --git a/lib/config/presets/npm/index.ts b/lib/config/presets/npm/index.ts index 959582869459131fcc8adb825e983514031c13d5..6c9b5cc508d51ae542a900d73d668d7a564d48f0 100644 --- a/lib/config/presets/npm/index.ts +++ b/lib/config/presets/npm/index.ts @@ -1,5 +1,8 @@ import { logger } from '../../../logger'; -import { resolvePackage } from '../../../modules/datasource/npm/npmrc'; +import { + resolvePackageUrl, + resolveRegistryUrl, +} from '../../../modules/datasource/npm/npmrc'; import type { NpmResponse } from '../../../modules/datasource/npm/types'; import { Http } from '../../../util/http'; import type { Preset, PresetConfig } from '../types'; @@ -19,7 +22,8 @@ export async function getPreset({ }: PresetConfig): Promise<Preset> { let dep; try { - const { packageUrl } = resolvePackage(pkg); + const registryUrl = resolveRegistryUrl(pkg); + const packageUrl = resolvePackageUrl(registryUrl, pkg); // istanbul ignore if if (!packageUrl.startsWith('https://registry.npmjs.org/')) { logger.warn( diff --git a/lib/modules/datasource/index.ts b/lib/modules/datasource/index.ts index e2fd5fa40db8a77f9b7989716a4db71b4ce39de0..3a65c3c9e3fb5a8b8e3650b040061b85cbf782a5 100644 --- a/lib/modules/datasource/index.ts +++ b/lib/modules/datasource/index.ts @@ -11,6 +11,8 @@ import { trimTrailingSlash } from '../../util/url'; import * as allVersioning from '../versioning'; import datasources from './api'; import { addMetaData } from './metadata'; +import { setNpmrc } from './npm'; +import { resolveRegistryUrl } from './npm/npmrc'; import type { DatasourceApi, DigestConfig, @@ -239,15 +241,24 @@ async function fetchReleases( config: GetReleasesInternalConfig ): Promise<ReleaseResult | null> { const { datasource: datasourceName } = config; + let { registryUrls } = config; if (!datasourceName || getDatasourceFor(datasourceName) === undefined) { logger.warn('Unknown datasource: ' + datasourceName); return null; } + if (datasourceName === 'npm') { + if (is.string(config.npmrc)) { + setNpmrc(config.npmrc); + } + if (!is.nonEmptyArray(registryUrls)) { + registryUrls = [resolveRegistryUrl(config.packageName)]; + } + } const datasource = getDatasourceFor(datasourceName); - const registryUrls = resolveRegistryUrls( + registryUrls = resolveRegistryUrls( datasource, config.defaultRegistryUrls, - config.registryUrls + registryUrls ); let dep: ReleaseResult = null; const registryStrategy = datasource.registryStrategy || 'hunt'; diff --git a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap index acf789ba8696b5df352ea3e364e29f29ba0dbca8..6228c47ed26395bd5789342e62d5f9c50b4c6848 100644 --- a/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap +++ b/lib/modules/datasource/npm/__snapshots__/index.spec.ts.snap @@ -19,7 +19,7 @@ Array [ exports[`modules/datasource/npm/index should fetch package info from custom registry 1`] = ` Object { "name": "foobar", - "registryUrl": "https://npm.mycustomregistry.com/", + "registryUrl": "https://npm.mycustomregistry.com", "releases": Array [ Object { "releaseTimestamp": "2018-05-06T05:21:53.000Z", @@ -561,7 +561,7 @@ Array [ exports[`modules/datasource/npm/index should use host rules by baseUrl if provided 1`] = ` Object { "name": "foobar", - "registryUrl": "https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry/", + "registryUrl": "https://npm.mycustomregistry.com/_packaging/mycustomregistry/npm/registry", "releases": Array [ Object { "releaseTimestamp": "2018-05-06T05:21:53.000Z", @@ -600,7 +600,7 @@ Array [ exports[`modules/datasource/npm/index should use host rules by hostName if provided 1`] = ` Object { "name": "foobar", - "registryUrl": "https://npm.mycustomregistry.com/", + "registryUrl": "https://npm.mycustomregistry.com", "releases": Array [ Object { "releaseTimestamp": "2018-05-06T05:21:53.000Z", diff --git a/lib/modules/datasource/npm/get.spec.ts b/lib/modules/datasource/npm/get.spec.ts index ea0d27d83ad955ab7874e61daa7bde08f11e6a15..1c3dce29bef4ea7c886488f1285c5a64e8735f9a 100644 --- a/lib/modules/datasource/npm/get.spec.ts +++ b/lib/modules/datasource/npm/get.spec.ts @@ -3,7 +3,7 @@ import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as hostRules from '../../../util/host-rules'; import { Http } from '../../../util/http'; import { getDependency, resetMemCache } from './get'; -import { setNpmrc } from './npmrc'; +import { resolveRegistryUrl, setNpmrc } from './npmrc'; function getPath(s = ''): string { const [x] = s.split('\n'); @@ -44,7 +44,8 @@ describe('modules/datasource/npm/get', () => { .reply(200, { name: '@myco/test' }); setNpmrc(npmrc); - await getDependency(http, '@myco/test'); + const registryUrl = resolveRegistryUrl('@myco/test'); + await getDependency(http, registryUrl, '@myco/test'); const trace = httpMock.getTrace(); expect(trace[0].headers.authorization).toBe('Bearer XXX'); @@ -76,7 +77,8 @@ describe('modules/datasource/npm/get', () => { .get(getPath(npmrc)) .reply(200, { name: '@myco/test' }); setNpmrc(npmrc); - await getDependency(http, '@myco/test'); + const registryUrl = resolveRegistryUrl('@myco/test'); + await getDependency(http, registryUrl, '@myco/test'); const trace = httpMock.getTrace(); expect(trace[0].headers.authorization).toBe('Basic dGVzdDp0ZXN0'); @@ -99,7 +101,8 @@ describe('modules/datasource/npm/get', () => { .get(getPath(npmrc)) .reply(200, { name: '@myco/test' }); setNpmrc(npmrc); - await getDependency(http, '@myco/test'); + const registryUrl = resolveRegistryUrl('@myco/test'); + await getDependency(http, registryUrl, '@myco/test'); const trace = httpMock.getTrace(); expect(trace[0].headers.authorization).toBeUndefined(); @@ -125,7 +128,8 @@ describe('modules/datasource/npm/get', () => { .get(getPath(npmrc)) .reply(200, { name: '@myco/test' }); setNpmrc(npmrc); - await getDependency(http, '@myco/test'); + const registryUrl = resolveRegistryUrl('@myco/test'); + await getDependency(http, registryUrl, '@myco/test'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -146,7 +150,8 @@ describe('modules/datasource/npm/get', () => { .get('/renovate') .reply(200, { name: 'renovate' }); setNpmrc(npmrc); - await getDependency(http, 'renovate'); + const registryUrl = resolveRegistryUrl('renovate'); + await getDependency(http, registryUrl, 'renovate'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -168,7 +173,8 @@ describe('modules/datasource/npm/get', () => { .get('/renovate') .reply(200, { name: 'renovate' }); setNpmrc(npmrc); - await getDependency(http, 'renovate'); + const registryUrl = resolveRegistryUrl('renovate'); + await getDependency(http, registryUrl, 'renovate'); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -181,7 +187,8 @@ describe('modules/datasource/npm/get', () => { .scope('https://test.org') .get('/none') .reply(200, { name: '@myco/test' }); - expect(await getDependency(http, 'none')).toBeNull(); + let registryUrl = resolveRegistryUrl('none'); + expect(await getDependency(http, registryUrl, 'none')).toBeNull(); httpMock .scope('https://test.org') @@ -192,7 +199,8 @@ describe('modules/datasource/npm/get', () => { versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, }); - expect(await getDependency(http, '@myco/test')).toBeDefined(); + registryUrl = resolveRegistryUrl('@myco/test'); + expect(await getDependency(http, registryUrl, '@myco/test')).toBeDefined(); httpMock .scope('https://test.org') @@ -202,34 +210,40 @@ describe('modules/datasource/npm/get', () => { versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, }); - expect(await getDependency(http, '@myco/test2')).toBeDefined(); + registryUrl = resolveRegistryUrl('@myco/test2'); + expect(await getDependency(http, registryUrl, '@myco/test2')).toBeDefined(); httpMock.scope('https://test.org').get('/error-401').reply(401); - expect(await getDependency(http, 'error-401')).toBeNull(); + registryUrl = resolveRegistryUrl('error-401'); + expect(await getDependency(http, registryUrl, 'error-401')).toBeNull(); httpMock.scope('https://test.org').get('/error-402').reply(402); - expect(await getDependency(http, 'error-402')).toBeNull(); + registryUrl = resolveRegistryUrl('error-402'); + expect(await getDependency(http, registryUrl, 'error-402')).toBeNull(); httpMock.scope('https://test.org').get('/error-404').reply(404); - expect(await getDependency(http, 'error-404')).toBeNull(); + registryUrl = resolveRegistryUrl('error-404'); + expect(await getDependency(http, registryUrl, 'error-404')).toBeNull(); httpMock.scope('https://test.org').get('/error4').reply(200, null); - expect(await getDependency(http, 'error4')).toBeNull(); + registryUrl = resolveRegistryUrl('error4'); + expect(await getDependency(http, registryUrl, 'error4')).toBeNull(); setNpmrc(); httpMock .scope('https://registry.npmjs.org') .get('/npm-parse-error') .reply(200, 'not-a-json'); - await expect(getDependency(http, 'npm-parse-error')).rejects.toThrow( - ExternalHostError - ); + registryUrl = resolveRegistryUrl('npm-parse-error'); + await expect( + getDependency(http, registryUrl, 'npm-parse-error') + ).rejects.toThrow(ExternalHostError); httpMock .scope('https://registry.npmjs.org') .get('/npm-error-402') .reply(402); - expect(await getDependency(http, 'npm-error-402')).toBeNull(); + expect(await getDependency(http, registryUrl, 'npm-error-402')).toBeNull(); expect(httpMock.getTrace()).toMatchSnapshot(); }); @@ -249,8 +263,8 @@ describe('modules/datasource/npm/get', () => { versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, }); - - const dep = await getDependency(http, '@neutrinojs/react'); + const registryUrl = resolveRegistryUrl('@neutrinojs/react'); + const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino'); expect(dep.sourceDirectory).toBe('packages/react'); @@ -300,8 +314,8 @@ describe('modules/datasource/npm/get', () => { }, 'dist-tags': { latest: '2.0.0' }, }); - - const dep = await getDependency(http, 'vue'); + const registryUrl = resolveRegistryUrl('vue'); + const dep = await getDependency(http, registryUrl, 'vue'); expect(dep.sourceUrl).toBe('https://github.com/vuejs/vue.git'); expect(dep.releases[0].sourceUrl).toBeUndefined(); @@ -326,8 +340,8 @@ describe('modules/datasource/npm/get', () => { versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, }); - - const dep = await getDependency(http, '@neutrinojs/react'); + const registryUrl = resolveRegistryUrl('@neutrinojs/react'); + const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); expect(dep.sourceUrl).toBe('https://github.com/neutrinojs/neutrino'); expect(dep.sourceDirectory).toBe('packages/foo'); @@ -364,8 +378,8 @@ describe('modules/datasource/npm/get', () => { versions: { '1.0.0': {} }, 'dist-tags': { latest: '1.0.0' }, }); - - const dep = await getDependency(http, '@neutrinojs/react'); + const registryUrl = resolveRegistryUrl('@neutrinojs/react'); + const dep = await getDependency(http, registryUrl, '@neutrinojs/react'); expect(dep.sourceUrl).toBe( 'https://bitbucket.org/neutrinojs/neutrino/tree/master/packages/react' diff --git a/lib/modules/datasource/npm/get.ts b/lib/modules/datasource/npm/get.ts index 1c20f1d65b6efa22f8bb949fef9880eb1ab0b214..44d1c7aa93b81c66c6b0b285f3bf7d6f05dd84b7 100644 --- a/lib/modules/datasource/npm/get.ts +++ b/lib/modules/datasource/npm/get.ts @@ -4,8 +4,8 @@ import { logger } from '../../../logger'; import { ExternalHostError } from '../../../types/errors/external-host-error'; import * as packageCache from '../../../util/cache/package'; import type { Http } from '../../../util/http'; +import { joinUrlParts } from '../../../util/url'; import { id } from './common'; -import { resolvePackage } from './npmrc'; import type { NpmDependency, NpmRelease, NpmResponse } from './types'; let memcache: Record<string, string> = {}; @@ -52,6 +52,7 @@ function getPackageSource(repository: any): PackageSource { export async function getDependency( http: Http, + registryUrl: string, packageName: string ): Promise<NpmDependency | null> { logger.trace(`npm.getDependency(${packageName})`); @@ -62,7 +63,7 @@ export async function getDependency( return JSON.parse(memcache[packageName]) as NpmDependency; } - const { packageUrl, registryUrl } = resolvePackage(packageName); + const packageUrl = joinUrlParts(registryUrl, packageName.replace('/', '%2F')); // Now check the persistent cache const cacheNamespace = 'datasource-npm'; diff --git a/lib/modules/datasource/npm/index.ts b/lib/modules/datasource/npm/index.ts index 1e22feecc512381e042c708006ea4467663e74da..cab5ed593207529ca5ddd8a2ad8ddd9dda2b6f90 100644 --- a/lib/modules/datasource/npm/index.ts +++ b/lib/modules/datasource/npm/index.ts @@ -1,20 +1,18 @@ -import is from '@sindresorhus/is'; import * as npmVersioning from '../../versioning/npm'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import { id } from './common'; import { getDependency } from './get'; -import { setNpmrc } from './npmrc'; export { resetMemCache, resetCache } from './get'; export { setNpmrc } from './npmrc'; -export const customRegistrySupport = false; - export class NpmDatasource extends Datasource { static readonly id = id; - override readonly customRegistrySupport = false; + override readonly customRegistrySupport = true; + + override readonly registryStrategy = 'first'; override readonly defaultVersioning = npmVersioning.id; @@ -24,12 +22,9 @@ export class NpmDatasource extends Datasource { async getReleases({ packageName, - npmrc, + registryUrl, }: GetReleasesConfig): Promise<ReleaseResult | null> { - if (is.string(npmrc)) { - setNpmrc(npmrc); - } - const res = await getDependency(this.http, packageName); + const res = await getDependency(this.http, registryUrl, packageName); if (res) { res.tags = res['dist-tags']; delete res['dist-tags']; diff --git a/lib/modules/datasource/npm/npmrc.ts b/lib/modules/datasource/npm/npmrc.ts index c542a45cf850ee03878d06167a4415bff2d156fd..b18cf8242d432a0dae606038af4f10cc52fb4464 100644 --- a/lib/modules/datasource/npm/npmrc.ts +++ b/lib/modules/datasource/npm/npmrc.ts @@ -10,7 +10,7 @@ import { regEx } from '../../../util/regex'; import { fromBase64 } from '../../../util/string'; import { ensureTrailingSlash, validateUrl } from '../../../util/url'; import { defaultRegistryUrls } from './common'; -import type { NpmrcRules, PackageResolution } from './types'; +import type { NpmrcRules } from './types'; let npmrc: Record<string, any> = {}; let npmrcRaw = ''; @@ -179,11 +179,12 @@ export function resolveRegistryUrl(packageName: string): string { return registryUrl; } -export function resolvePackage(packageName: string): PackageResolution { - const registryUrl = resolveRegistryUrl(packageName); - const packageUrl = url.resolve( +export function resolvePackageUrl( + registryUrl: string, + packageName: string +): string { + return url.resolve( ensureTrailingSlash(registryUrl), encodeURIComponent(packageName).replace(regEx(/^%40/), '@') ); - return { packageUrl, registryUrl }; } diff --git a/lib/modules/datasource/npm/types.ts b/lib/modules/datasource/npm/types.ts index ac79e35562f36abdc061ee3f555014d944224bb2..10b600a75b0e3924456101f889e2e2e38dc5dd0f 100644 --- a/lib/modules/datasource/npm/types.ts +++ b/lib/modules/datasource/npm/types.ts @@ -47,8 +47,3 @@ export interface NpmDependency extends ReleaseResult { } export type Npmrc = Record<string, any>; - -export interface PackageResolution { - packageUrl: string; - registryUrl: string; -} diff --git a/lib/modules/datasource/types.ts b/lib/modules/datasource/types.ts index 98362b3ec7059fffb551f165c5952beeee708205..318835e8ae6b09aa144a828997465dc46ca471a6 100644 --- a/lib/modules/datasource/types.ts +++ b/lib/modules/datasource/types.ts @@ -18,7 +18,6 @@ export interface DigestConfig { } export interface GetReleasesConfig { - npmrc?: string; packageName: string; registryUrl?: string; }