Added logger.js to format console logs (adding timestamps).

Re-ordered modules loading in lolisafe.js, and a few other minor edits.

Updated dev dependencies.

A few other minor edits.
This commit is contained in:
Bobby Wibowo 2019-08-27 00:02:06 +07:00
parent 51f12ad900
commit 7e3d177d00
No known key found for this signature in database
GPG Key ID: 51C3A1E1E22D26CF
9 changed files with 180 additions and 177 deletions

View File

@ -2,6 +2,7 @@ const config = require('./../config')
const db = require('knex')(config.database)
const EventEmitter = require('events')
const fs = require('fs')
const logger = require('./../logger')
const path = require('path')
const randomstring = require('randomstring')
const utils = require('./utilsController')
@ -124,7 +125,7 @@ albumsController.getUniqueRandomName = () => {
.where('identifier', identifier)
.then(rows => {
if (!rows || !rows.length) return resolve(identifier)
console.log(`An album with identifier ${identifier} already exists (${++i}/${maxTries}).`)
logger.log(`An album with identifier ${identifier} already exists (${++i}/${maxTries}).`)
if (i < maxTries) return select(i)
// eslint-disable-next-line prefer-promise-reject-errors
return reject('Sorry, we could not allocate a unique random identifier. Try again?')
@ -182,7 +183,7 @@ albumsController.delete = async (req, res, next) => {
const zipPath = path.join(zipsDir, `${identifier}.zip`)
fs.unlink(zipPath, error => {
if (error && error.code !== 'ENOENT') {
console.error(error)
logger.error(error)
return res.json({ success: false, description: error.toString(), failed })
}
res.json({ success: true, failed })
@ -259,7 +260,7 @@ albumsController.edit = async (req, res, next) => {
if (error) return res.json({ success: true, identifier })
fs.rename(zipPath, path.join(zipsDir, `${identifier}.zip`), error => {
if (!error) return res.json({ success: true, identifier })
console.error(error)
logger.error(error)
res.json({ success: false, description: error.toString() })
})
})
@ -363,7 +364,7 @@ albumsController.generateZip = async (req, res, next) => {
}
if (albumsController.zipEmitters.has(identifier)) {
console.log(`Waiting previous zip task for album: ${identifier}.`)
logger.log(`Waiting previous zip task for album: ${identifier}.`)
return albumsController.zipEmitters.get(identifier).once('done', (filePath, fileName, json) => {
if (filePath && fileName)
download(filePath, fileName)
@ -374,12 +375,12 @@ albumsController.generateZip = async (req, res, next) => {
albumsController.zipEmitters.set(identifier, new ZipEmitter(identifier))
console.log(`Starting zip task for album: ${identifier}.`)
logger.log(`Starting zip task for album: ${identifier}.`)
const files = await db.table('files')
.select('name', 'size')
.where('albumid', album.id)
if (files.length === 0) {
console.log(`Finished zip task for album: ${identifier} (no files).`)
logger.log(`Finished zip task for album: ${identifier} (no files).`)
const json = { success: false, description: 'There are no files in the album.' }
albumsController.zipEmitters.get(identifier).emit('done', null, null, json)
return res.json(json)
@ -388,7 +389,7 @@ albumsController.generateZip = async (req, res, next) => {
if (zipMaxTotalSize) {
const totalSizeBytes = files.reduce((accumulator, file) => accumulator + parseInt(file.size), 0)
if (totalSizeBytes > zipMaxTotalSizeBytes) {
console.log(`Finished zip task for album: ${identifier} (size exceeds).`)
logger.log(`Finished zip task for album: ${identifier} (size exceeds).`)
const json = {
success: false,
description: `Total size of all files in the album exceeds the configured limit (${zipMaxTotalSize}).`
@ -405,7 +406,7 @@ albumsController.generateZip = async (req, res, next) => {
for (const file of files)
fs.readFile(path.join(uploadsDir, file.name), (error, data) => {
if (error)
console.error(error)
logger.error(error)
else
archive.file(file.name, data)
@ -415,7 +416,7 @@ albumsController.generateZip = async (req, res, next) => {
.generateNodeStream(zipOptions)
.pipe(fs.createWriteStream(zipPath))
.on('finish', async () => {
console.log(`Finished zip task for album: ${identifier} (success).`)
logger.log(`Finished zip task for album: ${identifier} (success).`)
await db.table('albums')
.where('id', album.id)
.update('zipGeneratedAt', Math.floor(Date.now() / 1000))
@ -471,7 +472,7 @@ albumsController.addFiles = async (req, res, next) => {
const updateDb = await db.table('files')
.whereIn('id', files.map(file => file.id))
.update('albumid', albumid)
.catch(console.error)
.catch(logger.error)
if (!updateDb)
return res.json({
@ -487,7 +488,7 @@ albumsController.addFiles = async (req, res, next) => {
await db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
.catch(console.error)
.catch(logger.error)
return res.json({ success: true, failed })
}

View File

@ -1,6 +1,7 @@
const bcrypt = require('bcrypt')
const config = require('./../config')
const db = require('knex')(config.database)
const logger = require('./../logger')
const perms = require('./permissionController')
const randomstring = require('randomstring')
const tokens = require('./tokenController')
@ -24,7 +25,7 @@ authController.verify = async (req, res, next) => {
bcrypt.compare(password, user.password, (error, result) => {
if (error) {
console.error(error)
logger.error(error)
return res.json({ success: false, description: 'There was an error.' })
}
if (result === false) return res.json({ success: false, description: 'Wrong password.' })
@ -56,7 +57,7 @@ authController.register = async (req, res, next) => {
bcrypt.hash(password, 10, async (error, hash) => {
if (error) {
console.error(error)
logger.error(error)
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻.' })
}
@ -90,7 +91,7 @@ authController.changePassword = async (req, res, next) => {
bcrypt.hash(password, 10, async (error, hash) => {
if (error) {
console.error(error)
logger.error(error)
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻.' })
}
@ -198,7 +199,7 @@ authController.editUser = async (req, res, next) => {
const password = randomstring.generate(16)
bcrypt.hash(password, 10, async (error, hash) => {
if (error) {
console.error(error)
logger.error(error)
return res.json({ success: false, description: 'Error generating password hash (╯°□°)╯︵ ┻━┻.' })
}

View File

@ -3,6 +3,7 @@ const crypto = require('crypto')
const db = require('knex')(config.database)
const fetch = require('node-fetch')
const fs = require('fs')
const logger = require('./../logger')
const multer = require('multer')
const path = require('path')
const perms = require('./permissionController')
@ -30,7 +31,7 @@ const storage = multer.diskStorage({
if (!error) return cb(null, uuidDir)
fs.mkdir(uuidDir, error => {
if (!error) return cb(null, uuidDir)
console.error(error)
logger.error(error)
// eslint-disable-next-line standard/no-callback-literal
return cb('Could not process the chunked upload. Try again?')
})
@ -120,13 +121,13 @@ uploadsController.getUniqueRandomName = (length, extension, set) => {
if (config.uploads.cacheFileIdentifiers) {
// Check whether the identifier is already used in cache
if (set.has(identifier)) {
console.log(`Identifier ${identifier} is already in use (${++i}/${maxTries}).`)
logger.log(`Identifier ${identifier} is already in use (${++i}/${maxTries}).`)
if (i < maxTries) return access(i)
// eslint-disable-next-line prefer-promise-reject-errors
return reject('Sorry, we could not allocate a unique random name. Try again?')
}
set.add(identifier)
// console.log(`Added ${identifier} to identifiers cache`)
// logger.log(`Added ${identifier} to identifiers cache`)
return resolve(identifier + extension)
} else {
// Less stricter collision check, as in the same identifier
@ -134,7 +135,7 @@ uploadsController.getUniqueRandomName = (length, extension, set) => {
const name = identifier + extension
fs.access(path.join(uploadsDir, name), error => {
if (error) return resolve(name)
console.log(`A file named ${name} already exists (${++i}/${maxTries}).`)
logger.log(`A file named ${name} already exists (${++i}/${maxTries}).`)
if (i < maxTries) return access(i)
// eslint-disable-next-line prefer-promise-reject-errors
return reject('Sorry, we could not allocate a unique random name. Try again?')
@ -172,7 +173,7 @@ uploadsController.upload = async (req, res, next) => {
uploadsController.actuallyUpload = async (req, res, user, albumid) => {
const erred = error => {
const isError = error instanceof Error
if (isError) console.error(error)
if (isError) logger.error(error)
res.status(400).json({
success: false,
description: isError ? error.toString() : error
@ -205,7 +206,7 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
if (config.filterEmptyFile && infoMap.some(file => file.data.size === 0)) {
infoMap.forEach(file => {
utils.deleteFile(file.data.filename, req.app.get('uploads-set')).catch(console.error)
utils.deleteFile(file.data.filename, req.app.get('uploads-set')).catch(logger.error)
})
return erred('Empty files are not allowed.')
}
@ -226,7 +227,7 @@ uploadsController.actuallyUpload = async (req, res, user, albumid) => {
uploadsController.actuallyUploadByUrl = async (req, res, user, albumid) => {
const erred = error => {
const isError = error instanceof Error
if (isError) console.error(error)
if (isError) logger.error(error)
res.status(400).json({
success: false,
description: isError ? error.toString() : error
@ -340,7 +341,7 @@ uploadsController.finishChunks = async (req, res, next) => {
uploadsController.actuallyFinishChunks = async (req, res, user, albumid) => {
const erred = error => {
const isError = error instanceof Error
if (isError) console.error(error)
if (isError) logger.error(error)
res.status(400).json({
success: false,
description: isError ? error.toString() : error
@ -467,7 +468,7 @@ uploadsController.appendToStream = (destFileStream, uuidDr, chunkNames) => {
append(i + 1)
})
.on('error', error => {
console.error(error)
logger.error(error)
destFileStream.end()
return reject(error)
})
@ -551,7 +552,7 @@ uploadsController.formatInfoMap = (req, res, user, infoMap) => {
})
utils.invalidateStatsCache('uploads')
} else {
utils.deleteFile(info.data.filename, req.app.get('uploads-set')).catch(console.error)
utils.deleteFile(info.data.filename, req.app.get('uploads-set')).catch(logger.error)
existingFiles.push(dbFile)
}
@ -576,7 +577,7 @@ uploadsController.scanFiles = (req, infoMap) => {
if (!reply.includes('OK') || reply.includes('FOUND')) {
// eslint-disable-next-line no-control-regex
const virus = reply.replace(/^stream: /, '').replace(/ FOUND\u0000$/, '')
console.log(`ClamAV: ${info.data.filename}: ${virus} FOUND.`)
logger.log(`[ClamAV]: ${info.data.filename}: ${virus} FOUND.`)
return resolve({ virus, lastIteration })
}
if (lastIteration) resolve(null)
@ -586,19 +587,19 @@ uploadsController.scanFiles = (req, infoMap) => {
// If there is at least one dirty file, then delete all files
const set = req.app.get('uploads-set')
infoMap.forEach(info => {
utils.deleteFile(info.data.filename).catch(console.error)
utils.deleteFile(info.data.filename).catch(logger.error)
if (set) {
const identifier = info.data.filename.split('.')[0]
set.delete(identifier)
// console.log(`Removed ${identifier} from identifiers cache (formatInfoMap)`)
// logger.log(`Removed ${identifier} from identifiers cache (formatInfoMap)`)
}
})
// Unfortunately, we will only be returning name of the first virus
// even if the current session was made up by multiple virus types
return `Threat found: ${result.virus}${result.lastIteration ? '' : ', and maybe more'}.`
}).catch(error => {
console.error(`ClamAV: ${error.toString()}.`)
return `ClamAV: ${error.code !== undefined ? `${error.code}, p` : 'P'}lease contact the site owner.`
logger.error(`[ClamAV]: ${error.toString()}.`)
return `[ClamAV]: ${error.code !== undefined ? `${error.code}, p` : 'P'}lease contact the site owner.`
})
}
@ -646,7 +647,7 @@ uploadsController.processFilesForDisplay = async (req, res, files, existingFiles
db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
.catch(console.error)
.catch(logger.error)
}
uploadsController.delete = async (req, res) => {

View File

@ -4,6 +4,7 @@ const db = require('knex')(config.database)
const fetch = require('node-fetch')
const ffmpeg = require('fluent-ffmpeg')
const fs = require('fs')
const logger = require('./../logger')
const os = require('os')
const path = require('path')
const perms = require('./permissionController')
@ -159,7 +160,7 @@ utilsController.generateThumbs = (name, force) => {
const thumbname = path.join(thumbsDir, name.slice(0, -extname.length) + '.png')
fs.lstat(thumbname, async (error, stats) => {
if (error && error.code !== 'ENOENT') {
console.error(error)
logger.error(error)
return resolve(false)
}
@ -167,7 +168,7 @@ utilsController.generateThumbs = (name, force) => {
// Unlink symlink
const unlink = await new Promise(resolve => {
fs.unlink(thumbname, error => {
if (error) console.error(error)
if (error) logger.error(error)
resolve(!error)
})
})
@ -229,7 +230,8 @@ utilsController.generateThumbs = (name, force) => {
// Skip files that do not have video streams/channels
if (!metadata.streams || !metadata.streams.some(s => s.codec_type === 'video'))
return reject(new Error('File does not contain any video stream'))
// eslint-disable-next-line prefer-promise-reject-errors
return reject('File does not contain any video stream')
ffmpeg(input)
.inputOptions([
@ -245,7 +247,7 @@ utilsController.generateThumbs = (name, force) => {
// Since ffmpeg may have already created an incomplete thumbnail
fs.unlink(thumbname, err => {
if (err && err.code !== 'ENOENT')
console.error(`${name}: ${err.toString()}`)
logger.error(`[${name}]: ${err.toString()}`)
reject(error)
})
})
@ -258,15 +260,15 @@ utilsController.generateThumbs = (name, force) => {
// Suppress error logging for errors these patterns
const errorString = error.toString()
const suppress = [
/Error: Input file contains unsupported image format/,
/Error: ffprobe exited with code 1/,
/Error: File does not contain any video stream/
/Input file contains unsupported image format/,
/Invalid data found when processing input/,
/File does not contain any video stream/
]
if (!suppress.some(t => t.test(errorString)))
console.error(`${name}: ${errorString}`)
logger.error(`[${name}]: ${errorString}`)
fs.symlink(thumbPlaceholder, thumbname, err => {
if (err) console.error(err)
if (err) logger.error(err)
// We return true anyway
// if we could make a symlink to the placeholder image
resolve(!err)
@ -285,7 +287,7 @@ utilsController.deleteFile = (filename, set) => {
// eslint-disable-next-line curly
if (set) {
set.delete(identifier)
// console.log(`Removed ${identifier} from identifiers cache (deleteFile)`)
// logger.log(`Removed ${identifier} from identifiers cache (deleteFile)`)
}
if (utilsController.imageExtensions.includes(extname) || utilsController.videoExtensions.includes(extname)) {
const thumb = `${identifier}.png`
@ -332,7 +334,7 @@ utilsController.bulkDeleteFiles = async (field, values, user, set) => {
.then(() => deletedFiles.push(file))
.catch(error => {
failed.push(file[field])
console.error(error)
logger.error(error)
})
))
@ -348,7 +350,7 @@ utilsController.bulkDeleteFiles = async (field, values, user, set) => {
deletedFiles.forEach(file => {
const identifier = file.name.split('.')[0]
set.delete(identifier)
// console.log(`Removed ${identifier} from identifiers cache (bulkDeleteFiles)`)
// logger.log(`Removed ${identifier} from identifiers cache (bulkDeleteFiles)`)
})
// Update albums if necessary
@ -361,7 +363,7 @@ utilsController.bulkDeleteFiles = async (field, values, user, set) => {
await db.table('albums')
.whereIn('id', albumids)
.update('editedAt', Math.floor(Date.now() / 1000))
.catch(console.error)
.catch(logger.error)
}
// Purge Cloudflare's cache if necessary
@ -370,10 +372,10 @@ utilsController.bulkDeleteFiles = async (field, values, user, set) => {
.then(results => {
for (const result of results)
if (result.errors.length)
result.errors.forEach(error => console.error(`CF: ${error}`))
result.errors.forEach(error => logger.error(`[CF]: ${error}`))
})
} catch (error) {
console.error(error)
logger.error(error)
}
}
return new Promise(resolve => job().then(() => resolve()))

View File

@ -1,3 +1,4 @@
const logger = require('./../logger')
const perms = require('./../controllers/permissionController')
const init = function (db) {
@ -50,7 +51,7 @@ const init = function (db) {
db.table('users').where({ username: 'root' }).then((user) => {
if (user.length > 0) return
require('bcrypt').hash('root', 10, function (error, hash) {
if (error) console.error('Error generating password hash for root')
if (error) logger.error('Error generating password hash for root')
db.table('users').insert({
username: 'root',
password: hash,

26
logger.js Normal file
View File

@ -0,0 +1,26 @@
const { inspect } = require('util')
const logger = {}
logger.clean = item => {
if (typeof item === 'string') return item
const cleaned = inspect(item, { depth: 0 })
return cleaned
}
logger.write = (content, options = {}) => {
const date = new Date().toISOString()
.replace(/T/, ' ')
.replace(/\..*/, '')
const stream = options.error ? process.stderr : process.stdout
stream.write(`[${date}]: ${options.prefix || ''}${logger.clean(content)}\n`)
}
logger.log = logger.write
logger.error = (content, options = {}) => {
options.error = true
logger.write(content, options)
}
module.exports = logger

View File

@ -1,28 +1,29 @@
const config = require('./config')
const api = require('./routes/api')
const album = require('./routes/album')
const nojs = require('./routes/nojs')
const utils = require('./controllers/utilsController')
const express = require('express')
const bodyParser = require('body-parser')
const clamd = require('clamdjs')
const db = require('knex')(config.database)
const config = require('./config')
const express = require('express')
const fs = require('fs')
const helmet = require('helmet')
const logger = require('./logger')
const nunjucks = require('nunjucks')
const RateLimit = require('express-rate-limit')
const readline = require('readline')
const safe = express()
// It appears to be best to catch these before doing anything else
// Probably before require() too, especially require('knex')(db), but nevermind
process.on('uncaughtException', error => {
console.error('Uncaught Exception:', error)
logger.error(error, { prefix: 'Uncaught Exception: ' })
})
process.on('unhandledRejection', error => {
console.error('Unhandled Rejection (Promise):', error)
logger.error(error, { prefix: 'Unhandled Rejection (Promise): ' })
})
const utils = require('./controllers/utilsController')
const album = require('./routes/album')
const api = require('./routes/api')
const nojs = require('./routes/nojs')
const db = require('knex')(config.database)
require('./database/db.js')(db)
// Check and create missing directories
@ -111,7 +112,7 @@ safe.use('/', nojs)
safe.use('/api', api)
if (!Array.isArray(config.pages) || !config.pages.length) {
console.error('config.pages is not an array or is an empty array. This won\'t do!')
logger.error('Config does not haves any frontend pages enabled')
process.exit(1)
}
@ -147,7 +148,7 @@ safe.use((req, res, next) => {
res.status(404).sendFile(config.errorPages[404], { root: config.errorPages.rootDir })
})
safe.use((error, req, res, next) => {
console.error(error)
logger.error(error)
res.status(500).sendFile(config.errorPages[500], { root: config.errorPages.rootDir })
})
@ -158,9 +159,9 @@ const start = async () => {
if (error) return reject(error)
resolve(stdout.replace(/\n$/, ''))
})
}).catch(console.error)
}).catch(logger.error)
if (!gitHash) return
console.log(`Git commit: ${gitHash}`)
logger.log(`Git commit: ${gitHash}`)
safe.set('git-hash', gitHash)
}
@ -172,13 +173,13 @@ const start = async () => {
throw new Error('clamd IP or port is missing')
const version = await clamd.version(scan.ip, scan.port)
console.log(`${scan.ip}:${scan.port} ${version}`)
logger.log(`${scan.ip}:${scan.port} ${version}`)
const scanner = clamd.createScanner(scan.ip, scan.port)
safe.set('clam-scanner', scanner)
return true
} catch (error) {
console.error(`ClamAV: ${error.toString()}`)
logger.error(`[ClamAV]: ${error.toString()}`)
return false
}
}
@ -187,7 +188,6 @@ const start = async () => {
if (config.uploads.cacheFileIdentifiers) {
// Cache tree of uploads directory
process.stdout.write('Caching identifiers in uploads directory ...')
const setSize = await new Promise((resolve, reject) => {
const uploadsDir = `./${config.uploads.folder}`
fs.readdir(uploadsDir, (error, names) => {
@ -197,34 +197,31 @@ const start = async () => {
safe.set('uploads-set', set)
resolve(set.size)
})
}).catch(error => console.error(error.toString()))
}).catch(error => logger.error(error.toString()))
if (!setSize) return process.exit(1)
process.stdout.write(` ${setSize} OK!\n`)
logger.log(`Cached ${setSize} identifiers in uploads directory`)
}
safe.listen(config.port, async () => {
console.log(`lolisafe started on port ${config.port}`)
logger.log(`lolisafe started on port ${config.port}`)
// safe.fiery.me-exclusive cache control
if (config.cacheControl) {
process.stdout.write('Cache control enabled. Purging Cloudflare\'s cache ...')
logger.log('Cache control enabled')
const routes = config.pages.concat(['api/check'])
const results = await utils.purgeCloudflareCache(routes)
let errored = false
let succeeded = 0
for (const result of results) {
if (result.errors.length) {
if (!errored) {
errored = true
process.stdout.write(' ERROR!\n')
}
result.errors.forEach(error => console.log(`CF: ${error}`))
if (!errored) errored = true
result.errors.forEach(error => logger.log(`[CF]: ${error}`))
continue
}
succeeded += result.files.length
}
if (!errored)
process.stdout.write(` ${succeeded} OK!\n`)
logger.log(`Purged ${succeeded} Cloudflare's cache`)
}
// NODE_ENV=development yarn start
@ -238,14 +235,14 @@ const start = async () => {
try {
if (line === '.exit') process.exit(0)
// eslint-disable-next-line no-eval
process.stdout.write(`${require('util').inspect(eval(line), { depth: 0 })}\n`)
logger.log(eval(line))
} catch (error) {
console.error(error.toString())
logger.error(error.toString())
}
}).on('SIGINT', () => {
process.exit(0)
})
console.log('Development mode enabled (disabled nunjucks caching & enabled readline interface)')
logger.log('Development mode enabled (disabled Nunjucks caching & enabled readline interface)')
}
})
}

View File

@ -42,8 +42,8 @@
"sqlite3": "^4.1.0"
},
"devDependencies": {
"eslint": "^6.2.0",
"eslint-config-standard": "^14.0.0",
"eslint": "^6.2.2",
"eslint-config-standard": "^14.0.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-node": "^9.1.0",
"eslint-plugin-promise": "^4.2.1",

168
yarn.lock
View File

@ -36,10 +36,10 @@ accepts@~1.3.7:
mime-types "~2.1.24"
negotiator "0.6.2"
acorn-jsx@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e"
integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==
acorn-jsx@^5.0.2:
version "5.0.2"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.2.tgz#84b68ea44b373c4f8686023a551f61a21b7c4a4f"
integrity sha512-tiNTrP1MP0QrChmD2DdupCr6HWSFeKVw5d/dHTu4Y7rkAkRhU/Dt7dphAfIUyxtHpl/eBVip5uTNSpQJHylpAw==
acorn@^7.0.0:
version "7.0.0"
@ -56,12 +56,10 @@ ajv@^6.10.0, ajv@^6.10.2, ajv@^6.5.5:
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ansi-escapes@^4.2.1:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.2.1.tgz#4dccdb846c3eee10f6d64dea66273eab90c37228"
integrity sha512-Cg3ymMAdN10wOk/VYfLV7KCQyv7EDirJ64500sU7n9UlmioEtDuU5Gd+hj73hXSU/ex7tHJSssmyftDdkMLO8Q==
dependencies:
type-fest "^0.5.2"
ansi-escapes@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b"
integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==
ansi-regex@^2.0.0:
version "2.1.1"
@ -400,9 +398,9 @@ chardet@^0.7.0:
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
chokidar@^2.0.0:
version "2.1.6"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.6.tgz#b6cad653a929e244ce8a834244164d241fa954c5"
integrity sha512-V2jUo67OKkc6ySiRpJrjlpJKl9kDuG+Xb8VgsGzb+aEouhgS1D0weyPU4lEzdAcsCAvrih2J2BqyXqHWvVLw5g==
version "2.1.8"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917"
integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==
dependencies:
anymatch "^2.0.0"
async-each "^1.0.1"
@ -438,12 +436,12 @@ class-utils@^0.3.5:
isobject "^3.0.0"
static-extend "^0.1.1"
cli-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307"
integrity sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==
cli-cursor@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=
dependencies:
restore-cursor "^3.1.0"
restore-cursor "^2.0.0"
cli-width@^2.0.0:
version "2.2.0"
@ -775,11 +773,6 @@ emoji-regex@^7.0.1:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156"
integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==
emoji-regex@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
encodeurl@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
@ -830,10 +823,10 @@ escape-string-regexp@^1.0.5:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
eslint-config-standard@^14.0.0:
version "14.0.0"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.0.tgz#1de7bf5af37542dc6eef879ab7eb5e5e0f830747"
integrity sha512-bV6e2LFvJEetrLjVAy4KWPOUsIhPWr040c649MigTPR6yUtaGuOt6CEAyNeez2lRiC+2+vjGWa02byjs25EB3A==
eslint-config-standard@^14.0.1:
version "14.0.1"
resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.1.tgz#375c3636fb4bd453cb95321d873de12e4eef790b"
integrity sha512-1RWsAKTDTZgA8bIM6PSC9aTGDAUlKqNkYNJlTZ5xYD/HYkIM6GlcefFvgcJ8xi0SWG5203rttKYX28zW+rKNOg==
eslint-import-resolver-node@^0.3.2:
version "0.3.2"
@ -906,10 +899,10 @@ eslint-scope@^5.0.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-utils@^1.3.0, eslint-utils@^1.3.1, eslint-utils@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.0.tgz#e2c3c8dba768425f897cf0f9e51fe2e241485d4c"
integrity sha512-7ehnzPaP5IIEh1r1tkjuIrxqhNkzUJa9z3R92tLJdZIVdWaczEhr3EbhGtsMrVxi1KeR8qA7Off6SWc5WNQqyQ==
eslint-utils@^1.3.0, eslint-utils@^1.3.1, eslint-utils@^1.4.2:
version "1.4.2"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
integrity sha512-eAZS2sEUMlIeCjBeubdj45dmBHQwPHWyBcT1VSYB7o9x9WRRqKxyUoiXlRjyAwzN7YEzHJlYg0NmzDRWx6GP4Q==
dependencies:
eslint-visitor-keys "^1.0.0"
@ -918,10 +911,10 @@ eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
eslint@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.0.tgz#4c42c20e3fc03f28db25f34ccba621a9a47e8b56"
integrity sha512-sS0SZwm5UAoI83F+cgdomz0cBNPs+AnRvEboNYeWvrZ8UcDHCu/5muocwoDL2TkHq9skkP0GvZjmwI8HG7S3sw==
eslint@^6.2.2:
version "6.2.2"
resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.2.2.tgz#03298280e7750d81fcd31431f3d333e43d93f24f"
integrity sha512-mf0elOkxHbdyGX1IJEUsNBzCDdyoUgljF3rRlgfyYh0pwGnreLc0jjD6ZuleOibjmnUWZLY2eXwSooeOgGJ2jw==
dependencies:
"@babel/code-frame" "^7.0.0"
ajv "^6.10.0"
@ -930,9 +923,9 @@ eslint@^6.2.0:
debug "^4.0.1"
doctrine "^3.0.0"
eslint-scope "^5.0.0"
eslint-utils "^1.4.0"
eslint-utils "^1.4.2"
eslint-visitor-keys "^1.1.0"
espree "^6.1.0"
espree "^6.1.1"
esquery "^1.0.1"
esutils "^2.0.2"
file-entry-cache "^5.0.1"
@ -961,13 +954,13 @@ eslint@^6.2.0:
text-table "^0.2.0"
v8-compile-cache "^2.0.3"
espree@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.0.tgz#a1e8aa65bf29a331d70351ed814a80e7534e0884"
integrity sha512-boA7CHRLlVWUSg3iL5Kmlt/xT3Q+sXnKoRYYzj1YeM10A76TEJBbotV5pKbnK42hEUIr121zTv+QLRM5LsCPXQ==
espree@^6.1.1:
version "6.1.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de"
integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ==
dependencies:
acorn "^7.0.0"
acorn-jsx "^5.0.0"
acorn-jsx "^5.0.2"
eslint-visitor-keys "^1.1.0"
esprima@^4.0.0:
@ -1148,10 +1141,10 @@ feature-policy@0.3.0:
resolved "https://registry.yarnpkg.com/feature-policy/-/feature-policy-0.3.0.tgz#7430e8e54a40da01156ca30aaec1a381ce536069"
integrity sha512-ZtijOTFN7TzCujt1fnNhfWPFPSHeZkesff9AXZj+UEjYBynWNUIYpC87Ve4wHzyexQsImicLu7WsC2LHq7/xrQ==
figures@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-3.0.0.tgz#756275c964646163cc6f9197c7a0295dbfd04de9"
integrity sha512-HKri+WoWoUgr83pehn/SIgLOMZ9nAWC6dcGj26RY2R4F50u4+RTUz0RCrUlOV3nKRAICW1UGzyb+kcX2qK1S/g==
figures@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
dependencies:
escape-string-regexp "^1.0.5"
@ -1649,21 +1642,21 @@ ini@^1.3.4, ini@~1.3.0:
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
inquirer@^6.4.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.1.tgz#8bfb7a5ac02dac6ff641ac4c5ff17da112fcdb42"
integrity sha512-uxNHBeQhRXIoHWTSNYUFhQVrHYFThIt6IVo2fFmSe8aBwdR3/w6b58hJpiL/fMukFkvGzjg+hSxFtwvVmKZmXw==
version "6.5.2"
resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca"
integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==
dependencies:
ansi-escapes "^4.2.1"
ansi-escapes "^3.2.0"
chalk "^2.4.2"
cli-cursor "^3.1.0"
cli-cursor "^2.1.0"
cli-width "^2.0.0"
external-editor "^3.0.3"
figures "^3.0.0"
lodash "^4.17.15"
mute-stream "0.0.8"
figures "^2.0.0"
lodash "^4.17.12"
mute-stream "0.0.7"
run-async "^2.2.0"
rxjs "^6.4.0"
string-width "^4.1.0"
string-width "^2.1.0"
strip-ansi "^5.1.0"
through "^2.3.6"
@ -1797,11 +1790,6 @@ is-fullwidth-code-point@^2.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
is-fullwidth-code-point@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-glob@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a"
@ -2062,7 +2050,7 @@ locate-path@^2.0.0:
p-locate "^2.0.0"
path-exists "^3.0.0"
lodash@^4.17.14, lodash@^4.17.15:
lodash@^4.17.12, lodash@^4.17.14, lodash@^4.17.15:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@ -2137,10 +2125,10 @@ mime@1.6.0:
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
mimic-fn@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
mimic-response@^1.0.0:
version "1.0.1"
@ -2165,9 +2153,9 @@ minimist@^1.2.0:
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minipass@^2.2.1, minipass@^2.3.5:
version "2.3.5"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848"
integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==
version "2.4.0"
resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.4.0.tgz#38f0af94f42fb6f34d3d7d82a90e2c99cd3ff485"
integrity sha512-6PmOuSP4NnZXzs2z6rbwzLJu/c5gdzYg1mRI/WIYdx45iiX7T+a4esOzavD6V/KmBzAaopFSTZPZcUx73bqKWA==
dependencies:
safe-buffer "^5.1.2"
yallist "^3.0.0"
@ -2223,10 +2211,10 @@ multer@^1.4.2:
type-is "^1.6.4"
xtend "^4.0.0"
mute-stream@0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d"
integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==
mute-stream@0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab"
integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=
nan@2.13.2:
version "2.13.2"
@ -2487,12 +2475,12 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0:
dependencies:
wrappy "1"
onetime@^5.1.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.0.tgz#fff0f3c91617fe62bb50189636e99ac8a6df7be5"
integrity sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==
onetime@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
integrity sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=
dependencies:
mimic-fn "^2.1.0"
mimic-fn "^1.0.0"
optionator@^0.8.2:
version "0.8.2"
@ -2942,12 +2930,12 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.10.1, resolve@^1.11.
dependencies:
path-parse "^1.0.6"
restore-cursor@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-3.1.0.tgz#39f67c54b3a7a58cea5236d95cf0034239631f7e"
integrity sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
integrity sha1-n37ih/gv0ybU/RYpI9YhKe7g368=
dependencies:
onetime "^5.1.0"
onetime "^2.0.0"
signal-exit "^3.0.2"
ret@~0.1.10:
@ -3285,7 +3273,7 @@ string-width@^1.0.1:
is-fullwidth-code-point "^1.0.0"
strip-ansi "^3.0.0"
"string-width@^1.0.2 || 2":
"string-width@^1.0.2 || 2", string-width@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
@ -3302,15 +3290,6 @@ string-width@^3.0.0:
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
string-width@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.1.0.tgz#ba846d1daa97c3c596155308063e075ed1c99aff"
integrity sha512-NrX+1dVVh+6Y9dnQ19pR0pP4FiEIlUvdTGn8pw6CKTNq5sgib2nIhmUNT5TAmhWmvKr3WcxBcP3E8nWezuipuQ==
dependencies:
emoji-regex "^8.0.0"
is-fullwidth-code-point "^3.0.0"
strip-ansi "^5.2.0"
string_decoder@~0.10.x:
version "0.10.31"
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94"
@ -3506,11 +3485,6 @@ type-check@~0.3.2:
dependencies:
prelude-ls "~1.1.2"
type-fest@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2"
integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw==
type-is@^1.6.4, type-is@~1.6.17, type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"