mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-30 22:34:10 -05:00
refactor: migrate request to node-fetch at hooks package (#1946)
* refactor(hooks): new structure for notifications * chore: fix build * chore: add debug * chore: add changeset
This commit is contained in:
parent
8c730c0694
commit
65cb26cf31
19 changed files with 278 additions and 456 deletions
4
.babelrc
4
.babelrc
|
@ -2,6 +2,10 @@
|
||||||
"presets": [ [
|
"presets": [ [
|
||||||
"@babel/env",
|
"@babel/env",
|
||||||
{
|
{
|
||||||
|
"useBuiltIns": "usage",
|
||||||
|
"corejs": {
|
||||||
|
"version": 3, "proposals": true
|
||||||
|
},
|
||||||
"targets": {
|
"targets": {
|
||||||
"node": 10
|
"node": 10
|
||||||
}
|
}
|
||||||
|
|
6
.changeset/late-parents-act.md
Normal file
6
.changeset/late-parents-act.md
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
---
|
||||||
|
'@verdaccio/hooks': patch
|
||||||
|
'@verdaccio/proxy': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
refactor: migrate request to node-fetch at hooks package
|
|
@ -1,4 +1,4 @@
|
||||||
FROM node:12.18.3-alpine as builder
|
FROM node:12.18.4-alpine as builder
|
||||||
|
|
||||||
ENV NODE_ENV=development \
|
ENV NODE_ENV=development \
|
||||||
VERDACCIO_BUILD_REGISTRY=https://registry.verdaccio.org
|
VERDACCIO_BUILD_REGISTRY=https://registry.verdaccio.org
|
||||||
|
@ -12,14 +12,14 @@ RUN apk --no-cache add openssl ca-certificates wget && \
|
||||||
WORKDIR /opt/verdaccio-build
|
WORKDIR /opt/verdaccio-build
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN npm -g i pnpm@latest && \
|
RUN npm -g i pnpm@5.5.12 && \
|
||||||
pnpm config set registry $VERDACCIO_BUILD_REGISTRY && \
|
pnpm config set registry $VERDACCIO_BUILD_REGISTRY && \
|
||||||
pnpm recursive install --frozen-lockfile --ignore-scripts && \
|
pnpm recursive install --frozen-lockfile --ignore-scripts && \
|
||||||
pnpm run build && \
|
pnpm run build && \
|
||||||
pnpm run lint && \
|
pnpm run lint && \
|
||||||
pnpm install --prod --ignore-scripts
|
pnpm install --prod --ignore-scripts
|
||||||
|
|
||||||
FROM node:12.18.3-alpine
|
FROM node:12.18.4-alpine
|
||||||
LABEL maintainer="https://github.com/verdaccio/verdaccio"
|
LABEL maintainer="https://github.com/verdaccio/verdaccio"
|
||||||
|
|
||||||
ENV VERDACCIO_APPDIR=/opt/verdaccio \
|
ENV VERDACCIO_APPDIR=/opt/verdaccio \
|
||||||
|
|
|
@ -28,6 +28,7 @@
|
||||||
"@verdaccio/loaders": "workspace:5.0.0-alpha.0",
|
"@verdaccio/loaders": "workspace:5.0.0-alpha.0",
|
||||||
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
|
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
|
||||||
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
|
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
|
||||||
|
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"express": "4.17.1",
|
"express": "4.17.1",
|
||||||
"lodash": "4.17.15"
|
"lodash": "4.17.15"
|
||||||
|
|
|
@ -15,7 +15,7 @@
|
||||||
"homepage": "https://verdaccio.org",
|
"homepage": "https://verdaccio.org",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@verdaccio/types": "workspace:10.0.0-beta"
|
"@verdaccio/types": "workspace:*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./build",
|
"clean": "rimraf ./build",
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
"access": "public"
|
"access": "public"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@verdaccio/types": "^9.7.2"
|
"@verdaccio/types": "workspace:*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./build",
|
"clean": "rimraf ./build",
|
||||||
|
|
5
packages/core/types/index.d.ts
vendored
5
packages/core/types/index.d.ts
vendored
|
@ -218,6 +218,7 @@ declare module '@verdaccio/types' {
|
||||||
max_users: number;
|
max_users: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FUTURE: rename to Notification
|
||||||
interface Notifications {
|
interface Notifications {
|
||||||
method: string;
|
method: string;
|
||||||
packagePattern: RegExp;
|
packagePattern: RegExp;
|
||||||
|
@ -227,6 +228,8 @@ declare module '@verdaccio/types' {
|
||||||
headers: Headers;
|
headers: Headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Notification = Notifications;
|
||||||
|
|
||||||
interface ConfigFile {
|
interface ConfigFile {
|
||||||
storage: string;
|
storage: string;
|
||||||
plugins: string;
|
plugins: string;
|
||||||
|
@ -364,7 +367,9 @@ declare module '@verdaccio/types' {
|
||||||
https_proxy?: string;
|
https_proxy?: string;
|
||||||
no_proxy?: string;
|
no_proxy?: string;
|
||||||
max_body_size?: string;
|
max_body_size?: string;
|
||||||
|
// deprecated
|
||||||
notifications?: Notifications;
|
notifications?: Notifications;
|
||||||
|
notify?: Notifications | Notifications[];
|
||||||
middlewares?: any;
|
middlewares?: any;
|
||||||
filters?: any;
|
filters?: any;
|
||||||
checkSecretKey(token: string): string;
|
checkSecretKey(token: string): string;
|
||||||
|
|
|
@ -15,18 +15,20 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://verdaccio.org",
|
"homepage": "https://verdaccio.org",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"debug": "^4.2.0",
|
||||||
"@verdaccio/commons-api": "workspace:*",
|
"@verdaccio/commons-api": "workspace:*",
|
||||||
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
|
"@verdaccio/logger": "workspace:5.0.0-alpha.0",
|
||||||
"handlebars": "4.5.3",
|
"handlebars": "4.5.3",
|
||||||
"lodash": "^4.17.20",
|
"request": "2.87.0",
|
||||||
"request": "2.87.0"
|
"node-fetch": "^2.6.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
|
"@verdaccio/auth": "workspace:5.0.0-alpha.0",
|
||||||
"@verdaccio/config": "workspace:5.0.0-alpha.0",
|
"@verdaccio/config": "workspace:5.0.0-alpha.0",
|
||||||
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
|
"@verdaccio/dev-commons": "workspace:5.0.0-alpha.0",
|
||||||
"@verdaccio/types": "workspace:*",
|
"@verdaccio/types": "workspace:*",
|
||||||
"@verdaccio/utils": "workspace:5.0.0-alpha.0"
|
"@verdaccio/utils": "workspace:5.0.0-alpha.0",
|
||||||
|
"nock": "^13.0.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./build",
|
"clean": "rimraf ./build",
|
||||||
|
|
|
@ -1,23 +1,38 @@
|
||||||
import isNil from 'lodash/isNil';
|
import fetch, { RequestInit } from 'node-fetch';
|
||||||
import request, { RequiredUriUrl } from 'request';
|
import buildDebug from 'debug';
|
||||||
|
|
||||||
import { logger } from '@verdaccio/logger';
|
import { logger } from '@verdaccio/logger';
|
||||||
import { HTTP_STATUS } from '@verdaccio/commons-api';
|
import { HTTP_STATUS } from '@verdaccio/commons-api';
|
||||||
|
|
||||||
export function notifyRequest(options: RequiredUriUrl, content): Promise<any | Error> {
|
const debug = buildDebug('verdaccio:hooks:request');
|
||||||
return new Promise((resolve, reject): void => {
|
export type NotifyRequestOptions = RequestInit;
|
||||||
request(options, function (err, response, body): void {
|
|
||||||
if (err || response.statusCode >= HTTP_STATUS.BAD_REQUEST) {
|
export async function notifyRequest(url: string, options: NotifyRequestOptions): Promise<boolean> {
|
||||||
const errorMessage = isNil(err) ? response.body : err.message;
|
let response;
|
||||||
logger.error({ errorMessage }, 'notify service has thrown an error: @{errorMessage}');
|
try {
|
||||||
reject(errorMessage);
|
debug('uri %o', url);
|
||||||
}
|
response = await fetch(url, {
|
||||||
logger.info({ content }, 'A notification has been shipped: @{content}');
|
body: JSON.stringify(options.body),
|
||||||
if (isNil(body) === false) {
|
method: 'POST',
|
||||||
logger.debug({ body }, ' body: @{body}');
|
headers: { 'Content-Type': 'application/json' },
|
||||||
resolve(body);
|
|
||||||
}
|
|
||||||
reject(Error('body is missing'));
|
|
||||||
});
|
});
|
||||||
});
|
debug('response.status %o', response.status);
|
||||||
|
const body = await response.json();
|
||||||
|
if (response.status >= HTTP_STATUS.BAD_REQUEST) {
|
||||||
|
throw new Error(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
{ content: options.body },
|
||||||
|
'The notification @{content} has been successfully dispatched'
|
||||||
|
);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
debug('request error %o', err);
|
||||||
|
logger.error(
|
||||||
|
{ errorMessage: err?.message },
|
||||||
|
'notify service has thrown an error: @{errorMessage}'
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,62 @@
|
||||||
import Handlebars from 'handlebars';
|
import Handlebars from 'handlebars';
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { OptionsWithUrl } from 'request';
|
import buildDebug from 'debug';
|
||||||
import { Config, Package, RemoteUser } from '@verdaccio/types';
|
import { Config, Package, RemoteUser, Notification } from '@verdaccio/types';
|
||||||
import { notifyRequest } from './notify-request';
|
import { logger } from '@verdaccio/logger';
|
||||||
|
import { notifyRequest, NotifyRequestOptions } from './notify-request';
|
||||||
|
|
||||||
|
const debug = buildDebug('verdaccio:hooks');
|
||||||
type TemplateMetadata = Package & { publishedPackage: string };
|
type TemplateMetadata = Package & { publishedPackage: string };
|
||||||
|
|
||||||
export function handleNotify(
|
export function compileTemplate(content, metadata) {
|
||||||
metadata: Package,
|
// FUTURE: multiple handlers
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let handler;
|
||||||
|
try {
|
||||||
|
if (!handler) {
|
||||||
|
debug('compile default template handler %o', content);
|
||||||
|
const template: HandlebarsTemplateDelegate = Handlebars.compile(content);
|
||||||
|
return resolve(template(metadata));
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
debug('error template handler %o', error);
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleNotify(
|
||||||
|
metadata: Partial<Package>,
|
||||||
notifyEntry,
|
notifyEntry,
|
||||||
remoteUser: RemoteUser,
|
remoteUser: Partial<RemoteUser>,
|
||||||
publishedPackage: string
|
publishedPackage: string
|
||||||
): Promise<any> | void {
|
): Promise<boolean> {
|
||||||
let regex;
|
let regex;
|
||||||
if (metadata.name && notifyEntry.packagePattern) {
|
if (metadata.name && notifyEntry.packagePattern) {
|
||||||
regex = new RegExp(notifyEntry.packagePattern, notifyEntry.packagePatternFlags || '');
|
regex = new RegExp(notifyEntry.packagePattern, notifyEntry.packagePatternFlags || '');
|
||||||
if (!regex.test(metadata.name)) {
|
if (!regex.test(metadata.name)) {
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const template: HandlebarsTemplateDelegate = Handlebars.compile(notifyEntry.content);
|
let content;
|
||||||
// don't override 'publisher' if package.json already has that
|
// FIXME: publisher is not part of the expected types metadata
|
||||||
/* eslint no-unused-vars: 0 */
|
|
||||||
/* eslint @typescript-eslint/no-unused-vars: 0 */
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (_.isNil(metadata.publisher)) {
|
if (typeof metadata?.publisher === 'undefined' || metadata?.publisher === null) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
metadata = { ...metadata, publishedPackage, publisher: { name: remoteUser.name as string } };
|
metadata = { ...metadata, publishedPackage, publisher: { name: remoteUser.name as string } };
|
||||||
|
debug('template metadata %o', metadata);
|
||||||
|
content = await compileTemplate(notifyEntry.content, metadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
const content: string = template(metadata);
|
const options: NotifyRequestOptions = {
|
||||||
|
body: JSON.stringify(content),
|
||||||
const options: OptionsWithUrl = {
|
|
||||||
body: content,
|
|
||||||
url: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// provides fallback support, it's accept an Object {} and Array of {}
|
// provides fallback support, it's accept an Object {} and Array of {}
|
||||||
if (notifyEntry.headers && _.isArray(notifyEntry.headers)) {
|
if (notifyEntry.headers && Array.isArray(notifyEntry.headers)) {
|
||||||
const header = {};
|
const header = {};
|
||||||
|
// FIXME: we can simplify this
|
||||||
notifyEntry.headers.map(function (item): void {
|
notifyEntry.headers.map(function (item): void {
|
||||||
if (Object.is(item, item)) {
|
if (Object.is(item, item)) {
|
||||||
for (const key in item) {
|
for (const key in item) {
|
||||||
|
@ -56,44 +72,67 @@ export function handleNotify(
|
||||||
options.headers = notifyEntry.headers;
|
options.headers = notifyEntry.headers;
|
||||||
}
|
}
|
||||||
|
|
||||||
options.method = notifyEntry.method;
|
if (!notifyEntry.endpoint) {
|
||||||
|
debug('error due endpoint is missing');
|
||||||
if (notifyEntry.endpoint) {
|
throw new Error('missing parameter');
|
||||||
options.url = notifyEntry.endpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return notifyRequest(options, content);
|
return notifyRequest(notifyEntry.endpoint, {
|
||||||
|
method: notifyEntry.method,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sendNotification(
|
export function sendNotification(
|
||||||
metadata: Package,
|
metadata: Partial<Package>,
|
||||||
notify: Notification,
|
notify: Notification,
|
||||||
remoteUser: RemoteUser,
|
remoteUser: Partial<RemoteUser>,
|
||||||
publishedPackage: string
|
publishedPackage: string
|
||||||
): Promise<any> {
|
): Promise<boolean> {
|
||||||
return handleNotify(metadata, notify, remoteUser, publishedPackage) as Promise<any>;
|
return handleNotify(metadata, notify, remoteUser, publishedPackage) as Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function notify(
|
export async function notify(
|
||||||
metadata: Package,
|
metadata: Partial<Package>,
|
||||||
config: Config,
|
config: Partial<Config>,
|
||||||
remoteUser: RemoteUser,
|
remoteUser: Partial<RemoteUser>,
|
||||||
publishedPackage: string
|
publishedPackage: string
|
||||||
): Promise<any> | void {
|
): Promise<boolean[]> {
|
||||||
|
debug('init send notification');
|
||||||
if (config.notify) {
|
if (config.notify) {
|
||||||
if (config.notify.content) {
|
const isSingle = Object.keys(config.notify).includes('method');
|
||||||
return sendNotification(
|
if (isSingle) {
|
||||||
metadata,
|
debug('send single notification');
|
||||||
(config.notify as unknown) as Notification,
|
try {
|
||||||
remoteUser,
|
const response = await sendNotification(
|
||||||
publishedPackage
|
metadata,
|
||||||
);
|
config.notify as Notification,
|
||||||
}
|
remoteUser,
|
||||||
// multiple notifications endpoints PR #108
|
publishedPackage
|
||||||
return Promise.all(
|
);
|
||||||
_.map(config.notify, (key) => sendNotification(metadata, key, remoteUser, publishedPackage))
|
return [response];
|
||||||
);
|
} catch {
|
||||||
}
|
debug('error on sending single notification');
|
||||||
|
return [false];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('send multiples notification');
|
||||||
|
const results = await Promise.allSettled(
|
||||||
|
Object.keys(config.notify).map((keyId: string) => {
|
||||||
|
// @ts-ignore
|
||||||
|
const item = config.notify[keyId];
|
||||||
|
debug('send item %o', item);
|
||||||
|
return sendNotification(metadata, item, remoteUser, publishedPackage);
|
||||||
|
})
|
||||||
|
).catch((error) => {
|
||||||
|
logger.error({ error }, 'notify request has failed: @error');
|
||||||
|
});
|
||||||
|
|
||||||
return Promise.resolve();
|
// @ts-ignore
|
||||||
|
return Object.keys(results).map((promiseValue) => results[promiseValue].value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug('no notifications configuration detected');
|
||||||
|
return [false];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
85
packages/hooks/test/notify-request.spec.ts
Normal file
85
packages/hooks/test/notify-request.spec.ts
Normal file
|
@ -0,0 +1,85 @@
|
||||||
|
import nock from 'nock';
|
||||||
|
import { createRemoteUser, parseConfigFile } from '@verdaccio/utils';
|
||||||
|
import { Config } from '@verdaccio/types';
|
||||||
|
import { notify } from '../src/notify';
|
||||||
|
import { parseConfigurationFile } from './__helper';
|
||||||
|
|
||||||
|
const parseConfigurationNotifyFile = (name) => {
|
||||||
|
return parseConfigurationFile(`notify/${name}`);
|
||||||
|
};
|
||||||
|
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
|
||||||
|
const singleHeaderNotificationConfig = parseConfigFile(
|
||||||
|
parseConfigurationNotifyFile('single.header.notify')
|
||||||
|
);
|
||||||
|
const packagePatternNotificationConfig = parseConfigFile(
|
||||||
|
parseConfigurationNotifyFile('single.packagePattern.notify')
|
||||||
|
);
|
||||||
|
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
|
||||||
|
|
||||||
|
const mockInfo = jest.fn();
|
||||||
|
jest.mock('@verdaccio/logger', () => ({
|
||||||
|
setup: jest.fn(),
|
||||||
|
logger: {
|
||||||
|
child: jest.fn(),
|
||||||
|
debug: jest.fn(),
|
||||||
|
trace: jest.fn(),
|
||||||
|
warn: jest.fn(),
|
||||||
|
info: () => mockInfo(),
|
||||||
|
error: jest.fn(),
|
||||||
|
fatal: jest.fn(),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const domain = 'http://slack-service';
|
||||||
|
|
||||||
|
describe('Notifications:: notifyRequest', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
nock.cleanAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when sending a empty notification', async () => {
|
||||||
|
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
|
||||||
|
|
||||||
|
const notificationResponse = await notify({}, {}, createRemoteUser('foo', []), 'bar');
|
||||||
|
expect(notificationResponse).toEqual([false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when sending a single notification', async () => {
|
||||||
|
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
|
||||||
|
|
||||||
|
const notificationResponse = await notify(
|
||||||
|
{},
|
||||||
|
singleHeaderNotificationConfig,
|
||||||
|
createRemoteUser('foo', []),
|
||||||
|
'bar'
|
||||||
|
);
|
||||||
|
expect(notificationResponse).toEqual([true]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when notification endpoint is missing', async () => {
|
||||||
|
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
|
||||||
|
const name = 'package';
|
||||||
|
const config: Partial<Config> = {
|
||||||
|
// @ts-ignore
|
||||||
|
notify: {
|
||||||
|
method: 'POST',
|
||||||
|
endpoint: undefined,
|
||||||
|
content: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const notificationResponse = await notify({ name }, config, createRemoteUser('foo', []), 'bar');
|
||||||
|
expect(notificationResponse).toEqual([false]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('when multiple notifications', async () => {
|
||||||
|
nock(domain).post('/foo?auth_token=mySecretToken').reply(200, { body: 'test' });
|
||||||
|
nock(domain).post('/foo?auth_token=mySecretToken').reply(400, {});
|
||||||
|
nock(domain)
|
||||||
|
.post('/foo?auth_token=mySecretToken')
|
||||||
|
.reply(500, { message: 'Something bad happened' });
|
||||||
|
|
||||||
|
const name = 'package';
|
||||||
|
const responses = await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
|
||||||
|
expect(responses).toEqual([true, false, false]);
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,87 +0,0 @@
|
||||||
import { parseConfigFile } from '@verdaccio/utils';
|
|
||||||
import { setup } from '@verdaccio/logger';
|
|
||||||
|
|
||||||
import { notify } from '../src';
|
|
||||||
import { notifyRequest } from '../src/notify-request';
|
|
||||||
import { parseConfigurationFile } from './__helper';
|
|
||||||
|
|
||||||
setup([]);
|
|
||||||
|
|
||||||
jest.mock('../src/notify-request', () => ({
|
|
||||||
notifyRequest: jest.fn((options, content) => Promise.resolve([options, content])),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const parseConfigurationNotifyFile = (name) => {
|
|
||||||
return parseConfigurationFile(`notify/${name}`);
|
|
||||||
};
|
|
||||||
const singleNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('single.notify'));
|
|
||||||
const singleHeaderNotificationConfig = parseConfigFile(
|
|
||||||
parseConfigurationNotifyFile('single.header.notify')
|
|
||||||
);
|
|
||||||
const packagePatternNotificationConfig = parseConfigFile(
|
|
||||||
parseConfigurationNotifyFile('single.packagePattern.notify')
|
|
||||||
);
|
|
||||||
const multiNotificationConfig = parseConfigFile(parseConfigurationNotifyFile('multiple.notify'));
|
|
||||||
|
|
||||||
describe('Notifications:: Notify', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.clearAllMocks();
|
|
||||||
});
|
|
||||||
|
|
||||||
// FUTURE: we should add some sort of health check of all props, (not implemented yet)
|
|
||||||
|
|
||||||
test('should not fails if config is not provided', async () => {
|
|
||||||
// @ts-ignore
|
|
||||||
await notify({}, {});
|
|
||||||
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should send notification', async () => {
|
|
||||||
const name = 'package';
|
|
||||||
// @ts-ignore
|
|
||||||
const response = await notify({ name }, singleNotificationConfig, { name: 'foo' }, 'bar');
|
|
||||||
const [options, content] = response;
|
|
||||||
|
|
||||||
expect(options.headers).toBeDefined();
|
|
||||||
expect(options.url).toBeDefined();
|
|
||||||
expect(options.body).toBeDefined();
|
|
||||||
expect(content).toMatch(name);
|
|
||||||
expect(response).toBeTruthy();
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should send single header notification', async () => {
|
|
||||||
// @ts-ignore
|
|
||||||
await notify({}, singleHeaderNotificationConfig, { name: 'foo' }, 'bar');
|
|
||||||
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should send multiple notification', async () => {
|
|
||||||
const name = 'package';
|
|
||||||
// @ts-ignore
|
|
||||||
await notify({ name }, multiNotificationConfig, { name: 'foo' }, 'bar');
|
|
||||||
|
|
||||||
expect(notifyRequest).toHaveBeenCalled();
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(3);
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('packagePatternFlags', () => {
|
|
||||||
test('should send single notification with packagePatternFlags', async () => {
|
|
||||||
const name = 'package';
|
|
||||||
// @ts-ignore
|
|
||||||
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
|
|
||||||
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should not match on send single notification with packagePatternFlags', async () => {
|
|
||||||
const name = 'no-mach-name';
|
|
||||||
// @ts-ignore
|
|
||||||
await notify({ name }, packagePatternNotificationConfig, { name: 'foo' }, 'bar');
|
|
||||||
|
|
||||||
expect(notifyRequest).toHaveBeenCalledTimes(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -2,15 +2,15 @@ notify:
|
||||||
'example-google-chat':
|
'example-google-chat':
|
||||||
method: POST
|
method: POST
|
||||||
headers: [{ 'Content-Type': 'application/json' }]
|
headers: [{ 'Content-Type': 'application/json' }]
|
||||||
endpoint: https://chat.googleapis.com/v1/spaces/AAAAB_TcJYs/messages?key=myKey&token=myToken
|
endpoint: http://slack-service/foo?auth_token=mySecretToken
|
||||||
content: '{"text":"New package published: `{{ name }}{{#each versions}} v{{version}}{{/each}}`"}'
|
content: '{"text":"New package published: `{{ name }}{{#each versions}} v{{version}}{{/each}}`"}'
|
||||||
'example-hipchat':
|
'example-hipchat':
|
||||||
method: POST
|
method: POST
|
||||||
headers: [{ 'Content-Type': 'application/json' }]
|
headers: [{ 'Content-Type': 'application/json' }]
|
||||||
endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
|
endpoint: http://slack-service/foo?auth_token=mySecretToken
|
||||||
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
|
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
|
||||||
'example-stride':
|
'example-stride':
|
||||||
method: POST
|
method: POST
|
||||||
headers: [{ 'Content-Type': 'application/json' }, { 'authorization': 'Bearer secretToken' }]
|
headers: [{ 'Content-Type': 'application/json' }, { 'authorization': 'Bearer secretToken' }]
|
||||||
endpoint: https://api.atlassian.com/site/{cloudId}/conversation/{conversationId}/message
|
endpoint: http://slack-service/foo?auth_token=mySecretToken
|
||||||
content: '{"body": {"version": 1,"type": "doc","content": [{"type": "paragraph","content": [{"type": "text","text": "New package published: * {{ name }}* Publisher name: * {{ publisher.name }}"}]}]}}'
|
content: '{"body": {"version": 1,"type": "doc","content": [{"type": "paragraph","content": [{"type": "text","text": "New package published: * {{ name }}* Publisher name: * {{ publisher.name }}"}]}]}}'
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
notify:
|
notify:
|
||||||
method: POST
|
method: POST
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
endpoint: https://usagge.hipchat.com/v2/room/3729485/notification?auth_token=mySecretToken
|
endpoint: http://slack-service/foo?auth_token=mySecretToken
|
||||||
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
|
content: '{"color":"green","message":"New package published: * {{ name }}*","notify":true,"message_format":"text"}'
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
import { HTTP_STATUS, API_ERROR } from '@verdaccio/dev-commons';
|
|
||||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mocks Logger Service
|
|
||||||
*/
|
|
||||||
const logger = {
|
|
||||||
logger: {
|
|
||||||
error: jest.fn(),
|
|
||||||
debug: jest.fn(),
|
|
||||||
info: jest.fn(),
|
|
||||||
},
|
|
||||||
};
|
|
||||||
jest.doMock('@verdaccio/logger', () => logger);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test Data
|
|
||||||
*/
|
|
||||||
const options = {
|
|
||||||
url: 'http://slack-service',
|
|
||||||
};
|
|
||||||
const content = 'Verdaccio@x.x.x successfully published';
|
|
||||||
|
|
||||||
describe('Notifications:: notifyRequest', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
jest.resetModules();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when notification service throws error', async () => {
|
|
||||||
jest.doMock('request', () => (options, resolver) => {
|
|
||||||
const response = {
|
|
||||||
statusCode: HTTP_STATUS.BAD_REQUEST,
|
|
||||||
};
|
|
||||||
const error = {
|
|
||||||
message: API_ERROR.BAD_DATA,
|
|
||||||
};
|
|
||||||
resolver(error, response);
|
|
||||||
});
|
|
||||||
|
|
||||||
const notification = require('../src/notify-request');
|
|
||||||
const args = [
|
|
||||||
{ errorMessage: 'bad data' },
|
|
||||||
'notify service has thrown an error: @{errorMessage}',
|
|
||||||
];
|
|
||||||
|
|
||||||
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
|
|
||||||
expect(logger.logger.error).toHaveBeenCalledWith(...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when notification service throws error with null error value', async () => {
|
|
||||||
jest.doMock('request', () => (options, resolver) => {
|
|
||||||
const response = {
|
|
||||||
statusCode: HTTP_STATUS.BAD_REQUEST,
|
|
||||||
body: API_ERROR.BAD_DATA,
|
|
||||||
};
|
|
||||||
|
|
||||||
resolver(null, response);
|
|
||||||
});
|
|
||||||
|
|
||||||
const notification = require('../src/notify-request');
|
|
||||||
const args = [
|
|
||||||
{ errorMessage: 'bad data' },
|
|
||||||
'notify service has thrown an error: @{errorMessage}',
|
|
||||||
];
|
|
||||||
|
|
||||||
await expect(notification.notifyRequest(options, content)).rejects.toEqual(API_ERROR.BAD_DATA);
|
|
||||||
expect(logger.logger.error).toHaveBeenCalledWith(...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when notification is successfully delivered', async () => {
|
|
||||||
jest.doMock('request', () => (options, resolver) => {
|
|
||||||
const response = {
|
|
||||||
statusCode: HTTP_STATUS.OK,
|
|
||||||
body: 'Successfully delivered',
|
|
||||||
};
|
|
||||||
|
|
||||||
resolver(null, response, response.body);
|
|
||||||
});
|
|
||||||
|
|
||||||
const notification = require('../src/notify-request');
|
|
||||||
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
|
|
||||||
const debugArgs = [{ body: 'Successfully delivered' }, ' body: @{body}'];
|
|
||||||
|
|
||||||
await expect(notification.notifyRequest(options, content)).resolves.toEqual(
|
|
||||||
'Successfully delivered'
|
|
||||||
);
|
|
||||||
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
|
|
||||||
expect(logger.logger.debug).toHaveBeenCalledWith(...debugArgs);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('when notification is successfully delivered but body is undefined/null', async () => {
|
|
||||||
jest.doMock('request', () => (options, resolver) => {
|
|
||||||
const response = {
|
|
||||||
statusCode: HTTP_STATUS.OK,
|
|
||||||
};
|
|
||||||
|
|
||||||
resolver(null, response);
|
|
||||||
});
|
|
||||||
|
|
||||||
const notification = require('../src/notify-request');
|
|
||||||
const infoArgs = [{ content }, 'A notification has been shipped: @{content}'];
|
|
||||||
|
|
||||||
await expect(notification.notifyRequest(options, content)).rejects.toThrow('body is missing');
|
|
||||||
expect(logger.logger.info).toHaveBeenCalledWith(...infoArgs);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -20,7 +20,6 @@
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rimraf ./build",
|
"clean": "rimraf ./build",
|
||||||
"build": "tsc --emitDeclarationOnly -p tsconfig.build.json",
|
|
||||||
"test": "echo \"Error: no test specified\" && exit 0"
|
"test": "echo \"Error: no test specified\" && exit 0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,6 @@ import distTagsMerge from './tags/dist-tags-merge';
|
||||||
import addtag from './tags/addtag';
|
import addtag from './tags/addtag';
|
||||||
import adduser from './adduser/adduser';
|
import adduser from './adduser/adduser';
|
||||||
import logout from './adduser/logout';
|
import logout from './adduser/logout';
|
||||||
import notify from './notifications/notify';
|
|
||||||
import incomplete from './sanity/incomplete';
|
import incomplete from './sanity/incomplete';
|
||||||
import mirror from './sanity/mirror';
|
import mirror from './sanity/mirror';
|
||||||
import readme from './readme/readme';
|
import readme from './readme/readme';
|
||||||
|
@ -56,7 +55,6 @@ describe('functional test verdaccio', function () {
|
||||||
security(server1);
|
security(server1);
|
||||||
addtag(server1);
|
addtag(server1);
|
||||||
pluginsAuth(server2);
|
pluginsAuth(server2);
|
||||||
notify(app);
|
|
||||||
uplinkTimeout(server1, server2, server3);
|
uplinkTimeout(server1, server2, server3);
|
||||||
// requires packages published to server1/server2
|
// requires packages published to server1/server2
|
||||||
upLinkCache(server1, server2, server3);
|
upLinkCache(server1, server2, server3);
|
||||||
|
|
|
@ -1,175 +0,0 @@
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
import { HEADERS } from '@verdaccio/dev-commons';
|
|
||||||
import { notify } from '@verdaccio/hooks';
|
|
||||||
import { DOMAIN_SERVERS, PORT_SERVER_APP } from '../config.functional';
|
|
||||||
import { RemoteUser } from '@verdaccio/types';
|
|
||||||
|
|
||||||
export default function (express) {
|
|
||||||
const config = {
|
|
||||||
notify: {
|
|
||||||
method: 'POST',
|
|
||||||
headers: [
|
|
||||||
{
|
|
||||||
'Content-Type': HEADERS.JSON,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
endpoint: `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify`,
|
|
||||||
content: `{"color":"green","message":"New package published: * {{ name }}*. Publisher name: * {{ publisher.name }} *.","notify":true,"message_format":"text"}`,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const publisherInfo: RemoteUser = {
|
|
||||||
name: 'publisher-name-test',
|
|
||||||
real_groups: [],
|
|
||||||
groups: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
describe('notifications', () => {
|
|
||||||
function parseBody(notification) {
|
|
||||||
const jsonBody = JSON.parse(notification);
|
|
||||||
|
|
||||||
return jsonBody;
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeAll(function () {
|
|
||||||
express.post('/api/notify', function (req, res) {
|
|
||||||
res.send(req.body);
|
|
||||||
});
|
|
||||||
express.post('/api/notify/bad', function (req, res) {
|
|
||||||
res.status(400);
|
|
||||||
res.send('bad response');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('notification should be send', (done) => {
|
|
||||||
const metadata = {
|
|
||||||
name: 'pkg-test',
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
notify(metadata, config, publisherInfo, 'foo').then(
|
|
||||||
function (body) {
|
|
||||||
const jsonBody = parseBody(body);
|
|
||||||
expect(
|
|
||||||
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
|
|
||||||
).toBe(jsonBody.message);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('notification should be send single header', (done) => {
|
|
||||||
const metadata = {
|
|
||||||
name: 'pkg-test',
|
|
||||||
};
|
|
||||||
|
|
||||||
const configMultipleHeader = _.cloneDeep(config);
|
|
||||||
configMultipleHeader.notify.headers = {
|
|
||||||
// @ts-ignore
|
|
||||||
'Content-Type': HEADERS.JSON,
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
notify(metadata, configMultipleHeader, publisherInfo).then(
|
|
||||||
function (body) {
|
|
||||||
const jsonBody = parseBody(body);
|
|
||||||
expect(
|
|
||||||
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
|
|
||||||
).toBe(jsonBody.message);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('notification should be send multiple notifications endpoints', (done) => {
|
|
||||||
const metadata = {
|
|
||||||
name: 'pkg-test',
|
|
||||||
};
|
|
||||||
// let notificationsCounter = 0;
|
|
||||||
|
|
||||||
const multipleNotificationsEndpoint = {
|
|
||||||
notify: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
const notificationSettings = _.cloneDeep(config.notify);
|
|
||||||
// basically we allow al notifications
|
|
||||||
// @ts-ignore
|
|
||||||
notificationSettings.packagePattern = /^pkg-test$/;
|
|
||||||
// notificationSettings.packagePatternFlags = 'i';
|
|
||||||
// @ts-ignore
|
|
||||||
multipleNotificationsEndpoint.notify.push(notificationSettings);
|
|
||||||
}
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
notify(metadata, multipleNotificationsEndpoint, publisherInfo).then(
|
|
||||||
function (body) {
|
|
||||||
body.forEach(function (notification) {
|
|
||||||
const jsonBody = parseBody(notification);
|
|
||||||
expect(
|
|
||||||
`New package published: * ${metadata.name}*. Publisher name: * ${publisherInfo.name} *.`
|
|
||||||
).toBe(jsonBody.message);
|
|
||||||
});
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('notification should fails', (done) => {
|
|
||||||
const metadata = {
|
|
||||||
name: 'pkg-test',
|
|
||||||
};
|
|
||||||
const configFail = _.cloneDeep(config);
|
|
||||||
configFail.notify.endpoint = `http://${DOMAIN_SERVERS}:${PORT_SERVER_APP}/api/notify/bad`;
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
notify(metadata, configFail, publisherInfo).then(
|
|
||||||
function () {
|
|
||||||
expect(false).toBe('This service should fails with status code 400');
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
expect(err).toEqual('bad response');
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('publisher property should not be overridden if it exists in metadata', (done) => {
|
|
||||||
const metadata = {
|
|
||||||
name: 'pkg-test',
|
|
||||||
publisher: {
|
|
||||||
name: 'existing-publisher-name',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-ignore
|
|
||||||
notify(metadata, config, publisherInfo).then(
|
|
||||||
function (body) {
|
|
||||||
const jsonBody = parseBody(body);
|
|
||||||
expect(
|
|
||||||
`New package published: * ${metadata.name}*. Publisher name: * ${metadata.publisher.name} *.`
|
|
||||||
).toBe(jsonBody.message);
|
|
||||||
done();
|
|
||||||
},
|
|
||||||
function (err) {
|
|
||||||
expect(err).toBeDefined();
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -216,6 +216,7 @@ importers:
|
||||||
supertest: next
|
supertest: next
|
||||||
packages/auth:
|
packages/auth:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@verdaccio/auth': 'link:'
|
||||||
'@verdaccio/commons-api': 'link:../core/commons-api'
|
'@verdaccio/commons-api': 'link:../core/commons-api'
|
||||||
'@verdaccio/dev-commons': 'link:../commons'
|
'@verdaccio/dev-commons': 'link:../commons'
|
||||||
'@verdaccio/loaders': 'link:../loaders'
|
'@verdaccio/loaders': 'link:../loaders'
|
||||||
|
@ -229,6 +230,7 @@ importers:
|
||||||
'@verdaccio/mock': 'link:../mock'
|
'@verdaccio/mock': 'link:../mock'
|
||||||
'@verdaccio/types': 'link:../core/types'
|
'@verdaccio/types': 'link:../core/types'
|
||||||
specifiers:
|
specifiers:
|
||||||
|
'@verdaccio/auth': 'workspace:5.0.0-alpha.0'
|
||||||
'@verdaccio/commons-api': 'workspace:*'
|
'@verdaccio/commons-api': 'workspace:*'
|
||||||
'@verdaccio/config': 'workspace:5.0.0-alpha.0'
|
'@verdaccio/config': 'workspace:5.0.0-alpha.0'
|
||||||
'@verdaccio/dev-commons': 'workspace:5.0.0-alpha.0'
|
'@verdaccio/dev-commons': 'workspace:5.0.0-alpha.0'
|
||||||
|
@ -265,7 +267,7 @@ importers:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@verdaccio/types': 'link:../core/types'
|
'@verdaccio/types': 'link:../core/types'
|
||||||
specifiers:
|
specifiers:
|
||||||
'@verdaccio/types': 'workspace:10.0.0-beta'
|
'@verdaccio/types': 'workspace:*'
|
||||||
packages/config:
|
packages/config:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@verdaccio/dev-commons': 'link:../commons'
|
'@verdaccio/dev-commons': 'link:../commons'
|
||||||
|
@ -354,9 +356,9 @@ importers:
|
||||||
marked: 1.1.1
|
marked: 1.1.1
|
||||||
packages/core/streams:
|
packages/core/streams:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@verdaccio/types': 9.7.2
|
'@verdaccio/types': 'link:../types'
|
||||||
specifiers:
|
specifiers:
|
||||||
'@verdaccio/types': ^9.7.2
|
'@verdaccio/types': 'workspace:*'
|
||||||
packages/core/types:
|
packages/core/types:
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node': 14.6.0
|
'@types/node': 14.6.0
|
||||||
|
@ -366,8 +368,9 @@ importers:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@verdaccio/commons-api': 'link:../core/commons-api'
|
'@verdaccio/commons-api': 'link:../core/commons-api'
|
||||||
'@verdaccio/logger': 'link:../logger'
|
'@verdaccio/logger': 'link:../logger'
|
||||||
|
debug: 4.2.0
|
||||||
handlebars: 4.5.3
|
handlebars: 4.5.3
|
||||||
lodash: 4.17.20
|
node-fetch: 2.6.1
|
||||||
request: 2.87.0
|
request: 2.87.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@verdaccio/auth': 'link:../auth'
|
'@verdaccio/auth': 'link:../auth'
|
||||||
|
@ -375,6 +378,7 @@ importers:
|
||||||
'@verdaccio/dev-commons': 'link:../commons'
|
'@verdaccio/dev-commons': 'link:../commons'
|
||||||
'@verdaccio/types': 'link:../core/types'
|
'@verdaccio/types': 'link:../core/types'
|
||||||
'@verdaccio/utils': 'link:../utils'
|
'@verdaccio/utils': 'link:../utils'
|
||||||
|
nock: 13.0.4
|
||||||
specifiers:
|
specifiers:
|
||||||
'@verdaccio/auth': 'workspace:5.0.0-alpha.0'
|
'@verdaccio/auth': 'workspace:5.0.0-alpha.0'
|
||||||
'@verdaccio/commons-api': 'workspace:*'
|
'@verdaccio/commons-api': 'workspace:*'
|
||||||
|
@ -383,8 +387,10 @@ importers:
|
||||||
'@verdaccio/logger': 'workspace:5.0.0-alpha.0'
|
'@verdaccio/logger': 'workspace:5.0.0-alpha.0'
|
||||||
'@verdaccio/types': 'workspace:*'
|
'@verdaccio/types': 'workspace:*'
|
||||||
'@verdaccio/utils': 'workspace:5.0.0-alpha.0'
|
'@verdaccio/utils': 'workspace:5.0.0-alpha.0'
|
||||||
|
debug: ^4.2.0
|
||||||
handlebars: 4.5.3
|
handlebars: 4.5.3
|
||||||
lodash: ^4.17.20
|
nock: ^13.0.4
|
||||||
|
node-fetch: ^2.6.1
|
||||||
request: 2.87.0
|
request: 2.87.0
|
||||||
packages/loaders:
|
packages/loaders:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5239,10 +5245,6 @@ packages:
|
||||||
npm: '>=5'
|
npm: '>=5'
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-SoCG1btVFPxOcrs8w9wLJCfe8nfE6EaEXCXyRwGbh+Sr3NLEG0R8JOugGJbuSE+zIRuUs5JaUKjzSec+JKLvZw==
|
integrity: sha512-SoCG1btVFPxOcrs8w9wLJCfe8nfE6EaEXCXyRwGbh+Sr3NLEG0R8JOugGJbuSE+zIRuUs5JaUKjzSec+JKLvZw==
|
||||||
/@verdaccio/types/9.7.2:
|
|
||||||
dev: true
|
|
||||||
resolution:
|
|
||||||
integrity: sha512-zv8sMrghtrzkxfo+IOojZYk/j4D5kmF/DMwXS9GnmObTM2nAOTsBzlOxHHBdHaiOK+cntw2YRYQJAebMG5J5sA==
|
|
||||||
/@verdaccio/ui-theme/0.3.13:
|
/@verdaccio/ui-theme/0.3.13:
|
||||||
dev: true
|
dev: true
|
||||||
engines:
|
engines:
|
||||||
|
@ -8406,6 +8408,19 @@ packages:
|
||||||
ms: 2.1.2
|
ms: 2.1.2
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
integrity: sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
|
||||||
|
/debug/4.2.0:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.2
|
||||||
|
dev: false
|
||||||
|
engines:
|
||||||
|
node: '>=6.0'
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
resolution:
|
||||||
|
integrity: sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==
|
||||||
/decamelize-keys/1.1.0:
|
/decamelize-keys/1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
decamelize: 1.2.0
|
decamelize: 1.2.0
|
||||||
|
@ -14925,6 +14940,10 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=
|
integrity: sha1-XkKRsMdT+hq+sKq4+ynfG2bwf20=
|
||||||
|
/lodash.set/4.3.2:
|
||||||
|
dev: true
|
||||||
|
resolution:
|
||||||
|
integrity: sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
|
||||||
/lodash.sortby/4.7.0:
|
/lodash.sortby/4.7.0:
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
integrity: sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||||
|
@ -15875,6 +15894,17 @@ packages:
|
||||||
node: '>= 10.13'
|
node: '>= 10.13'
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==
|
integrity: sha512-QNb/j8kbFnKCiyqi9C5DD0jH/FubFGj5rt9NQFONXwQm3IPB0CULECg/eS3AU1KgZb/6SwUa4/DTRKhVxkGABw==
|
||||||
|
/nock/13.0.4:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.1.1
|
||||||
|
json-stringify-safe: 5.0.1
|
||||||
|
lodash.set: 4.3.2
|
||||||
|
propagate: 2.0.1
|
||||||
|
dev: true
|
||||||
|
engines:
|
||||||
|
node: '>= 10.13'
|
||||||
|
resolution:
|
||||||
|
integrity: sha512-alqTV8Qt7TUbc74x1pKRLSENzfjp4nywovcJgi/1aXDiUxXdt7TkruSTF5MDWPP7UoPVgea4F9ghVdmX0xxnSA==
|
||||||
/node-abi/2.19.1:
|
/node-abi/2.19.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
semver: 5.7.1
|
semver: 5.7.1
|
||||||
|
@ -15920,6 +15950,12 @@ packages:
|
||||||
node: 4.x || >=6.0.0
|
node: 4.x || >=6.0.0
|
||||||
resolution:
|
resolution:
|
||||||
integrity: sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
integrity: sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||||
|
/node-fetch/2.6.1:
|
||||||
|
dev: false
|
||||||
|
engines:
|
||||||
|
node: 4.x || >=6.0.0
|
||||||
|
resolution:
|
||||||
|
integrity: sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||||
/node-forge/0.9.0:
|
/node-forge/0.9.0:
|
||||||
engines:
|
engines:
|
||||||
node: '>= 4.5.0'
|
node: '>= 4.5.0'
|
||||||
|
|
Loading…
Reference in a new issue