mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-11 14:36:25 +00:00
feat(npm): try auth recursive (#5698)
This commit is contained in:
parent
6cbd4a7743
commit
707d35db30
7 changed files with 156 additions and 44 deletions
|
@ -1,6 +1,6 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`api/npm should fetch package info from custom registry 1`] = `
|
exports[`datasource/npm/index should fetch package info from custom registry 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -28,7 +28,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should fetch package info from npm 1`] = `
|
exports[`datasource/npm/index should fetch package info from npm 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -56,7 +56,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should handle foobar 1`] = `
|
exports[`datasource/npm/index should handle foobar 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -84,7 +84,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should handle no time 1`] = `
|
exports[`datasource/npm/index should handle no time 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -110,7 +110,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should parse repo url (string) 1`] = `
|
exports[`datasource/npm/index should parse repo url (string) 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": undefined,
|
"homepage": undefined,
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -131,7 +131,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should parse repo url 1`] = `
|
exports[`datasource/npm/index should parse repo url 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": undefined,
|
"homepage": undefined,
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -152,7 +152,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should replace any environment variable in npmrc 1`] = `
|
exports[`datasource/npm/index should replace any environment variable in npmrc 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -180,7 +180,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should return deprecated 1`] = `
|
exports[`datasource/npm/index should return deprecated 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"deprecationMessage": "On registry \`https://registry.npmjs.org/\`, the \\"latest\\" version (v0.0.2) of dependency \`foobar\` has the following deprecation notice:
|
"deprecationMessage": "On registry \`https://registry.npmjs.org/\`, the \\"latest\\" version (v0.0.2) of dependency \`foobar\` has the following deprecation notice:
|
||||||
|
|
||||||
|
@ -214,7 +214,7 @@ Marking the latest version of an npm package as deprecated results in the entire
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should return deprecated 2`] = `
|
exports[`datasource/npm/index should return deprecated 2`] = `
|
||||||
"On registry \`https://registry.npmjs.org/\`, the \\"latest\\" version (v0.0.2) of dependency \`foobar\` has the following deprecation notice:
|
"On registry \`https://registry.npmjs.org/\`, the \\"latest\\" version (v0.0.2) of dependency \`foobar\` has the following deprecation notice:
|
||||||
|
|
||||||
\`This is deprecated\`
|
\`This is deprecated\`
|
||||||
|
@ -222,7 +222,7 @@ exports[`api/npm should return deprecated 2`] = `
|
||||||
Marking 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."
|
Marking 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."
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should send an authorization header if provided 1`] = `
|
exports[`datasource/npm/index should send an authorization header if provided 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -250,7 +250,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should use NPM_TOKEN if provided 1`] = `
|
exports[`datasource/npm/index should use NPM_TOKEN if provided 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
@ -278,7 +278,7 @@ Object {
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`api/npm should use default registry if missing from npmrc 1`] = `
|
exports[`datasource/npm/index should use default registry if missing from npmrc 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"homepage": "https://github.com/renovateapp/dummy",
|
"homepage": "https://github.com/renovateapp/dummy",
|
||||||
"latestVersion": "0.0.1",
|
"latestVersion": "0.0.1",
|
||||||
|
|
128
lib/datasource/npm/get.spec.ts
Normal file
128
lib/datasource/npm/get.spec.ts
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
import got from 'got';
|
||||||
|
import { getName, partial } from '../../../test/util';
|
||||||
|
import { getDependency, resetMemCache } from './get';
|
||||||
|
import { setNpmrc } from './npmrc';
|
||||||
|
import * as _got from '../../util/got';
|
||||||
|
import { DatasourceError } from '../common';
|
||||||
|
|
||||||
|
jest.mock('../../util/got');
|
||||||
|
|
||||||
|
const api: jest.Mock<got.GotPromise<object>> = _got.api as never;
|
||||||
|
|
||||||
|
describe(getName(__filename), () => {
|
||||||
|
function mock(body: object): void {
|
||||||
|
api.mockResolvedValueOnce(
|
||||||
|
partial<got.Response<object>>({ body })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
resetMemCache();
|
||||||
|
mock({ body: { name: '@myco/test' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('has bearer auth', () => {
|
||||||
|
const configs = [
|
||||||
|
`registry=https://test.org\n//test.org/:_authToken=XXX`,
|
||||||
|
`registry=https://test.org/sub\n//test.org/:_authToken=XXX`,
|
||||||
|
`registry=https://test.org/sub\n//test.org/sub/:_authToken=XXX`,
|
||||||
|
`registry=https://test.org/sub\n_authToken=XXX`,
|
||||||
|
`registry=https://test.org\n_authToken=XXX`,
|
||||||
|
`registry=https://test.org\n_authToken=XXX`,
|
||||||
|
`@myco:registry=https://test.org\n//test.org/:_authToken=XXX`,
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(configs)('%p', async npmrc => {
|
||||||
|
expect.assertions(1);
|
||||||
|
setNpmrc(npmrc);
|
||||||
|
await getDependency('@myco/test', 0);
|
||||||
|
|
||||||
|
expect(api.mock.calls[0][1].headers.authorization).toEqual('Bearer XXX');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('has basic auth', () => {
|
||||||
|
const configs = [
|
||||||
|
`registry=https://test.org\n//test.org/:_auth=dGVzdDp0ZXN0`,
|
||||||
|
`registry=https://test.org\n//test.org/:username=test\n//test.org/:_password=dGVzdA==`,
|
||||||
|
`registry=https://test.org/sub\n//test.org/:_auth=dGVzdDp0ZXN0`,
|
||||||
|
`registry=https://test.org/sub\n//test.org/sub/:_auth=dGVzdDp0ZXN0`,
|
||||||
|
`registry=https://test.org/sub\n_auth=dGVzdDp0ZXN0`,
|
||||||
|
`registry=https://test.org\n_auth=dGVzdDp0ZXN0`,
|
||||||
|
`registry=https://test.org\n_auth=dGVzdDp0ZXN0`,
|
||||||
|
`@myco:registry=https://test.org\n//test.org/:_auth=dGVzdDp0ZXN0`,
|
||||||
|
`@myco:registry=https://test.org\n_auth=dGVzdDp0ZXN0`,
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(configs)('%p', async npmrc => {
|
||||||
|
expect.assertions(1);
|
||||||
|
setNpmrc(npmrc);
|
||||||
|
await getDependency('@myco/test', 0);
|
||||||
|
|
||||||
|
expect(api.mock.calls[0][1].headers.authorization).toEqual(
|
||||||
|
'Basic dGVzdDp0ZXN0'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('no auth', () => {
|
||||||
|
const configs = [
|
||||||
|
`@myco:registry=https://test.org\n_authToken=XXX`,
|
||||||
|
`@myco:registry=https://test.org\n//test.org/sub/:_authToken=XXX`,
|
||||||
|
`@myco:registry=https://test.org\n//test.org/sub/:_auth=dGVzdDp0ZXN0`,
|
||||||
|
`@myco:registry=https://test.org`,
|
||||||
|
`registry=https://test.org`,
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(configs)('%p', async npmrc => {
|
||||||
|
expect.assertions(1);
|
||||||
|
setNpmrc(npmrc);
|
||||||
|
await getDependency('@myco/test', 0);
|
||||||
|
|
||||||
|
expect(api.mock.calls[0][1].headers.authorization).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('cover all paths', async () => {
|
||||||
|
expect.assertions(9);
|
||||||
|
|
||||||
|
setNpmrc('registry=https://test.org\n_authToken=XXX');
|
||||||
|
|
||||||
|
expect(await getDependency('none', 0)).toBeNull();
|
||||||
|
|
||||||
|
mock({
|
||||||
|
name: '@myco/test',
|
||||||
|
repository: {},
|
||||||
|
versions: { '1.0.0': {} },
|
||||||
|
'dist-tags': { latest: '1.0.0' },
|
||||||
|
});
|
||||||
|
expect(await getDependency('@myco/test', 0)).toBeDefined();
|
||||||
|
|
||||||
|
mock({
|
||||||
|
name: '@myco/test2',
|
||||||
|
versions: { '1.0.0': {} },
|
||||||
|
'dist-tags': { latest: '1.0.0' },
|
||||||
|
});
|
||||||
|
expect(await getDependency('@myco/test2', 0)).toBeDefined();
|
||||||
|
|
||||||
|
api.mockRejectedValueOnce({ statusCode: 401 });
|
||||||
|
expect(await getDependency('error-401', 0)).toBeNull();
|
||||||
|
api.mockRejectedValueOnce({ statusCode: 402 });
|
||||||
|
expect(await getDependency('error-402', 0)).toBeNull();
|
||||||
|
api.mockRejectedValueOnce({ statusCode: 404 });
|
||||||
|
expect(await getDependency('error-404', 0)).toBeNull();
|
||||||
|
|
||||||
|
api.mockRejectedValueOnce({});
|
||||||
|
expect(await getDependency('error4', 0)).toBeNull();
|
||||||
|
|
||||||
|
setNpmrc();
|
||||||
|
api.mockRejectedValueOnce({ name: 'ParseError', body: 'parse-error' });
|
||||||
|
await expect(getDependency('npm-parse-error', 0)).rejects.toThrow(
|
||||||
|
DatasourceError
|
||||||
|
);
|
||||||
|
|
||||||
|
api.mockRejectedValueOnce({ statusCode: 402 });
|
||||||
|
expect(await getDependency('npm-error-402', 0)).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
|
@ -3,7 +3,6 @@ import moment from 'moment';
|
||||||
import url from 'url';
|
import url from 'url';
|
||||||
import getRegistryUrl from 'registry-auth-token/registry-url';
|
import getRegistryUrl from 'registry-auth-token/registry-url';
|
||||||
import registryAuthToken from 'registry-auth-token';
|
import registryAuthToken from 'registry-auth-token';
|
||||||
import isBase64 from 'validator/lib/isBase64';
|
|
||||||
import { OutgoingHttpHeaders } from 'http';
|
import { OutgoingHttpHeaders } from 'http';
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
|
@ -75,15 +74,19 @@ export async function getDependency(
|
||||||
if (cachedResult) {
|
if (cachedResult) {
|
||||||
return cachedResult;
|
return cachedResult;
|
||||||
}
|
}
|
||||||
const authInfo = registryAuthToken(regUrl, { npmrc });
|
|
||||||
const headers: OutgoingHttpHeaders = {};
|
const headers: OutgoingHttpHeaders = {};
|
||||||
|
let authInfo = registryAuthToken(regUrl, { npmrc, recursive: true });
|
||||||
|
|
||||||
|
if (
|
||||||
|
!authInfo &&
|
||||||
|
npmrc &&
|
||||||
|
npmrc._authToken &&
|
||||||
|
regUrl.replace(/\/?$/, '/') === npmrc.registry?.replace(/\/?$/, '/')
|
||||||
|
) {
|
||||||
|
authInfo = { type: 'Bearer', token: npmrc._authToken };
|
||||||
|
}
|
||||||
|
|
||||||
if (authInfo && authInfo.type && authInfo.token) {
|
if (authInfo && authInfo.type && authInfo.token) {
|
||||||
// istanbul ignore if
|
|
||||||
if (npmrc && npmrc.massagedAuth && isBase64(authInfo.token)) {
|
|
||||||
logger.debug('Massaging authorization type to Basic');
|
|
||||||
authInfo.type = 'Basic';
|
|
||||||
}
|
|
||||||
headers.authorization = `${authInfo.type} ${authInfo.token}`;
|
headers.authorization = `${authInfo.type} ${authInfo.token}`;
|
||||||
logger.trace(
|
logger.trace(
|
||||||
{ token: maskToken(authInfo.token), npmName: packageName },
|
{ token: maskToken(authInfo.token), npmName: packageName },
|
||||||
|
@ -115,7 +118,6 @@ export async function getDependency(
|
||||||
useCache,
|
useCache,
|
||||||
};
|
};
|
||||||
const raw = await got(pkgUrl, opts);
|
const raw = await got(pkgUrl, opts);
|
||||||
// istanbul ignore if
|
|
||||||
if (retries < 3) {
|
if (retries < 3) {
|
||||||
logger.debug({ pkgUrl, retries }, 'Recovered from npm error');
|
logger.debug({ pkgUrl, retries }, 'Recovered from npm error');
|
||||||
}
|
}
|
||||||
|
@ -207,7 +209,6 @@ export async function getDependency(
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// istanbul ignore if
|
|
||||||
if (err.statusCode === 402) {
|
if (err.statusCode === 402) {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
{
|
{
|
||||||
|
@ -231,7 +232,6 @@ export async function getDependency(
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (uri.host === 'registry.npmjs.org') {
|
if (uri.host === 'registry.npmjs.org') {
|
||||||
// istanbul ignore if
|
|
||||||
if (
|
if (
|
||||||
(err.name === 'ParseError' ||
|
(err.name === 'ParseError' ||
|
||||||
err.code === 'ECONNRESET' ||
|
err.code === 'ECONNRESET' ||
|
||||||
|
@ -242,13 +242,11 @@ export async function getDependency(
|
||||||
await delay(5000);
|
await delay(5000);
|
||||||
return getDependency(packageName, retries - 1);
|
return getDependency(packageName, retries - 1);
|
||||||
}
|
}
|
||||||
// istanbul ignore if
|
|
||||||
if (err.name === 'ParseError' && err.body) {
|
if (err.name === 'ParseError' && err.body) {
|
||||||
err.body = 'err.body deleted by Renovate';
|
err.body = 'err.body deleted by Renovate';
|
||||||
}
|
}
|
||||||
throw new DatasourceError(err);
|
throw new DatasourceError(err);
|
||||||
}
|
}
|
||||||
// istanbul ignore next
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,12 @@ import nock from 'nock';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as npm from '.';
|
import * as npm from '.';
|
||||||
import { DATASOURCE_FAILURE } from '../../constants/error-messages';
|
import { DATASOURCE_FAILURE } from '../../constants/error-messages';
|
||||||
|
import { getName } from '../../../test/util';
|
||||||
|
|
||||||
jest.mock('registry-auth-token');
|
jest.mock('registry-auth-token');
|
||||||
jest.mock('delay');
|
jest.mock('delay');
|
||||||
|
|
||||||
const registryAuthToken: any = _registryAuthToken;
|
const registryAuthToken: jest.Mock<_registryAuthToken.NpmCredentials> = _registryAuthToken as never;
|
||||||
let npmResponse: any;
|
let npmResponse: any;
|
||||||
|
|
||||||
function getRelease(
|
function getRelease(
|
||||||
|
@ -19,13 +20,14 @@ function getRelease(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('api/npm', () => {
|
describe(getName(__filename), () => {
|
||||||
delete process.env.NPM_TOKEN;
|
delete process.env.NPM_TOKEN;
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
jest.resetAllMocks();
|
jest.resetAllMocks();
|
||||||
global.repoCache = {};
|
global.repoCache = {};
|
||||||
global.trustLevel = 'low';
|
global.trustLevel = 'low';
|
||||||
npm.resetCache();
|
npm.resetCache();
|
||||||
|
npm.setNpmrc();
|
||||||
npmResponse = {
|
npmResponse = {
|
||||||
name: 'foobar',
|
name: 'foobar',
|
||||||
versions: {
|
versions: {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import is from '@sindresorhus/is';
|
import is from '@sindresorhus/is';
|
||||||
import ini from 'ini';
|
import ini from 'ini';
|
||||||
import isBase64 from 'validator/lib/isBase64';
|
|
||||||
import { logger } from '../../logger';
|
import { logger } from '../../logger';
|
||||||
|
|
||||||
let npmrc: Record<string, any> | null = null;
|
let npmrc: Record<string, any> | null = null;
|
||||||
|
@ -36,7 +35,6 @@ export function setNpmrc(input?: string): void {
|
||||||
npmrcRaw = input;
|
npmrcRaw = input;
|
||||||
logger.debug('Setting npmrc');
|
logger.debug('Setting npmrc');
|
||||||
npmrc = ini.parse(input.replace(/\\n/g, '\n'));
|
npmrc = ini.parse(input.replace(/\\n/g, '\n'));
|
||||||
// massage _auth to _authToken
|
|
||||||
for (const [key, val] of Object.entries(npmrc)) {
|
for (const [key, val] of Object.entries(npmrc)) {
|
||||||
// istanbul ignore if
|
// istanbul ignore if
|
||||||
if (
|
if (
|
||||||
|
@ -52,21 +50,13 @@ export function setNpmrc(input?: string): void {
|
||||||
npmrc = existingNpmrc;
|
npmrc = existingNpmrc;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (key !== '_auth' && key.endsWith('_auth') && isBase64(val)) {
|
|
||||||
logger.debug('Massaging _auth to _authToken');
|
|
||||||
npmrc[key + 'Token'] = val;
|
|
||||||
npmrc.massagedAuth = true;
|
|
||||||
delete npmrc[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (global.trustLevel !== 'high') {
|
if (global.trustLevel !== 'high') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (const key in npmrc) {
|
for (const key of Object.keys(npmrc)) {
|
||||||
if (Object.prototype.hasOwnProperty.call(npmrc, key)) {
|
|
||||||
npmrc[key] = envReplace(npmrc[key]);
|
npmrc[key] = envReplace(npmrc[key]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if (npmrc) {
|
} else if (npmrc) {
|
||||||
logger.debug('Resetting npmrc');
|
logger.debug('Resetting npmrc');
|
||||||
npmrc = null;
|
npmrc = null;
|
||||||
|
|
|
@ -165,7 +165,6 @@
|
||||||
"traverse": "0.6.6",
|
"traverse": "0.6.6",
|
||||||
"upath": "1.2.0",
|
"upath": "1.2.0",
|
||||||
"validate-npm-package-name": "3.0.0",
|
"validate-npm-package-name": "3.0.0",
|
||||||
"validator": "12.2.0",
|
|
||||||
"www-authenticate": "0.6.2",
|
"www-authenticate": "0.6.2",
|
||||||
"xmldoc": "1.1.2",
|
"xmldoc": "1.1.2",
|
||||||
"yarn": "1.22.4",
|
"yarn": "1.22.4",
|
||||||
|
|
|
@ -9867,11 +9867,6 @@ validate-npm-package-name@3.0.0, validate-npm-package-name@^3.0.0, validate-npm-
|
||||||
dependencies:
|
dependencies:
|
||||||
builtins "^1.0.3"
|
builtins "^1.0.3"
|
||||||
|
|
||||||
validator@12.2.0:
|
|
||||||
version "12.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/validator/-/validator-12.2.0.tgz#660d47e96267033fd070096c3b1a6f2db4380a0a"
|
|
||||||
integrity sha512-jJfE/DW6tIK1Ek8nCfNFqt8Wb3nzMoAbocBF6/Icgg1ZFSBpObdnwVY2jQj6qUqzhx5jc71fpvBWyLGO7Xl+nQ==
|
|
||||||
|
|
||||||
verror@1.10.0:
|
verror@1.10.0:
|
||||||
version "1.10.0"
|
version "1.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
|
|
Loading…
Reference in a new issue