Commit b5a39e21 authored by Rafael Dias's avatar Rafael Dias

Issue #108: Add Log

Signed-off-by: Rafael Dias's avatarrafaelcosc <rpd17@inf.ufpr.br>
parent 83bc0295
Pipeline #21453 passed with stages
in 1 minute and 1 second
...@@ -21,3 +21,4 @@ ...@@ -21,3 +21,4 @@
/service /service
schema.sql schema.sql
/doc/code /doc/code
.nyc_output/*
BLENDB_SCHEMA_FILE=config/config.yaml.example BLENDB_SCHEMA_FILE=config/config.yaml.example
PORT=3000 PORT=3000
BLENDB_LOG_FILE=/var/log/blendb.log
BLENDB_LOG_LEVEL=debug
BLENDB_N_DB=1 BLENDB_N_DB=1
BLENDB_DB0_USER=blendb BLENDB_DB0_USER=blendb
BLENDB_DB0_NAME=blendb-test BLENDB_DB0_NAME=blendb-test
......
PORT=3000 PORT=3000
BLENDB_N_DB=2 BLENDB_N_DB=2
BLENDB_LOG_FILE=/var/log/blendb.log
BLENDB_LOG_LEVEL=debug
BLENDB_DB0_USER=runner BLENDB_DB0_USER=runner
BLENDB_DB0_NAME=blendb_fixture BLENDB_DB0_NAME=blendb_fixture
BLENDB_DB0_PASSWORD= BLENDB_DB0_PASSWORD=
......
...@@ -15,10 +15,20 @@ ...@@ -15,10 +15,20 @@
"doc-code": "typedoc --mode 'file' --module 'commonjs' --target 'ES6' --ignoreCompilerErrors --exclude '**/*.spec.ts' --out 'doc/code' 'src'" "doc-code": "typedoc --mode 'file' --module 'commonjs' --target 'ES6' --ignoreCompilerErrors --exclude '**/*.spec.ts' --out 'doc/code' 'src'"
}, },
"nyc": { "nyc": {
"include": ["src/**/*.ts"], "include": [
"extension": [".ts"], "src/**/*.ts"
"require": ["ts-node/register"], ],
"reporter": ["text-summary", "html"], "extension": [
".ts"
],
"require": [
"ts-node/register"
],
"reporter": [
"text-summary",
"text",
"lcov"
],
"sourceMap": true, "sourceMap": true,
"instrument": true "instrument": true
}, },
...@@ -38,6 +48,7 @@ ...@@ -38,6 +48,7 @@
"express": "^4.17.1", "express": "^4.17.1",
"js-yaml": "^3.13.1", "js-yaml": "^3.13.1",
"json-2-csv": "^3.5.6", "json-2-csv": "^3.5.6",
"log4js": "^5.1.0",
"monetdb": "^1.1.4", "monetdb": "^1.1.4",
"osprey": "^0.3.2", "osprey": "^0.3.2",
"pg": "^7.12.1", "pg": "^7.12.1",
...@@ -60,4 +71,5 @@ ...@@ -60,4 +71,5 @@
"engines": { "engines": {
"node": "^10.16.3" "node": "^10.16.3"
} }
} }
...@@ -61,6 +61,9 @@ echo "To set different user and port use npm run service -- <port> [<user>]" ...@@ -61,6 +61,9 @@ echo "To set different user and port use npm run service -- <port> [<user>]"
echo "Run this commands, as root (or sudo) to finish the process and start blendb" echo "Run this commands, as root (or sudo) to finish the process and start blendb"
SYSTEMD_PATH=/etc/systemd/system/blendb.service SYSTEMD_PATH=/etc/systemd/system/blendb.service
mkdir -p /var/log/
touch /var/log/blendb.log
chown root:$REAL_USER /var/log/blendb.log
echo -n "rm -f $SYSTEMD_PATH && " echo -n "rm -f $SYSTEMD_PATH && "
echo -n "ln -s $WORKSPACE/service/blendb.service $SYSTEMD_PATH && " echo -n "ln -s $WORKSPACE/service/blendb.service $SYSTEMD_PATH && "
echo "systemctl daemon-reload && systemctl restart blendb.service" echo "systemctl daemon-reload && systemctl restart blendb.service"
......
...@@ -133,9 +133,11 @@ export class CollectCtrl { ...@@ -133,9 +133,11 @@ export class CollectCtrl {
// true/false, it must guarantee that it isn't a boolean // true/false, it must guarantee that it isn't a boolean
// then it'll test if it's empty // then it'll test if it's empty
if (!(typeof(data[i]) === "boolean") && !data[i]){ if (!(typeof(data[i]) === "boolean") && !data[i]){
throw new Error("[Collect error] '" + fields[i].name + const message = "[Collect error] '" + fields[i].name +
"' is mandatory, but no data was received. Review the data sent."); "' is mandatory, but no data was received. Review the data sent."
throw new Error(message);
} }
req.log.debug("Sucessfuly accepted the data: " + data[i] + " from source: ",source.name);
} }
for (let i = 0; i < fields.length; i++){ for (let i = 0; i < fields.length; i++){
...@@ -151,22 +153,29 @@ export class CollectCtrl { ...@@ -151,22 +153,29 @@ export class CollectCtrl {
} }
} }
if (!found) { if (!found) {
throw new Error("[Collect error] EnumType: '" + data[i] + "' from '" + fields[i].name + const message = "[Collect error] EnumType: '" + data[i] + "' from '" + fields[i].name +
"' isn't allowed on " + fields[i].enumType + "' isn't allowed on " + fields[i].enumType +
". Review configuration files."); ". Review configuration files."
req.log.warn(message);
throw new Error(message);
} }
}else if (!validador[EnumHandler.stringfyDataType(fields[i].dataType)](data[i]) === true){ }else if (!validador[EnumHandler.stringfyDataType(fields[i].dataType)](data[i]) === true){
throw new Error("[Collect error] Datatype: '" + data[i] + "' from '" + fields[i].name + const message = "[Collect error] Datatype: '" + data[i] + "' from '" + fields[i].name +
"' could not be converted to type: " + [EnumHandler.stringfyDataType(fields[i].dataType)] + "' could not be converted to type: " + [EnumHandler.stringfyDataType(fields[i].dataType)] +
". Review configuration files."); ". Review configuration files."
req.log.warn(message);
throw new Error(message);
} }
req.log.debug("Sucessfuly accepted the enumType data: " + data[i] + " from source: ",source.name);
} }
} }
catch (e) { catch (e) {
const message = "Query execution failed: " +
"Could not construct query with the given parameters."
req.log.warn(message,e)
res.status(500).json({ res.status(500).json({
message: "Query execution failed: " + message: message,
"Could not construct query with the given parameters.",
error: e.message error: e.message
}); });
return; return;
...@@ -174,14 +183,18 @@ export class CollectCtrl { ...@@ -174,14 +183,18 @@ export class CollectCtrl {
req.adapter.insertIntoSource(source, req.body, (err: Error, result: any[]) => { req.adapter.insertIntoSource(source, req.body, (err: Error, result: any[]) => {
if (err) { if (err) {
const message = "Insertion has failed";
req.log.error(message,err);
res.status(500).json({ res.status(500).json({
message: "Insertion has failed", message: message,
error: err error: err
}); });
return; return;
} }
else{ else{
res.status(200).json({message: "Data has been successfully received and stored by the server"}); const message = "Data has been successfully received and stored by the server";
req.log.info(message);
res.status(200).json({message: message});
return; return;
} }
......
...@@ -35,6 +35,7 @@ export class DataCtrl { ...@@ -35,6 +35,7 @@ export class DataCtrl {
* by typescript definition of route. * by typescript definition of route.
*/ */
public static read(req: Request, res: express.Response, next: express.NextFunction) { public static read(req: Request, res: express.Response, next: express.NextFunction) {
req.log.info("Query: ",req.query);
let metrics = req.query.metrics.split(",").filter((item: string) => item !== ""); let metrics = req.query.metrics.split(",").filter((item: string) => item !== "");
let dimensions = req.query.dimensions.split(",").filter((item: string) => item !== ""); let dimensions = req.query.dimensions.split(",").filter((item: string) => item !== "");
let clauses = []; let clauses = [];
...@@ -45,7 +46,6 @@ export class DataCtrl { ...@@ -45,7 +46,6 @@ export class DataCtrl {
if (req.query.sort) { if (req.query.sort) {
sort = req.query.sort.split(",").filter((item: string) => item !== ""); sort = req.query.sort.split(",").filter((item: string) => item !== "");
} }
let format = "json"; let format = "json";
if (req.query.format) { if (req.query.format) {
format = req.query.format; format = req.query.format;
...@@ -78,11 +78,13 @@ export class DataCtrl { ...@@ -78,11 +78,13 @@ export class DataCtrl {
"' is not present in neither metrics nor dimensions list"); "' is not present in neither metrics nor dimensions list");
} }
else { else {
req.log.debug("Sorting dimension found: " + sort[i]);
query.sort.push(d); query.sort.push(d);
} }
} }
else { else {
req.log.debug("Sorting metric found: " + sort[i]);
query.sort.push(m); query.sort.push(m);
} }
...@@ -90,9 +92,11 @@ export class DataCtrl { ...@@ -90,9 +92,11 @@ export class DataCtrl {
view = req.engine.query(query); view = req.engine.query(query);
} }
catch (e) { catch (e) {
const message = "Query execution failed: " +
"Could not construct query with the given parameters."
req.log.error(message,e);
res.status(500).json({ res.status(500).json({
message: "Query execution failed: " + message: message,
"Could not construct query with the given parameters.",
error: e.message error: e.message
}); });
return; return;
...@@ -100,29 +104,35 @@ export class DataCtrl { ...@@ -100,29 +104,35 @@ export class DataCtrl {
req.adapter.getDataFromView(view, (err: Error, result: any[]) => { req.adapter.getDataFromView(view, (err: Error, result: any[]) => {
if (err) { if (err) {
const message = "Query execution failed " +
"failed on execute query on database."
req.log.error(message,err);
res.status(500).json({ res.status(500).json({
message: "Query execution failed " + message: message,
"failed on execute query on database.",
error: err error: err
}); });
return; return;
} }
if (format === "json") { if (format === "json") {
req.log.info("Response (json) send with success");
res.status(200).json(result); res.status(200).json(result);
} }
else { else {
req.csvParser(result, format, (error: Error, csv: string) => { req.csvParser(result, format, (error: Error, csv: string) => {
if (error) { if (error) {
const message = "Error generating csv file. " +
"Try json format."
req.log.error(message,error);
res.status(500).json({ res.status(500).json({
message: "Error generating csv file. " + message: message,
"Try json format.",
error: error error: error
}); });
return; return;
} }
req.log.info("Response (csv) send with success");
res.setHeader("Content-Type", "text/csv"); res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-disposition", "attachment;filename=data.csv"); res.setHeader("Content-disposition", "attachment;filename=data.csv");
res.status(200).send(csv); res.status(200).send(csv);
......
...@@ -42,12 +42,14 @@ export class EngineCtrl { ...@@ -42,12 +42,14 @@ export class EngineCtrl {
} }
if (format === "json") { if (format === "json") {
req.log.info("Response (json) send with success");
res.status(200).json(list); res.status(200).json(list);
} }
else { else {
req.csvParser(list, format, (error: Error, csv: string) => { req.csvParser(list, format, (error: Error, csv: string) => {
if (error) { if (error) {
req.log.error("Error generating csv file: ",error);
res.status(500).json({ res.status(500).json({
message: "Error generating csv file. " + message: "Error generating csv file. " +
"Try json format.", "Try json format.",
...@@ -57,6 +59,7 @@ export class EngineCtrl { ...@@ -57,6 +59,7 @@ export class EngineCtrl {
} }
const disposition = "attachment;filename=" + fileName + ".csv"; const disposition = "attachment;filename=" + fileName + ".csv";
req.log.info("Response (csv) send with success");
res.setHeader("Content-Type", "text/csv"); res.setHeader("Content-Type", "text/csv");
res.setHeader("Content-disposition", disposition); res.setHeader("Content-disposition", disposition);
res.status(200).send(csv); res.status(200).send(csv);
......
/*
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana
*
* This file is part of blendb.
*
* blendb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* blendb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with blendb. If not, see <http://www.gnu.org/licenses/>.
*/
import { Log } from "../../util/log";
import { Middleware } from "../types";
/**
* Creates a log and middleware that
* inserts the log into the request objects.
*/
export function LogMw (): Middleware {
let log: Log = new Log();
return function logMiddleware(req, res, next) {
req.log = log;
next();
};
}
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
import * as express from "express"; import * as express from "express";
import { Engine } from "../core/engine"; import { Engine } from "../core/engine";
import { Adapter} from "../core/adapter"; import { Adapter} from "../core/adapter";
import { Log } from "../core/log";
/** /**
* Extension of Express requests that suports the addition * Extension of Express requests that suports the addition
...@@ -37,6 +38,8 @@ export interface Request extends express.Request { ...@@ -37,6 +38,8 @@ export interface Request extends express.Request {
adapter: Adapter; adapter: Adapter;
/** A csvParser function. Used to parse json object into csv file. */ /** A csvParser function. Used to parse json object into csv file. */
csvParser: (json: any, format: string, cb: (err: Error, csv?: string)); csvParser: (json: any, format: string, cb: (err: Error, csv?: string));
/** A log object. Used store logs into file. */
log: Log;
} }
/** /**
......
/*
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana
*
* This file is part of blendb.
*
* blendb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* blendb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with blendb. If not, see <http://www.gnu.org/licenses/>.
*/
import { expect } from "chai";
import { Log } from "../util/log";
describe("log class", () => {
it("should modifiy loglevel to error if wrong level", () => {
process.env.BLENDB_LOG_LEVEL = "test";
const logLevelWrong = new Log();
expect(logLevelWrong.getLogLevel()).to.be.equals("error");
});
it("should modifiy loglevel to error if empty level", () => {
process.env.BLENDB_LOG_LEVEL = "";
const logLevelEmpty = new Log();
expect(logLevelEmpty.getLogLevel()).to.be.equals("error");
});
it("should modifiy logFile to ./blendb.log if empty file", () => {
process.env.BLENDB_LOG_FILE = "";
const LogFileEmpty = new Log();
expect(LogFileEmpty.getLogFile()).to.be.equals("stderr");
});
});
...@@ -55,9 +55,11 @@ import { EngineMw } from "./api/middlewares/engine"; ...@@ -55,9 +55,11 @@ import { EngineMw } from "./api/middlewares/engine";
import { PostgresMw, MonetMw } from "./api/middlewares/adapter"; import { PostgresMw, MonetMw } from "./api/middlewares/adapter";
import { ErrorMw } from "./api/middlewares/error"; import { ErrorMw } from "./api/middlewares/error";
import { CsvMw } from "./api/middlewares/csv"; import { CsvMw } from "./api/middlewares/csv";
import { LogMw } from "./api/middlewares/log"
app.use(EngineMw(config)); app.use(EngineMw(config));
app.use(CsvMw()); app.use(CsvMw());
app.use(LogMw());
if (config.adapters[0] === "postgres") { if (config.adapters[0] === "postgres") {
app.use(PostgresMw(config.connections[0])); app.use(PostgresMw(config.connections[0]));
} }
......
/*
* Copyright (C) 2019 Centro de Computacao Cientifica e Software Livre
* Departamento de Informatica - Universidade Federal do Parana
*
* This file is part of blendb.
*
* blendb is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* blendb is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with blendb. If not, see <http://www.gnu.org/licenses/>.
*/
import { configure, getLogger } from "log4js";
import * as fs from "fs";
/**
* Define the levels that BlenDB log must have
*/
interface LogInterface {
debug(msg: string, ...additionalInfo: any[]): void;
info(msg: string, ...additionalInfo: any[]): void;
warn(msg: string, ...additionalInfo: any[]): void;
error(msg: string, ...additionalInfo: any[]): void;
}
/**
* Logging for BlenDB using the framework log4js,
* using debug, info, warn and error as levels.
* Hierarchy of levels for showing messages are:
* debug > info > warn > error
*/
export class Log implements LogInterface {
private levels = ["debug" , "info" , "warn" , "error"];
private file: string;
private level: string;
private logger: any;
/**
* Definitions to use log4js, such as file destination,
* level of log and configure log4js.
*/
constructor(){
this.file = (process.env["BLENDB_LOG_FILE"]) ? process.env["BLENDB_LOG_FILE"] : "stderr";
this.level = (process.env["BLENDB_LOG_LEVEL"]) ? process.env["BLENDB_LOG_LEVEL"] : "error";
let find = this.levels.some( element => {
return element === this.level;
});
if (!find) {
this.level = "error";
}
if (!(this.file === "stderr")) {
try {
fs.appendFileSync(this.file, "");
}
catch (e) {
this.file = "stderr";
}
}
if (this.file === "stderr") {
configure({
appenders: { defaultLog: { type : "stderr" }
},
categories: {
default: { appenders: ["defaultLog"], level: this.level },
}
});
this.logger = getLogger("defaultLog");
}else{
configure({
appenders: { blendb: { type: "file", layout: {
type: "pattern",
pattern: "%d [%p] host: [%h] user: [%x{user}] %n %[%m%n%]",
tokens: {
user: function(logEvent) {
return process.env.USER;
}
}
},
filename: this.file }
},
categories: {
default: { appenders: ["blendb"], level: this.level }
}
});
this.logger = getLogger("blendb");
}
this.logger.level = this.level;
}
/**
* Get log level
*/
public getLogLevel(){
return this.level;
}
/**
* Get lof filename
*/
public getLogFile(){
return this.file;
}
/**
* Log level debug
* @param msg description of log
* @param additionalInfo useful additional information
*/
public debug(msg: string, ...additionalInfo: any[]){
this.emtigLogMessage("debug", msg, additionalInfo);
}
/**
* Log level info
* @param msg description of log
* @param additionalInfo useful additional information
*/
public info(msg: string, ...additionalInfo: any[]){
this.emtigLogMessage("info", msg, additionalInfo);
}
/**
* Log level warning
* @param msg description of log
* @param additionalInfo useful additional information
*/
public warn(msg: string, ...additionalInfo: any[]){
this.emtigLogMessage("warn", msg, additionalInfo);
}
/**
* Log level error
* @param msg description of log
* @param additionalInfo useful additional information
*/
public error(msg: string, ...additionalInfo: any[]){
this.emtigLogMessage("error", msg, additionalInfo);
}
/**
* Handle data between log class and log4js framework
* @param msgType level of log being use
* @param msg description of log
* @param additionalInfo useful additional information
*/
private emtigLogMessage(logLevel: "debug" | "info" | "warn" | "error" , msg: string, additionalInfo: any[]){
if (additionalInfo.length){
this.logger[logLevel](msg, additionalInfo);
}else{
this.logger[logLevel](msg);
}
}
}
...@@ -991,6 +991,11 @@ date-and-time@0.7.0: ...@@ -991,6 +991,11 @@ date-and-time@0.7.0:
resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.7.0.tgz#26273355558877799f9c95888293fccee92fdb94" resolved "https://registry.yarnpkg.com/date-and-time/-/date-and-time-0.7.0.tgz#26273355558877799f9c95888293fccee92fdb94"
integrity sha512-qPHBPG0AQqbjP7wVf7vLv25/0bZRjYPiJiJtE0t6RqTswJR/6ExCXQLDnL5w4986j7i6470TMtalJxC8/UHrww== integrity sha512-qPHBPG0AQqbjP7wVf7vLv25/0bZRjYPiJiJtE0t6RqTswJR/6ExCXQLDnL5w4986j7i6470TMtalJxC8/UHrww==
date-format@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/date-format/-/date-format-2.1.0.tgz#31d5b5ea211cf5fd764cd38baf9d033df7e125cf"
integrity sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==
debug@2.6.9, debug@2.x.x, debug@^2.2.0, debug@^2.3.3: debug@2.6.9, debug@2.x.x, debug@^2.2.0, debug@^2.3.3:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
...@@ -1544,6 +1549,11 @@ flat@^4.1.0: ...@@ -1544,6 +1549,11 @@ flat@^4.1.0:
dependencies: dependencies:
is-buffer "~2.0.3" is-buffer "~2.0.3"
flatted@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
for-in@^1.0.2: for-in@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
...@@ -2486,6 +2496,17 @@ log-symbols@^1.0.2: ...@@ -2486,6 +2496,17 @@ log-symbols@^1.0.2:
dependencies: dependencies:
chalk "^1.0.0" chalk "^1.0.0"
log4js@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/log4js/-/log4js-5.1.0.tgz#3fa5372055a4c2611ab92d80496bffc100841508"
integrity sha512-QtXrBGZiIwfwBrH9zF2uQarvBuJ5+Icqx9fW+nQL4pnmPITJw8n6kh3bck5IkcTDBQatDeKqUMXXX41fp0TIqw==
dependencies:
date-format "^2.1.0"
debug "^4.1.1"
flatted "^2.0.1"
rfdc "^1.1.4"
streamroller "^2.1.0"
loophole@1.1.0: loophole@1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/loophole/-/loophole-1.1.0.tgz#37949fea453b6256acc725c320ce0c5a7f70a2bd" resolved "https://registry.yarnpkg.com/loophole/-/loophole-1.1.0.tgz#37949fea453b6256acc725c320ce0c5a7f70a2bd"
...@@ -4000,6 +4021,11 @@ ret@~0.1.10: ...@@ -4000,6 +4021,11 @@ ret@~0.1.10:
resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc"
integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==
rfdc@^1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.1.4.tgz#ba72cc1367a0ccd9cf81a870b3b58bd3ad07f8c2"
integrity sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==
rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
version "2.7.1" version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
...@@ -4007,20 +4033,7 @@ rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3: ...@@ -4007,20 +4033,7 @@ rimraf@^2.6.1, rimraf@^2.6.2, rimraf@^2.6.3:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
"router@git+https://github.com/blakeembrey/router#router-engine": router@blakeembrey/router#router-engine:
version "1.1.4"
uid "5eb68560e91b302251ff17a70cd1b6af1fc36d30"
resolved "git+https://github.com/blakeembrey/router#5eb68560e91b302251ff17a70cd1b6af1fc36d30"
dependencies:
array-flatten "2.0.0"
debug "^3.1.0"
methods "~1.1.2"
parseurl "~1.3.1"
path-to-regexp "0.1.7"
setprototypeof "1.0.0"