diff --git a/controllers/albumsController.js b/controllers/albumsController.js index 79275cd..e5ce8d9 100644 --- a/controllers/albumsController.js +++ b/controllers/albumsController.js @@ -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 }) } diff --git a/controllers/authController.js b/controllers/authController.js index cb3e0d1..08001d6 100644 --- a/controllers/authController.js +++ b/controllers/authController.js @@ -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 (╯°□°)╯︵ ┻━┻.' }) } diff --git a/controllers/uploadController.js b/controllers/uploadController.js index 1c607a3..c4c6c1b 100644 --- a/controllers/uploadController.js +++ b/controllers/uploadController.js @@ -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) => { diff --git a/controllers/utilsController.js b/controllers/utilsController.js index 7c8e372..d388806 100644 --- a/controllers/utilsController.js +++ b/controllers/utilsController.js @@ -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())) diff --git a/database/db.js b/database/db.js index 3e94fe8..607ad5a 100644 --- a/database/db.js +++ b/database/db.js @@ -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, diff --git a/logger.js b/logger.js new file mode 100644 index 0000000..15b4aaf --- /dev/null +++ b/logger.js @@ -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 diff --git a/lolisafe.js b/lolisafe.js index 8144281..5f7ba05 100644 --- a/lolisafe.js +++ b/lolisafe.js @@ -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)') } }) } diff --git a/package.json b/package.json index 700d3fc..14af4fe 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/yarn.lock b/yarn.lock index 591ebba..e471cf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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"