diff --git a/services/docstore/.dockerignore b/services/docstore/.dockerignore index 386f26df30..ba1c3442de 100644 --- a/services/docstore/.dockerignore +++ b/services/docstore/.dockerignore @@ -5,5 +5,3 @@ gitrev .npm .nvmrc nodemon.json -app.js -**/js/* diff --git a/services/docstore/.eslintrc b/services/docstore/.eslintrc new file mode 100644 index 0000000000..42a4b5cace --- /dev/null +++ b/services/docstore/.eslintrc @@ -0,0 +1,65 @@ +// this file was auto-generated, do not edit it directly. +// instead run bin/update_build_scripts from +// https://github.com/sharelatex/sharelatex-dev-environment +// Version: 1.3.5 +{ + "extends": [ + "standard", + "prettier", + "prettier/standard" + ], + "parserOptions": { + "ecmaVersion": 2017 + }, + "plugins": [ + "mocha", + "chai-expect", + "chai-friendly" + ], + "env": { + "node": true, + "mocha": true + }, + "rules": { + // Swap the no-unused-expressions rule with a more chai-friendly one + "no-unused-expressions": 0, + "chai-friendly/no-unused-expressions": "error" + }, + "overrides": [ + { + // Test specific rules + "files": ["test/**/*.js"], + "globals": { + "expect": true + }, + "rules": { + // mocha-specific rules + "mocha/handle-done-callback": "error", + "mocha/no-exclusive-tests": "error", + "mocha/no-global-tests": "error", + "mocha/no-identical-title": "error", + "mocha/no-nested-tests": "error", + "mocha/no-pending-tests": "error", + "mocha/no-skipped-tests": "error", + "mocha/no-mocha-arrows": "error", + + // chai-specific rules + "chai-expect/missing-assertion": "error", + "chai-expect/terminating-properties": "error", + + // prefer-arrow-callback applies to all callbacks, not just ones in mocha tests. + // we don't enforce this at the top-level - just in tests to manage `this` scope + // based on mocha's context mechanism + "mocha/prefer-arrow-callback": "error" + } + }, + { + // Backend specific rules + "files": ["app/**/*.js", "app.js", "index.js"], + "rules": { + // don't allow console.log in backend code + "no-console": "error" + } + } + ] +} diff --git a/services/docstore/.gitignore b/services/docstore/.gitignore index 6a355b35a8..50678c09e9 100644 --- a/services/docstore/.gitignore +++ b/services/docstore/.gitignore @@ -1,7 +1,2 @@ node_modules -app/js/ -test/unit/js -test/acceptance/js -app.js -**/*.map forever diff --git a/services/docstore/.prettierrc b/services/docstore/.prettierrc new file mode 100644 index 0000000000..5845b82113 --- /dev/null +++ b/services/docstore/.prettierrc @@ -0,0 +1,8 @@ +# This file was auto-generated, do not edit it directly. +# Instead run bin/update_build_scripts from +# https://github.com/sharelatex/sharelatex-dev-environment +# Version: 1.3.5 +{ + "semi": false, + "singleQuote": true +} diff --git a/services/docstore/Dockerfile b/services/docstore/Dockerfile index e538fb48d9..2d8b097e26 100644 --- a/services/docstore/Dockerfile +++ b/services/docstore/Dockerfile @@ -17,7 +17,6 @@ RUN npm install --quiet COPY . /app -RUN npm run compile:all FROM base diff --git a/services/docstore/Jenkinsfile b/services/docstore/Jenkinsfile index 14ea3db943..95132d21db 100644 --- a/services/docstore/Jenkinsfile +++ b/services/docstore/Jenkinsfile @@ -37,6 +37,13 @@ pipeline { } } + stage('Linting') { + steps { + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make format' + sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make lint' + } + } + stage('Unit Tests') { steps { sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_unit' diff --git a/services/docstore/Makefile b/services/docstore/Makefile index d720f78b65..1314c0d76d 100644 --- a/services/docstore/Makefile +++ b/services/docstore/Makefile @@ -19,12 +19,17 @@ DOCKER_COMPOSE := BUILD_NUMBER=$(BUILD_NUMBER) \ clean: docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - rm -f app.js - rm -rf app/js - rm -rf test/unit/js - rm -rf test/acceptance/js -test: test_unit test_acceptance +format: + $(DOCKER_COMPOSE) run --rm test_unit npm run format + +format_fix: + $(DOCKER_COMPOSE) run --rm test_unit npm run format:fix + +lint: + $(DOCKER_COMPOSE) run --rm test_unit npm run lint + +test: format lint test_unit test_acceptance test_unit: @[ ! -d test/unit ] && echo "docstore has no unit tests" || $(DOCKER_COMPOSE) run --rm test_unit diff --git a/services/docstore/app.coffee b/services/docstore/app.coffee deleted file mode 100644 index c32e3a6b19..0000000000 --- a/services/docstore/app.coffee +++ /dev/null @@ -1,67 +0,0 @@ -Metrics = require "metrics-sharelatex" -Metrics.initialize("docstore") -Settings = require "settings-sharelatex" -logger = require "logger-sharelatex" -express = require "express" -bodyParser = require "body-parser" -Errors = require "./app/js/Errors" -HttpController = require "./app/js/HttpController" -Path = require "path" - - -logger.initialize("docstore") -Metrics.event_loop?.monitor(logger) - -app = express() - -app.use Metrics.http.monitor(logger) - -Metrics.injectMetricsRoute(app) - -app.param 'project_id', (req, res, next, project_id) -> - if project_id?.match /^[0-9a-f]{24}$/ - next() - else - next new Error("invalid project id") - -app.param 'doc_id', (req, res, next, doc_id) -> - if doc_id?.match /^[0-9a-f]{24}$/ - next() - else - next new Error("invalid doc id") - -Metrics.injectMetricsRoute(app) - -app.get '/project/:project_id/doc', HttpController.getAllDocs -app.get '/project/:project_id/ranges', HttpController.getAllRanges -app.get '/project/:project_id/doc/:doc_id', HttpController.getDoc -app.get '/project/:project_id/doc/:doc_id/raw', HttpController.getRawDoc -# Add 64kb overhead for the JSON encoding, and double the size to allow for ranges in the json payload -app.post '/project/:project_id/doc/:doc_id', bodyParser.json(limit: (Settings.max_doc_length + 64 * 1024) * 2), HttpController.updateDoc -app.del '/project/:project_id/doc/:doc_id', HttpController.deleteDoc - -app.post '/project/:project_id/archive', HttpController.archiveAllDocs -app.post '/project/:project_id/unarchive', HttpController.unArchiveAllDocs -app.post '/project/:project_id/destroy', HttpController.destroyAllDocs - -app.get "/health_check", HttpController.healthCheck - -app.get '/status', (req, res)-> - res.send('docstore is alive') - -app.use (error, req, res, next) -> - logger.error err: error, req:req, "request errored" - if error instanceof Errors.NotFoundError - res.send 404 - else - res.send(500, "Oops, something went wrong") - -port = Settings.internal.docstore.port -host = Settings.internal.docstore.host - -if !module.parent # Called directly - app.listen port, host, (error) -> - throw error if error? - logger.info "Docstore starting up, listening on #{host}:#{port}" - -module.exports = app diff --git a/services/docstore/app.js b/services/docstore/app.js new file mode 100644 index 0000000000..ebf7209b42 --- /dev/null +++ b/services/docstore/app.js @@ -0,0 +1,87 @@ +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const Metrics = require('metrics-sharelatex') +Metrics.initialize('docstore') +const Settings = require('settings-sharelatex') +const logger = require('logger-sharelatex') +const express = require('express') +const bodyParser = require('body-parser') +const Errors = require('./app/js/Errors') +const HttpController = require('./app/js/HttpController') + +logger.initialize('docstore') +if (Metrics.event_loop != null) { + Metrics.event_loop.monitor(logger) +} + +const app = express() + +app.use(Metrics.http.monitor(logger)) + +Metrics.injectMetricsRoute(app) + +app.param('project_id', function(req, res, next, projectId) { + if (projectId != null ? projectId.match(/^[0-9a-f]{24}$/) : undefined) { + return next() + } else { + return next(new Error('invalid project id')) + } +}) + +app.param('doc_id', function(req, res, next, docId) { + if (docId != null ? docId.match(/^[0-9a-f]{24}$/) : undefined) { + return next() + } else { + return next(new Error('invalid doc id')) + } +}) + +Metrics.injectMetricsRoute(app) + +app.get('/project/:project_id/doc', HttpController.getAllDocs) +app.get('/project/:project_id/ranges', HttpController.getAllRanges) +app.get('/project/:project_id/doc/:doc_id', HttpController.getDoc) +app.get('/project/:project_id/doc/:doc_id/raw', HttpController.getRawDoc) +// Add 64kb overhead for the JSON encoding, and double the size to allow for ranges in the json payload +app.post( + '/project/:project_id/doc/:doc_id', + bodyParser.json({ limit: (Settings.max_doc_length + 64 * 1024) * 2 }), + HttpController.updateDoc +) +app.del('/project/:project_id/doc/:doc_id', HttpController.deleteDoc) + +app.post('/project/:project_id/archive', HttpController.archiveAllDocs) +app.post('/project/:project_id/unarchive', HttpController.unArchiveAllDocs) +app.post('/project/:project_id/destroy', HttpController.destroyAllDocs) + +app.get('/health_check', HttpController.healthCheck) + +app.get('/status', (req, res) => res.send('docstore is alive')) + +app.use(function(error, req, res, next) { + logger.error({ err: error, req }, 'request errored') + if (error instanceof Errors.NotFoundError) { + return res.send(404) + } else { + return res.send(500, 'Oops, something went wrong') + } +}) + +const { port } = Settings.internal.docstore +const { host } = Settings.internal.docstore + +if (!module.parent) { + // Called directly + app.listen(port, host, function(error) { + if (error != null) { + throw error + } + return logger.info(`Docstore starting up, listening on ${host}:${port}`) + }) +} + +module.exports = app diff --git a/services/docstore/app/coffee/DocArchiveManager.coffee b/services/docstore/app/coffee/DocArchiveManager.coffee deleted file mode 100644 index 1fb6fb2657..0000000000 --- a/services/docstore/app/coffee/DocArchiveManager.coffee +++ /dev/null @@ -1,156 +0,0 @@ -MongoManager = require "./MongoManager" -Errors = require "./Errors" -logger = require "logger-sharelatex" -_ = require "underscore" -async = require "async" -settings = require("settings-sharelatex") -request = require("request") -crypto = require("crypto") -RangeManager = require("./RangeManager") -thirtySeconds = 30 * 1000 - -module.exports = DocArchive = - - archiveAllDocs: (project_id, callback = (err, docs) ->) -> - MongoManager.getProjectsDocs project_id, {include_deleted: true}, {lines: true, ranges: true, rev: true, inS3: true}, (err, docs) -> - if err? - return callback(err) - else if !docs? - return callback new Errors.NotFoundError("No docs for project #{project_id}") - docs = _.filter docs, (doc)-> doc.inS3 != true - jobs = _.map docs, (doc) -> - (cb)-> - DocArchive.archiveDoc project_id, doc, cb - async.parallelLimit jobs, 5, callback - - - archiveDoc: (project_id, doc, callback)-> - logger.log project_id: project_id, doc_id: doc._id, "sending doc to s3" - try - options = DocArchive.buildS3Options(project_id+"/"+doc._id) - catch e - return callback e - DocArchive._mongoDocToS3Doc doc, (error, json_doc) -> - return callback(error) if error? - options.body = json_doc - options.headers = - 'Content-Type': "application/json" - request.put options, (err, res) -> - if err? || res.statusCode != 200 - logger.err err:err, res:res, project_id:project_id, doc_id: doc._id, statusCode: res?.statusCode, "something went wrong archiving doc in aws" - return callback new Error("Error in S3 request") - md5lines = crypto.createHash("md5").update(json_doc, "utf8").digest("hex") - md5response = res.headers.etag.toString().replace(/\"/g, '') - if md5lines != md5response - logger.err responseMD5:md5response, linesMD5:md5lines, project_id:project_id, doc_id: doc?._id, "err in response md5 from s3" - return callback new Error("Error in S3 md5 response") - MongoManager.markDocAsArchived doc._id, doc.rev, (err) -> - return callback(err) if err? - callback() - - unArchiveAllDocs: (project_id, callback = (err) ->) -> - MongoManager.getArchivedProjectDocs project_id, (err, docs) -> - if err? - logger.err err:err, project_id:project_id, "error unarchiving all docs" - return callback(err) - else if !docs? - return callback new Errors.NotFoundError("No docs for project #{project_id}") - jobs = _.map docs, (doc) -> - (cb)-> - if !doc.inS3? - return cb() - else - DocArchive.unarchiveDoc project_id, doc._id, cb - async.parallelLimit jobs, 5, callback - - unarchiveDoc: (project_id, doc_id, callback)-> - logger.log project_id: project_id, doc_id: doc_id, "getting doc from s3" - try - options = DocArchive.buildS3Options(project_id+"/"+doc_id) - catch e - return callback e - options.json = true - request.get options, (err, res, doc)-> - if err? || res.statusCode != 200 - logger.err err:err, res:res, project_id:project_id, doc_id:doc_id, "something went wrong unarchiving doc from aws" - return callback new Errors.NotFoundError("Error in S3 request") - DocArchive._s3DocToMongoDoc doc, (error, mongo_doc) -> - return callback(error) if error? - MongoManager.upsertIntoDocCollection project_id, doc_id.toString(), mongo_doc, (err) -> - return callback(err) if err? - logger.log project_id: project_id, doc_id: doc_id, "deleting doc from s3" - DocArchive._deleteDocFromS3 project_id, doc_id, callback - - destroyAllDocs: (project_id, callback = (err) ->) -> - MongoManager.getProjectsDocs project_id, {include_deleted: true}, {_id: 1}, (err, docs) -> - if err? - logger.err err:err, project_id:project_id, "error getting project's docs" - return callback(err) - else if !docs? - return callback() - jobs = _.map docs, (doc) -> - (cb)-> - DocArchive.destroyDoc(project_id, doc._id, cb) - async.parallelLimit jobs, 5, callback - - destroyDoc: (project_id, doc_id, callback)-> - logger.log project_id: project_id, doc_id: doc_id, "removing doc from mongo and s3" - MongoManager.findDoc project_id, doc_id, {inS3: 1}, (error, doc) -> - return callback error if error? - return callback new Errors.NotFoundError("Doc not found in Mongo") unless doc? - if doc.inS3 == true - DocArchive._deleteDocFromS3 project_id, doc_id, (err) -> - return err if err? - MongoManager.destroyDoc doc_id, callback - else - MongoManager.destroyDoc doc_id, callback - - _deleteDocFromS3: (project_id, doc_id, callback) -> - try - options = DocArchive.buildS3Options(project_id+"/"+doc_id) - catch e - return callback e - options.json = true - request.del options, (err, res, body)-> - if err? || res.statusCode != 204 - logger.err err:err, res:res, project_id:project_id, doc_id:doc_id, "something went wrong deleting doc from aws" - return callback new Error("Error in S3 request") - callback() - - _s3DocToMongoDoc: (doc, callback = (error, mongo_doc) ->) -> - mongo_doc = {} - if doc.schema_v == 1 and doc.lines? - mongo_doc.lines = doc.lines - if doc.ranges? - mongo_doc.ranges = RangeManager.jsonRangesToMongo(doc.ranges) - else if doc instanceof Array - mongo_doc.lines = doc - else - return callback(new Error("I don't understand the doc format in s3")) - return callback null, mongo_doc - - _mongoDocToS3Doc: (doc, callback = (error, s3_doc) ->) -> - if !doc.lines? - return callback(new Error("doc has no lines")) - json = JSON.stringify({ - lines: doc.lines - ranges: doc.ranges - schema_v: 1 - }) - if json.indexOf("\u0000") != -1 - error = new Error("null bytes detected") - logger.err {err: error, doc, json}, error.message - return callback(error) - return callback null, json - - buildS3Options: (key)-> - if !settings.docstore.s3? - throw new Error("S3 settings are not configured") - return { - aws: - key: settings.docstore.s3.key - secret: settings.docstore.s3.secret - bucket: settings.docstore.s3.bucket - timeout: thirtySeconds - uri:"https://#{settings.docstore.s3.bucket}.s3.amazonaws.com/#{key}" - } diff --git a/services/docstore/app/coffee/DocManager.coffee b/services/docstore/app/coffee/DocManager.coffee deleted file mode 100644 index be55a23e44..0000000000 --- a/services/docstore/app/coffee/DocManager.coffee +++ /dev/null @@ -1,130 +0,0 @@ -MongoManager = require "./MongoManager" -Errors = require "./Errors" -logger = require "logger-sharelatex" -_ = require "underscore" -DocArchive = require "./DocArchiveManager" -RangeManager = require "./RangeManager" - -module.exports = DocManager = - - - # TODO: For historical reasons, the doc version is currently stored in the docOps - # collection (which is all that this collection contains). In future, we should - # migrate this version property to be part of the docs collection, to guarantee - # consitency between lines and version when writing/reading, and for a simpler schema. - _getDoc: (project_id, doc_id, filter = {}, callback = (error, doc) ->) -> - if filter.inS3 != true - return callback("must include inS3 when getting doc") - - MongoManager.findDoc project_id, doc_id, filter, (err, doc)-> - if err? - return callback(err) - else if !doc? - return callback new Errors.NotFoundError("No such doc: #{doc_id} in project #{project_id}") - else if doc?.inS3 - DocArchive.unarchiveDoc project_id, doc_id, (err)-> - if err? - logger.err err:err, project_id:project_id, doc_id:doc_id, "error unarchiving doc" - return callback(err) - DocManager._getDoc project_id, doc_id, filter, callback - else - if filter.version - MongoManager.getDocVersion doc_id, (error, version) -> - return callback(error) if error? - doc.version = version - callback err, doc - else - callback err, doc - - checkDocExists: (project_id, doc_id, callback = (err, exists)->)-> - DocManager._getDoc project_id, doc_id, {_id:1, inS3:true}, (err, doc)-> - if err? - return callback(err) - callback(err, doc?) - - getFullDoc: (project_id, doc_id, callback = (err, doc)->)-> - DocManager._getDoc project_id, doc_id, {lines: true, rev: true, deleted: true, version: true, ranges: true, inS3:true}, (err, doc)-> - if err? - return callback(err) - callback(err, doc) - - - getDocLines: (project_id, doc_id, callback = (err, doc)->)-> - DocManager._getDoc project_id, doc_id, {lines:true, inS3:true}, (err, doc)-> - if err? - return callback(err) - callback(err, doc) - - getAllNonDeletedDocs: (project_id, filter, callback = (error, docs) ->) -> - DocArchive.unArchiveAllDocs project_id, (error) -> - if error? - return callback(error) - MongoManager.getProjectsDocs project_id, {include_deleted: false}, filter, (error, docs) -> - if err? - return callback(error) - else if !docs? - return callback new Errors.NotFoundError("No docs for project #{project_id}") - else - return callback(null, docs) - - updateDoc: (project_id, doc_id, lines, version, ranges, callback = (error, modified, rev) ->) -> - if !lines? or !version? or !ranges? - return callback(new Error("no lines, version or ranges provided")) - - DocManager._getDoc project_id, doc_id, {version: true, rev: true, lines: true, version: true, ranges: true, inS3:true}, (err, doc)-> - if err? and !(err instanceof Errors.NotFoundError) - logger.err project_id: project_id, doc_id: doc_id, err:err, "error getting document for update" - return callback(err) - - ranges = RangeManager.jsonRangesToMongo(ranges) - - if !doc? - # If the document doesn't exist, we'll make sure to create/update all parts of it. - updateLines = true - updateVersion = true - updateRanges = true - else - updateLines = not _.isEqual(doc.lines, lines) - updateVersion = (doc.version != version) - updateRanges = RangeManager.shouldUpdateRanges(doc.ranges, ranges) - - modified = false - rev = doc?.rev || 0 - - updateLinesAndRangesIfNeeded = (cb) -> - if updateLines or updateRanges - update = {} - if updateLines - update.lines = lines - if updateRanges - update.ranges = ranges - logger.log { project_id, doc_id }, "updating doc lines and ranges" - - modified = true - rev += 1 # rev will be incremented in mongo by MongoManager.upsertIntoDocCollection - MongoManager.upsertIntoDocCollection project_id, doc_id, update, cb - else - logger.log { project_id, doc_id, }, "doc lines have not changed - not updating" - cb() - - updateVersionIfNeeded = (cb) -> - if updateVersion - logger.log { project_id, doc_id, oldVersion: doc?.version, newVersion: version }, "updating doc version" - modified = true - MongoManager.setDocVersion doc_id, version, cb - else - logger.log { project_id, doc_id, version }, "doc version has not changed - not updating" - cb() - - updateLinesAndRangesIfNeeded (error) -> - return callback(error) if error? - updateVersionIfNeeded (error) -> - return callback(error) if error? - callback null, modified, rev - - deleteDoc: (project_id, doc_id, callback = (error) ->) -> - DocManager.checkDocExists project_id, doc_id, (error, exists) -> - return callback(error) if error? - return callback new Errors.NotFoundError("No such project/doc to delete: #{project_id}/#{doc_id}") if !exists - MongoManager.markDocAsDeleted project_id, doc_id, callback - diff --git a/services/docstore/app/coffee/Errors.coffee b/services/docstore/app/coffee/Errors.coffee deleted file mode 100644 index 4a29822efc..0000000000 --- a/services/docstore/app/coffee/Errors.coffee +++ /dev/null @@ -1,10 +0,0 @@ -NotFoundError = (message) -> - error = new Error(message) - error.name = "NotFoundError" - error.__proto__ = NotFoundError.prototype - return error -NotFoundError.prototype.__proto__ = Error.prototype - -module.exports = Errors = - NotFoundError: NotFoundError - diff --git a/services/docstore/app/coffee/HealthChecker.coffee b/services/docstore/app/coffee/HealthChecker.coffee deleted file mode 100644 index 4280f8aa58..0000000000 --- a/services/docstore/app/coffee/HealthChecker.coffee +++ /dev/null @@ -1,44 +0,0 @@ -{db, ObjectId} = require "./mongojs" -request = require("request") -async = require("async") -_ = require("underscore") -crypto = require("crypto") -settings = require("settings-sharelatex") -port = settings.internal.docstore.port -logger = require "logger-sharelatex" - - -module.exports = - check : (callback)-> - doc_id = ObjectId() - project_id = ObjectId(settings.docstore.healthCheck.project_id) - url = "http://localhost:#{port}/project/#{project_id}/doc/#{doc_id}" - lines = ["smoke test - delete me", "#{crypto.randomBytes(32).toString("hex")}"] - getOpts = -> {url:url, timeout:3000} - logger.log lines:lines, url:url, doc_id:doc_id, project_id:project_id, "running health check" - jobs = [ - (cb)-> - opts = getOpts() - opts.json = {lines: lines, version: 42, ranges: {}} - request.post(opts, cb) - (cb)-> - opts = getOpts() - opts.json = true - request.get opts, (err, res, body)-> - if err? - logger.err err:err, "docstore returned a error in health check get" - cb(err) - else if !res? - cb("no response from docstore with get check") - else if res?.statusCode != 200 - cb("status code not 200, its #{res.statusCode}") - else if _.isEqual(body?.lines, lines) and body?._id == doc_id.toString() - cb() - else - cb("health check lines not equal #{body.lines} != #{lines}") - (cb)-> - db.docs.remove {_id: doc_id, project_id: project_id}, cb - (cb)-> - db.docOps.remove {doc_id: doc_id}, cb - ] - async.series jobs, callback diff --git a/services/docstore/app/coffee/HttpController.coffee b/services/docstore/app/coffee/HttpController.coffee deleted file mode 100644 index cec535e1cd..0000000000 --- a/services/docstore/app/coffee/HttpController.coffee +++ /dev/null @@ -1,144 +0,0 @@ -DocManager = require "./DocManager" -logger = require "logger-sharelatex" -DocArchive = require "./DocArchiveManager" -HealthChecker = require "./HealthChecker" -Settings = require "settings-sharelatex" - - -module.exports = HttpController = - getDoc: (req, res, next = (error) ->) -> - project_id = req.params.project_id - doc_id = req.params.doc_id - include_deleted = req.query?.include_deleted == "true" - logger.log project_id: project_id, doc_id: doc_id, "getting doc" - DocManager.getFullDoc project_id, doc_id, (error, doc) -> - return next(error) if error? - logger.log {doc_id, project_id}, "got doc" - if !doc? - res.send 404 - else if doc.deleted && !include_deleted - res.send 404 - else - res.json HttpController._buildDocView(doc) - - getRawDoc: (req, res, next = (error)->)-> - project_id = req.params.project_id - doc_id = req.params.doc_id - logger.log project_id: project_id, doc_id: doc_id, "getting raw doc" - DocManager.getDocLines project_id, doc_id, (error, doc) -> - return next(error) if error? - if !doc? - res.send 404 - else - res.setHeader('content-type', 'text/plain') - res.send HttpController._buildRawDocView(doc) - - getAllDocs: (req, res, next = (error) ->) -> - project_id = req.params.project_id - logger.log project_id: project_id, "getting all docs" - DocManager.getAllNonDeletedDocs project_id, {lines: true, rev: true}, (error, docs = []) -> - return next(error) if error? - res.json HttpController._buildDocsArrayView(project_id, docs) - - getAllRanges: (req, res, next = (error) ->) -> - project_id = req.params.project_id - logger.log {project_id}, "getting all ranges" - DocManager.getAllNonDeletedDocs project_id, {ranges: true}, (error, docs = []) -> - return next(error) if error? - res.json HttpController._buildDocsArrayView(project_id, docs) - - updateDoc: (req, res, next = (error) ->) -> - project_id = req.params.project_id - doc_id = req.params.doc_id - lines = req.body?.lines - version = req.body?.version - ranges = req.body?.ranges - - if !lines? or lines not instanceof Array - logger.error project_id: project_id, doc_id: doc_id, "no doc lines provided" - res.send 400 # Bad Request - return - - if !version? or typeof version is not "number" - logger.error project_id: project_id, doc_id: doc_id, "no doc version provided" - res.send 400 # Bad Request - return - - if !ranges? - logger.error project_id: project_id, doc_id: doc_id, "no doc ranges provided" - res.send 400 # Bad Request - return - - bodyLength = lines.reduce( - (len, line) => line.length + len - 0 - ) - if bodyLength > Settings.max_doc_length - logger.error project_id: project_id, doc_id: doc_id, bodyLength: bodyLength, "document body too large" - res.status(413).send("document body too large") - return - - logger.log project_id: project_id, doc_id: doc_id, "got http request to update doc" - DocManager.updateDoc project_id, doc_id, lines, version, ranges, (error, modified, rev) -> - return next(error) if error? - res.json { - modified: modified - rev: rev - } - - deleteDoc: (req, res, next = (error) ->) -> - project_id = req.params.project_id - doc_id = req.params.doc_id - logger.log project_id: project_id, doc_id: doc_id, "deleting doc" - DocManager.deleteDoc project_id, doc_id, (error) -> - return next(error) if error? - res.send 204 - - _buildDocView: (doc) -> - doc_view = { _id: doc._id?.toString() } - for attribute in ["lines", "rev", "version", "ranges", "deleted"] - if doc[attribute]? - doc_view[attribute] = doc[attribute] - return doc_view - - _buildRawDocView: (doc)-> - return (doc?.lines or []).join("\n") - - _buildDocsArrayView: (project_id, docs) -> - docViews = [] - for doc in docs - if doc? # There can end up being null docs for some reason :( (probably a race condition) - docViews.push HttpController._buildDocView(doc) - else - logger.error err: new Error("null doc"), project_id: project_id, "encountered null doc" - return docViews - - archiveAllDocs: (req, res, next = (error) ->) -> - project_id = req.params.project_id - logger.log project_id: project_id, "archiving all docs" - DocArchive.archiveAllDocs project_id, (error) -> - return next(error) if error? - res.send 204 - - unArchiveAllDocs: (req, res, next = (error) ->) -> - project_id = req.params.project_id - logger.log project_id: project_id, "unarchiving all docs" - DocArchive.unArchiveAllDocs project_id, (error) -> - return next(error) if error? - res.send 200 - - destroyAllDocs: (req, res, next = (error) ->) -> - project_id = req.params.project_id - logger.log project_id: project_id, "destroying all docs" - DocArchive.destroyAllDocs project_id, (error) -> - return next(error) if error? - res.send 204 - - healthCheck: (req, res)-> - HealthChecker.check (err)-> - if err? - logger.err err:err, "error performing health check" - res.send 500 - else - res.send 200 - diff --git a/services/docstore/app/coffee/MongoManager.coffee b/services/docstore/app/coffee/MongoManager.coffee deleted file mode 100644 index 3bd9eb34c5..0000000000 --- a/services/docstore/app/coffee/MongoManager.coffee +++ /dev/null @@ -1,93 +0,0 @@ -{db, ObjectId} = require "./mongojs" -logger = require 'logger-sharelatex' -metrics = require 'metrics-sharelatex' - -module.exports = MongoManager = - - findDoc: (project_id, doc_id, filter, callback = (error, doc) ->) -> - db.docs.find {_id: ObjectId(doc_id.toString()), project_id: ObjectId(project_id.toString())}, filter, (error, docs = []) -> - callback error, docs[0] - - getProjectsDocs: (project_id, options = {include_deleted: true}, filter, callback)-> - query = {project_id: ObjectId(project_id.toString())} - if !options.include_deleted - query.deleted = { $ne: true } - db.docs.find query, filter, callback - - getArchivedProjectDocs: (project_id, callback)-> - query = - project_id: ObjectId(project_id.toString()) - inS3: true - db.docs.find query, {}, callback - - upsertIntoDocCollection: (project_id, doc_id, updates, callback)-> - update = - $set: updates - $inc: - rev: 1 - $unset: - inS3: true - update.$set["project_id"] = ObjectId(project_id) - db.docs.update _id: ObjectId(doc_id), update, {upsert: true}, callback - - markDocAsDeleted: (project_id, doc_id, callback)-> - db.docs.update { - _id: ObjectId(doc_id), - project_id: ObjectId(project_id) - }, { - $set: { deleted: true } - }, callback - - markDocAsArchived: (doc_id, rev, callback)-> - update = - $set: {} - $unset: {} - update.$set["inS3"] = true - update.$unset["lines"] = true - update.$unset["ranges"] = true - query = - _id: doc_id - rev: rev - db.docs.update query, update, (err)-> - callback(err) - - getDocVersion: (doc_id, callback = (error, version) ->) -> - db.docOps.find { - doc_id: ObjectId(doc_id) - }, { - version: 1 - }, (error, docs) -> - return callback(error) if error? - if docs.length < 1 or !docs[0].version? - return callback null, 0 - else - return callback null, docs[0].version - - setDocVersion: (doc_id, version, callback = (error) ->) -> - db.docOps.update { - doc_id: ObjectId(doc_id) - }, { - $set: version: version - }, { - upsert: true - }, callback - - destroyDoc: (doc_id, callback) -> - db.docs.remove { - _id: ObjectId(doc_id) - }, (err) -> - return callback(err) if err? - db.docOps.remove { - doc_id: ObjectId(doc_id) - }, callback - -[ - 'findDoc', - 'getProjectsDocs', - 'getArchivedProjectDocs', - 'upsertIntoDocCollection', - 'markDocAsArchived', - 'getDocVersion', - 'setDocVersion' -].map (method) -> - metrics.timeAsyncMethod(MongoManager, method, 'mongo.MongoManager', logger) diff --git a/services/docstore/app/coffee/RangeManager.coffee b/services/docstore/app/coffee/RangeManager.coffee deleted file mode 100644 index 4867ffbe19..0000000000 --- a/services/docstore/app/coffee/RangeManager.coffee +++ /dev/null @@ -1,40 +0,0 @@ -_ = require "underscore" -{ObjectId} = require("./mongojs") - -module.exports = RangeManager = - shouldUpdateRanges: (doc_ranges, incoming_ranges) -> - if !incoming_ranges? - throw new Error("expected incoming_ranges") - - # If the ranges are empty, we don't store them in the DB, so set - # doc_ranges to an empty object as default, since this is was the - # incoming_ranges will be for an empty range set. - if !doc_ranges? - doc_ranges = {} - - return not _.isEqual(doc_ranges, incoming_ranges) - - jsonRangesToMongo: (ranges) -> - return null if !ranges? - - updateMetadata = (metadata) -> - if metadata?.ts? - metadata.ts = new Date(metadata.ts) - if metadata?.user_id? - metadata.user_id = RangeManager._safeObjectId(metadata.user_id) - - for change in ranges.changes or [] - change.id = RangeManager._safeObjectId(change.id) - updateMetadata(change.metadata) - for comment in ranges.comments or [] - comment.id = RangeManager._safeObjectId(comment.id) - if comment.op?.t? - comment.op.t = RangeManager._safeObjectId(comment.op.t) - updateMetadata(comment.metadata) - return ranges - - _safeObjectId: (data) -> - try - return ObjectId(data) - catch error - return data \ No newline at end of file diff --git a/services/docstore/app/coffee/mongojs.coffee b/services/docstore/app/coffee/mongojs.coffee deleted file mode 100644 index 0153c3cfcd..0000000000 --- a/services/docstore/app/coffee/mongojs.coffee +++ /dev/null @@ -1,7 +0,0 @@ -Settings = require "settings-sharelatex" -mongojs = require "mongojs" -db = mongojs(Settings.mongo.url, ["docs", "docOps"]) -module.exports = - db: db - ObjectId: mongojs.ObjectId - diff --git a/services/docstore/app/js/DocArchiveManager.js b/services/docstore/app/js/DocArchiveManager.js new file mode 100644 index 0000000000..af1af21957 --- /dev/null +++ b/services/docstore/app/js/DocArchiveManager.js @@ -0,0 +1,292 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-useless-escape, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let DocArchive +const MongoManager = require('./MongoManager') +const Errors = require('./Errors') +const logger = require('logger-sharelatex') +const _ = require('underscore') +const async = require('async') +const settings = require('settings-sharelatex') +const request = require('request') +const crypto = require('crypto') +const RangeManager = require('./RangeManager') +const thirtySeconds = 30 * 1000 + +module.exports = DocArchive = { + archiveAllDocs(project_id, callback) { + if (callback == null) { + callback = function(err, docs) {} + } + return MongoManager.getProjectsDocs( + project_id, + { include_deleted: true }, + { lines: true, ranges: true, rev: true, inS3: true }, + function(err, docs) { + if (err != null) { + return callback(err) + } else if (docs == null) { + return callback( + new Errors.NotFoundError(`No docs for project ${project_id}`) + ) + } + docs = _.filter(docs, doc => doc.inS3 !== true) + const jobs = _.map(docs, doc => cb => + DocArchive.archiveDoc(project_id, doc, cb) + ) + return async.parallelLimit(jobs, 5, callback) + } + ) + }, + + archiveDoc(project_id, doc, callback) { + let options + logger.log({ project_id, doc_id: doc._id }, 'sending doc to s3') + try { + options = DocArchive.buildS3Options(project_id + '/' + doc._id) + } catch (e) { + return callback(e) + } + return DocArchive._mongoDocToS3Doc(doc, function(error, json_doc) { + if (error != null) { + return callback(error) + } + options.body = json_doc + options.headers = { 'Content-Type': 'application/json' } + return request.put(options, function(err, res) { + if (err != null || res.statusCode !== 200) { + logger.err( + { + err, + res, + project_id, + doc_id: doc._id, + statusCode: res != null ? res.statusCode : undefined + }, + 'something went wrong archiving doc in aws' + ) + return callback(new Error('Error in S3 request')) + } + const md5lines = crypto + .createHash('md5') + .update(json_doc, 'utf8') + .digest('hex') + const md5response = res.headers.etag.toString().replace(/\"/g, '') + if (md5lines !== md5response) { + logger.err( + { + responseMD5: md5response, + linesMD5: md5lines, + project_id, + doc_id: doc != null ? doc._id : undefined + }, + 'err in response md5 from s3' + ) + return callback(new Error('Error in S3 md5 response')) + } + return MongoManager.markDocAsArchived(doc._id, doc.rev, function(err) { + if (err != null) { + return callback(err) + } + return callback() + }) + }) + }) + }, + + unArchiveAllDocs(project_id, callback) { + if (callback == null) { + callback = function(err) {} + } + return MongoManager.getArchivedProjectDocs(project_id, function(err, docs) { + if (err != null) { + logger.err({ err, project_id }, 'error unarchiving all docs') + return callback(err) + } else if (docs == null) { + return callback( + new Errors.NotFoundError(`No docs for project ${project_id}`) + ) + } + const jobs = _.map( + docs, + doc => + function(cb) { + if (doc.inS3 == null) { + return cb() + } else { + return DocArchive.unarchiveDoc(project_id, doc._id, cb) + } + } + ) + return async.parallelLimit(jobs, 5, callback) + }) + }, + + unarchiveDoc(project_id, doc_id, callback) { + let options + logger.log({ project_id, doc_id }, 'getting doc from s3') + try { + options = DocArchive.buildS3Options(project_id + '/' + doc_id) + } catch (e) { + return callback(e) + } + options.json = true + return request.get(options, function(err, res, doc) { + if (err != null || res.statusCode !== 200) { + logger.err( + { err, res, project_id, doc_id }, + 'something went wrong unarchiving doc from aws' + ) + return callback(new Errors.NotFoundError('Error in S3 request')) + } + return DocArchive._s3DocToMongoDoc(doc, function(error, mongo_doc) { + if (error != null) { + return callback(error) + } + return MongoManager.upsertIntoDocCollection( + project_id, + doc_id.toString(), + mongo_doc, + function(err) { + if (err != null) { + return callback(err) + } + logger.log({ project_id, doc_id }, 'deleting doc from s3') + return DocArchive._deleteDocFromS3(project_id, doc_id, callback) + } + ) + }) + }) + }, + + destroyAllDocs(project_id, callback) { + if (callback == null) { + callback = function(err) {} + } + return MongoManager.getProjectsDocs( + project_id, + { include_deleted: true }, + { _id: 1 }, + function(err, docs) { + if (err != null) { + logger.err({ err, project_id }, "error getting project's docs") + return callback(err) + } else if (docs == null) { + return callback() + } + const jobs = _.map(docs, doc => cb => + DocArchive.destroyDoc(project_id, doc._id, cb) + ) + return async.parallelLimit(jobs, 5, callback) + } + ) + }, + + destroyDoc(project_id, doc_id, callback) { + logger.log({ project_id, doc_id }, 'removing doc from mongo and s3') + return MongoManager.findDoc(project_id, doc_id, { inS3: 1 }, function( + error, + doc + ) { + if (error != null) { + return callback(error) + } + if (doc == null) { + return callback(new Errors.NotFoundError('Doc not found in Mongo')) + } + if (doc.inS3 === true) { + return DocArchive._deleteDocFromS3(project_id, doc_id, function(err) { + if (err != null) { + return err + } + return MongoManager.destroyDoc(doc_id, callback) + }) + } else { + return MongoManager.destroyDoc(doc_id, callback) + } + }) + }, + + _deleteDocFromS3(project_id, doc_id, callback) { + let options + try { + options = DocArchive.buildS3Options(project_id + '/' + doc_id) + } catch (e) { + return callback(e) + } + options.json = true + return request.del(options, function(err, res, body) { + if (err != null || res.statusCode !== 204) { + logger.err( + { err, res, project_id, doc_id }, + 'something went wrong deleting doc from aws' + ) + return callback(new Error('Error in S3 request')) + } + return callback() + }) + }, + + _s3DocToMongoDoc(doc, callback) { + if (callback == null) { + callback = function(error, mongo_doc) {} + } + const mongo_doc = {} + if (doc.schema_v === 1 && doc.lines != null) { + mongo_doc.lines = doc.lines + if (doc.ranges != null) { + mongo_doc.ranges = RangeManager.jsonRangesToMongo(doc.ranges) + } + } else if (doc instanceof Array) { + mongo_doc.lines = doc + } else { + return callback(new Error("I don't understand the doc format in s3")) + } + return callback(null, mongo_doc) + }, + + _mongoDocToS3Doc(doc, callback) { + if (callback == null) { + callback = function(error, s3_doc) {} + } + if (doc.lines == null) { + return callback(new Error('doc has no lines')) + } + const json = JSON.stringify({ + lines: doc.lines, + ranges: doc.ranges, + schema_v: 1 + }) + if (json.indexOf('\u0000') !== -1) { + const error = new Error('null bytes detected') + logger.err({ err: error, doc, json }, error.message) + return callback(error) + } + return callback(null, json) + }, + + buildS3Options(key) { + if (settings.docstore.s3 == null) { + throw new Error('S3 settings are not configured') + } + return { + aws: { + key: settings.docstore.s3.key, + secret: settings.docstore.s3.secret, + bucket: settings.docstore.s3.bucket + }, + timeout: thirtySeconds, + uri: `https://${settings.docstore.s3.bucket}.s3.amazonaws.com/${key}` + } + } +} diff --git a/services/docstore/app/js/DocManager.js b/services/docstore/app/js/DocManager.js new file mode 100644 index 0000000000..15a782861b --- /dev/null +++ b/services/docstore/app/js/DocManager.js @@ -0,0 +1,289 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-dupe-keys, + no-undef, + standard/no-callback-literal, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let DocManager +const MongoManager = require('./MongoManager') +const Errors = require('./Errors') +const logger = require('logger-sharelatex') +const _ = require('underscore') +const DocArchive = require('./DocArchiveManager') +const RangeManager = require('./RangeManager') + +module.exports = DocManager = { + // TODO: For historical reasons, the doc version is currently stored in the docOps + // collection (which is all that this collection contains). In future, we should + // migrate this version property to be part of the docs collection, to guarantee + // consitency between lines and version when writing/reading, and for a simpler schema. + _getDoc(project_id, doc_id, filter, callback) { + if (filter == null) { + filter = {} + } + if (callback == null) { + callback = function(error, doc) {} + } + if (filter.inS3 !== true) { + return callback('must include inS3 when getting doc') + } + + return MongoManager.findDoc(project_id, doc_id, filter, function(err, doc) { + if (err != null) { + return callback(err) + } else if (doc == null) { + return callback( + new Errors.NotFoundError( + `No such doc: ${doc_id} in project ${project_id}` + ) + ) + } else if (doc != null ? doc.inS3 : undefined) { + return DocArchive.unarchiveDoc(project_id, doc_id, function(err) { + if (err != null) { + logger.err({ err, project_id, doc_id }, 'error unarchiving doc') + return callback(err) + } + return DocManager._getDoc(project_id, doc_id, filter, callback) + }) + } else { + if (filter.version) { + return MongoManager.getDocVersion(doc_id, function(error, version) { + if (error != null) { + return callback(error) + } + doc.version = version + return callback(err, doc) + }) + } else { + return callback(err, doc) + } + } + }) + }, + + checkDocExists(project_id, doc_id, callback) { + if (callback == null) { + callback = function(err, exists) {} + } + return DocManager._getDoc( + project_id, + doc_id, + { _id: 1, inS3: true }, + function(err, doc) { + if (err != null) { + return callback(err) + } + return callback(err, doc != null) + } + ) + }, + + getFullDoc(project_id, doc_id, callback) { + if (callback == null) { + callback = function(err, doc) {} + } + return DocManager._getDoc( + project_id, + doc_id, + { + lines: true, + rev: true, + deleted: true, + version: true, + ranges: true, + inS3: true + }, + function(err, doc) { + if (err != null) { + return callback(err) + } + return callback(err, doc) + } + ) + }, + + getDocLines(project_id, doc_id, callback) { + if (callback == null) { + callback = function(err, doc) {} + } + return DocManager._getDoc( + project_id, + doc_id, + { lines: true, inS3: true }, + function(err, doc) { + if (err != null) { + return callback(err) + } + return callback(err, doc) + } + ) + }, + + getAllNonDeletedDocs(project_id, filter, callback) { + if (callback == null) { + callback = function(error, docs) {} + } + return DocArchive.unArchiveAllDocs(project_id, function(error) { + if (error != null) { + return callback(error) + } + return MongoManager.getProjectsDocs( + project_id, + { include_deleted: false }, + filter, + function(error, docs) { + if (typeof err !== 'undefined' && err !== null) { + return callback(error) + } else if (docs == null) { + return callback( + new Errors.NotFoundError(`No docs for project ${project_id}`) + ) + } else { + return callback(null, docs) + } + } + ) + }) + }, + + updateDoc(project_id, doc_id, lines, version, ranges, callback) { + if (callback == null) { + callback = function(error, modified, rev) {} + } + if (lines == null || version == null || ranges == null) { + return callback(new Error('no lines, version or ranges provided')) + } + + return DocManager._getDoc( + project_id, + doc_id, + { + version: true, + rev: true, + lines: true, + version: true, + ranges: true, + inS3: true + }, + function(err, doc) { + let updateLines, updateRanges, updateVersion + if (err != null && !(err instanceof Errors.NotFoundError)) { + logger.err( + { project_id, doc_id, err }, + 'error getting document for update' + ) + return callback(err) + } + + ranges = RangeManager.jsonRangesToMongo(ranges) + + if (doc == null) { + // If the document doesn't exist, we'll make sure to create/update all parts of it. + updateLines = true + updateVersion = true + updateRanges = true + } else { + updateLines = !_.isEqual(doc.lines, lines) + updateVersion = doc.version !== version + updateRanges = RangeManager.shouldUpdateRanges(doc.ranges, ranges) + } + + let modified = false + let rev = (doc != null ? doc.rev : undefined) || 0 + + const updateLinesAndRangesIfNeeded = function(cb) { + if (updateLines || updateRanges) { + const update = {} + if (updateLines) { + update.lines = lines + } + if (updateRanges) { + update.ranges = ranges + } + logger.log({ project_id, doc_id }, 'updating doc lines and ranges') + + modified = true + rev += 1 // rev will be incremented in mongo by MongoManager.upsertIntoDocCollection + return MongoManager.upsertIntoDocCollection( + project_id, + doc_id, + update, + cb + ) + } else { + logger.log( + { project_id, doc_id }, + 'doc lines have not changed - not updating' + ) + return cb() + } + } + + const updateVersionIfNeeded = function(cb) { + if (updateVersion) { + logger.log( + { + project_id, + doc_id, + oldVersion: doc != null ? doc.version : undefined, + newVersion: version + }, + 'updating doc version' + ) + modified = true + return MongoManager.setDocVersion(doc_id, version, cb) + } else { + logger.log( + { project_id, doc_id, version }, + 'doc version has not changed - not updating' + ) + return cb() + } + } + + return updateLinesAndRangesIfNeeded(function(error) { + if (error != null) { + return callback(error) + } + return updateVersionIfNeeded(function(error) { + if (error != null) { + return callback(error) + } + return callback(null, modified, rev) + }) + }) + } + ) + }, + + deleteDoc(project_id, doc_id, callback) { + if (callback == null) { + callback = function(error) {} + } + return DocManager.checkDocExists(project_id, doc_id, function( + error, + exists + ) { + if (error != null) { + return callback(error) + } + if (!exists) { + return callback( + new Errors.NotFoundError( + `No such project/doc to delete: ${project_id}/${doc_id}` + ) + ) + } + return MongoManager.markDocAsDeleted(project_id, doc_id, callback) + }) + } +} diff --git a/services/docstore/app/js/Errors.js b/services/docstore/app/js/Errors.js new file mode 100644 index 0000000000..f3bc8e37eb --- /dev/null +++ b/services/docstore/app/js/Errors.js @@ -0,0 +1,16 @@ +/* eslint-disable + no-proto, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +let Errors +var NotFoundError = function(message) { + const error = new Error(message) + error.name = 'NotFoundError' + error.__proto__ = NotFoundError.prototype + return error +} +NotFoundError.prototype.__proto__ = Error.prototype + +module.exports = Errors = { NotFoundError } diff --git a/services/docstore/app/js/HealthChecker.js b/services/docstore/app/js/HealthChecker.js new file mode 100644 index 0000000000..87b16a9951 --- /dev/null +++ b/services/docstore/app/js/HealthChecker.js @@ -0,0 +1,68 @@ +/* eslint-disable + camelcase, + standard/no-callback-literal, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const { db, ObjectId } = require('./mongojs') +const request = require('request') +const async = require('async') +const _ = require('underscore') +const crypto = require('crypto') +const settings = require('settings-sharelatex') +const { port } = settings.internal.docstore +const logger = require('logger-sharelatex') + +module.exports = { + check(callback) { + const doc_id = ObjectId() + const project_id = ObjectId(settings.docstore.healthCheck.project_id) + const url = `http://localhost:${port}/project/${project_id}/doc/${doc_id}` + const lines = [ + 'smoke test - delete me', + `${crypto.randomBytes(32).toString('hex')}` + ] + const getOpts = () => ({ + url, + timeout: 3000 + }) + logger.log({ lines, url, doc_id, project_id }, 'running health check') + const jobs = [ + function(cb) { + const opts = getOpts() + opts.json = { lines, version: 42, ranges: {} } + return request.post(opts, cb) + }, + function(cb) { + const opts = getOpts() + opts.json = true + return request.get(opts, function(err, res, body) { + if (err != null) { + logger.err({ err }, 'docstore returned a error in health check get') + return cb(err) + } else if (res == null) { + return cb('no response from docstore with get check') + } else if ((res != null ? res.statusCode : undefined) !== 200) { + return cb(`status code not 200, its ${res.statusCode}`) + } else if ( + _.isEqual(body != null ? body.lines : undefined, lines) && + (body != null ? body._id : undefined) === doc_id.toString() + ) { + return cb() + } else { + return cb(`health check lines not equal ${body.lines} != ${lines}`) + } + }) + }, + cb => db.docs.remove({ _id: doc_id, project_id }, cb), + cb => db.docOps.remove({ doc_id }, cb) + ] + return async.series(jobs, callback) + } +} diff --git a/services/docstore/app/js/HttpController.js b/services/docstore/app/js/HttpController.js new file mode 100644 index 0000000000..7e17c15b65 --- /dev/null +++ b/services/docstore/app/js/HttpController.js @@ -0,0 +1,263 @@ +/* eslint-disable + camelcase, + handle-callback-err, + valid-typeof, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let HttpController +const DocManager = require('./DocManager') +const logger = require('logger-sharelatex') +const DocArchive = require('./DocArchiveManager') +const HealthChecker = require('./HealthChecker') +const Settings = require('settings-sharelatex') + +module.exports = HttpController = { + getDoc(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + const { doc_id } = req.params + const include_deleted = + (req.query != null ? req.query.include_deleted : undefined) === 'true' + logger.log({ project_id, doc_id }, 'getting doc') + return DocManager.getFullDoc(project_id, doc_id, function(error, doc) { + if (error != null) { + return next(error) + } + logger.log({ doc_id, project_id }, 'got doc') + if (doc == null) { + return res.send(404) + } else if (doc.deleted && !include_deleted) { + return res.send(404) + } else { + return res.json(HttpController._buildDocView(doc)) + } + }) + }, + + getRawDoc(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + const { doc_id } = req.params + logger.log({ project_id, doc_id }, 'getting raw doc') + return DocManager.getDocLines(project_id, doc_id, function(error, doc) { + if (error != null) { + return next(error) + } + if (doc == null) { + return res.send(404) + } else { + res.setHeader('content-type', 'text/plain') + return res.send(HttpController._buildRawDocView(doc)) + } + }) + }, + + getAllDocs(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + logger.log({ project_id }, 'getting all docs') + return DocManager.getAllNonDeletedDocs( + project_id, + { lines: true, rev: true }, + function(error, docs) { + if (docs == null) { + docs = [] + } + if (error != null) { + return next(error) + } + return res.json(HttpController._buildDocsArrayView(project_id, docs)) + } + ) + }, + + getAllRanges(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + logger.log({ project_id }, 'getting all ranges') + return DocManager.getAllNonDeletedDocs( + project_id, + { ranges: true }, + function(error, docs) { + if (docs == null) { + docs = [] + } + if (error != null) { + return next(error) + } + return res.json(HttpController._buildDocsArrayView(project_id, docs)) + } + ) + }, + + updateDoc(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + const { doc_id } = req.params + const lines = req.body != null ? req.body.lines : undefined + const version = req.body != null ? req.body.version : undefined + const ranges = req.body != null ? req.body.ranges : undefined + + if (lines == null || !(lines instanceof Array)) { + logger.error({ project_id, doc_id }, 'no doc lines provided') + res.send(400) // Bad Request + return + } + + if (version == null || typeof version === !'number') { + logger.error({ project_id, doc_id }, 'no doc version provided') + res.send(400) // Bad Request + return + } + + if (ranges == null) { + logger.error({ project_id, doc_id }, 'no doc ranges provided') + res.send(400) // Bad Request + return + } + + const bodyLength = lines.reduce((len, line) => line.length + len, 0) + if (bodyLength > Settings.max_doc_length) { + logger.error( + { project_id, doc_id, bodyLength }, + 'document body too large' + ) + res.status(413).send('document body too large') + return + } + + logger.log({ project_id, doc_id }, 'got http request to update doc') + return DocManager.updateDoc( + project_id, + doc_id, + lines, + version, + ranges, + function(error, modified, rev) { + if (error != null) { + return next(error) + } + return res.json({ + modified, + rev + }) + } + ) + }, + + deleteDoc(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + const { doc_id } = req.params + logger.log({ project_id, doc_id }, 'deleting doc') + return DocManager.deleteDoc(project_id, doc_id, function(error) { + if (error != null) { + return next(error) + } + return res.send(204) + }) + }, + + _buildDocView(doc) { + const doc_view = { _id: doc._id != null ? doc._id.toString() : undefined } + for (const attribute of ['lines', 'rev', 'version', 'ranges', 'deleted']) { + if (doc[attribute] != null) { + doc_view[attribute] = doc[attribute] + } + } + return doc_view + }, + + _buildRawDocView(doc) { + return ((doc != null ? doc.lines : undefined) || []).join('\n') + }, + + _buildDocsArrayView(project_id, docs) { + const docViews = [] + for (const doc of Array.from(docs)) { + if (doc != null) { + // There can end up being null docs for some reason :( (probably a race condition) + docViews.push(HttpController._buildDocView(doc)) + } else { + logger.error( + { err: new Error('null doc'), project_id }, + 'encountered null doc' + ) + } + } + return docViews + }, + + archiveAllDocs(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + logger.log({ project_id }, 'archiving all docs') + return DocArchive.archiveAllDocs(project_id, function(error) { + if (error != null) { + return next(error) + } + return res.send(204) + }) + }, + + unArchiveAllDocs(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + logger.log({ project_id }, 'unarchiving all docs') + return DocArchive.unArchiveAllDocs(project_id, function(error) { + if (error != null) { + return next(error) + } + return res.send(200) + }) + }, + + destroyAllDocs(req, res, next) { + if (next == null) { + next = function(error) {} + } + const { project_id } = req.params + logger.log({ project_id }, 'destroying all docs') + return DocArchive.destroyAllDocs(project_id, function(error) { + if (error != null) { + return next(error) + } + return res.send(204) + }) + }, + + healthCheck(req, res) { + return HealthChecker.check(function(err) { + if (err != null) { + logger.err({ err }, 'error performing health check') + return res.send(500) + } else { + return res.send(200) + } + }) + } +} diff --git a/services/docstore/app/js/MongoManager.js b/services/docstore/app/js/MongoManager.js new file mode 100644 index 0000000000..66699c9ba3 --- /dev/null +++ b/services/docstore/app/js/MongoManager.js @@ -0,0 +1,175 @@ +/* eslint-disable + camelcase, + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let MongoManager +const { db, ObjectId } = require('./mongojs') +const logger = require('logger-sharelatex') +const metrics = require('metrics-sharelatex') + +module.exports = MongoManager = { + findDoc(project_id, doc_id, filter, callback) { + if (callback == null) { + callback = function(error, doc) {} + } + return db.docs.find( + { + _id: ObjectId(doc_id.toString()), + project_id: ObjectId(project_id.toString()) + }, + filter, + function(error, docs) { + if (docs == null) { + docs = [] + } + return callback(error, docs[0]) + } + ) + }, + + getProjectsDocs(project_id, options, filter, callback) { + if (options == null) { + options = { include_deleted: true } + } + const query = { project_id: ObjectId(project_id.toString()) } + if (!options.include_deleted) { + query.deleted = { $ne: true } + } + return db.docs.find(query, filter, callback) + }, + + getArchivedProjectDocs(project_id, callback) { + const query = { + project_id: ObjectId(project_id.toString()), + inS3: true + } + return db.docs.find(query, {}, callback) + }, + + upsertIntoDocCollection(project_id, doc_id, updates, callback) { + const update = { + $set: updates, + $inc: { + rev: 1 + }, + $unset: { + inS3: true + } + } + update.$set.project_id = ObjectId(project_id) + return db.docs.update( + { _id: ObjectId(doc_id) }, + update, + { upsert: true }, + callback + ) + }, + + markDocAsDeleted(project_id, doc_id, callback) { + return db.docs.update( + { + _id: ObjectId(doc_id), + project_id: ObjectId(project_id) + }, + { + $set: { deleted: true } + }, + callback + ) + }, + + markDocAsArchived(doc_id, rev, callback) { + const update = { + $set: {}, + $unset: {} + } + update.$set.inS3 = true + update.$unset.lines = true + update.$unset.ranges = true + const query = { + _id: doc_id, + rev + } + return db.docs.update(query, update, err => callback(err)) + }, + + getDocVersion(doc_id, callback) { + if (callback == null) { + callback = function(error, version) {} + } + return db.docOps.find( + { + doc_id: ObjectId(doc_id) + }, + { + version: 1 + }, + function(error, docs) { + if (error != null) { + return callback(error) + } + if (docs.length < 1 || docs[0].version == null) { + return callback(null, 0) + } else { + return callback(null, docs[0].version) + } + } + ) + }, + + setDocVersion(doc_id, version, callback) { + if (callback == null) { + callback = function(error) {} + } + return db.docOps.update( + { + doc_id: ObjectId(doc_id) + }, + { + $set: { version } + }, + { + upsert: true + }, + callback + ) + }, + + destroyDoc(doc_id, callback) { + return db.docs.remove( + { + _id: ObjectId(doc_id) + }, + function(err) { + if (err != null) { + return callback(err) + } + return db.docOps.remove( + { + doc_id: ObjectId(doc_id) + }, + callback + ) + } + ) + } +} +;[ + 'findDoc', + 'getProjectsDocs', + 'getArchivedProjectDocs', + 'upsertIntoDocCollection', + 'markDocAsArchived', + 'getDocVersion', + 'setDocVersion' +].map(method => + metrics.timeAsyncMethod(MongoManager, method, 'mongo.MongoManager', logger) +) diff --git a/services/docstore/app/js/RangeManager.js b/services/docstore/app/js/RangeManager.js new file mode 100644 index 0000000000..5a0061a9de --- /dev/null +++ b/services/docstore/app/js/RangeManager.js @@ -0,0 +1,69 @@ +/* eslint-disable + camelcase, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let RangeManager +const _ = require('underscore') +const { ObjectId } = require('./mongojs') + +module.exports = RangeManager = { + shouldUpdateRanges(doc_ranges, incoming_ranges) { + if (incoming_ranges == null) { + throw new Error('expected incoming_ranges') + } + + // If the ranges are empty, we don't store them in the DB, so set + // doc_ranges to an empty object as default, since this is was the + // incoming_ranges will be for an empty range set. + if (doc_ranges == null) { + doc_ranges = {} + } + + return !_.isEqual(doc_ranges, incoming_ranges) + }, + + jsonRangesToMongo(ranges) { + if (ranges == null) { + return null + } + + const updateMetadata = function(metadata) { + if ((metadata != null ? metadata.ts : undefined) != null) { + metadata.ts = new Date(metadata.ts) + } + if ((metadata != null ? metadata.user_id : undefined) != null) { + return (metadata.user_id = RangeManager._safeObjectId(metadata.user_id)) + } + } + + for (const change of Array.from(ranges.changes || [])) { + change.id = RangeManager._safeObjectId(change.id) + updateMetadata(change.metadata) + } + for (const comment of Array.from(ranges.comments || [])) { + comment.id = RangeManager._safeObjectId(comment.id) + if ((comment.op != null ? comment.op.t : undefined) != null) { + comment.op.t = RangeManager._safeObjectId(comment.op.t) + } + updateMetadata(comment.metadata) + } + return ranges + }, + + _safeObjectId(data) { + try { + return ObjectId(data) + } catch (error) { + return data + } + } +} diff --git a/services/docstore/app/js/mongojs.js b/services/docstore/app/js/mongojs.js new file mode 100644 index 0000000000..41f3bec470 --- /dev/null +++ b/services/docstore/app/js/mongojs.js @@ -0,0 +1,9 @@ +// TODO: This file was created by bulk-decaffeinate. +// Sanity-check the conversion and remove this comment. +const Settings = require('settings-sharelatex') +const mongojs = require('mongojs') +const db = mongojs(Settings.mongo.url, ['docs', 'docOps']) +module.exports = { + db, + ObjectId: mongojs.ObjectId +} diff --git a/services/docstore/buildscript.txt b/services/docstore/buildscript.txt index 8c8706f1cf..77a5b00f8c 100644 --- a/services/docstore/buildscript.txt +++ b/services/docstore/buildscript.txt @@ -1,10 +1,10 @@ docstore ---public-repo=True ---language=coffeescript ---env-add= --node-version=10.19.0 ---acceptance-creds=aws ---dependencies=mongo ---docker-repos=gcr.io/overleaf-ops ---env-pass-through= --script-version=1.3.5 +--dependencies=mongo +--language=es +--docker-repos=gcr.io/overleaf-ops +--acceptance-creds=aws +--env-pass-through= +--env-add= +--public-repo=True diff --git a/services/docstore/config/settings.defaults.coffee b/services/docstore/config/settings.defaults.coffee deleted file mode 100644 index e45570d3fe..0000000000 --- a/services/docstore/config/settings.defaults.coffee +++ /dev/null @@ -1,31 +0,0 @@ -http = require('http') -http.globalAgent.maxSockets = 300 - -Settings = - internal: - docstore: - port: 3016 - host: process.env['LISTEN_ADDRESS'] or "localhost" - - mongo:{} - - docstore: - healthCheck: - project_id: process.env['HEALTH_CHECK_PROJECT_ID'] - - max_doc_length: 2 * 1024 * 1024 # 2mb - -if process.env['MONGO_CONNECTION_STRING']? - Settings.mongo.url = process.env['MONGO_CONNECTION_STRING'] -else if process.env['MONGO_HOST']? - Settings.mongo.url = "mongodb://#{process.env['MONGO_HOST']}/sharelatex" -else - Settings.mongo.url = "mongodb://127.0.0.1/sharelatex" - -if process.env['AWS_ACCESS_KEY_ID']? and process.env['AWS_SECRET_ACCESS_KEY']? and process.env['AWS_BUCKET']? - Settings.docstore.s3 = - key: process.env['AWS_ACCESS_KEY_ID'] - secret: process.env['AWS_SECRET_ACCESS_KEY'] - bucket: process.env['AWS_BUCKET'] - -module.exports = Settings diff --git a/services/docstore/config/settings.defaults.js b/services/docstore/config/settings.defaults.js new file mode 100644 index 0000000000..92bf08e324 --- /dev/null +++ b/services/docstore/config/settings.defaults.js @@ -0,0 +1,48 @@ +/* + * decaffeinate suggestions: + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const http = require('http') +http.globalAgent.maxSockets = 300 + +const Settings = { + internal: { + docstore: { + port: 3016, + host: process.env.LISTEN_ADDRESS || 'localhost' + } + }, + + mongo: {}, + + docstore: { + healthCheck: { + project_id: process.env.HEALTH_CHECK_PROJECT_ID + } + }, + + max_doc_length: 2 * 1024 * 1024 // 2mb +} + +if (process.env.MONGO_CONNECTION_STRING != null) { + Settings.mongo.url = process.env.MONGO_CONNECTION_STRING +} else if (process.env.MONGO_HOST != null) { + Settings.mongo.url = `mongodb://${process.env.MONGO_HOST}/sharelatex` +} else { + Settings.mongo.url = 'mongodb://127.0.0.1/sharelatex' +} + +if ( + process.env.AWS_ACCESS_KEY_ID != null && + process.env.AWS_SECRET_ACCESS_KEY != null && + process.env.AWS_BUCKET != null +) { + Settings.docstore.s3 = { + key: process.env.AWS_ACCESS_KEY_ID, + secret: process.env.AWS_SECRET_ACCESS_KEY, + bucket: process.env.AWS_BUCKET + } +} + +module.exports = Settings diff --git a/services/docstore/nodemon.json b/services/docstore/nodemon.json index 98db38d71b..5826281b84 100644 --- a/services/docstore/nodemon.json +++ b/services/docstore/nodemon.json @@ -10,10 +10,9 @@ }, "watch": [ - "app/coffee/", - "app.coffee", + "app/js/", + "app.js", "config/" ], - "ext": "coffee" - + "ext": "js" } diff --git a/services/docstore/package-lock.json b/services/docstore/package-lock.json index 9eab5b1bd3..f241983350 100644 --- a/services/docstore/package-lock.json +++ b/services/docstore/package-lock.json @@ -4,6 +4,146 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", + "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.8.3" + } + }, + "@babel/generator": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.8.4.tgz", + "integrity": "sha512-PwhclGdRpNAf3IxZb0YVuITPZmmrXz9zf6fH8lT4XbrmfQKr6ryBzhv593P5C6poJRciFCL/eHGW2NuGrgEyxA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3", + "jsesc": "^2.5.1", + "lodash": "^4.17.13", + "source-map": "^0.5.0" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, + "@babel/helper-function-name": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz", + "integrity": "sha512-BCxgX1BC2hD/oBlIFUgOCQDOPV8nSINxCwM3o93xP4P9Fq6aV5sgv2cOOITDMtCfQ+3PvHp3l689XZvAM9QyOA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.8.3", + "@babel/template": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz", + "integrity": "sha512-FVDR+Gd9iLjUMY1fzE2SR0IuaJToR4RkCDARVfsBBPSP53GEqSFjD8gNyxg246VUyc/ALRxFaAK8rVG7UT7xRA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz", + "integrity": "sha512-3x3yOeyBhW851hroze7ElzdkeRXQYQbFIb7gLK1WQYsw2GWDay5gAJNw1sWJ0VFP6z5J1whqeXH/WCdCjZv6dA==", + "dev": true, + "requires": { + "@babel/types": "^7.8.3" + } + }, + "@babel/highlight": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.8.3.tgz", + "integrity": "sha512-PX4y5xQUvy0fnEVHrYOarRPXVWafSjTW9T0Hab8gVIawpl2Sj0ORyrygANq+KjcNlSSTw0YCLSNA8OyZ1I4yEg==", + "dev": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.8.4.tgz", + "integrity": "sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==", + "dev": true + }, + "@babel/runtime": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", + "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + } + }, + "@babel/template": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.8.3.tgz", + "integrity": "sha512-04m87AcQgAFdvuoyiQ2kgELr2tV8B4fP/xJAVUL3Yb3bkNdMedD3d0rlSQr3PegP0cms3eHjl1F7PWlvWbU8FQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/parser": "^7.8.3", + "@babel/types": "^7.8.3" + } + }, + "@babel/traverse": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.8.4.tgz", + "integrity": "sha512-NGLJPZwnVEyBPLI+bl9y9aSnxMhsKz42so7ApAv9D+b4vAFPpY013FTS9LdKxcABoIYFU52HcYga1pPlx454mg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.8.3", + "@babel/generator": "^7.8.4", + "@babel/helper-function-name": "^7.8.3", + "@babel/helper-split-export-declaration": "^7.8.3", + "@babel/parser": "^7.8.4", + "@babel/types": "^7.8.3", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.13" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "@babel/types": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.8.3.tgz", + "integrity": "sha512-jBD+G8+LWpMBBWvVcdr4QysjUE4mU/syrhN17o1u3gx0/WzJB1kwiVZAXRtWbsIPOwW8pF/YJV5+nmetPzepXg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.13", + "to-fast-properties": "^2.0.0" + } + }, "@google-cloud/common": { "version": "0.32.1", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-0.32.1.tgz", @@ -104,7 +244,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==" }, "gcp-metadata": { "version": "0.9.3", @@ -184,7 +324,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "semver": { "version": "6.1.1", @@ -201,7 +341,7 @@ "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", - "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==" }, "@protobufjs/base64": { "version": "1.1.2", @@ -216,12 +356,12 @@ "@protobufjs/eventemitter": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", - "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==" }, "@protobufjs/fetch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", - "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", "requires": { "@protobufjs/aspromise": "^1.1.1", "@protobufjs/inquire": "^1.1.0" @@ -230,27 +370,27 @@ "@protobufjs/float": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", - "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==" }, "@protobufjs/inquire": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", - "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==" }, "@protobufjs/path": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", - "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==" }, "@protobufjs/pool": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", - "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==" }, "@protobufjs/utf8": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", - "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@sindresorhus/is": { "version": "0.15.0", @@ -284,6 +424,12 @@ "@types/node": "*" } }, + "@types/eslint-visitor-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", + "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", + "dev": true + }, "@types/form-data": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-2.2.1.tgz", @@ -292,6 +438,12 @@ "@types/node": "*" } }, + "@types/json-schema": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz", + "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", + "dev": true + }, "@types/long": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.0.tgz", @@ -323,6 +475,51 @@ "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz", "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg==" }, + "@typescript-eslint/experimental-utils": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz", + "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-scope": "^4.0.0" + }, + "dependencies": { + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + } + } + }, + "@typescript-eslint/parser": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-1.13.0.tgz", + "integrity": "sha512-ITMBs52PCPgLb2nGPoeT4iU3HdQZHcPaZVw+7CsFagRJHUhyeTgorEwHXhFf3e7Evzi8oujKNpHc8TONth8AdQ==", + "dev": true, + "requires": { + "@types/eslint-visitor-keys": "^1.0.0", + "@typescript-eslint/experimental-utils": "1.13.0", + "@typescript-eslint/typescript-estree": "1.13.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz", + "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==", + "dev": true, + "requires": { + "lodash.unescape": "4.0.1", + "semver": "5.5.0" + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -332,12 +529,19 @@ } }, "accepts": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.1.tgz", - "integrity": "sha1-weBtYT5iRrqHRnjW2bkjibfOMQw=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.0.7.tgz", + "integrity": "sha512-iq8ew2zitUlNcUca0wye3fYwQ6sSPItDo38oC0R+XA5KTzeXRN+GF7NjOXs3dVItj4J+gQVdpq4/qbnMb1hMHw==", "requires": { - "mime": "~1.2.11", - "negotiator": "~0.4.0" + "mime-types": "~1.0.0", + "negotiator": "0.4.7" + }, + "dependencies": { + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha512-echfutj/t5SoTL4WZpqjA1DCud1XO0WQF3/GJ48YBmc4ZMhCK77QA6Z/w6VTQERLKuJ4drze3kw2TUT8xZXVNw==" + } } }, "acorn": { @@ -345,6 +549,12 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==" }, + "acorn-jsx": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz", + "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==", + "dev": true + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -364,6 +574,70 @@ "uri-js": "^4.2.2" } }, + "ansi-escapes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.0.tgz", + "integrity": "sha512-EiYhwo0v255HUL6eDyuLrXEkTi7WwVCLAw+SeOQ7M7qdun1z1pum4DEm/nuqIVbPvi9RPPc9k9LbyBv6H0DwVg==", + "dev": true, + "requires": { + "type-fest": "^0.8.1" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "aria-query": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", + "integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==", + "dev": true, + "requires": { + "ast-types-flow": "0.0.7", + "commander": "^2.11.0" + } + }, + "array-includes": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.1.tgz", + "integrity": "sha512-c2VXaCHl7zPsvpkFsw4nxvFie4fh1ur9bpcgsVkIjqn0H/Xwdg+7fv3n2r/isyS8EBj5b06M9kHyZuIr4El6WQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "is-string": "^1.0.5" + } + }, + "array.prototype.flat": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz", + "integrity": "sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -373,26 +647,36 @@ "version": "0.1.11", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.1.11.tgz", "integrity": "sha1-VZvhg3bQik7E2+gId9J4GGObLfc=", - "dev": true, - "optional": true + "dev": true }, "assert-plus": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.1.5.tgz", "integrity": "sha1-7nQAlBMALYTOxyGcasgRgS5yMWA=", - "dev": true, - "optional": true + "dev": true }, "assertion-error": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.0.tgz", - "integrity": "sha1-x/hUOP3UZrx8oWq5DIFRN5el0js=", + "integrity": "sha512-g/gZV+G476cnmtYI+Ko9d5khxSoCSoom/EaNmmCfwpOvBXEJ18qwFrxfP1/CsIqk2no1sAKKwxndV0tP7ROOFQ==", + "dev": true + }, + "ast-types-flow": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.7.tgz", + "integrity": "sha512-eBvWn1lvIApYMhzQMsu9ciLfkBY499mFZlNqG+/9WR7PVlroQw0vG30cOQQbaKz3sCEc44TAOu2ykzqXSNnwag==", + "dev": true + }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", "dev": true }, "async": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/async/-/async-0.8.0.tgz", - "integrity": "sha1-7mXsdymML/FFa8RBigUtDwZDURI=" + "integrity": "sha512-M2LC+aqW7VetFcnFiYEbjUsmASW6GSsMNkRzhUzwHoQNfNIRClf5GLgozwuJ4tAMLAfjywrKyQ2wWiODJivQmg==" }, "async-listener": { "version": "0.6.10", @@ -406,14 +690,13 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "aws-sign2": { "version": "0.5.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.5.0.tgz", "integrity": "sha1-xXED96F/wDfwLXwuZLYC6iI/fWM=", - "dev": true, - "optional": true + "dev": true }, "aws4": { "version": "1.8.0", @@ -429,10 +712,41 @@ "is-buffer": "^2.0.2" } }, + "axobject-query": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.1.2.tgz", + "integrity": "sha512-ICt34ZmrVt8UQnvPl6TVyDTkmhXmAyAT4Jh5ugfGUX4MOrZ+U/ZY6/sdylRw3qGNr9Ub5AJsaHeDMzNLehRdOQ==", + "dev": true + }, + "babel-eslint": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.0.3.tgz", + "integrity": "sha512-z3U7eMY6r/3f3/JB9mTsLjyxrv0Yb1zb8PCWCLpguxfCzBIZUwy23R1t/XKewP+8mEN2Ck8Dtr4q20z6ce6SoA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.0.0", + "@babel/traverse": "^7.0.0", + "@babel/types": "^7.0.0", + "eslint-visitor-keys": "^1.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "integrity": "sha512-9Y0g0Q8rmSt+H33DfKv7FOc3v+iRI+o1lbzt8jGcIosYW37IIW/2XVYq5NPdmaD5NQ59Nk26Kl/vZbwW9Fr8vg==" }, "base64-js": { "version": "1.3.0", @@ -442,7 +756,7 @@ "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "requires": { "tweetnacl": "^0.14.3" } @@ -463,24 +777,58 @@ "bintrees": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", - "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" + "integrity": "sha512-tbaUB1QpTIj4cKY8c1rvNAvEQXA+ekzHmbe4jzNfW3QWsF9GnnP/BRWyl6/qqS53heoYJ93naaFcm/jooONH8g==" + }, + "bl": { + "version": "0.9.5", + "resolved": "https://registry.npmjs.org/bl/-/bl-0.9.5.tgz", + "integrity": "sha1-wGt5evCF6gC8Unr8jvzxHeIjIFQ=", + "dev": true, + "requires": { + "readable-stream": "~1.0.26" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + }, + "readable-stream": { + "version": "1.0.34", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + } + } }, "body-parser": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.0.2.tgz", - "integrity": "sha1-NGFHmjJ4/gD8rr7DMUu1T8T3tHw=", + "integrity": "sha512-/VlRWmVVwfh8rYG/MHGIwesdJYdxBgeddggFG5ssLfX/Qhwt4NKEQhvzwdzVRcy2RhLxni1MKe0uuPfP/unjyw==", "requires": { "qs": "~0.6.6", "raw-body": "~1.1.2", "type-is": "~1.1.0" } }, + "boolify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/boolify/-/boolify-1.0.1.tgz", + "integrity": "sha512-ma2q0Tc760dW54CdOyJjhrg/a54317o1zYADQJFgperNGKIKgAUGIcKnuMiff8z57+yGlrGNEt4lPgZfCgTJgA==", + "dev": true + }, "boom": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/boom/-/boom-0.4.2.tgz", "integrity": "sha1-emNune1O/O+xnO9JR6PGffrukRs=", "dev": true, - "optional": true, "requires": { "hoek": "0.9.x" } @@ -497,7 +845,7 @@ "browser-stdout": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", - "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "integrity": "sha512-7Rfk377tpSM9TWBEeHs0FlDZGoAIei2V/4MdZJoFMBFAK6BqLpxAIUepGRHGdPFgGsLb02PXovC4qddyHvQqTg==", "dev": true }, "bson": { @@ -506,19 +854,19 @@ "integrity": "sha512-D8zmlb46xfuK2gGvKmUjIklQEouN2nQ0LEHHeZ/NoHM2LDiMk2EYzZ5Ntw/Urk+bgMDosOZxaRzXxvhI5TcAVQ==" }, "buffer-crc32": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.1.tgz", - "integrity": "sha1-vj5TgvwCttYySVasGvmKqYsIU0w=" + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.3.tgz", + "integrity": "sha512-HLvoSqq1z8fJEcT1lUlJZ4OJaXJZ1wsWm0+fBxkz9Bdf/WphA4Da7FtGUguNNyEXL4WB0hNMTaWmdFRFPy8YOQ==" }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, "buffer-shims": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" + "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" }, "builtin-modules": { "version": "3.1.0", @@ -528,7 +876,7 @@ "bunyan": { "version": "0.22.3", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-0.22.3.tgz", - "integrity": "sha1-ehncG0yMZF90AkGnQPIkUUfGfsI=", + "integrity": "sha512-v9dd5qmd6nJHEi7fiNo1fR2pMpE8AiB47Ap984p4iJKj+dEA69jSccmq6grFQn6pxIh0evvKpC5XO1SKfiaRoQ==", "dev": true, "requires": { "dtrace-provider": "0.2.8", @@ -538,48 +886,183 @@ "bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=" + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==" + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "camelcase-keys": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.1.2.tgz", + "integrity": "sha512-QfFrU0CIw2oltVvpndW32kuJ/9YOJwUnmWrjlXt1nnJZHCaS9i6bfOpg9R4Lw8aZjStkJWM+jc0cdXjWBgVJSw==", + "dev": true, + "requires": { + "camelcase": "^5.3.1", + "map-obj": "^4.0.0", + "quick-lru": "^4.0.1" + } }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" }, "chai": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/chai/-/chai-1.9.2.tgz", - "integrity": "sha1-Pxog+CsLnXQ3V30k1vErGmnTtZA=", + "integrity": "sha512-olRoaitftnzWHFEAza6MXR4w+FfZrOVyV7r7U/Z8ObJefCgL8IuWkAuASJjSXrpP9wvgoL8+1dB9RbMLc2FkNg==", "dev": true, "requires": { "assertion-error": "1.0.0", "deep-eql": "0.1.3" } }, - "coffee-script": { - "version": "1.12.7", - "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", - "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==" + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dev": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-width": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", + "integrity": "sha512-EJLbKSuvHTrVRynOXCYFTbQKZOFXWNe3/6DN1yrEH3TuuZT1x4dMQnCHnfCrBUUiGjO63enEIfaB17VaRl2d4A==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "combined-stream": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-0.0.7.tgz", "integrity": "sha1-ATfmV7qlp1QcV6w3rF/AfXO03B8=", "dev": true, - "optional": true, "requires": { "delayed-stream": "0.0.5" } }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "common-tags": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", + "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "console-log-level": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", "integrity": "sha512-VZzbIORbP+PPcN/gg3DXClTLPLg5Slwd5fL2MIc+o1qZ4BXBvWyc6QxPk6T/Mkr6IVjRpoAGf32XxP3ZWMVRcQ==" }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha512-OKZnPGeMQy2RPaUIBPFFd71iNf4791H12MCRuVQDnzGRwCYNYmTDy5pdafo2SLAcEMKzTOQnLWG4QdcjeJUMEg==", + "dev": true + }, "continuation-local-storage": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz", @@ -592,24 +1075,42 @@ "cookie": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.1.2.tgz", - "integrity": "sha1-cv7D0k5Io0Mgc9kMEmQgBQYQBLE=" + "integrity": "sha512-+mHmWbhevLwkiBf7QcbZXHr0v4ZQQ/OgHk3fsQHrsMMiGzuvAmU/YMUR+ZfrO/BLAGIWFfx2Z7Oyso0tZR/wiA==" }, "cookie-signature": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.3.tgz", - "integrity": "sha1-kc2ZfMUftkFZVzjGnNoCAyj1D/k=" + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.4.tgz", + "integrity": "sha512-k+lrG38ZC/S7zN6l1/HcF6xF4jMwkIUjnr5afDU7tzFxIfDmKzdqJdXo8HNYaXOuBJ3tPKxSiwCOTA0b3qQfaA==" + }, + "core-js": { + "version": "3.6.4", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", + "integrity": "sha512-4paDGScNgZP2IXXilaffL9X7968RuvwlkK3xWtZRVqgd8SYNiVKRJvkFd1aqqEuPfN7E68ZHEp9hDj6lHj4Hyw==", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" + }, + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } }, "cryptiles": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-0.2.2.tgz", "integrity": "sha1-7ZH/HxetE9N0gohZT4pIoNJvMlw=", "dev": true, - "optional": true, "requires": { "boom": "0.4.x" } @@ -618,13 +1119,18 @@ "version": "0.5.3", "resolved": "https://registry.npmjs.org/ctype/-/ctype-0.5.3.tgz", "integrity": "sha1-gsGMJGH3QRTvFsE1IkrQuRRMoS8=", - "dev": true, - "optional": true + "dev": true + }, + "damerau-levenshtein": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz", + "integrity": "sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug==", + "dev": true }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "requires": { "assert-plus": "^1.0.0" }, @@ -632,24 +1138,55 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" } } }, "debug": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.8.1.tgz", - "integrity": "sha1-IP9NJvXkIstoobrLu2EDmtjBwTA=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.2.tgz", + "integrity": "sha512-T9bufXIzQvCa4VrTIpLvvwdLhH+wuBtvIJJA3xgzVcaVETGmTIWMfEXQEd1K4p8BaRmQJPn6MPut38H7YQ+iIA==", + "requires": { + "ms": "0.6.2" + }, + "dependencies": { + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha512-/pc3eh7TWorTtbvXg8je4GvrvEqCfH7PA3P7iW01yL2E53FKixzgMBaQi0NOPbMJqY34cBSvR0tZtmlTkdUG4A==" + } + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true }, "deep-eql": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", - "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "integrity": "sha512-6sEotTRGBFiNcqVoeHwnfopbSpi5NbH1VWJmYCVkmxMmaVTT0bUTrNaGyBwhgP4MZL012W/mkzIn3Da+iDYweg==", "dev": true, "requires": { "type-detect": "0.1.1" } }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha512-GtxAN4HvBachZzm4OnWqc45ESpUCMwkYcsjnsPs23FwJbsO+k4t0k9bQCgOmzIlpHO28+WPK/KRbRk0DDHuuDw==", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, "delay": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/delay/-/delay-4.3.0.tgz", @@ -659,13 +1196,32 @@ "version": "0.0.5", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-0.0.5.tgz", "integrity": "sha1-1LH0OpPoKW3+AmlPRoC8N6MTxz8=", + "dev": true + }, + "depd": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-0.3.0.tgz", + "integrity": "sha512-Uyx3FgdvEYlpA3W4lf37Ide++2qOsjLlJ7dap0tbM63j/BxTCcxmyIOO6PXbKbOuNSko+fsDHzzx1DUeo1+3fA==" + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, - "optional": true + "requires": { + "esutils": "^2.0.2" + } }, "dtrace-provider": { "version": "0.2.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.2.8.tgz", - "integrity": "sha1-4kPxkhmqlfvw2PL/sH9b1k6U/iA=", + "integrity": "sha512-wufYnYt4ISHnT9MEiRgQ3trXuolt7mICTa/ckT+KYHR667K9H82lmI8KM7zKUJ8l5I343A34wJnvL++1TJn1iA==", "dev": true, "optional": true }, @@ -683,12 +1239,12 @@ "each-series": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/each-series/-/each-series-1.0.0.tgz", - "integrity": "sha1-+Ibmxm39sl7x/nNWQUbuXLR4r8s=" + "integrity": "sha512-4MQloCGGCmT5GJZK5ibgJSvTK1c1QSrNlDvLk6fEyRxjZnXjl+NNFfzhfXpmnWh33Owc9D9klrdzCUi7yc9r4Q==" }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -702,6 +1258,11 @@ "safe-buffer": "^5.0.1" } }, + "ee-first": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.0.3.tgz", + "integrity": "sha512-1q/3kz+ZwmrrWpJcCCrBZ3JnBzB1BMA5EVW9nxnIP1LxDZ16Cqs9VdolqLWlExet1vU+bar3WSkAa4/YrA9bIw==" + }, "emitter-listener": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", @@ -710,6 +1271,12 @@ "shimmer": "^1.2.0" } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, "end-of-stream": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", @@ -721,17 +1288,56 @@ "ent": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=" + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==" + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.4", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.4.tgz", + "integrity": "sha512-Ae3um/gb8F0mui/jPL+QiqmglkUsaQf7FwBEHYIFkztkneosu9imhqHpBzQ3h1vit8t5iQ74t6PEVvphBZiuiQ==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" + "integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g==" }, "es6-promisify": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "requires": { "es6-promise": "^4.0.3" }, @@ -746,12 +1352,460 @@ "escape-html": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.1.tgz", - "integrity": "sha1-GBoobq05ejmpKFfPsdQwUuNWv/A=" + "integrity": "sha512-z6kAnok8fqVTra7Yu77dZF2Y6ETJlxH58wN38wNyuNQLm8xXdKnfNrlSmfXsTePWP03rRVUKHubtUwanwUi7+g==" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "eslint": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.6.0.tgz", + "integrity": "sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.10.0", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^1.4.3", + "eslint-visitor-keys": "^1.1.0", + "espree": "^6.1.2", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.0.0", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^7.0.0", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.14", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^6.1.2", + "strip-ansi": "^5.2.0", + "strip-json-comments": "^3.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.0.tgz", + "integrity": "sha512-AtndijGte1rPILInUdHjvKEGbIV06NuvPrqlIEaEaWtbtvJh464mDeyGMdZEQMsGvC0ZVkiex1fSNcC4HAbRGg==", + "dev": true, + "requires": { + "get-stdin": "^6.0.0" + } + }, + "eslint-config-standard": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-14.1.0.tgz", + "integrity": "sha512-EF6XkrrGVbvv8hL/kYa/m6vnvmUT+K82pJJc4JJVMM6+Qgqh0pnwprSxdduDLB9p/7bIxD+YV5O0wfb8lmcPbA==", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-8.1.0.tgz", + "integrity": "sha512-ULVC8qH8qCqbU792ZOO6DaiaZyHNS/5CZt3hKqHkEhVlhPEPN3nfBqqxJCyp59XrjIBZPu1chMYe9T2DXZ7TMw==", + "dev": true + }, + "eslint-config-standard-react": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/eslint-config-standard-react/-/eslint-config-standard-react-9.2.0.tgz", + "integrity": "sha512-u+KRP2uCtthZ/W4DlLWCC59GZNV/y9k9yicWWammgTs/Omh8ZUUPF3EnYm81MAcbkYQq2Wg0oxutAhi/FQ8mIw==", + "dev": true, + "requires": { + "eslint-config-standard-jsx": "^8.0.0" + } + }, + "eslint-import-resolver-node": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.3.tgz", + "integrity": "sha512-b8crLDo0M5RSe5YG8Pu2DYBj71tSB6OvXkfzwbJU2w7y8P4/yo0MyF8jU26IEuEuHF2K5/gcAJE3LhQGqBBbVg==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.13.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-module-utils": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.5.2.tgz", + "integrity": "sha512-LGScZ/JSlqGKiT8OC+cYRxseMjyqt6QO54nl281CK93unD89ijSeRV6An8Ci/2nvWVKe8K/Tqdm75RQoIOCr+Q==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "eslint-plugin-chai-expect": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-expect/-/eslint-plugin-chai-expect-2.1.0.tgz", + "integrity": "sha512-rd0/4mjMV6c3i0o4DKkWI4uaFN9DK707kW+/fDphaDI6HVgxXnhML9Xgt5vHnTXmSSnDhupuCFBgsEAEpchXmQ==", + "dev": true + }, + "eslint-plugin-chai-friendly": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.5.0.tgz", + "integrity": "sha512-Pxe6z8C9fP0pn2X2nGFU/b3GBOCM/5FVus1hsMwJsXP3R7RiXFl7g0ksJbsc0GxiLyidTW4mEFk77qsNn7Tk7g==", + "dev": true + }, + "eslint-plugin-es": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz", + "integrity": "sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng==", + "dev": true, + "requires": { + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "regexpp": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.0.0.tgz", + "integrity": "sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==", + "dev": true + } + } + }, + "eslint-plugin-import": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.20.1.tgz", + "integrity": "sha512-qQHgFOTjguR+LnYRoToeZWT62XM55MBVXObHM6SKFd1VzDcX/vqT1kAz8ssqigh5eMj8qXcRoXXGZpPP6RfdCw==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "array.prototype.flat": "^1.2.1", + "contains-path": "^0.1.0", + "debug": "^2.6.9", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.2", + "eslint-module-utils": "^2.4.1", + "has": "^1.0.3", + "minimatch": "^3.0.4", + "object.values": "^1.1.0", + "read-pkg-up": "^2.0.0", + "resolve": "^1.12.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha512-lsGyRuYr4/PIB0txi+Fy2xOMI2dGaTguCaotzFGkVZuKR5usKfcRWIFKNM3QNrU7hh/+w2bwTW+ZeXPK5l8uVg==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-jsx-a11y": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz", + "integrity": "sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.4.5", + "aria-query": "^3.0.0", + "array-includes": "^3.0.3", + "ast-types-flow": "^0.0.7", + "axobject-query": "^2.0.2", + "damerau-levenshtein": "^1.0.4", + "emoji-regex": "^7.0.2", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.1" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + } + } + }, + "eslint-plugin-mocha": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz", + "integrity": "sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA==", + "dev": true, + "requires": { + "ramda": "^0.26.1" + } + }, + "eslint-plugin-node": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz", + "integrity": "sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg==", + "dev": true, + "requires": { + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" + }, + "dependencies": { + "eslint-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.0.0.tgz", + "integrity": "sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "eslint-plugin-prettier": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz", + "integrity": "sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, + "eslint-plugin-promise": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "dev": true + }, + "eslint-plugin-react": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.18.3.tgz", + "integrity": "sha512-Bt56LNHAQCoou88s8ViKRjMB2+36XRejCQ1VoLj716KI1MoE99HpTVvIThJ0rvFmG4E4Gsq+UgToEjn+j044Bg==", + "dev": true, + "requires": { + "array-includes": "^3.1.1", + "doctrine": "^2.1.0", + "has": "^1.0.3", + "jsx-ast-utils": "^2.2.3", + "object.entries": "^1.1.1", + "object.fromentries": "^2.0.2", + "object.values": "^1.1.1", + "prop-types": "^15.7.2", + "resolve": "^1.14.2", + "string.prototype.matchall": "^4.0.2" + }, + "dependencies": { + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "resolve": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", + "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "eslint-plugin-standard": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-4.0.1.tgz", + "integrity": "sha512-v/KBnfyaOMPmZc/dmc6ozOdWqekGp7bBGq4jLAecEfPGmfKiWS4sA8sC0LqiV9w5qmXAtXVn4M3p1jSyhY85SQ==", + "dev": true + }, + "eslint-scope": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.0.0.tgz", + "integrity": "sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-utils": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", + "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^1.1.0" + } + }, + "eslint-visitor-keys": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz", + "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==", + "dev": true + }, + "espree": { + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/espree/-/espree-6.1.2.tgz", + "integrity": "sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA==", + "dev": true, + "requires": { + "acorn": "^7.1.0", + "acorn-jsx": "^5.1.0", + "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "acorn": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz", + "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ==", + "dev": true + } + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.1.0.tgz", + "integrity": "sha512-MxYW9xKmROWF672KqjO75sszsA8Mxhw06YFeS5VHlB98KDHbOSurm3ArsjO60Eaf3QmGMCP1yn+0JQkNLo/97Q==", + "dev": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", + "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", + "dev": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, "event-target-shim": { @@ -760,27 +1814,48 @@ "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" }, "express": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.1.2.tgz", - "integrity": "sha1-yx0RQlVximWhvNaVgDbvcgxSlIc=", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.5.1.tgz", + "integrity": "sha512-36quUqw4ZqgDnQzQpYLnNphPx612kfmQjbZlFt9Mz6Up/27IhkACtYBG9L/dAZkrXD0il3cq58DU+lpSL/Bg1g==", "requires": { - "accepts": "1.0.1", - "buffer-crc32": "0.2.1", + "accepts": "~1.0.7", + "buffer-crc32": "0.2.3", "cookie": "0.1.2", - "cookie-signature": "1.0.3", - "debug": ">= 0.7.3 < 1", + "cookie-signature": "1.0.4", + "debug": "1.0.2", + "depd": "0.3.0", "escape-html": "1.0.1", + "finalhandler": "0.0.2", "fresh": "0.2.2", + "media-typer": "0.2.0", "merge-descriptors": "0.0.2", - "methods": "0.1.0", + "methods": "1.0.1", "parseurl": "1.0.1", "path-to-regexp": "0.1.2", + "proxy-addr": "1.0.1", "qs": "0.6.6", "range-parser": "1.0.0", - "send": "0.3.0", - "serve-static": "1.1.0", - "type-is": "1.1.0", - "utils-merge": "1.0.0" + "send": "0.5.0", + "serve-static": "~1.3.0", + "type-is": "~1.3.2", + "utils-merge": "1.0.0", + "vary": "0.1.0" + }, + "dependencies": { + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha512-echfutj/t5SoTL4WZpqjA1DCud1XO0WQF3/GJ48YBmc4ZMhCK77QA6Z/w6VTQERLKuJ4drze3kw2TUT8xZXVNw==" + }, + "type-is": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.3.2.tgz", + "integrity": "sha512-sdIhnvhWEyIP2DKjj1o9tL31m8vFxDfLPD56KXz2absqY5AF2QYkJC7Wrw2fkzsZA9mv+PCtgyB7EqYOgR+r3Q==", + "requires": { + "media-typer": "0.2.0", + "mime-types": "~1.0.1" + } + } } }, "extend": { @@ -788,35 +1863,144 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "dev": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==" + }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "integrity": "sha512-eIgZvM9C3P05kg0qxfqaVU6Tma4QedCPIByQOcemV0vju8ot3cS2DpHi4m2G2JvbSMI152rjfLX0p1pkSdyPlQ==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true }, "fast-text-encoding": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.0.tgz", "integrity": "sha512-R9bHCvweUxxwkDwhjav5vxpFvdPGlVngtqmx4pIZfSUhM/Q4NiIUHB456BAf+Q1Nwu3HEZYONtu+Rya+af4jiQ==" }, + "figures": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.1.0.tgz", + "integrity": "sha512-ravh8VRXqHuMvZt/d8GblBeqDMkdJMBdv/2KntFH+ra5MXkO7nxNKpzQ3n6QD/2da1kH0aWmNISdvhM7gl2gVg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", + "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", + "dev": true, + "requires": { + "flat-cache": "^2.0.1" + } + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" }, + "finalhandler": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-0.0.2.tgz", + "integrity": "sha512-SbpQfvWVwWEBlPTQyaM9gs0D5404ENTC0x2jzbb7t+P+EOD/cBlWjAAvfozIQYtOepUuNkxoLNLCK9/kS29f4w==", + "requires": { + "debug": "1.0.2", + "escape-html": "1.0.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, "findit2": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", - "integrity": "sha1-WKRmaX34piBc39vzlVNri9d3pfY=" + "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==" + }, + "finished": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/finished/-/finished-1.2.2.tgz", + "integrity": "sha512-HPJ8x7Gn1pmTS1zWyMoXmQ1yxHkYHRoFsBI66ONq4PS9iWBJy1iHYXOSqMWNp3ksMXfrBpenkSwBhl9WG4zr4Q==", + "requires": { + "ee-first": "1.0.3" + } + }, + "flat-cache": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", + "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", + "dev": true, + "requires": { + "flatted": "^2.0.0", + "rimraf": "2.6.3", + "write": "1.0.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "flatted": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz", + "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==", + "dev": true }, "follow-redirects": { "version": "1.5.10", @@ -847,7 +2031,6 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-0.1.4.tgz", "integrity": "sha1-kavXiKupcCsaq/qLwBAxoqyeOxI=", "dev": true, - "optional": true, "requires": { "async": "~0.9.0", "combined-stream": "~0.0.4", @@ -858,15 +2041,14 @@ "version": "0.9.2", "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", - "dev": true, - "optional": true + "dev": true } } }, "formatio": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", + "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", "dev": true, "requires": { "samsam": "1.x" @@ -875,12 +2057,24 @@ "fresh": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.2.2.tgz", - "integrity": "sha1-lzHc9WeMf660T7kDxPct9VGH+nc=" + "integrity": "sha512-ZGGi8GROK//ijm2gB33sUuN9TjN1tC/dvG4Bt4j6IWrVGpMmudUBCxx+Ir7qePsdREfkpQC4FL8W0jeSOsgv1w==" }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "dev": true }, "gaxios": { @@ -903,10 +2097,22 @@ "json-bigint": "^0.3.0" } }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", "requires": { "assert-plus": "^1.0.0" }, @@ -914,14 +2120,14 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" } } }, "glob": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", + "integrity": "sha512-MKZeRNyYZAVVVG1oZeLaWie1uweH40m9AZwIwxyPbTSX4hHrVYSzLg0Ro5Z5R7XKkIX+Cc6oD1rqeDJnwsB8/A==", "optional": true, "requires": { "inflight": "^1.0.4", @@ -931,6 +2137,21 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz", + "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, "google-auth-library": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-3.1.2.tgz", @@ -956,6 +2177,12 @@ "pify": "^4.0.0" } }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, "gtoken": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-2.3.3.tgz", @@ -978,7 +2205,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" }, "har-validator": { "version": "5.1.3", @@ -989,18 +2216,49 @@ "har-schema": "^2.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + } + } + }, "has-flag": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", - "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "integrity": "sha512-P+1n3MnwjR/Epg9BBo1KT8qbye2g2Ou4sFumihwt6I4tsUX7jnLcX4BTOSKg/B1ZrIYMN9FcEnG4x5a7NB8Eng==", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "hawk": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.0.0.tgz", - "integrity": "sha1-uQuxaYByhUEdp//LjdJZhQLTtS0=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/hawk/-/hawk-1.1.1.tgz", + "integrity": "sha1-h81JH5tG5OKurKM1QWdmiF0tHtk=", "dev": true, - "optional": true, "requires": { "boom": "0.4.x", "cryptiles": "0.2.x", @@ -1011,7 +2269,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", "dev": true }, "hex2dec": { @@ -1023,15 +2281,19 @@ "version": "0.9.1", "resolved": "https://registry.npmjs.org/hoek/-/hoek-0.9.1.tgz", "integrity": "sha1-PTIkYrrfB3Fup+uFuviAec3c5QU=", - "dev": true, - "optional": true + "dev": true + }, + "hosted-git-info": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", + "dev": true }, "http-signature": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-0.10.1.tgz", "integrity": "sha1-T72sEyVZqoMjEh5UB3nAoBKyfmY=", "dev": true, - "optional": true, "requires": { "asn1": "0.1.11", "assert-plus": "^0.1.5", @@ -1062,10 +2324,55 @@ } } }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -1074,42 +2381,182 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "inquirer": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.0.4.tgz", + "integrity": "sha512-Bu5Td5+j11sCkqfqmUTiwv+tWisMtP0L7Q8WrqA2C/BbBhy1YTdFrvjjlrKq8oagA/tLQBski2Gcx/Sqyi2qSQ==", + "dev": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^2.4.2", + "cli-cursor": "^3.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", + "run-async": "^2.2.0", + "rxjs": "^6.5.3", + "string-width": "^4.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + } + }, + "internal-slot": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.2.tgz", + "integrity": "sha512-2cQNfwhAfJIkU4KZPkDI+Gj5yNNnbqi40W9Gge6dfnk4TocEVm00B3bdiL+JINrbGJil2TeHvM4rETGzk/f/0g==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "has": "^1.0.3", + "side-channel": "^1.0.2" + } + }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, + "ipaddr.js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-0.1.2.tgz", + "integrity": "sha512-MGrEjHz4Hk5UVpJXZQ2tHB+bp6xgdRKCAEWdrgFsoAmXCgKAPtj8LqMxgvlWEAj9aN+PpTcvE051uZU3K3kLSQ==" }, "is": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/is/-/is-3.3.0.tgz", "integrity": "sha512-nW24QBoPcFGGHJGUwnfpI7Yc5CdqWNdsyHQszVE/z2pKHXzh7FZ5GWhJqSyaQ9wMkQnsTx+kAI8bHlCX4tKdbg==" }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true + }, "is-buffer": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-promise": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", + "integrity": "sha512-NECAi6wp6CgMesHuVUEK8JwjCvm/tvnn5pCbB42JOHp3mgUizN0nagXu4HEqQZBkieGEQ+jVcMKWqoVd6CDbLQ==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-string": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.5.tgz", + "integrity": "sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true }, "json-bigint": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-0.3.0.tgz", - "integrity": "sha1-DM2RLEuCcNBfBW+9E4FLU9OCWx4=", + "integrity": "sha512-u+c/u/F+JNPUekHCFyGVycRPyh9UHD5iUhSyIAn10kxbDTJxijwAbT6XHaONEOXuGGfmWUSroheXgHcml4gLgg==", "requires": { "bignumber.js": "^7.0.0" } @@ -1117,22 +2564,28 @@ "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha512-a3xHnILGMtk+hDOqNwHzF6e2fNbiMrXZvxKQiEv2MlgQP+pjIOzqAmKYD2mDpXYE/44M7g+n9p2bKkYWDUcXCQ==" }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "integrity": "sha512-4Dj8Rf+fQ+/Pn7C5qeEX02op1WfOss3PKTE9Nsop3Dx+6UPxlm1dr/og7o2cRa5hNN07CACr4NFzRLtj/rjWog==", "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -1143,10 +2596,20 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" } } }, + "jsx-ast-utils": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz", + "integrity": "sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA==", + "dev": true, + "requires": { + "array-includes": "^3.0.3", + "object.assign": "^4.1.0" + } + }, "just-extend": { "version": "1.1.27", "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-1.1.27.tgz", @@ -1172,10 +2635,74 @@ "safe-buffer": "^5.0.1" } }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha512-3p6ZOGNbiX4CdvEd1VcE6yi78UrGNpjHO33noGwHCnT/o2fyllJDepsm8+mFFv/DvtwFHht5HIHSyOy5a+ChVQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", + "dev": true + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lodash.pickby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.pickby/-/lodash.pickby-4.6.0.tgz", - "integrity": "sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=" + "integrity": "sha512-AZV+GsS/6ckvPOVQPXSiFFacKvKB4kOQu6ynt9wz0F3LO4R9Ij4K1ddYsIytDpSgLz88JHd9P+oaLeej5/Sl7Q==" + }, + "lodash.unescape": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz", + "integrity": "sha512-DhhGRshNS1aX6s5YdBE3njCCouPgnG29ebyHvImlZzXZf2SHgt+J08DHgytTPnpywNbO1Y8mNUFyQuIDBq2JZg==", + "dev": true }, "logger-sharelatex": { "version": "1.7.0", @@ -1190,17 +2717,17 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" }, "bunyan": { "version": "1.8.12", "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=", + "integrity": "sha512-dmDUbGHeGcvCDLRFOscZkwx1ZO/aFz3bJOCi5nCgzdhFGPxwK+y5AcDBnqagNGlJZ7lje/l6JUEz9mQcutttdg==", "requires": { "dtrace-provider": "~0.8", "moment": "^2.10.6", @@ -1219,12 +2746,12 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, "dtrace-provider": { "version": "0.8.7", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.7.tgz", - "integrity": "sha1-3JObTT4GIM/gwc2APQ0tftBP/QQ=", + "integrity": "sha512-V+HIGbAdxCIxddHNDwzXi6cx8Cz5RRlQOVcsryHfsyVVebpBEnDwHSgqxpgKzqeU/6/0DWqRLAGUwkbg2ecN1Q==", "optional": true, "requires": { "nan": "^2.10.0" @@ -1233,7 +2760,7 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" }, "form-data": { "version": "2.3.3", @@ -1248,7 +2775,7 @@ "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -1304,7 +2831,7 @@ "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "requires": { "safe-buffer": "^5.0.1" } @@ -1316,6 +2843,64 @@ } } }, + "loglevel": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz", + "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A==", + "dev": true + }, + "loglevel-colored-level-prefix": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/loglevel-colored-level-prefix/-/loglevel-colored-level-prefix-1.0.0.tgz", + "integrity": "sha512-u45Wcxxc+SdAlh4yeF/uKlC1SPUPCy0gullSNKXod5I4bmifzk+Q4lSLExNEVn19tGaJipbZ4V4jbFn79/6mVA==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "loglevel": "^1.4.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", + "dev": true + } + } + }, "lolex": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/lolex/-/lolex-2.6.0.tgz", @@ -1327,6 +2912,15 @@ "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" }, + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -1338,31 +2932,83 @@ "lsmod": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" + "integrity": "sha512-Y+6V75r+mGWzWEPr9h6PFmStielICu5JBHLUg18jCsD2VFmEfgHbq/EgnY4inElsUD9eKL9id1qp34w46rSIKQ==" }, "lynx": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", - "integrity": "sha1-Mxjc7xaQi4KG6Bisz9sxzXQkj50=", + "integrity": "sha512-JI52N0NwK2b/Md0TFPdPtUBI46kjyJXF7+q08l2yvQ56q6QA8s7ZjZQQRoxFpS2jDXNf/B0p8ID+OIKcTsZwzw==", "requires": { "mersenne": "~0.0.3", "statsd-parser": "~0.0.4" } }, + "make-plural": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz", + "integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", + "dev": true, + "optional": true + } + } + }, + "map-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", + "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==", + "dev": true + }, + "media-typer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.2.0.tgz", + "integrity": "sha512-TSggxYk75oP4tae7JkT8InpcFGUP4340zg1dOWjcu9qcphaDKtXEuNUv3OD4vJ+gVTvIDK797W0uYeNm8qqsDg==" + }, "merge-descriptors": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-0.0.2.tgz", - "integrity": "sha1-w2pSp4FDdRPFcnXzndnTF1FKyMc=" + "integrity": "sha512-dYBT4Ep+t/qnPeJcnMymmhTdd4g8/hn48ciaDqLAkfRf8abzLPS6Rb6EBdz5CZCL8tzZuI5ps9MhGQGxk+EuKg==" }, "mersenne": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/mersenne/-/mersenne-0.0.4.tgz", - "integrity": "sha1-QB/ex+whzbngPNPTAhOY2iGycIU=" + "integrity": "sha512-XoSUL+nF8hMTKGQxUs8r3Btdsf1yuKKBdCCGbh3YXgCXuVKishpZv1CNc385w9s8t4Ynwc5h61BwW/FCVulkbg==" + }, + "messageformat": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz", + "integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==", + "dev": true, + "requires": { + "make-plural": "^4.3.0", + "messageformat-formatters": "^2.0.1", + "messageformat-parser": "^4.1.2" + } + }, + "messageformat-formatters": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz", + "integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==", + "dev": true + }, + "messageformat-parser": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.2.tgz", + "integrity": "sha512-7dWuifeyldz7vhEuL96Kwq1fhZXBW+TUfbnHN4UCrCxoXQTYjHnR78eI66Gk9LaLLsAvzPNVJBaa66DRfFNaiA==", + "dev": true }, "methods": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/methods/-/methods-0.1.0.tgz", - "integrity": "sha1-M11Cnu/SG3us8unJIqjSvRSjDk8=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.0.1.tgz", + "integrity": "sha512-2403MfnVypWSNIEpmQ26/ObZ5kSUx37E8NHRvriw0+I8Sne7k0HGuLGCk0OrCqURh4UIygD0cSsYq+Ll+kzNqA==" }, "metrics-sharelatex": { "version": "2.2.0", @@ -1381,14 +3027,14 @@ "coffee-script": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + "integrity": "sha512-Tx8itEfCsQp8RbLDFt7qwjqXycAx2g6SI7//4PPUR2j6meLmNifYm6zKrNDcU1+Q/GWRhjhEZk7DaLG1TfIzGA==" } } }, "mime": { "version": "1.2.11", "resolved": "https://registry.npmjs.org/mime/-/mime-1.2.11.tgz", - "integrity": "sha1-WCA+7Ybjpe8XrtK32evUfwpg3RA=" + "integrity": "sha512-Ysa2F/nqTNGHhhm9MV8ure4+Hc+Y8AWiqUdHxsO7xu8zc92ND9f3kpALHjaP026Ft17UfxrMt95c50PLUeynBw==" }, "mime-db": { "version": "1.40.0", @@ -1403,6 +3049,12 @@ "mime-db": "1.40.0" } }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1414,12 +3066,12 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==" }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", "requires": { "minimist": "0.0.8" } @@ -1488,7 +3140,7 @@ "module-details-from-path": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.3.tgz", - "integrity": "sha1-EUyUlnPiqKNenTV4hSeqN7Z52is=" + "integrity": "sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==" }, "moment": { "version": "2.24.0", @@ -1509,7 +3161,7 @@ "readable-stream": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", + "integrity": "sha512-a6ibcfWFhgihuTw/chl+u3fB5ykBZFmnvpyZHebY0MCQE4vvYcsCLpCeaQ1BkH7HdJYavNSqF0WDLeo4IPHQaQ==", "requires": { "buffer-shims": "~1.0.0", "core-util-is": "~1.0.0", @@ -1542,7 +3194,7 @@ "mongojs": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/mongojs/-/mongojs-2.4.0.tgz", - "integrity": "sha1-8of7/UV/7fWItakBHmhRPZ3TK/s=", + "integrity": "sha512-QqqJ06x5EBBG121dxXbm0kQ8mNVAOGMeG3RKbNvtbiFez5EhCTMDUikYcsa0V9tphlvsheO5riMHcXHxW0q3lw==", "requires": { "each-series": "^1.0.0", "mongodb": "^2.0.45", @@ -1557,12 +3209,18 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "dev": true }, "mv": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz", - "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=", + "integrity": "sha512-at/ZndSy3xEGJ8i0ygALh8ru9qy7gWW1cmkaqBN29JmMlIvM//MEO9y1sk/avxuwnPcfhkejkLsuPxH81BrkSg==", "optional": true, "requires": { "mkdirp": "~0.5.1", @@ -1578,19 +3236,31 @@ "native-promise-only": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", + "dev": true + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, "ncp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", - "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", "optional": true }, "negotiator": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.9.tgz", - "integrity": "sha1-kuRrbbU8fkIe1koryU8IvnYw3z8=" + "version": "0.4.7", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.4.7.tgz", + "integrity": "sha512-ujxWwyRfZ6udAgHGECQC3JDO9e6UAsuItfUMcqA0Xf2OLNQTveFVFx+fHGIJ5p0MJaJrZyGQqPwzuN0NxJzEKA==" + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "nise": { "version": "1.3.3", @@ -1608,13 +3278,13 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "integrity": "sha512-nifX1uj4S9IrK/w3Xe7kKvNEepXivANs9ng60Iq7PU/BlouV3yL/VUhFqTuTq33ykwUqoNcTeGo5vdOBP4jS/Q==", "dev": true, "requires": { "isarray": "0.0.1" @@ -1638,21 +3308,127 @@ "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=", "dev": true }, - "oauth-sign": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.3.0.tgz", - "integrity": "sha1-y1QPk7srIqfVlBaRoojWDo6pOG4=", + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "dev": true, - "optional": true + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "oauth-sign": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.4.0.tgz", + "integrity": "sha1-8ilW8x6nFRqCHl8vsywRPK2Ln2k=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.entries": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", + "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.fromentries": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.2.tgz", + "integrity": "sha512-r3ZiBH7MQppDJVLx6fhD618GKNG40CZYH9wgwdhKxBDDbQgjeWGGd4AtkZad84d291YxvWe7bJGuE65Anh0dxQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } + }, + "object.values": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.1.tgz", + "integrity": "sha512-WTa54g2K8iu0kmS/us18jEmdv1a4Wi//BZ/DTVYEcH0XhLM5NYdpDHja3gt57VrZLcNAO2WGA+KpWsDBaHt6eA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1", + "has": "^1.0.3" + } }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "requires": { "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true + }, "p-limit": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz", @@ -1661,20 +3437,64 @@ "p-try": "^2.0.0" } }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + }, + "dependencies": { + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "dev": true + } + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-duration": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.1.tgz", - "integrity": "sha1-ExFN3JiRwezSgANiRFVN5DZHoiY=" + "integrity": "sha512-MPkERaX8suJ97HH2TtTN81ASYdFWouJqcnfVYSFHvWCI13vN4NzbvKsBOYN/7o8cTSoaNVMz4H8wG7GjTj0q6g==" + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } }, "parse-mongo-url": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-mongo-url/-/parse-mongo-url-1.1.1.tgz", - "integrity": "sha1-ZiON9fjnwMjKTNlw1KtqE3PrdbU=" + "integrity": "sha512-7bZUusQIrFLwvsLHBnCz2WKYQ5LKO/LwKPnvQxbMIh9gDx8H5ZsknRmLjZdn6GVdrgVOwqDrZKsY0qDLNmRgcw==" }, "parse-ms": { "version": "2.1.0", @@ -1684,12 +3504,30 @@ "parseurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.0.1.tgz", - "integrity": "sha1-Llfc5u/dN8NRhwEDCUTCK/OIt7Q=" + "integrity": "sha512-6W9+0+9Ihayqwjgp4OaLLqZ3KDtqPY2PtUPz8YNiy4PamjJv+7x6J9GO93O9rUZOLgaanTPxsKTasxqKkO1iSw==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true }, "path-parse": { "version": "1.0.6", @@ -1699,18 +3537,647 @@ "path-to-regexp": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.2.tgz", - "integrity": "sha1-mysVH5zDAYye6lDKlXKeBXgXErQ=" + "integrity": "sha512-BZU7Qr+qKkXJX9UBypMNikdZ85cQSjtfMhUykJFLJn4SNF0jhEbb9nMRpnNdA76ESryY8JpMA4k6XKdz3JS1pw==" + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha512-dUnb5dXUf+kzhC/W/F4e5/SkluXIFf5VUHolW1Eg1irn1hGWjPGdsRcvYJ1nD6lhk8Ir7VM0bHJKsYTx8Jx9OQ==", + "dev": true, + "requires": { + "pify": "^2.0.0" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, "pify": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "dev": true + }, + "prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true + }, + "prettier-eslint": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/prettier-eslint/-/prettier-eslint-9.0.1.tgz", + "integrity": "sha512-KZT65QTosSAqBBqmrC+RpXbsMRe7Os2YSR9cAfFbDlyPAopzA/S5bioiZ3rpziNQNSJaOxmtXSx07EQ+o2Dlug==", + "dev": true, + "requires": { + "@typescript-eslint/parser": "^1.10.2", + "common-tags": "^1.4.0", + "core-js": "^3.1.4", + "dlv": "^1.1.0", + "eslint": "^5.0.0", + "indent-string": "^4.0.0", + "lodash.merge": "^4.6.0", + "loglevel-colored-level-prefix": "^1.0.0", + "prettier": "^1.7.0", + "pretty-format": "^23.0.1", + "require-relative": "^0.8.7", + "typescript": "^3.2.1", + "vue-eslint-parser": "^2.0.2" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha512-wFUFA5bg5dviipbQQ32yOQhl6gcJaJXiHE7dvR8VYPG97+J/GNC5FKGepKdEDUFeXRzDxPF1X/Btc8L+v7oqIQ==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, + "prettier-eslint-cli": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/prettier-eslint-cli/-/prettier-eslint-cli-5.0.0.tgz", + "integrity": "sha512-cei9UbN1aTrz3sQs88CWpvY/10PYTevzd76zoG1tdJ164OhmNTFRKPTOZrutVvscoQWzbnLKkviS3gu5JXwvZg==", + "dev": true, + "requires": { + "arrify": "^2.0.1", + "boolify": "^1.0.0", + "camelcase-keys": "^6.0.0", + "chalk": "^2.4.2", + "common-tags": "^1.8.0", + "core-js": "^3.1.4", + "eslint": "^5.0.0", + "find-up": "^4.1.0", + "get-stdin": "^7.0.0", + "glob": "^7.1.4", + "ignore": "^5.1.2", + "lodash.memoize": "^4.1.2", + "loglevel-colored-level-prefix": "^1.0.0", + "messageformat": "^2.2.1", + "prettier-eslint": "^9.0.0", + "rxjs": "^6.5.2", + "yargs": "^13.2.4" + }, + "dependencies": { + "ansi-escapes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", + "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha512-wFUFA5bg5dviipbQQ32yOQhl6gcJaJXiHE7dvR8VYPG97+J/GNC5FKGepKdEDUFeXRzDxPF1X/Btc8L+v7oqIQ==", + "dev": true + }, + "cli-cursor": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", + "integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==", + "dev": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint": { + "version": "5.16.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz", + "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "ajv": "^6.9.1", + "chalk": "^2.1.0", + "cross-spawn": "^6.0.5", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "eslint-scope": "^4.0.3", + "eslint-utils": "^1.3.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^5.0.1", + "esquery": "^1.0.1", + "esutils": "^2.0.2", + "file-entry-cache": "^5.0.1", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.7.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "inquirer": "^6.2.2", + "js-yaml": "^3.13.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.11", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "progress": "^2.0.0", + "regexpp": "^2.0.1", + "semver": "^5.5.1", + "strip-ansi": "^4.0.0", + "strip-json-comments": "^2.0.1", + "table": "^5.2.3", + "text-table": "^0.2.0" + }, + "dependencies": { + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + } + } + }, + "eslint-scope": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", + "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz", + "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==", + "dev": true, + "requires": { + "acorn": "^6.0.7", + "acorn-jsx": "^5.0.0", + "eslint-visitor-keys": "^1.0.0" + } + }, + "figures": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", + "integrity": "sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stdin": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-7.0.0.tgz", + "integrity": "sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "inquirer": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz", + "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==", + "dev": true, + "requires": { + "ansi-escapes": "^3.2.0", + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^3.0.3", + "figures": "^2.0.0", + "lodash": "^4.17.12", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rxjs": "^6.4.0", + "string-width": "^2.1.0", + "strip-ansi": "^5.1.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "mute-stream": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", + "integrity": "sha512-r65nCZhrbXXb6dXOACihYApHw2Q6pV0M3V0PSxd74N0+D8nzAdEAITq2oAjA1jVnKI+tGvEBUpqiMh0+rW6zDQ==", + "dev": true + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "restore-cursor": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", + "integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==", + "dev": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, + "pretty-format": { + "version": "23.6.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", + "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0", + "ansi-styles": "^3.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha512-wFUFA5bg5dviipbQQ32yOQhl6gcJaJXiHE7dvR8VYPG97+J/GNC5FKGepKdEDUFeXRzDxPF1X/Btc8L+v7oqIQ==", + "dev": true + } + } + }, "pretty-ms": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-4.0.0.tgz", @@ -1722,7 +4189,13 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==" + }, + "progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "dev": true }, "prom-client": { "version": "11.5.1", @@ -1732,6 +4205,17 @@ "tdigest": "^0.1.1" } }, + "prop-types": { + "version": "15.7.2", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz", + "integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==", + "dev": true, + "requires": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.8.1" + } + }, "protobufjs": { "version": "6.8.8", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.8.tgz", @@ -1759,6 +4243,14 @@ } } }, + "proxy-addr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.1.tgz", + "integrity": "sha512-rIUGzBlSfkJMWWCgsd4N5wvVSNAcJZg//UwPZumDIbScHRUzuSOjBmIdyICiKkB9yArv+er9qC6RA/NL3AWc6A==", + "requires": { + "ipaddr.js": "0.1.2" + } + }, "psl": { "version": "1.1.32", "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.32.tgz", @@ -1767,22 +4259,34 @@ "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "qs": { "version": "0.6.6", "resolved": "https://registry.npmjs.org/qs/-/qs-0.6.6.tgz", - "integrity": "sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc=" + "integrity": "sha512-kN+yNdAf29Jgp+AYHUmC7X4QdJPR8czuMWLNLc0aRxkQ7tB3vJQEONKKT9ou/rW7EbqVec11srC9q9BiVbcnHA==" + }, + "quick-lru": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", + "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==", + "dev": true + }, + "ramda": { + "version": "0.26.1", + "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz", + "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==", + "dev": true }, "range-parser": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.0.0.tgz", - "integrity": "sha1-pLJkz+C+XONqvjdlrJwqJIdG28A=" + "integrity": "sha512-wOH5LIH2ZHo0P7/bwkR+aNbJ+kv3CHVX4B8qs9GqbtY29fi1bGPV5xczrutN20G+Z4XhRqRMTW3q0S4iyJJPfw==" }, "raven": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/raven/-/raven-1.1.3.tgz", - "integrity": "sha1-QnPBrm005CMPUbLAEEGjK5Iygio=", + "integrity": "sha512-RYov4wAaflZasWiCrZuizd3jNXxCOkW1WrXgWsGVb8kRpdHNZ+vPY27R6RhVtqzWp+DG9a5l6iP0QUPK4EgzaQ==", "requires": { "cookie": "0.3.1", "json-stringify-safe": "5.0.1", @@ -1794,24 +4298,51 @@ "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "integrity": "sha512-+IJOX0OqlHCszo2mBUq+SrEbCj6w7Kpffqx60zYbPTFaO4+yYgRjHwcZNpWvaTylDHaV7PPmBHzSecZiMhtPgw==" }, "uuid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + "integrity": "sha512-rqE1LoOVLv3QrZMjb4NkF5UWlkurCfPyItVnFPNKDDGkHw4dQUdE4zMcLqx28+0Kcf3+bnUk4PisaiRJT4aiaQ==" } } }, "raw-body": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha1-HQJ8K/oRasxmI7yo8AAWVyqH1CU=", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", "requires": { "bytes": "1", "string_decoder": "0.10" } }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha512-eFIBOPW7FGjzBuk3hdXEuNSiTZS/xEMlH49HxMyzb0hyPfu4EhVjT2DH32K1hSSmVq4sebAWnZuuY5auISUTGA==", + "dev": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha512-1orxQfbWGUiTn9XsPlChs6rLie/AV9jwZTGmu2NZw/CUDJQchXJFYE0Fq5j7+n558T1JhDWLdhyd1Zj+wLY//w==", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, "readable-stream": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", @@ -1841,26 +4372,78 @@ } } }, + "regenerator-runtime": { + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==", + "dev": true + }, + "regexp.prototype.flags": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.0.tgz", + "integrity": "sha512-2+Q0C5g951OlYlJz6yu5/M33IcsESLlLfsyIaLJaG4FA2r4yP8MvVMJUUP/fVBkSpbbbZlS5gynbEWLipiiXiQ==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "regexpp": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", + "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", + "dev": true + }, "request": { - "version": "2.34.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.34.0.tgz", - "integrity": "sha1-tdi5UmrdSi1GKfTUFxJFc5lkRa4=", + "version": "2.47.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.47.0.tgz", + "integrity": "sha1-Cen9Gk/tZZOoBe+CArIPDF7LSF8=", "dev": true, "requires": { "aws-sign2": "~0.5.0", + "bl": "~0.9.0", + "caseless": "~0.6.0", + "combined-stream": "~0.0.5", "forever-agent": "~0.5.0", "form-data": "~0.1.0", - "hawk": "~1.0.0", + "hawk": "1.1.1", "http-signature": "~0.10.0", "json-stringify-safe": "~5.0.0", - "mime": "~1.2.9", + "mime-types": "~1.0.1", "node-uuid": "~1.4.0", - "oauth-sign": "~0.3.0", - "qs": "~0.6.0", + "oauth-sign": "~0.4.0", + "qs": "~2.3.1", + "stringstream": "~0.0.4", "tough-cookie": ">=0.12.0", - "tunnel-agent": "~0.3.0" + "tunnel-agent": "~0.4.0" + }, + "dependencies": { + "caseless": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.6.0.tgz", + "integrity": "sha1-gWfBq4OX+1u5X5bSjlqBxQ8kesQ=", + "dev": true + }, + "mime-types": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-1.0.2.tgz", + "integrity": "sha1-mVrhOSq4r/y/yyZB3QVOlDwNXc4=", + "dev": true + }, + "qs": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-2.3.3.tgz", + "integrity": "sha1-6eha2+ddoLvkyOBHaghikPhjtAQ=", + "dev": true + } } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, "require-in-the-middle": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-4.0.0.tgz", @@ -1889,7 +4472,19 @@ "require-like": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/require-like/-/require-like-0.1.2.tgz", - "integrity": "sha1-rW8wwTvs15cBDEaK+ndcDAprR/o=", + "integrity": "sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "require-relative": { + "version": "0.8.7", + "resolved": "https://registry.npmjs.org/require-relative/-/require-relative-0.8.7.tgz", + "integrity": "sha512-AKGr4qvHiryxRb19m3PsLRGuKVAbJLUD7E6eOaHkfKhwc+vSgVOCY5xNvm9EkolBKTOf0GrQAZKLimOCz81Khg==", "dev": true }, "require_optional": { @@ -1912,7 +4507,17 @@ "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dev": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } }, "retry-axios": { "version": "0.3.2", @@ -1930,12 +4535,30 @@ "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", - "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=", + "integrity": "sha512-J5xnxTyqaiw06JjMftq7L9ouA448dw/E7dKghkP9WpKNuwmARNNg+Gk8/u5ryb9N/Yo2+z3MCwuqFK/+qPOPfQ==", "optional": true, "requires": { "glob": "^6.0.1" } }, + "run-async": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", + "integrity": "sha512-Fx+QT3fGtS0jk8OvKyKgAB2YHPsrmqBRcMeTC5AZ+lp4vzXKPPrFSY3iLdgvjA3HVBkIvJeM6J80LRjx8bQwhA==", + "dev": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -1961,7 +4584,7 @@ "sandboxed-module": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-0.3.0.tgz", - "integrity": "sha1-8fvvvYCaT2kHO9B8rm/H2y6vX2o=", + "integrity": "sha512-/2IfB1wtca3eNVPXbQrb6UkhE/1pV4Wz+5CdG6DPYqeaDsYDzxglBT7/cVaqyrlRyQKdmw+uTZUTRos9FFD2PQ==", "dev": true, "requires": { "require-like": "0.1.2", @@ -1971,7 +4594,7 @@ "stack-trace": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.6.tgz", - "integrity": "sha1-HnGb1qJin/CcGJ4Xqe+QKpT8XbA=", + "integrity": "sha512-5/6uZt7RYjjAl8z2j1mXWAewz+I4Hk2/L/3n6NRLIQ31+uQ7nMd9O6G69QCdrrufHv0QGRRHl/jwUEGTqhelTA==", "dev": true } } @@ -1982,33 +4605,77 @@ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "send": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.3.0.tgz", - "integrity": "sha1-lxgyRjSAb8dbxPj15R9X2dZmBuc=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.5.0.tgz", + "integrity": "sha512-uuPWJQK6p0btveYhOxRqvcB9Cfcs9ugNrMd0SoM/tqzOC9A86bX4rDEQN84X3Cc5Gpo5Az2cB4Olo5c3Aso2Sg==", "requires": { - "buffer-crc32": "0.2.1", - "debug": "0.8.0", - "fresh": "~0.2.1", + "debug": "1.0.2", + "escape-html": "1.0.1", + "finished": "1.2.2", + "fresh": "0.2.2", "mime": "1.2.11", + "ms": "0.6.2", "range-parser": "~1.0.0" }, "dependencies": { - "debug": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-0.8.0.tgz", - "integrity": "sha1-BUHqkfDlA/3wxe7UGKMlUCNJZ/A=" + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha512-/pc3eh7TWorTtbvXg8je4GvrvEqCfH7PA3P7iW01yL2E53FKixzgMBaQi0NOPbMJqY34cBSvR0tZtmlTkdUG4A==" } } }, "serve-static": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.1.0.tgz", - "integrity": "sha1-RU36Bbs93U5wGokVuDoniqkcVkM=", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.3.2.tgz", + "integrity": "sha512-KwjCeYUx7IM1neg8/P0+O1DZsl76XcOSuV0ZxrI0r60vwGlcjMjKOYCK/OFLJy/a2CFuIyAa/x0PuQ0yuG+IgQ==", "requires": { - "parseurl": "1.0.1", - "send": "0.3.0" + "escape-html": "1.0.1", + "parseurl": "~1.1.3", + "send": "0.6.0" + }, + "dependencies": { + "debug": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-1.0.3.tgz", + "integrity": "sha512-MltK7Ykj/udtD728gD/RrONStwVnDpBNIP1h+CBcnwnJdHqHxfWHI1E8XLootUl7NOPAYTCCXlb8/Qmy7WyB1w==", + "requires": { + "ms": "0.6.2" + } + }, + "ms": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz", + "integrity": "sha512-/pc3eh7TWorTtbvXg8je4GvrvEqCfH7PA3P7iW01yL2E53FKixzgMBaQi0NOPbMJqY34cBSvR0tZtmlTkdUG4A==" + }, + "parseurl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.1.3.tgz", + "integrity": "sha512-7y9IL/9x2suvr1uIvoAc3yv3f28hZ55g2OM+ybEtnZqV6Ykeg36sy1PCsTN9rQUZYzb9lTKLzzmJM11jaXSloA==" + }, + "send": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.6.0.tgz", + "integrity": "sha512-A3EwHmDwcPcmLxIRNjr2YbXiYWq6M9JyUq4303pLKVFs4m5oeME0a9Cpcu9N22fED5XVepldjPYGo9eJifb7Yg==", + "requires": { + "debug": "1.0.3", + "depd": "0.3.0", + "escape-html": "1.0.1", + "finished": "1.2.2", + "fresh": "0.2.2", + "mime": "1.2.11", + "ms": "0.6.2", + "range-parser": "~1.0.0" + } + } } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true + }, "settings-sharelatex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/settings-sharelatex/-/settings-sharelatex-1.1.0.tgz", @@ -2020,15 +4687,46 @@ "coffee-script": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz", - "integrity": "sha1-gIs5bhEPU9AhoZpO8fZb4OjjX6M=" + "integrity": "sha512-Tx8itEfCsQp8RbLDFt7qwjqXycAx2g6SI7//4PPUR2j6meLmNifYm6zKrNDcU1+Q/GWRhjhEZk7DaLG1TfIzGA==" } } }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "dev": true + }, "shimmer": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.2.1.tgz", "integrity": "sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==" }, + "side-channel": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.2.tgz", + "integrity": "sha512-7rL9YlPHg7Ancea1S96Pa8/QWb4BtXL/TZvS6B8XFetGBeuhAsfmUspK6DokBeZ64+Kj9TCNRD/30pVz1BvQNA==", + "dev": true, + "requires": { + "es-abstract": "^1.17.0-next.1", + "object-inspect": "^1.7.0" + } + }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha512-meQNNykwecVxdu1RlYMKpQx4+wefIYpmxi6gexo/KAbwquJrBUrBmKYJrE8KFkVQAAVWEnwNdu21PgrD77J3xA==", + "dev": true + }, "sinon": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/sinon/-/sinon-3.2.1.tgz", @@ -2055,13 +4753,13 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, "path-to-regexp": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "integrity": "sha512-nifX1uj4S9IrK/w3Xe7kKvNEepXivANs9ng60Iq7PU/BlouV3yL/VUhFqTuTq33ykwUqoNcTeGo5vdOBP4jS/Q==", "dev": true, "requires": { "isarray": "0.0.1" @@ -2075,12 +4773,30 @@ } } }, + "slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + } + } + }, "sntp": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/sntp/-/sntp-0.2.4.tgz", "integrity": "sha1-+4hfGLDzqtGJ+CSGJTa87ux1CQA=", "dev": true, - "optional": true, "requires": { "hoek": "0.9.x" } @@ -2090,6 +4806,38 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" }, + "spdx-correct": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", + "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", + "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", + "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", + "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", + "dev": true + }, "split": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", @@ -2098,6 +4846,12 @@ "through": "2" } }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -2125,29 +4879,120 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" } } }, "stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha1-qPbq7KkGdMMz58Q5U/J1tFFRBpU=" + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==" }, "statsd-parser": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/statsd-parser/-/statsd-parser-0.0.4.tgz", - "integrity": "sha1-y9JDlTzELv/VSLXSI4jtaJ7GOb0=" + "integrity": "sha512-7XO+ur89EalMXXFQaydsczB8sclr5nDsNIoUu0IzJx1pIbHUhO3LtpSzBwetIuU9DyTLMiVaJBMtWS/Nb2KR4g==" }, "stream-shift": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + "integrity": "sha512-Afuc4BKirbx0fwm9bKOehZPG01DJkm/4qbklw4lo9nMPqd2x0kZTLcgwQUXdGiPPY489l3w8cQ5xEEAGbg8ACQ==" + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string.prototype.matchall": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.2.tgz", + "integrity": "sha512-N/jp6O5fMf9os0JU3E72Qhf590RSRZU/ungsL/qJUYVTNv7hTG0P/dbPjxINVN9jpscu3nzYwKESU3P3RY5tOg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0", + "has-symbols": "^1.0.1", + "internal-slot": "^1.0.2", + "regexp.prototype.flags": "^1.3.0", + "side-channel": "^1.0.2" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "stringstream": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", + "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, + "strip-json-comments": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.0.1.tgz", + "integrity": "sha512-VTyMAUfdm047mwKl+u79WIdrZxtFtn+nBxHeb844XBQ9uMNTuTHdx2hc5RiAJYqwTj3wc/xe5HLSdJSkJ+WfZw==", + "dev": true }, "supports-color": { "version": "4.4.0", @@ -2158,10 +5003,65 @@ "has-flag": "^2.0.0" } }, + "table": { + "version": "5.4.6", + "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", + "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", + "dev": true, + "requires": { + "ajv": "^6.10.2", + "lodash": "^4.17.14", + "slice-ansi": "^2.1.0", + "string-width": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, "tdigest": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", - "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", + "integrity": "sha512-CXcDY/NIgIbKZPx5H4JJNpq6JwJhU5Z4+yWj4ZghDc7/9nVajiRlPPyMXRePPPlBfcayUqtoCXjo7/Hm82ecUA==", "requires": { "bintrees": "1.0.1" } @@ -2186,13 +5086,19 @@ "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "dev": true + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", "dev": true }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" }, "through2": { "version": "2.0.5", @@ -2206,53 +5112,103 @@ "thunky": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/thunky/-/thunky-0.1.0.tgz", - "integrity": "sha1-vzAUaCTituZ7Dy16Ssi+smkIaE4=" + "integrity": "sha512-vquTt/sKNzFqFK8DKLg33U7deg93WKYH4CE2Ul9hOyMCfm7VXgM7GJQRpPAgnmgnrf407Fcq8TQVEKlbavAu+A==" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "dev": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true }, "to-mongodb-core": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-mongodb-core/-/to-mongodb-core-2.0.0.tgz", - "integrity": "sha1-NZbsdhOsmtO5ioncua77pWnNJ+s=" + "integrity": "sha512-vfXXcGYFP8+0L5IPOtUzzVIvPE/G3GN0TKa/PRBlzPqYyhm+UxhPmvv634EQgO4Ot8dHbBFihOslMJQclY8Z9A==" }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", + "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", "dev": true, - "optional": true, "requires": { - "punycode": "^1.4.1" + "ip-regex": "^2.1.0", + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } } }, + "tslib": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", + "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==", + "dev": true + }, "tunnel-agent": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.3.0.tgz", - "integrity": "sha1-rWgbaPUyGtKCfEz7G31d8s/pQu4=", - "dev": true, - "optional": true + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", + "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", + "dev": true }, "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==" + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } }, "type-detect": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", - "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "integrity": "sha512-5rqszGVwYgBoDkIm2oUtvkfZMQ0vk29iDMU0W2qCa3rG0vPDNczCMT4hV/bLBgLg8k8ri6+u3Zbt+S/14eMzlA==", + "dev": true + }, + "type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", "dev": true }, "type-is": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.1.0.tgz", - "integrity": "sha1-0CReyLJnZmjVndDPMlUGBnalfbY=", + "integrity": "sha512-bGTRFAlk7i/YYyx/d1xYm6gLrALwTcY2HLwsVqAIPKJjjtlI/rGXRgjQrcOln2fcHCbAqi0hrueZ2yPnHvCipQ==", "requires": { "mime": "~1.2.11" } }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, "underscore": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", - "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==" }, "uri-js": { "version": "4.2.2", @@ -2272,17 +5228,38 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "utils-merge": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", - "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + "integrity": "sha512-HwU9SLQEtyo+0uoKXd1nkLqigUWLB+QuNQR4OcmB73eWqksM5ovuqcycks2x043W8XVb75rG1HQ0h93TMXkzQQ==" + }, + "v8-compile-cache": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz", + "integrity": "sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==", + "dev": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/vary/-/vary-0.1.0.tgz", + "integrity": "sha512-tyyeG46NQdwyVP/RsWLSrT78ouwEuvwk9gK8vQK4jdXmqoXtTXW+vsCfNcnqRhigF8olV34QVZarmAi6wBV2Mw==" }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -2292,24 +5269,251 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + } + } + }, + "vue-eslint-parser": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz", + "integrity": "sha512-ZezcU71Owm84xVF6gfurBQUGg8WQ+WZGxgDEQu1IHFBZNx7BFZg3L1yHxrCBNNwbwFtE1GuvfJKMtb6Xuwc/Bw==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "acorn": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha512-AU7pnZkguthwBjKgCg6998ByQNIMjbuDQZ8bb78QAFZwPfmKia8AIzgY/gWgqCjnht8JLdXmB4YxA0KaV60ncQ==", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha512-OLUyIIZ7mF5oaAUT1w0TFqQS81q3saT46x8t7ukpPjMNk+nbs4ZHhs7ToV8EWnLYLepjETXd4XaCE4uxkMeqUw==", + "dev": true + } + } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "eslint-scope": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.3.tgz", + "integrity": "sha512-W+B0SvF4gamyCTmUc+uITPY0989iXVfKvhwtmJocTaYoc/3khEHmEmvfY/Gn9HA9VV75jrQECsHizkNw1b68FA==", + "dev": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } } } }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } }, "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "integrity": "sha512-iTwvhNBRetXWe81+VcIw5YeadVSWyze7uA7nVnpP13ulrpnJ3UfQm5ApGnrkmxDJFdrblRdZs0EvaTCIfei5oQ==" + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yallist": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + }, + "dependencies": { + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } } } } diff --git a/services/docstore/package.json b/services/docstore/package.json index b0b24ce551..82d2e34426 100644 --- a/services/docstore/package.json +++ b/services/docstore/package.json @@ -8,23 +8,20 @@ "url": "https://github.com/sharelatex/docstore-sharelatex.git" }, "scripts": { - "compile:app": "([ -e app/coffee ] && coffee -m $COFFEE_OPTIONS -o app/js -c app/coffee || echo 'No CoffeeScript folder to compile') && ( [ -e app.coffee ] && coffee -m $COFFEE_OPTIONS -c app.coffee || echo 'No CoffeeScript app to compile')", - "start": "npm run compile:app && node $NODE_APP_OPTIONS app.js", - "test:acceptance:_run": "AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY AWS_BUCKET=$AWS_BUCKET AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID mocha --recursive --reporter spec --timeout 30000 --exit $@ test/acceptance/js", - "test:acceptance": "npm run compile:app && npm run compile:acceptance_tests && npm run test:acceptance:_run -- --grep=$MOCHA_GREP", - "test:unit:_run": "mocha --recursive --reporter spec --exit $@ test/unit/js", - "test:unit": "npm run compile:app && npm run compile:unit_tests && npm run test:unit:_run -- --grep=$MOCHA_GREP", - "compile:unit_tests": "[ ! -e test/unit/coffee ] && echo 'No unit tests to compile' || coffee -o test/unit/js -c test/unit/coffee", - "compile:acceptance_tests": "[ ! -e test/acceptance/coffee ] && echo 'No acceptance tests to compile' || coffee -o test/acceptance/js -c test/acceptance/coffee", - "compile:all": "npm run compile:app && npm run compile:unit_tests && npm run compile:acceptance_tests && npm run compile:smoke_tests", + "start": "node $NODE_APP_OPTIONS app.js", + "test:acceptance:_run": "mocha --recursive --reporter spec --timeout 15000 --exit $@ test/acceptance/js", + "test:acceptance": "npm run test:acceptance:_run -- --grep=$MOCHA_GREP", + "test:unit:_run": "mocha --recursive --reporter spec $@ test/unit/js", + "test:unit": "npm run test:unit:_run -- --grep=$MOCHA_GREP", "nodemon": "nodemon --config nodemon.json", - "compile:smoke_tests": "[ ! -e test/smoke/coffee ] && echo 'No smoke tests to compile' || coffee -o test/smoke/js -c test/smoke/coffee" + "lint": "node_modules/.bin/eslint .", + "format": "node_modules/.bin/prettier-eslint '**/*.js' --list-different", + "format:fix": "node_modules/.bin/prettier-eslint '**/*.js' --write" }, "dependencies": { "async": "~0.8.0", "body-parser": "~1.0.2", - "coffee-script": "^1.12.7", - "express": "~4.1.1", + "express": "~4.5.0", "logger-sharelatex": "^1.7.0", "metrics-sharelatex": "^2.2.0", "mongojs": "2.4.0", @@ -32,10 +29,28 @@ "underscore": "~1.6.0" }, "devDependencies": { + "babel-eslint": "^10.0.3", "bunyan": "~0.22.3", "chai": "~1.9.1", + "eslint": "^6.6.0", + "eslint-config-prettier": "^6.10.0", + "eslint-config-standard": "^14.1.0", + "eslint-config-standard-jsx": "^8.1.0", + "eslint-config-standard-react": "^9.2.0", + "eslint-plugin-chai-expect": "^2.1.0", + "eslint-plugin-chai-friendly": "^0.5.0", + "eslint-plugin-import": "^2.20.1", + "eslint-plugin-jsx-a11y": "^6.2.3", + "eslint-plugin-mocha": "^6.2.2", + "eslint-plugin-node": "^11.0.0", + "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-react": "^7.18.3", + "eslint-plugin-standard": "^4.0.1", "mocha": "^4.0.1", - "request": "~2.34.0", + "prettier": "^1.19.1", + "prettier-eslint-cli": "^5.0.0", + "request": "~2.47.0", "sandboxed-module": "~0.3.0", "sinon": "~3.2.1" }, diff --git a/services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee b/services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee deleted file mode 100644 index 0291eef032..0000000000 --- a/services/docstore/test/acceptance/coffee/ArchiveDocsTests.coffee +++ /dev/null @@ -1,392 +0,0 @@ -sinon = require "sinon" -chai = require("chai") -should = chai.should() -{db, ObjectId, ISODate} = require "../../../app/js/mongojs" -async = require "async" -Settings = require("settings-sharelatex") -DocArchiveManager = require("../../../app/js/DocArchiveManager.js") -request = require "request" -DocstoreApp = require "./helpers/DocstoreApp" -DocstoreClient = require "./helpers/DocstoreClient" - - -describe "Archiving", -> - - before (done)-> - DocstoreApp.ensureRunning(done) - - describe "multiple docs in a project", -> - before (done) -> - @project_id = ObjectId() - @docs = [{ - _id: ObjectId() - lines: ["one", "two", "three"] - ranges: {} - version: 2 - }, { - _id: ObjectId() - lines: ["aaa", "bbb", "ccc"] - ranges: {} - version: 4 - }] - jobs = for doc in @docs - do (doc) => - (callback) => - DocstoreClient.createDoc @project_id, doc._id, doc.lines, doc.version, doc.ranges, callback - - async.series jobs, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - done() - - it "should archive all the docs", (done) -> - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - jobs = for doc in @docs - do (doc) => - (callback) => - db.docs.findOne _id: doc._id, (error, doc) => - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - callback() - async.series jobs, done - - it "should set the docs in s3 correctly", (done) -> - jobs = for doc in @docs - do (doc) => - (callback) => - DocstoreClient.getS3Doc @project_id, doc._id, (error, res, s3_doc) => - s3_doc.lines.should.deep.equal doc.lines - s3_doc.ranges.should.deep.equal doc.ranges - callback() - async.series jobs, done - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should return the docs", (done) -> - for doc, i in @fetched_docs - doc.lines.should.deep.equal @docs[i].lines - done() - - it "should restore the docs to mongo", (done) -> - jobs = for doc, i in @docs - do (doc, i) => - (callback) => - db.docs.findOne _id: doc._id, (error, doc) => - doc.lines.should.deep.equal @docs[i].lines - doc.ranges.should.deep.equal @docs[i].ranges - should.not.exist doc.inS3 - callback() - async.series jobs, done - - describe "a deleted doc", -> - before (done) -> - @project_id = ObjectId() - @doc = { - _id: ObjectId() - lines: ["one", "two", "three"] - ranges: {} - version: 2 - } - DocstoreClient.createDoc @project_id, @doc._id, @doc.lines, @doc.version, @doc.ranges, (error) => - throw error if error? - DocstoreClient.deleteDoc @project_id, @doc._id, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - done() - - it "should successully archive the docs", (done) -> - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - doc.deleted.should.equal true - done() - - it "should set the doc in s3 correctly", (done) -> - DocstoreClient.getS3Doc @project_id, @doc._id, (error, res, s3_doc) => - throw error if error? - s3_doc.lines.should.deep.equal @doc.lines - s3_doc.ranges.should.deep.equal @doc.ranges - done() - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should not included the deleted", (done) -> - @fetched_docs.length.should.equal 0 - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - doc.ranges.should.deep.equal @doc.ranges - should.not.exist doc.inS3 - doc.deleted.should.equal true - done() - - describe "a doc with large lines", -> - before (done) -> - @project_id = ObjectId() - @timeout 1000 * 30 - quarterMegInBytes = 250000 - big_line = require("crypto").randomBytes(quarterMegInBytes).toString("hex") - @doc = { - _id: ObjectId() - lines: [big_line, big_line, big_line, big_line] - ranges: {} - version: 2 - } - DocstoreClient.createDoc @project_id, @doc._id, @doc.lines, @doc.version, @doc.ranges, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - done() - - it "should successully archive the docs", (done) -> - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - done() - - it "should set the doc in s3 correctly", (done) -> - DocstoreClient.getS3Doc @project_id, @doc._id, (error, res, s3_doc) => - throw error if error? - s3_doc.lines.should.deep.equal @doc.lines - s3_doc.ranges.should.deep.equal @doc.ranges - done() - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - doc.ranges.should.deep.equal @doc.ranges - should.not.exist doc.inS3 - done() - - describe "a doc with naughty strings", -> - before (done) -> - @project_id = ObjectId() - @doc = { - _id: ObjectId() - lines: [ "", "undefined", "undef", "null", "NULL", "(null)", "nil", "NIL", "true", "false", "True", "False", "None", "\\", "\\\\", "0", "1", "1.00", "$1.00", "1/2", "1E2", "1E02", "1E+02", "-1", "-1.00", "-$1.00", "-1/2", "-1E2", "-1E02", "-1E+02", "1/0", "0/0", "-2147483648/-1", "-9223372036854775808/-1", "0.00", "0..0", ".", "0.0.0", "0,00", "0,,0", ",", "0,0,0", "0.0/0", "1.0/0.0", "0.0/0.0", "1,0/0,0", "0,0/0,0", "--1", "-", "-.", "-,", "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", "NaN", "Infinity", "-Infinity", "0x0", "0xffffffff", "0xffffffffffffffff", "0xabad1dea", "123456789012345678901234567890123456789", "1,000.00", "1 000.00", "1'000.00", "1,000,000.00", "1 000 000.00", "1'000'000.00", "1.000,00", "1 000,00", "1'000,00", "1.000.000,00", "1 000i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟", "̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕", "Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮", "˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥", "00˙Ɩ$-", "The quick brown fox jumps over the lazy dog", "𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠", "𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌", "𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈", "𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰", "𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘", "𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐", "⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢", "", "<script>alert('123');</script>", "", " ", "\">", "'>", ">", "", "< / script >< script >alert(123)< / script >", " onfocus=JaVaSCript:alert(123) autofocus ", "\" onfocus=JaVaSCript:alert(123) autofocus ", "' onfocus=JaVaSCript:alert(123) autofocus ", "<script>alert(123)</script>", "ript>alert(123)ript>", "-->", "\";alert(123);t=\"", "';alert(123);t='", "JavaSCript:alert(123)", ";alert(123);", "src=JaVaSCript:prompt(132)", "\"><\\x3Cscript>javascript:alert(1) ", "'`\"><\\x00script>javascript:alert(1)", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "ABC
DEF", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "test", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "`\"'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "\"`'>", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "XXX", "javascript:alert(1)\"` `>", "", "", "<a href=http://foo.bar/#x=`y></a><img alt=\"`><img src=x:x onerror=javascript:alert(1)></a>\">", "<!--[if]><script>javascript:alert(1)</script -->", "<!--[if<img src=x onerror=javascript:alert(1)//]> -->", "<script src=\"/\\%(jscript)s\"></script>", "<script src=\"\\\\%(jscript)s\"></script>", "<IMG \"\"\"><SCRIPT>alert(\"XSS\")</SCRIPT>\">", "<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>", "<IMG SRC=# onmouseover=\"alert('xxs')\">", "<IMG SRC= onmouseover=\"alert('xxs')\">", "<IMG onmouseover=\"alert('xxs')\">", "<IMG SRC=javascript:alert('XSS')>", "<IMG SRC=javascript:alert('XSS')>", "<IMG SRC=javascript:alert('XSS')>", "<IMG SRC=\"jav ascript:alert('XSS');\">", "<IMG SRC=\"jav ascript:alert('XSS');\">", "<IMG SRC=\"jav ascript:alert('XSS');\">", "<IMG SRC=\"jav ascript:alert('XSS');\">", "perl -e 'print \"<IMG SRC=java\\0script:alert(\\\"XSS\\\")>\";' > out", "<IMG SRC=\"  javascript:alert('XSS');\">", "<SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert(\"XSS\")>", "<SCRIPT/SRC=\"http://ha.ckers.org/xss.js\"></SCRIPT>", "<<SCRIPT>alert(\"XSS\");//<</SCRIPT>", "<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >", "<SCRIPT SRC=//ha.ckers.org/.j>", "<IMG SRC=\"javascript:alert('XSS')\"", "<iframe src=http://ha.ckers.org/scriptlet.html <", "\\\";alert('XSS');//", "<plaintext>", "1;DROP TABLE users", "1'; DROP TABLE users-- 1", "' OR 1=1 -- 1", "' OR '1'='1", "-", "--", "--version", "--help", "$USER", "/dev/null; touch /tmp/blns.fail ; echo", "`touch /tmp/blns.fail`", "$(touch /tmp/blns.fail)", "@{[system \"touch /tmp/blns.fail\"]}", "eval(\"puts 'hello world'\")", "System(\"ls -al /\")", "`ls -al /`", "Kernel.exec(\"ls -al /\")", "Kernel.exit(1)", "%x('ls -al /')", "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM \"file:///etc/passwd\" >]><foo>&xxe;</foo>", "$HOME", "$ENV{'HOME'}", "%d", "%s", "%*.*s", "../../../../../../../../../../../etc/passwd%00", "../../../../../../../../../../../etc/hosts", "() { 0; }; touch /tmp/blns.shellshock1.fail;", "() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }", "CON", "PRN", "AUX", "CLOCK$", "NUL", "A:", "ZZ:", "COM1", "LPT1", "LPT2", "LPT3", "COM2", "COM3", "COM4", "Scunthorpe General Hospital", "Penistone Community Church", "Lightwater Country Park", "Jimmy Clitheroe", "Horniman Museum", "shitake mushrooms", "RomansInSussex.co.uk", "http://www.cum.qc.ca/", "Craig Cockburn, Software Specialist", "Linda Callahan", "Dr. Herman I. Libshitz", "magna cum laude", "Super Bowl XXX", "medieval erection of parapets", "evaluate", "mocha", "expression", "Arsenal canal", "classic", "Tyson Gay", "If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.", "Roses are \u001b[0;31mred\u001b[0m, violets are \u001b[0;34mblue. Hope you enjoy terminal hue", "But now...\u001b[20Cfor my greatest trick...\u001b[8m", "The quic\b\b\b\b\b\bk brown fo\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007x... [Beeeep]", "Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗" ] - ranges: {} - version: 2 - } - DocstoreClient.createDoc @project_id, @doc._id, @doc.lines, @doc.version, @doc.ranges, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - done() - - it "should successully archive the docs", (done) -> - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - done() - - it "should set the doc in s3 correctly", (done) -> - DocstoreClient.getS3Doc @project_id, @doc._id, (error, res, s3_doc) => - throw error if error? - s3_doc.lines.should.deep.equal @doc.lines - s3_doc.ranges.should.deep.equal @doc.ranges - done() - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - doc.ranges.should.deep.equal @doc.ranges - should.not.exist doc.inS3 - done() - - describe "a doc with ranges", -> - before (done) -> - @project_id = ObjectId() - @doc = { - _id: ObjectId() - lines: ["one", "two", "three"] - ranges: { - changes: - [{ - id : ObjectId(), - op : { "i" : "foo", "p" : 24 } - metadata : { "user_id" : ObjectId(), "ts" : new Date("2017-01-27T16:10:44.194Z") } - }, { - id : ObjectId(), - op : { "d" : "bar", "p" : 50 } - metadata : { "user_id" : ObjectId(), "ts" : new Date("2017-01-27T18:10:44.194Z") } - }] - comments: [{ - id: ObjectId(), - op: { "c" : "comment", "p" : 284, "t" : ObjectId() }, - metadata: { "user_id" : ObjectId(), "ts" : new Date("2017-01-26T14:22:04.869Z") } - }] - } - version: 2 - } - DocstoreClient.createDoc @project_id, @doc._id, @doc.lines, @doc.version, @doc.ranges, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - done() - - it "should successully archive the docs", (done) -> - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - done() - - it "should set the doc in s3 correctly", (done) -> - DocstoreClient.getS3Doc @project_id, @doc._id, (error, res, s3_doc) => - throw error if error? - s3_doc.lines.should.deep.equal @doc.lines - ranges = JSON.parse(JSON.stringify(@doc.ranges)) # ObjectId -> String - s3_doc.ranges.should.deep.equal ranges - done() - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - doc.ranges.should.deep.equal @doc.ranges - should.not.exist doc.inS3 - done() - - describe "a doc that is archived twice", -> - before (done) -> - @project_id = ObjectId() - @doc = { - _id: ObjectId() - lines: ["abc", "def", "ghi"] - ranges: {} - version: 2 - } - DocstoreClient.createDoc @project_id, @doc._id, @doc.lines, @doc.version, @doc.ranges, (error) => - throw error if error? - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - @res.statusCode.should.equal 204 - DocstoreClient.archiveAllDoc @project_id, (error, @res) => - throw error if error? - @res.statusCode.should.equal 204 - done() - - it "should set inS3 and unset lines and ranges in each doc", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - should.not.exist doc.lines - should.not.exist doc.ranges - doc.inS3.should.equal true - done() - - it "should set the doc in s3 correctly", (done) -> - DocstoreClient.getS3Doc @project_id, @doc._id, (error, res, s3_doc) => - throw error if error? - s3_doc.lines.should.deep.equal @doc.lines - s3_doc.ranges.should.deep.equal @doc.ranges - done() - - describe "after unarchiving from a request for the project", -> - before (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - doc.ranges.should.deep.equal @doc.ranges - should.not.exist doc.inS3 - done() - - describe "a doc with the old schema (just an array of lines)", -> - before (done) -> - @project_id = ObjectId() - @doc = { - _id: ObjectId() - lines: ["abc", "def", "ghi"] - ranges: {} - version: 2 - } - options = DocArchiveManager.buildS3Options("#{@project_id}/#{@doc._id}") - options.json = @doc.lines - request.put options, (error, res, body) => - throw error if error? - res.statusCode.should.equal 200 - db.docs.insert { - project_id: @project_id - _id: @doc._id - rev: @doc.version - inS3: true - }, (error) => - throw error if error? - DocstoreClient.getAllDocs @project_id, (error, res, @fetched_docs) => - throw error if error? - done() - - it "should restore the doc to mongo", (done) -> - db.docs.findOne _id: @doc._id, (error, doc) => - throw error if error? - doc.lines.should.deep.equal @doc.lines - should.not.exist doc.inS3 - done() - - it "should return the doc", (done) -> - @fetched_docs[0].lines.should.deep.equal @doc.lines - done() - diff --git a/services/docstore/test/acceptance/coffee/DeletingDocsTests.coffee b/services/docstore/test/acceptance/coffee/DeletingDocsTests.coffee deleted file mode 100644 index 4aac25902e..0000000000 --- a/services/docstore/test/acceptance/coffee/DeletingDocsTests.coffee +++ /dev/null @@ -1,85 +0,0 @@ -sinon = require "sinon" -chai = require("chai") -chai.should() -{db, ObjectId} = require "../../../app/js/mongojs" -expect = chai.expect -DocstoreApp = require "./helpers/DocstoreApp" - -DocstoreClient = require "./helpers/DocstoreClient" - -describe "Deleting a doc", -> - beforeEach (done) -> - @project_id = ObjectId() - @doc_id = ObjectId() - @lines = ["original", "lines"] - @version = 42 - @ranges = [] - DocstoreApp.ensureRunning => - DocstoreClient.createDoc @project_id, @doc_id, @lines, @version, @ranges, (error) => - throw error if error? - done() - - describe "when the doc exists", -> - beforeEach (done) -> - DocstoreClient.deleteDoc @project_id, @doc_id, (error, @res, doc) => - done() - - afterEach (done) -> - db.docs.remove({_id: @doc_id}, done) - - it "should insert a deleted doc into the docs collection", (done) -> - db.docs.find _id: @doc_id, (error, docs) => - docs[0]._id.should.deep.equal @doc_id - docs[0].lines.should.deep.equal @lines - docs[0].deleted.should.equal true - done() - - describe "when the doc does not exist", -> - it "should return a 404", (done) -> - missing_doc_id = ObjectId() - DocstoreClient.deleteDoc @project_id, missing_doc_id, (error, res, doc) -> - res.statusCode.should.equal 404 - done() - -describe "Destroying a project's documents", -> - describe "when the doc exists", -> - beforeEach (done) -> - db.docOps.insert {doc_id: ObjectId(@doc_id), version: 1}, (err) -> - return done(err) if err? - DocstoreClient.destroyAllDoc @project_id, done - - it "should remove the doc from the docs collection", (done) -> - db.docs.find _id: @doc_id, (err, docs) -> - expect(err).not.to.exist - expect(docs).to.deep.equal [] - done() - - it "should remove the docOps from the docOps collection", (done) -> - db.docOps.find doc_id: @doc_id, (err, docOps) -> - expect(err).not.to.exist - expect(docOps).to.deep.equal [] - done() - - describe "when the doc is archived", -> - beforeEach (done) -> - DocstoreClient.archiveAllDoc @project_id, (err) -> - return done(err) if err? - DocstoreClient.destroyAllDoc @project_id, done - - it "should remove the doc from the docs collection", (done) -> - db.docs.find _id: @doc_id, (err, docs) -> - expect(err).not.to.exist - expect(docs).to.deep.equal [] - done() - - it "should remove the docOps from the docOps collection", (done) -> - db.docOps.find doc_id: @doc_id, (err, docOps) -> - expect(err).not.to.exist - expect(docOps).to.deep.equal [] - done() - - it "should remove the doc contents from s3", (done) -> - DocstoreClient.getS3Doc @project_id, @doc_id, (error, res, s3_doc) => - throw error if error? - expect(res.statusCode).to.equal 404 - done() diff --git a/services/docstore/test/acceptance/coffee/GettingAllDocsTests.coffee b/services/docstore/test/acceptance/coffee/GettingAllDocsTests.coffee deleted file mode 100644 index 6a16006044..0000000000 --- a/services/docstore/test/acceptance/coffee/GettingAllDocsTests.coffee +++ /dev/null @@ -1,63 +0,0 @@ -sinon = require "sinon" -chai = require("chai") -chai.should() -{ObjectId} = require "mongojs" -async = require "async" -DocstoreApp = require "./helpers/DocstoreApp" - -DocstoreClient = require "./helpers/DocstoreClient" - -describe "Getting all docs", -> - beforeEach (done) -> - @project_id = ObjectId() - @docs = [{ - _id: ObjectId() - lines: ["one", "two", "three"] - ranges: {"mock": "one"} - rev: 2 - }, { - _id: ObjectId() - lines: ["aaa", "bbb", "ccc"] - ranges: {"mock": "two"} - rev: 4 - }, { - _id: ObjectId() - lines: ["111", "222", "333"] - ranges: {"mock": "three"} - rev: 6 - }] - @deleted_doc = { - _id: ObjectId() - lines: ["deleted"] - ranges: {"mock": "four"} - rev: 8 - } - version = 42 - jobs = for doc in @docs - do (doc) => - (callback) => - DocstoreClient.createDoc @project_id, doc._id, doc.lines, version, doc.ranges, callback - jobs.push (cb) => - DocstoreClient.createDoc @project_id, @deleted_doc._id, @deleted_doc.lines, version, @deleted_doc.ranges, (err)=> - DocstoreClient.deleteDoc @project_id, @deleted_doc._id, cb - jobs.unshift (cb)-> - DocstoreApp.ensureRunning cb - async.series jobs, done - - it "getAllDocs should return all the (non-deleted) docs", (done) -> - DocstoreClient.getAllDocs @project_id, (error, res, docs) => - throw error if error? - docs.length.should.equal @docs.length - for doc, i in docs - doc.lines.should.deep.equal @docs[i].lines - done() - - it "getAllRanges should return all the (non-deleted) doc ranges", (done) -> - DocstoreClient.getAllRanges @project_id, (error, res, docs) => - throw error if error? - docs.length.should.equal @docs.length - for doc, i in docs - doc.ranges.should.deep.equal @docs[i].ranges - done() - - diff --git a/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee b/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee deleted file mode 100644 index dbdbd31aa2..0000000000 --- a/services/docstore/test/acceptance/coffee/GettingDocsTests.coffee +++ /dev/null @@ -1,63 +0,0 @@ -sinon = require "sinon" -chai = require("chai") -chai.should() -{ObjectId} = require "mongojs" -DocstoreApp = require "./helpers/DocstoreApp" - -DocstoreClient = require "./helpers/DocstoreClient" - -describe "Getting a doc", -> - beforeEach (done) -> - @project_id = ObjectId() - @doc_id = ObjectId() - @lines = ["original", "lines"] - @version = 42 - @ranges = { - changes: [{ - id: ObjectId().toString() - op: { i: "foo", p: 3 } - meta: - user_id: ObjectId().toString() - ts: new Date().toString() - }] - } - DocstoreApp.ensureRunning => - DocstoreClient.createDoc @project_id, @doc_id, @lines, @version, @ranges, (error) => - throw error if error? - done() - - describe "when the doc exists", -> - it "should get the doc lines and version", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @lines - doc.version.should.equal @version - doc.ranges.should.deep.equal @ranges - done() - - describe "when the doc does not exist", -> - it "should return a 404", (done) -> - missing_doc_id = ObjectId() - DocstoreClient.getDoc @project_id, missing_doc_id, {}, (error, res, doc) -> - res.statusCode.should.equal 404 - done() - - describe "when the doc is a deleted doc", -> - beforeEach (done) -> - @deleted_doc_id = ObjectId() - DocstoreClient.createDoc @project_id, @deleted_doc_id, @lines, @version, @ranges, (error) => - throw error if error? - DocstoreClient.deleteDoc @project_id, @deleted_doc_id, done - - it "should return the doc", (done) -> - DocstoreClient.getDoc @project_id, @deleted_doc_id, {include_deleted:true},(error, res, doc) => - doc.lines.should.deep.equal @lines - doc.version.should.equal @version - doc.ranges.should.deep.equal @ranges - doc.deleted.should.equal true - done() - - it "should return a 404 when the query string is not set", (done)-> - DocstoreClient.getDoc @project_id, @deleted_doc_id, {},(error, res, doc) => - res.statusCode.should.equal 404 - done() - \ No newline at end of file diff --git a/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee b/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee deleted file mode 100644 index 2b916a3a83..0000000000 --- a/services/docstore/test/acceptance/coffee/UpdatingDocsTests.coffee +++ /dev/null @@ -1,225 +0,0 @@ -sinon = require "sinon" -chai = require("chai") -chai.should() -{ObjectId} = require "mongojs" -DocstoreApp = require "./helpers/DocstoreApp" - -DocstoreClient = require "./helpers/DocstoreClient" - -describe "Applying updates to a doc", -> - beforeEach (done) -> - @project_id = ObjectId() - @doc_id = ObjectId() - @originalLines = ["original", "lines"] - @newLines = ["new", "lines"] - @originalRanges = { - changes: [{ - id: ObjectId().toString() - op: { i: "foo", p: 3 } - meta: - user_id: ObjectId().toString() - ts: new Date().toString() - }] - } - @newRanges = { - changes: [{ - id: ObjectId().toString() - op: { i: "bar", p: 6 } - meta: - user_id: ObjectId().toString() - ts: new Date().toString() - }] - } - @version = 42 - DocstoreApp.ensureRunning => - DocstoreClient.createDoc @project_id, @doc_id, @originalLines, @version, @originalRanges, (error) => - throw error if error? - done() - - describe "when nothing has been updated", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version, @originalRanges, (error, res, @body) => - done() - - it "should return modified = false", -> - @body.modified.should.equal false - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - doc.version.should.equal @version - doc.ranges.should.deep.equal @originalRanges - done() - - describe "when the lines have changed", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, @newLines, @version, @originalRanges, (error, res, @body) => - done() - - it "should return modified = true", -> - @body.modified.should.equal true - - it "should return the rev", -> - @body.rev.should.equal 2 - - it "should update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @newLines - doc.version.should.equal @version - doc.ranges.should.deep.equal @originalRanges - done() - - describe "when the version has changed", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version + 1, @originalRanges, (error, res, @body) => - done() - - it "should return modified = true", -> - @body.modified.should.equal true - - it "should return the rev", -> - @body.rev.should.equal 1 - - it "should update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - doc.version.should.equal @version + 1 - doc.ranges.should.deep.equal @originalRanges - done() - - describe "when the ranges have changed", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, @version, @newRanges, (error, res, @body) => - done() - - it "should return modified = true", -> - @body.modified.should.equal true - - it "should return the rev", -> - @body.rev.should.equal 2 - - it "should update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - doc.version.should.equal @version - doc.ranges.should.deep.equal @newRanges - done() - - describe "when the doc does not exist", -> - beforeEach (done) -> - @missing_doc_id = ObjectId() - DocstoreClient.updateDoc @project_id, @missing_doc_id, @originalLines, 0, @originalRanges, (error, @res, @body) => - done() - - it "should create the doc", -> - @body.rev.should.equal 1 - - it "should be retreivable", (done)-> - DocstoreClient.getDoc @project_id, @missing_doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - doc.version.should.equal 0 - doc.ranges.should.deep.equal @originalRanges - done() - - describe "when malformed doc lines are provided", -> - describe "when the lines are not an array", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, { foo: "bar" }, @version, @originalRanges, (error, @res, @body) => - done() - - it "should return 400", -> - @res.statusCode.should.equal 400 - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - done() - - describe "when the lines are not present", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, null, @version, @originalRanges, (error, @res, @body) => - done() - - it "should return 400", -> - @res.statusCode.should.equal 400 - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - done() - - describe "when no version is provided", -> - beforeEach (done) -> - DocstoreClient.updateDoc @project_id, @doc_id, @originalLines, null, @originalRanges, (error, @res, @body) => - done() - - it "should return 400", -> - @res.statusCode.should.equal 400 - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - doc.version.should.equal @version - done() - - describe "when the content is large", -> - beforeEach (done) -> - line = new Array(1025).join("x") # 1kb - @largeLines = Array.apply(null, Array(1024)).map(() -> line) # 1mb - DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, @originalRanges, (error, res, @body) => - done() - - it "should return modified = true", -> - @body.modified.should.equal true - - it "should update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @largeLines - done() - - describe "when there is a large json payload", -> - beforeEach (done) -> - line = new Array(1025).join("x") # 1kb - @largeLines = Array.apply(null, Array(1024)).map(() -> line) # 1kb - @originalRanges.padding = Array.apply(null, Array(2049)).map(() -> line) # 2mb + 1kb - DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, @originalRanges, (error, @res, @body) => - done() - - it "should return modified = true", -> - @body.modified.should.equal true - - it "should update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @largeLines - done() - - describe "when the document body is too large", -> - beforeEach (done) -> - line = new Array(1025).join("x") # 1kb - @largeLines = Array.apply(null, Array(2049)).map(() -> line) # 2mb + 1kb - DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, @originalRanges, (error, @res, @body) => - done() - - it "should return 413", -> - @res.statusCode.should.equal 413 - - it "should report body too large", -> - @res.body.should.equal 'document body too large' - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - done() - - describe "when the json payload is too large", -> - beforeEach (done) -> - line = new Array(1025).join("x") # 1kb - @largeLines = Array.apply(null, Array(1024)).map(() -> line) # 1kb - @originalRanges.padding = Array.apply(null, Array(4096)).map(() -> line) # 4mb - DocstoreClient.updateDoc @project_id, @doc_id, @largeLines, @version, @originalRanges, (error, @res, @body) => - done() - - it "should not update the doc in the API", (done) -> - DocstoreClient.getDoc @project_id, @doc_id, {}, (error, res, doc) => - doc.lines.should.deep.equal @originalLines - done() diff --git a/services/docstore/test/acceptance/coffee/helpers/DocstoreApp.coffee b/services/docstore/test/acceptance/coffee/helpers/DocstoreApp.coffee deleted file mode 100644 index 4a916955a9..0000000000 --- a/services/docstore/test/acceptance/coffee/helpers/DocstoreApp.coffee +++ /dev/null @@ -1,21 +0,0 @@ -app = require('../../../../app') -require("logger-sharelatex").logger.level("error") -settings = require("settings-sharelatex") - -module.exports = - running: false - initing: false - callbacks: [] - ensureRunning: (callback = (error) ->) -> - if @running - return callback() - else if @initing - @callbacks.push callback - else - @initing = true - @callbacks.push callback - app.listen settings.internal.docstore.port, "localhost", (error) => - throw error if error? - @running = true - for callback in @callbacks - callback() \ No newline at end of file diff --git a/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee b/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee deleted file mode 100644 index 446f99ad58..0000000000 --- a/services/docstore/test/acceptance/coffee/helpers/DocstoreClient.coffee +++ /dev/null @@ -1,57 +0,0 @@ -request = require("request").defaults(jar: false) -{db, ObjectId} = require("../../../../app/js/mongojs") -settings = require("settings-sharelatex") -DocArchiveManager = require("../../../../app/js/DocArchiveManager.js") - -module.exports = DocstoreClient = - - createDoc: (project_id, doc_id, lines, version, ranges, callback = (error) ->) -> - DocstoreClient.updateDoc project_id, doc_id, lines, version, ranges, callback - - getDoc: (project_id, doc_id, qs, callback = (error, res, body) ->) -> - request.get { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/doc/#{doc_id}" - json: true - qs:qs - }, callback - - getAllDocs: (project_id, callback = (error, res, body) ->) -> - request.get { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/doc" - json: true - }, callback - - getAllRanges: (project_id, callback = (error, res, body) ->) -> - request.get { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/ranges" - json: true - }, callback - - updateDoc: (project_id, doc_id, lines, version, ranges, callback = (error, res, body) ->) -> - request.post { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/doc/#{doc_id}" - json: - lines: lines - version: version - ranges: ranges - }, callback - - deleteDoc: (project_id, doc_id, callback = (error, res, body) ->) -> - request.del { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/doc/#{doc_id}" - }, callback - - archiveAllDoc: (project_id, callback = (error, res, body) ->) -> - request.post { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/archive" - }, callback - - destroyAllDoc: (project_id, callback = (error, res, body) ->) -> - request.post { - url: "http://localhost:#{settings.internal.docstore.port}/project/#{project_id}/destroy" - }, callback - - getS3Doc: (project_id, doc_id, callback = (error, res, body) ->) -> - options = DocArchiveManager.buildS3Options(project_id+"/"+doc_id) - options.json = true - request.get options, callback diff --git a/services/docstore/test/acceptance/js/ArchiveDocsTests.js b/services/docstore/test/acceptance/js/ArchiveDocsTests.js new file mode 100644 index 0000000000..67d5d0e828 --- /dev/null +++ b/services/docstore/test/acceptance/js/ArchiveDocsTests.js @@ -0,0 +1,1108 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const sinon = require('sinon') +const chai = require('chai') +const should = chai.should() +const { db, ObjectId, ISODate } = require('../../../app/js/mongojs') +const async = require('async') +const Settings = require('settings-sharelatex') +const DocArchiveManager = require('../../../app/js/DocArchiveManager.js') +const request = require('request') +const DocstoreApp = require('./helpers/DocstoreApp') +const DocstoreClient = require('./helpers/DocstoreClient') + +describe('Archiving', function() { + before(function(done) { + return DocstoreApp.ensureRunning(done) + }) + + describe('multiple docs in a project', function() { + before(function(done) { + this.project_id = ObjectId() + this.docs = [ + { + _id: ObjectId(), + lines: ['one', 'two', 'three'], + ranges: {}, + version: 2 + }, + { + _id: ObjectId(), + lines: ['aaa', 'bbb', 'ccc'], + ranges: {}, + version: 4 + } + ] + const jobs = Array.from(this.docs).map(doc => + (doc => { + return callback => { + return DocstoreClient.createDoc( + this.project_id, + doc._id, + doc.lines, + doc.version, + doc.ranges, + callback + ) + } + })(doc) + ) + + return async.series(jobs, error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc(this.project_id, (error, res) => { + this.res = res + return done() + }) + }) + }) + + it('should archive all the docs', function(done) { + this.res.statusCode.should.equal(204) + return done() + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + const jobs = Array.from(this.docs).map(doc => + (doc => { + return callback => { + return db.docs.findOne({ _id: doc._id }, (error, doc) => { + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + return callback() + }) + } + })(doc) + ) + return async.series(jobs, done) + }) + + it('should set the docs in s3 correctly', function(done) { + const jobs = Array.from(this.docs).map(doc => + (doc => { + return callback => { + return DocstoreClient.getS3Doc( + this.project_id, + doc._id, + (error, res, s3_doc) => { + s3_doc.lines.should.deep.equal(doc.lines) + s3_doc.ranges.should.deep.equal(doc.ranges) + return callback() + } + ) + } + })(doc) + ) + return async.series(jobs, done) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + it('should return the docs', function(done) { + for (let i = 0; i < this.fetched_docs.length; i++) { + const doc = this.fetched_docs[i] + doc.lines.should.deep.equal(this.docs[i].lines) + } + return done() + }) + + return it('should restore the docs to mongo', function(done) { + const jobs = Array.from(this.docs).map((doc, i) => + ((doc, i) => { + return callback => { + return db.docs.findOne({ _id: doc._id }, (error, doc) => { + doc.lines.should.deep.equal(this.docs[i].lines) + doc.ranges.should.deep.equal(this.docs[i].ranges) + should.not.exist(doc.inS3) + return callback() + }) + } + })(doc, i) + ) + return async.series(jobs, done) + }) + }) + }) + + describe('a deleted doc', function() { + before(function(done) { + this.project_id = ObjectId() + this.doc = { + _id: ObjectId(), + lines: ['one', 'two', 'three'], + ranges: {}, + version: 2 + } + return DocstoreClient.createDoc( + this.project_id, + this.doc._id, + this.doc.lines, + this.doc.version, + this.doc.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.deleteDoc( + this.project_id, + this.doc._id, + error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc( + this.project_id, + (error, res) => { + this.res = res + if (error != null) { + throw error + } + return done() + } + ) + } + ) + } + ) + }) + + it('should successully archive the docs', function(done) { + this.res.statusCode.should.equal(204) + return done() + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + doc.deleted.should.equal(true) + return done() + }) + }) + + it('should set the doc in s3 correctly', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc._id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + s3_doc.lines.should.deep.equal(this.doc.lines) + s3_doc.ranges.should.deep.equal(this.doc.ranges) + return done() + } + ) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + it('should not included the deleted', function(done) { + this.fetched_docs.length.should.equal(0) + return done() + }) + + return it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + doc.ranges.should.deep.equal(this.doc.ranges) + should.not.exist(doc.inS3) + doc.deleted.should.equal(true) + return done() + }) + }) + }) + }) + + describe('a doc with large lines', function() { + before(function(done) { + this.project_id = ObjectId() + this.timeout(1000 * 30) + const quarterMegInBytes = 250000 + const big_line = require('crypto') + .randomBytes(quarterMegInBytes) + .toString('hex') + this.doc = { + _id: ObjectId(), + lines: [big_line, big_line, big_line, big_line], + ranges: {}, + version: 2 + } + return DocstoreClient.createDoc( + this.project_id, + this.doc._id, + this.doc.lines, + this.doc.version, + this.doc.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc(this.project_id, (error, res) => { + this.res = res + if (error != null) { + throw error + } + return done() + }) + } + ) + }) + + it('should successully archive the docs', function(done) { + this.res.statusCode.should.equal(204) + return done() + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + return done() + }) + }) + + it('should set the doc in s3 correctly', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc._id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + s3_doc.lines.should.deep.equal(this.doc.lines) + s3_doc.ranges.should.deep.equal(this.doc.ranges) + return done() + } + ) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + return it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + doc.ranges.should.deep.equal(this.doc.ranges) + should.not.exist(doc.inS3) + return done() + }) + }) + }) + }) + + describe('a doc with naughty strings', function() { + before(function(done) { + this.project_id = ObjectId() + this.doc = { + _id: ObjectId(), + lines: [ + '', + 'undefined', + 'undef', + 'null', + 'NULL', + '(null)', + 'nil', + 'NIL', + 'true', + 'false', + 'True', + 'False', + 'None', + '\\', + '\\\\', + '0', + '1', + '1.00', + '$1.00', + '1/2', + '1E2', + '1E02', + '1E+02', + '-1', + '-1.00', + '-$1.00', + '-1/2', + '-1E2', + '-1E02', + '-1E+02', + '1/0', + '0/0', + '-2147483648/-1', + '-9223372036854775808/-1', + '0.00', + '0..0', + '.', + '0.0.0', + '0,00', + '0,,0', + ',', + '0,0,0', + '0.0/0', + '1.0/0.0', + '0.0/0.0', + '1,0/0,0', + '0,0/0,0', + '--1', + '-', + '-.', + '-,', + '999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999', + 'NaN', + 'Infinity', + '-Infinity', + '0x0', + '0xffffffff', + '0xffffffffffffffff', + '0xabad1dea', + '123456789012345678901234567890123456789', + '1,000.00', + '1 000.00', + "1'000.00", + '1,000,000.00', + '1 000 000.00', + "1'000'000.00", + '1.000,00', + '1 000,00', + "1'000,00", + '1.000.000,00', + '1 000i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟', + '̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕', + 'Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮', + "˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥", + '00˙Ɩ$-', + 'The quick brown fox jumps over the lazy dog', + '𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠', + '𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌', + '𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈', + '𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰', + '𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘', + '𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐', + '⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢', + '<script>alert(123)</script>', + '<script>alert('123');</script>', + '<img src=x onerror=alert(123) />', + '<svg><script>123<1>alert(123)</script> ', + '"><script>alert(123)</script>', + "'><script>alert(123)</script>", + '><script>alert(123)</script>', + '</script><script>alert(123)</script>', + '< / script >< script >alert(123)< / script >', + ' onfocus=JaVaSCript:alert(123) autofocus ', + '" onfocus=JaVaSCript:alert(123) autofocus ', + "' onfocus=JaVaSCript:alert(123) autofocus ", + '<script>alert(123)</script>', + '<sc<script>ript>alert(123)</sc</script>ript>', + '--><script>alert(123)</script>', + '";alert(123);t="', + "';alert(123);t='", + 'JavaSCript:alert(123)', + ';alert(123);', + 'src=JaVaSCript:prompt(132)', + '"><script>alert(123);</script x="', + "'><script>alert(123);</script x='", + '><script>alert(123);</script x=', + '" autofocus onkeyup="javascript:alert(123)', + "' autofocus onkeyup='javascript:alert(123)", + '<script\\x20type="text/javascript">javascript:alert(1);</script>', + '<script\\x3Etype="text/javascript">javascript:alert(1);</script>', + '<script\\x0Dtype="text/javascript">javascript:alert(1);</script>', + '<script\\x09type="text/javascript">javascript:alert(1);</script>', + '<script\\x0Ctype="text/javascript">javascript:alert(1);</script>', + '<script\\x2Ftype="text/javascript">javascript:alert(1);</script>', + '<script\\x0Atype="text/javascript">javascript:alert(1);</script>', + '\'`"><\\x3Cscript>javascript:alert(1)</script> ', + '\'`"><\\x00script>javascript:alert(1)</script>', + 'ABC<div style="x\\x3Aexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:expression\\x5C(javascript:alert(1)">DEF', + 'ABC<div style="x:expression\\x00(javascript:alert(1)">DEF', + 'ABC<div style="x:exp\\x00ression(javascript:alert(1)">DEF', + 'ABC<div style="x:exp\\x5Cression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x0Aexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x09expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE3\\x80\\x80expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x84expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xC2\\xA0expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x80expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x8Aexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x0Dexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x0Cexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x87expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xEF\\xBB\\xBFexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x20expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x88expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x00expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x8Bexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x86expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x85expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x82expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\x0Bexpression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x81expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x83expression(javascript:alert(1)">DEF', + 'ABC<div style="x:\\xE2\\x80\\x89expression(javascript:alert(1)">DEF', + '<a href="\\x0Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x0Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xC2\\xA0javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x05javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE1\\xA0\\x8Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x18javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x11javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x88javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x89javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x17javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x03javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x0Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x00javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x10javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x82javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x20javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x13javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x09javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x8Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x14javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x19javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\xAFjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x81javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x87javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x07javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE1\\x9A\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x83javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x04javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x01javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x08javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x84javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x86javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE3\\x80\\x80javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x12javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x0Djavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x0Ajavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x0Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x15javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\xA8javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x16javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x02javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Bjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x06javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\xA9javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x80\\x85javascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Ejavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\xE2\\x81\\x9Fjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="\\x1Cjavascript:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="javascript\\x00:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="javascript\\x3A:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="javascript\\x09:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="javascript\\x0D:javascript:alert(1)" id="fuzzelement1">test</a>', + '<a href="javascript\\x0A:javascript:alert(1)" id="fuzzelement1">test</a>', + '`"\'><img src=xxx:x \\x0Aonerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x22onerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x0Bonerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x0Donerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x2Fonerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x09onerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x0Conerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x00onerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x27onerror=javascript:alert(1)>', + '`"\'><img src=xxx:x \\x20onerror=javascript:alert(1)>', + '"`\'><script>\\x3Bjavascript:alert(1)</script>', + '"`\'><script>\\x0Djavascript:alert(1)</script>', + '"`\'><script>\\xEF\\xBB\\xBFjavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x81javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x84javascript:alert(1)</script>', + '"`\'><script>\\xE3\\x80\\x80javascript:alert(1)</script>', + '"`\'><script>\\x09javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x89javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x85javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x88javascript:alert(1)</script>', + '"`\'><script>\\x00javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\xA8javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x8Ajavascript:alert(1)</script>', + '"`\'><script>\\xE1\\x9A\\x80javascript:alert(1)</script>', + '"`\'><script>\\x0Cjavascript:alert(1)</script>', + '"`\'><script>\\x2Bjavascript:alert(1)</script>', + '"`\'><script>\\xF0\\x90\\x96\\x9Ajavascript:alert(1)</script>', + '"`\'><script>-javascript:alert(1)</script>', + '"`\'><script>\\x0Ajavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\xAFjavascript:alert(1)</script>', + '"`\'><script>\\x7Ejavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x87javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x81\\x9Fjavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\xA9javascript:alert(1)</script>', + '"`\'><script>\\xC2\\x85javascript:alert(1)</script>', + '"`\'><script>\\xEF\\xBF\\xAEjavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x83javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x8Bjavascript:alert(1)</script>', + '"`\'><script>\\xEF\\xBF\\xBEjavascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x80javascript:alert(1)</script>', + '"`\'><script>\\x21javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x82javascript:alert(1)</script>', + '"`\'><script>\\xE2\\x80\\x86javascript:alert(1)</script>', + '"`\'><script>\\xE1\\xA0\\x8Ejavascript:alert(1)</script>', + '"`\'><script>\\x0Bjavascript:alert(1)</script>', + '"`\'><script>\\x20javascript:alert(1)</script>', + '"`\'><script>\\xC2\\xA0javascript:alert(1)</script>', + '<img \\x00src=x onerror="alert(1)">', + '<img \\x47src=x onerror="javascript:alert(1)">', + '<img \\x11src=x onerror="javascript:alert(1)">', + '<img \\x12src=x onerror="javascript:alert(1)">', + '<img\\x47src=x onerror="javascript:alert(1)">', + '<img\\x10src=x onerror="javascript:alert(1)">', + '<img\\x13src=x onerror="javascript:alert(1)">', + '<img\\x32src=x onerror="javascript:alert(1)">', + '<img\\x47src=x onerror="javascript:alert(1)">', + '<img\\x11src=x onerror="javascript:alert(1)">', + '<img \\x47src=x onerror="javascript:alert(1)">', + '<img \\x34src=x onerror="javascript:alert(1)">', + '<img \\x39src=x onerror="javascript:alert(1)">', + '<img \\x00src=x onerror="javascript:alert(1)">', + '<img src\\x09=x onerror="javascript:alert(1)">', + '<img src\\x10=x onerror="javascript:alert(1)">', + '<img src\\x13=x onerror="javascript:alert(1)">', + '<img src\\x32=x onerror="javascript:alert(1)">', + '<img src\\x12=x onerror="javascript:alert(1)">', + '<img src\\x11=x onerror="javascript:alert(1)">', + '<img src\\x00=x onerror="javascript:alert(1)">', + '<img src\\x47=x onerror="javascript:alert(1)">', + '<img src=x\\x09onerror="javascript:alert(1)">', + '<img src=x\\x10onerror="javascript:alert(1)">', + '<img src=x\\x11onerror="javascript:alert(1)">', + '<img src=x\\x12onerror="javascript:alert(1)">', + '<img src=x\\x13onerror="javascript:alert(1)">', + '<img[a][b][c]src[d]=x[e]onerror=[f]"alert(1)">', + '<img src=x onerror=\\x09"javascript:alert(1)">', + '<img src=x onerror=\\x10"javascript:alert(1)">', + '<img src=x onerror=\\x11"javascript:alert(1)">', + '<img src=x onerror=\\x12"javascript:alert(1)">', + '<img src=x onerror=\\x32"javascript:alert(1)">', + '<img src=x onerror=\\x00"javascript:alert(1)">', + '<a href=java script:javascript:alert(1)>XXX</a>', + '<img src="x` `<script>javascript:alert(1)</script>"` `>', + '<img src onerror /" \'"= alt=javascript:alert(1)//">', + '<title onpropertychange=javascript:alert(1)>', + '<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>">', + '<!--[if]><script>javascript:alert(1)</script -->', + '<!--[if<img src=x onerror=javascript:alert(1)//]> -->', + '<script src="/\\%(jscript)s"></script>', + '<script src="\\\\%(jscript)s"></script>', + '<IMG """><SCRIPT>alert("XSS")</SCRIPT>">', + '<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>', + '<IMG SRC=# onmouseover="alert(\'xxs\')">', + '<IMG SRC= onmouseover="alert(\'xxs\')">', + '<IMG onmouseover="alert(\'xxs\')">', + '<IMG SRC=javascript:alert('XSS')>', + '<IMG SRC=javascript:alert('XSS')>', + '<IMG SRC=javascript:alert('XSS')>', + '<IMG SRC="jav ascript:alert(\'XSS\');">', + '<IMG SRC="jav ascript:alert(\'XSS\');">', + '<IMG SRC="jav ascript:alert(\'XSS\');">', + '<IMG SRC="jav ascript:alert(\'XSS\');">', + 'perl -e \'print "<IMG SRC=java\\0script:alert(\\"XSS\\")>";\' > out', + '<IMG SRC="  javascript:alert(\'XSS\');">', + '<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT>', + '<BODY onload!#$%&()*~+-_.,:;?@[/|\\]^`=alert("XSS")>', + '<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT>', + '<<SCRIPT>alert("XSS");//<</SCRIPT>', + '<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >', + '<SCRIPT SRC=//ha.ckers.org/.j>', + '<IMG SRC="javascript:alert(\'XSS\')"', + '<iframe src=http://ha.ckers.org/scriptlet.html <', + "\\\";alert('XSS');//", + '<plaintext>', + '1;DROP TABLE users', + "1'; DROP TABLE users-- 1", + "' OR 1=1 -- 1", + "' OR '1'='1", + '-', + '--', + '--version', + '--help', + '$USER', + '/dev/null; touch /tmp/blns.fail ; echo', + '`touch /tmp/blns.fail`', + '$(touch /tmp/blns.fail)', + '@{[system "touch /tmp/blns.fail"]}', + 'eval("puts \'hello world\'")', + 'System("ls -al /")', + '`ls -al /`', + 'Kernel.exec("ls -al /")', + 'Kernel.exit(1)', + "%x('ls -al /')", + '<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo>', + '$HOME', + "$ENV{'HOME'}", + '%d', + '%s', + '%*.*s', + '../../../../../../../../../../../etc/passwd%00', + '../../../../../../../../../../../etc/hosts', + '() { 0; }; touch /tmp/blns.shellshock1.fail;', + '() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; }', + 'CON', + 'PRN', + 'AUX', + 'CLOCK$', + 'NUL', + 'A:', + 'ZZ:', + 'COM1', + 'LPT1', + 'LPT2', + 'LPT3', + 'COM2', + 'COM3', + 'COM4', + 'Scunthorpe General Hospital', + 'Penistone Community Church', + 'Lightwater Country Park', + 'Jimmy Clitheroe', + 'Horniman Museum', + 'shitake mushrooms', + 'RomansInSussex.co.uk', + 'http://www.cum.qc.ca/', + 'Craig Cockburn, Software Specialist', + 'Linda Callahan', + 'Dr. Herman I. Libshitz', + 'magna cum laude', + 'Super Bowl XXX', + 'medieval erection of parapets', + 'evaluate', + 'mocha', + 'expression', + 'Arsenal canal', + 'classic', + 'Tyson Gay', + "If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you.", + 'Roses are \u001b[0;31mred\u001b[0m, violets are \u001b[0;34mblue. Hope you enjoy terminal hue', + 'But now...\u001b[20Cfor my greatest trick...\u001b[8m', + 'The quic\b\b\b\b\b\bk brown fo\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007\u0007x... [Beeeep]', + 'Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗' + ], + ranges: {}, + version: 2 + } + return DocstoreClient.createDoc( + this.project_id, + this.doc._id, + this.doc.lines, + this.doc.version, + this.doc.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc(this.project_id, (error, res) => { + this.res = res + if (error != null) { + throw error + } + return done() + }) + } + ) + }) + + it('should successully archive the docs', function(done) { + this.res.statusCode.should.equal(204) + return done() + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + return done() + }) + }) + + it('should set the doc in s3 correctly', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc._id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + s3_doc.lines.should.deep.equal(this.doc.lines) + s3_doc.ranges.should.deep.equal(this.doc.ranges) + return done() + } + ) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + return it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + doc.ranges.should.deep.equal(this.doc.ranges) + should.not.exist(doc.inS3) + return done() + }) + }) + }) + }) + + describe('a doc with ranges', function() { + before(function(done) { + this.project_id = ObjectId() + this.doc = { + _id: ObjectId(), + lines: ['one', 'two', 'three'], + ranges: { + changes: [ + { + id: ObjectId(), + op: { i: 'foo', p: 24 }, + metadata: { + user_id: ObjectId(), + ts: new Date('2017-01-27T16:10:44.194Z') + } + }, + { + id: ObjectId(), + op: { d: 'bar', p: 50 }, + metadata: { + user_id: ObjectId(), + ts: new Date('2017-01-27T18:10:44.194Z') + } + } + ], + comments: [ + { + id: ObjectId(), + op: { c: 'comment', p: 284, t: ObjectId() }, + metadata: { + user_id: ObjectId(), + ts: new Date('2017-01-26T14:22:04.869Z') + } + } + ] + }, + version: 2 + } + return DocstoreClient.createDoc( + this.project_id, + this.doc._id, + this.doc.lines, + this.doc.version, + this.doc.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc(this.project_id, (error, res) => { + this.res = res + if (error != null) { + throw error + } + return done() + }) + } + ) + }) + + it('should successully archive the docs', function(done) { + this.res.statusCode.should.equal(204) + return done() + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + return done() + }) + }) + + it('should set the doc in s3 correctly', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc._id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + s3_doc.lines.should.deep.equal(this.doc.lines) + const ranges = JSON.parse(JSON.stringify(this.doc.ranges)) // ObjectId -> String + s3_doc.ranges.should.deep.equal(ranges) + return done() + } + ) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + return it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + doc.ranges.should.deep.equal(this.doc.ranges) + should.not.exist(doc.inS3) + return done() + }) + }) + }) + }) + + describe('a doc that is archived twice', function() { + before(function(done) { + this.project_id = ObjectId() + this.doc = { + _id: ObjectId(), + lines: ['abc', 'def', 'ghi'], + ranges: {}, + version: 2 + } + return DocstoreClient.createDoc( + this.project_id, + this.doc._id, + this.doc.lines, + this.doc.version, + this.doc.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.archiveAllDoc(this.project_id, (error, res) => { + this.res = res + if (error != null) { + throw error + } + this.res.statusCode.should.equal(204) + return DocstoreClient.archiveAllDoc( + this.project_id, + (error, res1) => { + this.res = res1 + if (error != null) { + throw error + } + this.res.statusCode.should.equal(204) + return done() + } + ) + }) + } + ) + }) + + it('should set inS3 and unset lines and ranges in each doc', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + should.not.exist(doc.lines) + should.not.exist(doc.ranges) + doc.inS3.should.equal(true) + return done() + }) + }) + + it('should set the doc in s3 correctly', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc._id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + s3_doc.lines.should.deep.equal(this.doc.lines) + s3_doc.ranges.should.deep.equal(this.doc.ranges) + return done() + } + ) + }) + + return describe('after unarchiving from a request for the project', function() { + before(function(done) { + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + }) + + return it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + doc.ranges.should.deep.equal(this.doc.ranges) + should.not.exist(doc.inS3) + return done() + }) + }) + }) + }) + + return describe('a doc with the old schema (just an array of lines)', function() { + before(function(done) { + this.project_id = ObjectId() + this.doc = { + _id: ObjectId(), + lines: ['abc', 'def', 'ghi'], + ranges: {}, + version: 2 + } + const options = DocArchiveManager.buildS3Options( + `${this.project_id}/${this.doc._id}` + ) + options.json = this.doc.lines + return request.put(options, (error, res, body) => { + if (error != null) { + throw error + } + res.statusCode.should.equal(200) + return db.docs.insert( + { + project_id: this.project_id, + _id: this.doc._id, + rev: this.doc.version, + inS3: true + }, + error => { + if (error != null) { + throw error + } + return DocstoreClient.getAllDocs( + this.project_id, + (error, res, fetched_docs) => { + this.fetched_docs = fetched_docs + if (error != null) { + throw error + } + return done() + } + ) + } + ) + }) + }) + + it('should restore the doc to mongo', function(done) { + return db.docs.findOne({ _id: this.doc._id }, (error, doc) => { + if (error != null) { + throw error + } + doc.lines.should.deep.equal(this.doc.lines) + should.not.exist(doc.inS3) + return done() + }) + }) + + return it('should return the doc', function(done) { + this.fetched_docs[0].lines.should.deep.equal(this.doc.lines) + return done() + }) + }) +}) diff --git a/services/docstore/test/acceptance/js/DeletingDocsTests.js b/services/docstore/test/acceptance/js/DeletingDocsTests.js new file mode 100644 index 0000000000..8941567359 --- /dev/null +++ b/services/docstore/test/acceptance/js/DeletingDocsTests.js @@ -0,0 +1,159 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const sinon = require('sinon') +const chai = require('chai') +chai.should() +const { db, ObjectId } = require('../../../app/js/mongojs') +const { expect } = chai +const DocstoreApp = require('./helpers/DocstoreApp') + +const DocstoreClient = require('./helpers/DocstoreClient') + +describe('Deleting a doc', function() { + beforeEach(function(done) { + this.project_id = ObjectId() + this.doc_id = ObjectId() + this.lines = ['original', 'lines'] + this.version = 42 + this.ranges = [] + return DocstoreApp.ensureRunning(() => { + return DocstoreClient.createDoc( + this.project_id, + this.doc_id, + this.lines, + this.version, + this.ranges, + error => { + if (error != null) { + throw error + } + return done() + } + ) + }) + }) + + describe('when the doc exists', function() { + beforeEach(function(done) { + return DocstoreClient.deleteDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + this.res = res + return done() + } + ) + }) + + afterEach(function(done) { + return db.docs.remove({ _id: this.doc_id }, done) + }) + + return it('should insert a deleted doc into the docs collection', function(done) { + return db.docs.find({ _id: this.doc_id }, (error, docs) => { + docs[0]._id.should.deep.equal(this.doc_id) + docs[0].lines.should.deep.equal(this.lines) + docs[0].deleted.should.equal(true) + return done() + }) + }) + }) + + return describe('when the doc does not exist', function() { + return it('should return a 404', function(done) { + const missing_doc_id = ObjectId() + return DocstoreClient.deleteDoc( + this.project_id, + missing_doc_id, + (error, res, doc) => { + res.statusCode.should.equal(404) + return done() + } + ) + }) + }) +}) + +describe("Destroying a project's documents", function() { + describe('when the doc exists', function() { + beforeEach(function(done) { + return db.docOps.insert( + { doc_id: ObjectId(this.doc_id), version: 1 }, + function(err) { + if (err != null) { + return done(err) + } + return DocstoreClient.destroyAllDoc(this.project_id, done) + } + ) + }) + + it('should remove the doc from the docs collection', function(done) { + return db.docs.find({ _id: this.doc_id }, (err, docs) => { + expect(err).not.to.exist + expect(docs).to.deep.equal([]) + return done() + }) + }) + + return it('should remove the docOps from the docOps collection', function(done) { + return db.docOps.find({ doc_id: this.doc_id }, (err, docOps) => { + expect(err).not.to.exist + expect(docOps).to.deep.equal([]) + return done() + }) + }) + }) + + return describe('when the doc is archived', function() { + beforeEach(function(done) { + return DocstoreClient.archiveAllDoc(this.project_id, function(err) { + if (err != null) { + return done(err) + } + return DocstoreClient.destroyAllDoc(this.project_id, done) + }) + }) + + it('should remove the doc from the docs collection', function(done) { + return db.docs.find({ _id: this.doc_id }, (err, docs) => { + expect(err).not.to.exist + expect(docs).to.deep.equal([]) + return done() + }) + }) + + it('should remove the docOps from the docOps collection', function(done) { + return db.docOps.find({ doc_id: this.doc_id }, (err, docOps) => { + expect(err).not.to.exist + expect(docOps).to.deep.equal([]) + return done() + }) + }) + + return it('should remove the doc contents from s3', function(done) { + return DocstoreClient.getS3Doc( + this.project_id, + this.doc_id, + (error, res, s3_doc) => { + if (error != null) { + throw error + } + expect(res.statusCode).to.equal(404) + return done() + } + ) + }) + }) +}) diff --git a/services/docstore/test/acceptance/js/GettingAllDocsTests.js b/services/docstore/test/acceptance/js/GettingAllDocsTests.js new file mode 100644 index 0000000000..4fc7f811d8 --- /dev/null +++ b/services/docstore/test/acceptance/js/GettingAllDocsTests.js @@ -0,0 +1,114 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const sinon = require('sinon') +const chai = require('chai') +chai.should() +const { ObjectId } = require('mongojs') +const async = require('async') +const DocstoreApp = require('./helpers/DocstoreApp') + +const DocstoreClient = require('./helpers/DocstoreClient') + +describe('Getting all docs', function() { + beforeEach(function(done) { + this.project_id = ObjectId() + this.docs = [ + { + _id: ObjectId(), + lines: ['one', 'two', 'three'], + ranges: { mock: 'one' }, + rev: 2 + }, + { + _id: ObjectId(), + lines: ['aaa', 'bbb', 'ccc'], + ranges: { mock: 'two' }, + rev: 4 + }, + { + _id: ObjectId(), + lines: ['111', '222', '333'], + ranges: { mock: 'three' }, + rev: 6 + } + ] + this.deleted_doc = { + _id: ObjectId(), + lines: ['deleted'], + ranges: { mock: 'four' }, + rev: 8 + } + const version = 42 + const jobs = Array.from(this.docs).map(doc => + (doc => { + return callback => { + return DocstoreClient.createDoc( + this.project_id, + doc._id, + doc.lines, + version, + doc.ranges, + callback + ) + } + })(doc) + ) + jobs.push(cb => { + return DocstoreClient.createDoc( + this.project_id, + this.deleted_doc._id, + this.deleted_doc.lines, + version, + this.deleted_doc.ranges, + err => { + return DocstoreClient.deleteDoc( + this.project_id, + this.deleted_doc._id, + cb + ) + } + ) + }) + jobs.unshift(cb => DocstoreApp.ensureRunning(cb)) + return async.series(jobs, done) + }) + + it('getAllDocs should return all the (non-deleted) docs', function(done) { + return DocstoreClient.getAllDocs(this.project_id, (error, res, docs) => { + if (error != null) { + throw error + } + docs.length.should.equal(this.docs.length) + for (let i = 0; i < docs.length; i++) { + const doc = docs[i] + doc.lines.should.deep.equal(this.docs[i].lines) + } + return done() + }) + }) + + return it('getAllRanges should return all the (non-deleted) doc ranges', function(done) { + return DocstoreClient.getAllRanges(this.project_id, (error, res, docs) => { + if (error != null) { + throw error + } + docs.length.should.equal(this.docs.length) + for (let i = 0; i < docs.length; i++) { + const doc = docs[i] + doc.ranges.should.deep.equal(this.docs[i].ranges) + } + return done() + }) + }) +}) diff --git a/services/docstore/test/acceptance/js/GettingDocsTests.js b/services/docstore/test/acceptance/js/GettingDocsTests.js new file mode 100644 index 0000000000..fb3df8a657 --- /dev/null +++ b/services/docstore/test/acceptance/js/GettingDocsTests.js @@ -0,0 +1,137 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const sinon = require('sinon') +const chai = require('chai') +chai.should() +const { ObjectId } = require('mongojs') +const DocstoreApp = require('./helpers/DocstoreApp') + +const DocstoreClient = require('./helpers/DocstoreClient') + +describe('Getting a doc', function() { + beforeEach(function(done) { + this.project_id = ObjectId() + this.doc_id = ObjectId() + this.lines = ['original', 'lines'] + this.version = 42 + this.ranges = { + changes: [ + { + id: ObjectId().toString(), + op: { i: 'foo', p: 3 }, + meta: { + user_id: ObjectId().toString(), + ts: new Date().toString() + } + } + ] + } + return DocstoreApp.ensureRunning(() => { + return DocstoreClient.createDoc( + this.project_id, + this.doc_id, + this.lines, + this.version, + this.ranges, + error => { + if (error != null) { + throw error + } + return done() + } + ) + }) + }) + + describe('when the doc exists', function() { + return it('should get the doc lines and version', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.lines) + doc.version.should.equal(this.version) + doc.ranges.should.deep.equal(this.ranges) + return done() + } + ) + }) + }) + + describe('when the doc does not exist', function() { + return it('should return a 404', function(done) { + const missing_doc_id = ObjectId() + return DocstoreClient.getDoc( + this.project_id, + missing_doc_id, + {}, + (error, res, doc) => { + res.statusCode.should.equal(404) + return done() + } + ) + }) + }) + + return describe('when the doc is a deleted doc', function() { + beforeEach(function(done) { + this.deleted_doc_id = ObjectId() + return DocstoreClient.createDoc( + this.project_id, + this.deleted_doc_id, + this.lines, + this.version, + this.ranges, + error => { + if (error != null) { + throw error + } + return DocstoreClient.deleteDoc( + this.project_id, + this.deleted_doc_id, + done + ) + } + ) + }) + + it('should return the doc', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.deleted_doc_id, + { include_deleted: true }, + (error, res, doc) => { + doc.lines.should.deep.equal(this.lines) + doc.version.should.equal(this.version) + doc.ranges.should.deep.equal(this.ranges) + doc.deleted.should.equal(true) + return done() + } + ) + }) + + return it('should return a 404 when the query string is not set', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.deleted_doc_id, + {}, + (error, res, doc) => { + res.statusCode.should.equal(404) + return done() + } + ) + }) + }) +}) diff --git a/services/docstore/test/acceptance/js/UpdatingDocsTests.js b/services/docstore/test/acceptance/js/UpdatingDocsTests.js new file mode 100644 index 0000000000..96f5dfac47 --- /dev/null +++ b/services/docstore/test/acceptance/js/UpdatingDocsTests.js @@ -0,0 +1,499 @@ +/* eslint-disable + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const sinon = require('sinon') +const chai = require('chai') +chai.should() +const { ObjectId } = require('mongojs') +const DocstoreApp = require('./helpers/DocstoreApp') + +const DocstoreClient = require('./helpers/DocstoreClient') + +describe('Applying updates to a doc', function() { + beforeEach(function(done) { + this.project_id = ObjectId() + this.doc_id = ObjectId() + this.originalLines = ['original', 'lines'] + this.newLines = ['new', 'lines'] + this.originalRanges = { + changes: [ + { + id: ObjectId().toString(), + op: { i: 'foo', p: 3 }, + meta: { + user_id: ObjectId().toString(), + ts: new Date().toString() + } + } + ] + } + this.newRanges = { + changes: [ + { + id: ObjectId().toString(), + op: { i: 'bar', p: 6 }, + meta: { + user_id: ObjectId().toString(), + ts: new Date().toString() + } + } + ] + } + this.version = 42 + return DocstoreApp.ensureRunning(() => { + return DocstoreClient.createDoc( + this.project_id, + this.doc_id, + this.originalLines, + this.version, + this.originalRanges, + error => { + if (error != null) { + throw error + } + return done() + } + ) + }) + }) + + describe('when nothing has been updated', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.originalLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.body = body + return done() + } + ) + }) + + it('should return modified = false', function() { + return this.body.modified.should.equal(false) + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + doc.version.should.equal(this.version) + doc.ranges.should.deep.equal(this.originalRanges) + return done() + } + ) + }) + }) + + describe('when the lines have changed', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.newLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.body = body + return done() + } + ) + }) + + it('should return modified = true', function() { + return this.body.modified.should.equal(true) + }) + + it('should return the rev', function() { + return this.body.rev.should.equal(2) + }) + + return it('should update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.newLines) + doc.version.should.equal(this.version) + doc.ranges.should.deep.equal(this.originalRanges) + return done() + } + ) + }) + }) + + describe('when the version has changed', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.originalLines, + this.version + 1, + this.originalRanges, + (error, res, body) => { + this.body = body + return done() + } + ) + }) + + it('should return modified = true', function() { + return this.body.modified.should.equal(true) + }) + + it('should return the rev', function() { + return this.body.rev.should.equal(1) + }) + + return it('should update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + doc.version.should.equal(this.version + 1) + doc.ranges.should.deep.equal(this.originalRanges) + return done() + } + ) + }) + }) + + describe('when the ranges have changed', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.originalLines, + this.version, + this.newRanges, + (error, res, body) => { + this.body = body + return done() + } + ) + }) + + it('should return modified = true', function() { + return this.body.modified.should.equal(true) + }) + + it('should return the rev', function() { + return this.body.rev.should.equal(2) + }) + + return it('should update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + doc.version.should.equal(this.version) + doc.ranges.should.deep.equal(this.newRanges) + return done() + } + ) + }) + }) + + describe('when the doc does not exist', function() { + beforeEach(function(done) { + this.missing_doc_id = ObjectId() + return DocstoreClient.updateDoc( + this.project_id, + this.missing_doc_id, + this.originalLines, + 0, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should create the doc', function() { + return this.body.rev.should.equal(1) + }) + + return it('should be retreivable', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.missing_doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + doc.version.should.equal(0) + doc.ranges.should.deep.equal(this.originalRanges) + return done() + } + ) + }) + }) + + describe('when malformed doc lines are provided', function() { + describe('when the lines are not an array', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + { foo: 'bar' }, + this.version, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return 400', function() { + return this.res.statusCode.should.equal(400) + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + return done() + } + ) + }) + }) + + return describe('when the lines are not present', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + null, + this.version, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return 400', function() { + return this.res.statusCode.should.equal(400) + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + return done() + } + ) + }) + }) + }) + + describe('when no version is provided', function() { + beforeEach(function(done) { + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.originalLines, + null, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return 400', function() { + return this.res.statusCode.should.equal(400) + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + doc.version.should.equal(this.version) + return done() + } + ) + }) + }) + + describe('when the content is large', function() { + beforeEach(function(done) { + const line = new Array(1025).join('x') // 1kb + this.largeLines = Array.apply(null, Array(1024)).map(() => line) // 1mb + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.largeLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.body = body + return done() + } + ) + }) + + it('should return modified = true', function() { + return this.body.modified.should.equal(true) + }) + + return it('should update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.largeLines) + return done() + } + ) + }) + }) + + describe('when there is a large json payload', function() { + beforeEach(function(done) { + const line = new Array(1025).join('x') // 1kb + this.largeLines = Array.apply(null, Array(1024)).map(() => line) // 1kb + this.originalRanges.padding = Array.apply(null, Array(2049)).map( + () => line + ) // 2mb + 1kb + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.largeLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return modified = true', function() { + return this.body.modified.should.equal(true) + }) + + return it('should update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.largeLines) + return done() + } + ) + }) + }) + + describe('when the document body is too large', function() { + beforeEach(function(done) { + const line = new Array(1025).join('x') // 1kb + this.largeLines = Array.apply(null, Array(2049)).map(() => line) // 2mb + 1kb + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.largeLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + it('should return 413', function() { + return this.res.statusCode.should.equal(413) + }) + + it('should report body too large', function() { + return this.res.body.should.equal('document body too large') + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + return done() + } + ) + }) + }) + + return describe('when the json payload is too large', function() { + beforeEach(function(done) { + const line = new Array(1025).join('x') // 1kb + this.largeLines = Array.apply(null, Array(1024)).map(() => line) // 1kb + this.originalRanges.padding = Array.apply(null, Array(4096)).map( + () => line + ) // 4mb + return DocstoreClient.updateDoc( + this.project_id, + this.doc_id, + this.largeLines, + this.version, + this.originalRanges, + (error, res, body) => { + this.res = res + this.body = body + return done() + } + ) + }) + + return it('should not update the doc in the API', function(done) { + return DocstoreClient.getDoc( + this.project_id, + this.doc_id, + {}, + (error, res, doc) => { + doc.lines.should.deep.equal(this.originalLines) + return done() + } + ) + }) + }) +}) diff --git a/services/docstore/test/acceptance/js/helpers/DocstoreApp.js b/services/docstore/test/acceptance/js/helpers/DocstoreApp.js new file mode 100644 index 0000000000..e29117bd78 --- /dev/null +++ b/services/docstore/test/acceptance/js/helpers/DocstoreApp.js @@ -0,0 +1,48 @@ +/* eslint-disable + handle-callback-err, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * DS205: Consider reworking code to avoid use of IIFEs + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const app = require('../../../../app') +require('logger-sharelatex').logger.level('error') +const settings = require('settings-sharelatex') + +module.exports = { + running: false, + initing: false, + callbacks: [], + ensureRunning(callback) { + if (callback == null) { + callback = function(error) {} + } + if (this.running) { + return callback() + } else if (this.initing) { + return this.callbacks.push(callback) + } else { + this.initing = true + this.callbacks.push(callback) + return app.listen(settings.internal.docstore.port, 'localhost', error => { + if (error != null) { + throw error + } + this.running = true + return (() => { + const result = [] + for (callback of Array.from(this.callbacks)) { + result.push(callback()) + } + return result + })() + }) + } + } +} diff --git a/services/docstore/test/acceptance/js/helpers/DocstoreClient.js b/services/docstore/test/acceptance/js/helpers/DocstoreClient.js new file mode 100644 index 0000000000..da91433880 --- /dev/null +++ b/services/docstore/test/acceptance/js/helpers/DocstoreClient.js @@ -0,0 +1,136 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * DS207: Consider shorter variations of null checks + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +let DocstoreClient +const request = require('request').defaults({ jar: false }) +const { db, ObjectId } = require('../../../../app/js/mongojs') +const settings = require('settings-sharelatex') +const DocArchiveManager = require('../../../../app/js/DocArchiveManager.js') + +module.exports = DocstoreClient = { + createDoc(project_id, doc_id, lines, version, ranges, callback) { + if (callback == null) { + callback = function(error) {} + } + return DocstoreClient.updateDoc( + project_id, + doc_id, + lines, + version, + ranges, + callback + ) + }, + + getDoc(project_id, doc_id, qs, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.get( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc/${doc_id}`, + json: true, + qs + }, + callback + ) + }, + + getAllDocs(project_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.get( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc`, + json: true + }, + callback + ) + }, + + getAllRanges(project_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.get( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/ranges`, + json: true + }, + callback + ) + }, + + updateDoc(project_id, doc_id, lines, version, ranges, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.post( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc/${doc_id}`, + json: { + lines, + version, + ranges + } + }, + callback + ) + }, + + deleteDoc(project_id, doc_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.del( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc/${doc_id}` + }, + callback + ) + }, + + archiveAllDoc(project_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.post( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/archive` + }, + callback + ) + }, + + destroyAllDoc(project_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + return request.post( + { + url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/destroy` + }, + callback + ) + }, + + getS3Doc(project_id, doc_id, callback) { + if (callback == null) { + callback = function(error, res, body) {} + } + const options = DocArchiveManager.buildS3Options(project_id + '/' + doc_id) + options.json = true + return request.get(options, callback) + } +} diff --git a/services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee b/services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee deleted file mode 100644 index 70196a4b86..0000000000 --- a/services/docstore/test/unit/coffee/DocArchiveManagerTests.coffee +++ /dev/null @@ -1,356 +0,0 @@ -assert = require("chai").assert -sinon = require('sinon') -chai = require('chai') -should = chai.should() -expect = chai.expect -modulePath = "../../../app/js/DocArchiveManager.js" -SandboxedModule = require('sandboxed-module') -ObjectId = require("mongojs").ObjectId -Errors = require "../../../app/js/Errors" -crypto = require("crypto") - -describe "DocArchiveManager", -> - - beforeEach -> - - @settings = - docstore: - s3: - secret: "secret" - key: "this_key" - bucket:"doc-archive-unit-test" - - @request = - put: {} - get: {} - del: {} - - @archivedDocs = [{ - _id: ObjectId() - inS3:true - rev: 2 - }, { - _id: ObjectId() - inS3:true - rev: 4 - }, { - _id: ObjectId() - inS3:true - rev: 6 - }] - - @mongoDocs = [{ - _id: ObjectId() - lines: ["one", "two", "three"] - rev: 2 - }, { - _id: ObjectId() - lines: ["aaa", "bbb", "ccc"] - rev: 4 - }, { - _id: ObjectId() - inS3: true - rev: 6 - }, { - _id: ObjectId() - inS3: true - rev: 6 - }, { - _id: ObjectId() - lines: ["111", "222", "333"] - rev: 6 - }] - - @unarchivedDocs = [{ - _id: ObjectId() - lines: ["wombat", "potato", "banana"] - rev: 2 - }, { - _id: ObjectId() - lines: ["llama", "turnip", "apple"] - rev: 4 - }, { - _id: ObjectId() - lines: ["elephant", "swede", "nectarine"] - rev: 6 - }] - - @mixedDocs = @archivedDocs.concat(@unarchivedDocs) - - @MongoManager = - markDocAsArchived: sinon.stub().callsArgWith(2, null) - upsertIntoDocCollection: sinon.stub().callsArgWith(3, null) - getProjectsDocs: sinon.stub().callsArgWith(3, null, @mongoDocs) - getArchivedProjectDocs: sinon.stub().callsArgWith(2, null, @mongoDocs) - - @requires = - "settings-sharelatex": @settings - "./MongoManager": @MongoManager - "request": @request - "./RangeManager": @RangeManager = {} - "logger-sharelatex": - log:-> - err:-> - @globals = - JSON: JSON - - @error = "my errror" - @project_id = ObjectId().toString() - @stubbedError = new Errors.NotFoundError("Error in S3 request") - @DocArchiveManager = SandboxedModule.require modulePath, requires: @requires, globals: @globals - - describe "archiveDoc", -> - - it "should use correct options", (done)-> - @request.put = sinon.stub().callsArgWith(1, null, {statusCode:200,headers:{etag:""}}) - @DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=> - opts = @request.put.args[0][0] - assert.deepEqual(opts.aws, {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket}) - opts.body.should.equal JSON.stringify( - lines: @mongoDocs[0].lines - ranges: @mongoDocs[0].ranges - schema_v: 1 - ) - opts.timeout.should.equal (30*1000) - opts.uri.should.equal "https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{@mongoDocs[0]._id}" - done() - - it "should return no md5 error", (done)-> - data = JSON.stringify( - lines: @mongoDocs[0].lines - ranges: @mongoDocs[0].ranges - schema_v: 1 - ) - @md5 = crypto.createHash("md5").update(data).digest("hex") - @request.put = sinon.stub().callsArgWith(1, null, {statusCode:200,headers:{etag:@md5}}) - @DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=> - should.not.exist err - done() - - it "should return the error", (done)-> - @request.put = sinon.stub().callsArgWith(1, @stubbedError, {statusCode:400,headers:{etag:""}}) - @DocArchiveManager.archiveDoc @project_id, @mongoDocs[0], (err)=> - should.exist err - done() - - describe "unarchiveDoc", -> - - it "should use correct options", (done)-> - @request.get = sinon.stub().callsArgWith(1, null, statusCode:200, @mongoDocs[0].lines) - @request.del = sinon.stub().callsArgWith(1, null, statusCode:204, {}) - @DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0]._id, (err)=> - opts = @request.get.args[0][0] - assert.deepEqual(opts.aws, {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket}) - opts.json.should.equal true - opts.timeout.should.equal (30*1000) - opts.uri.should.equal "https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{@mongoDocs[0]._id}" - done() - - it "should return the error", (done)-> - @request.get = sinon.stub().callsArgWith(1, @stubbedError, {}, {}) - @DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0], (err)=> - should.exist err - done() - - it "should error if the doc lines are a string not an array", (done)-> - @request.get = sinon.stub().callsArgWith(1, null, statusCode:200, "this is a string") - @request.del = sinon.stub() - @DocArchiveManager.unarchiveDoc @project_id, @mongoDocs[0], (err)=> - should.exist err - @request.del.called.should.equal false - done() - - describe "archiveAllDocs", -> - - it "should archive all project docs which are not in s3", (done)-> - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mongoDocs) - @DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null) - - @DocArchiveManager.archiveAllDocs @project_id, (err)=> - @DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[0]).should.equal true - @DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[1]).should.equal true - @DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[4]).should.equal true - - @DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[2]).should.equal false - @DocArchiveManager.archiveDoc.calledWith(@project_id, @mongoDocs[3]).should.equal false - - should.not.exist err - done() - - it "should return error if have no docs", (done)-> - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, null) - - @DocArchiveManager.archiveAllDocs @project_id, (err)=> - should.exist err - done() - - it "should return the error", (done)-> - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, @error, null) - - @DocArchiveManager.archiveAllDocs @project_id, (err)=> - err.should.equal @error - done() - - describe "when most have been already put in s3", -> - - beforeEach -> - numberOfDocs = 10 * 1000 - @mongoDocs = [] - while --numberOfDocs != 0 - @mongoDocs.push({inS3:true, _id: ObjectId()}) - - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mongoDocs) - @DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null) - - it "should not throw and error", (done)-> - @DocArchiveManager.archiveAllDocs @project_id, (err)=> - should.not.exist err - done() - - - describe "unArchiveAllDocs", -> - - it "should unarchive all inS3 docs", (done)-> - @MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, null, @archivedDocs) - @DocArchiveManager.unarchiveDoc = sinon.stub().callsArgWith(2, null) - @DocArchiveManager.unArchiveAllDocs @project_id, (err)=> - for doc in @archivedDocs - @DocArchiveManager.unarchiveDoc.calledWith(@project_id, doc._id).should.equal true - should.not.exist err - done() - - it "should return error if have no docs", (done)-> - @MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, null, null) - @DocArchiveManager.unArchiveAllDocs @project_id, (err)=> - should.exist err - done() - - it "should return the error", (done)-> - @MongoManager.getArchivedProjectDocs = sinon.stub().callsArgWith(1, @error, null) - @DocArchiveManager.unArchiveAllDocs @project_id, (err)=> - err.should.equal @error - done() - - describe "destroyAllDocs", -> - beforeEach -> - @request.del = sinon.stub().callsArgWith(1, null, statusCode:204, {}) - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @mixedDocs) - @MongoManager.findDoc = sinon.stub().callsArgWith(3, null, null) - @MongoManager.destroyDoc = sinon.stub().yields() - for doc in @mixedDocs - @MongoManager.findDoc.withArgs(@project_id, doc._id).callsArgWith(3, null, doc) - - it "should destroy all the docs", (done)-> - @DocArchiveManager.destroyDoc = sinon.stub().callsArgWith(2, null) - @DocArchiveManager.destroyAllDocs @project_id, (err)=> - for doc in @mixedDocs - @DocArchiveManager.destroyDoc.calledWith(@project_id, doc._id).should.equal true - should.not.exist err - done() - - it "should only the s3 docs from s3", (done)-> - docOpts = (doc) => - JSON.parse(JSON.stringify({ - aws: {key:@settings.docstore.s3.key, secret:@settings.docstore.s3.secret, bucket:@settings.docstore.s3.bucket}, - json: true, - timeout: 30 * 1000 - uri:"https://#{@settings.docstore.s3.bucket}.s3.amazonaws.com/#{@project_id}/#{doc._id}" - })) - - @DocArchiveManager.destroyAllDocs @project_id, (err)=> - expect(err).not.to.exist - - for doc in @archivedDocs - sinon.assert.calledWith(@request.del, docOpts(doc)) - for doc in @unarchivedDocs - expect(@request.del.calledWith(docOpts(doc))).to.equal false # no notCalledWith - - done() - - it "should remove the docs from mongo", (done)-> - @DocArchiveManager.destroyAllDocs @project_id, (err)=> - expect(err).not.to.exist - - for doc in @mixedDocs - sinon.assert.calledWith(@MongoManager.destroyDoc, doc._id) - - done() - - describe "_s3DocToMongoDoc", -> - describe "with the old schema", -> - it "should return the docs lines", (done) -> - @DocArchiveManager._s3DocToMongoDoc ["doc", "lines"], (error, doc) -> - expect(doc).to.deep.equal { - lines: ["doc", "lines"] - } - done() - - describe "with the new schema", -> - it "should return the doc lines and ranges", (done) -> - @RangeManager.jsonRangesToMongo = sinon.stub().returns {"mongo": "ranges"} - @DocArchiveManager._s3DocToMongoDoc { - lines: ["doc", "lines"] - ranges: {"json": "ranges"} - schema_v: 1 - }, (error, doc) -> - expect(doc).to.deep.equal { - lines: ["doc", "lines"] - ranges: {"mongo": "ranges"} - } - done() - - it "should return just the doc lines when there are no ranges", (done) -> - @DocArchiveManager._s3DocToMongoDoc { - lines: ["doc", "lines"] - schema_v: 1 - }, (error, doc) -> - expect(doc).to.deep.equal { - lines: ["doc", "lines"] - } - done() - - describe "with an unrecognised schema", -> - it "should return an error", (done) -> - @DocArchiveManager._s3DocToMongoDoc { - schema_v: 2 - }, (error, doc) -> - expect(error).to.exist - done() - - describe "_mongoDocToS3Doc", -> - describe "with a valid doc", -> - it "should return the json version", (done) -> - @DocArchiveManager._mongoDocToS3Doc doc = { - lines: ["doc", "lines"] - ranges: { "mock": "ranges" } - }, (err, s3_doc) -> - expect(s3_doc).to.equal JSON.stringify({ - lines: ["doc", "lines"] - ranges: { "mock": "ranges" } - schema_v: 1 - }) - done() - - describe "with null bytes in the result", -> - beforeEach -> - @_stringify = JSON.stringify - JSON.stringify = sinon.stub().returns '{"bad": "\u0000"}' - - afterEach -> - JSON.stringify = @_stringify - - it "should return an error", (done) -> - @DocArchiveManager._mongoDocToS3Doc { - lines: ["doc", "lines"] - ranges: { "mock": "ranges" } - }, (err, s3_doc) -> - expect(err).to.exist - done() - - describe "without doc lines", -> - it "should return an error", (done) -> - @DocArchiveManager._mongoDocToS3Doc {}, (err, s3_doc) -> - expect(err).to.exist - done() - - diff --git a/services/docstore/test/unit/coffee/DocManagerTests.coffee b/services/docstore/test/unit/coffee/DocManagerTests.coffee deleted file mode 100644 index 70cea2e2c8..0000000000 --- a/services/docstore/test/unit/coffee/DocManagerTests.coffee +++ /dev/null @@ -1,407 +0,0 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -chai = require('chai') -assert = require("chai").assert -chai.should() -expect = chai.expect -modulePath = require('path').join __dirname, '../../../app/js/DocManager' -ObjectId = require("mongojs").ObjectId -Errors = require "../../../app/js/Errors" - -describe "DocManager", -> - beforeEach -> - @DocManager = SandboxedModule.require modulePath, requires: - "./MongoManager": @MongoManager = {} - "./DocArchiveManager": @DocArchiveManager = {} - "./RangeManager": @RangeManager = { - jsonRangesToMongo: (r) -> r - shouldUpdateRanges: sinon.stub().returns false - } - "logger-sharelatex": @logger = - log: sinon.stub() - warn:-> - err:-> - @doc_id = ObjectId().toString() - @project_id = ObjectId().toString() - @another_project_id = ObjectId().toString() - @callback = sinon.stub() - @stubbedError = new Error("blew up") - - - describe "checkDocExists", -> - beforeEach -> - @DocManager._getDoc = sinon.stub() - - it "should call get doc with a quick filter", (done)-> - @DocManager._getDoc.callsArgWith(3, null, {_id:@doc_id}) - @DocManager.checkDocExists @project_id, @doc_id, (err, exist)=> - exist.should.equal true - @DocManager._getDoc.calledWith(@project_id, @doc_id, {_id:1, inS3:true}).should.equal true - done() - - it "should return false when doc is not there", (done)-> - @DocManager._getDoc.callsArgWith(3, null) - @DocManager.checkDocExists @project_id, @doc_id, (err, exist)=> - exist.should.equal false - done() - - it "should return error when get doc errors", (done)-> - @DocManager._getDoc.callsArgWith(3, "error") - @DocManager.checkDocExists @project_id, @doc_id, (err, exist)=> - err.should.equal "error" - done() - - describe "getFullDoc", -> - beforeEach -> - @DocManager._getDoc = sinon.stub() - @doc = - _id: @doc_id - lines:["2134"] - - it "should call get doc with a quick filter", (done)-> - @DocManager._getDoc.callsArgWith(3, null, @doc) - @DocManager.getFullDoc @project_id, @doc_id, (err, doc)=> - doc.should.equal @doc - @DocManager._getDoc.calledWith(@project_id, @doc_id, {lines: true, rev: true, deleted: true, version: true, ranges: true, inS3:true}).should.equal true - done() - - it "should return error when get doc errors", (done)-> - @DocManager._getDoc.callsArgWith(3, "error") - @DocManager.getFullDoc @project_id, @doc_id, (err, exist)=> - err.should.equal "error" - done() - - describe "getRawDoc", -> - - beforeEach -> - @DocManager._getDoc = sinon.stub() - @doc = - lines:["2134"] - - it "should call get doc with a quick filter", (done)-> - @DocManager._getDoc.callsArgWith(3, null, @doc) - @DocManager.getDocLines @project_id, @doc_id, (err, doc)=> - doc.should.equal @doc - @DocManager._getDoc.calledWith(@project_id, @doc_id, {lines: true, inS3:true}).should.equal true - done() - - it "should return error when get doc errors", (done)-> - @DocManager._getDoc.callsArgWith(3, "error") - @DocManager.getDocLines @project_id, @doc_id, (err, exist)=> - err.should.equal "error" - done() - - - - describe "getDoc", -> - beforeEach -> - @project = { name: "mock-project" } - @doc = { _id: @doc_id, project_id: @project_id, lines: ["mock-lines"] } - @version = 42 - @MongoManager.findDoc = sinon.stub() - @MongoManager.getDocVersion = sinon.stub().yields(null, @version) - - describe "when using a filter", -> - beforeEach -> - @MongoManager.findDoc.yields(null, @doc) - - it "should error if inS3 is not set to true", (done)-> - @DocManager._getDoc @project_id, @doc_id, {inS3: false}, (err)-> - expect(err).to.exist - done() - - it "should always get inS3 even when no filter is passed", (done)-> - @DocManager._getDoc @project_id, @doc_id, undefined, (err)=> - @MongoManager.findDoc.called.should.equal false - expect(err).to.exist - done() - - it "should not error if inS3 is set to true", (done)-> - @DocManager._getDoc @project_id, @doc_id, {inS3: true}, (err)-> - expect(err).to.not.exist - done() - - describe "when the doc is in the doc collection", -> - beforeEach -> - @MongoManager.findDoc.yields(null, @doc) - @DocManager._getDoc @project_id, @doc_id, {version: true, inS3:true}, @callback - - it "should get the doc from the doc collection", -> - @MongoManager.findDoc - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should get the doc version from the docOps collection", -> - @MongoManager.getDocVersion - .calledWith(@doc_id) - .should.equal true - - it "should return the callback with the doc with the version", -> - @callback.called.should.equal true - doc = @callback.args[0][1] - doc.lines.should.equal @doc.lines - doc.version.should.equal @version - - describe "without the version filter", -> - beforeEach -> - @MongoManager.findDoc.yields(null, @doc) - @DocManager._getDoc @project_id, @doc_id, {version: false, inS3:true}, @callback - - it "should not get the doc version from the docOps collection", -> - @MongoManager.getDocVersion.called.should.equal false - - describe "when MongoManager.findDoc errors", -> - beforeEach -> - @MongoManager.findDoc.yields(@stubbedError) - @DocManager._getDoc @project_id, @doc_id, {version: true, inS3:true}, @callback - - it "should return the error", -> - @callback.calledWith(@stubbedError).should.equal true - - describe "when the doc is archived", -> - beforeEach -> - @doc = { _id: @doc_id, project_id: @project_id, lines: ["mock-lines"], inS3: true } - @MongoManager.findDoc.yields(null, @doc) - @DocArchiveManager.unarchiveDoc = (project_id, doc_id, callback) => - @doc.inS3 = false - callback() - sinon.spy @DocArchiveManager, "unarchiveDoc" - @DocManager._getDoc @project_id, @doc_id, {version: true, inS3:true}, @callback - - it "should call the DocArchive to unarchive the doc", -> - @DocArchiveManager.unarchiveDoc.calledWith(@project_id, @doc_id).should.equal true - - it "should look up the doc twice", -> - @MongoManager.findDoc.calledTwice.should.equal true - - it "should return the doc", -> - @callback.calledWith(null, @doc).should.equal true - - describe "when the doc does not exist in the docs collection", -> - beforeEach -> - @MongoManager.findDoc = sinon.stub().yields(null, null) - @DocManager._getDoc @project_id, @doc_id, {version: true, inS3:true}, @callback - - it "should return a NotFoundError", -> - @callback - .calledWith(sinon.match.has('message', "No such doc: #{@doc_id} in project #{@project_id}")) - .should.equal true - - describe "getAllNonDeletedDocs", -> - describe "when the project exists", -> - beforeEach -> - @docs = [{ _id: @doc_id, project_id: @project_id, lines: ["mock-lines"] }] - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, @docs) - @DocArchiveManager.unArchiveAllDocs = sinon.stub().callsArgWith(1, null, @docs) - @filter = { lines: true } - @DocManager.getAllNonDeletedDocs @project_id, @filter, @callback - - it "should get the project from the database", -> - @MongoManager.getProjectsDocs - .calledWith(@project_id, {include_deleted: false}, @filter) - .should.equal true - - it "should return the docs", -> - @callback.calledWith(null, @docs).should.equal true - - describe "when there are no docs for the project", -> - beforeEach -> - @MongoManager.getProjectsDocs = sinon.stub().callsArgWith(3, null, null) - @DocArchiveManager.unArchiveAllDocs = sinon.stub().callsArgWith(1, null) - @DocManager.getAllNonDeletedDocs @project_id, @filter, @callback - - it "should return a NotFoundError", -> - @callback - .calledWith(sinon.match.has('message', "No docs for project #{@project_id}")) - .should.equal true - - describe "deleteDoc", -> - describe "when the doc exists", -> - beforeEach -> - @lines = ["mock", "doc", "lines"] - @rev = 77 - @DocManager.checkDocExists = sinon.stub().callsArgWith(2, null, true) - @MongoManager.markDocAsDeleted = sinon.stub().callsArg(2) - @DocManager.deleteDoc @project_id, @doc_id, @callback - - it "should get the doc", -> - @DocManager.checkDocExists - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should mark doc as deleted", -> - @MongoManager.markDocAsDeleted - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should return the callback", -> - @callback.called.should.equal true - - describe "when the doc does not exist", -> - beforeEach -> - @DocManager.checkDocExists = sinon.stub().callsArgWith(2, null, false) - @DocManager.deleteDoc @project_id, @doc_id, @callback - - it "should return a NotFoundError", -> - @callback - .calledWith(sinon.match.has('message', "No such project/doc to delete: #{@project_id}/#{@doc_id}")) - .should.equal true - - describe "updateDoc", -> - beforeEach -> - @oldDocLines = ["old", "doc", "lines"] - @newDocLines = ["new", "doc", "lines"] - @originalRanges = { - changes: [{ - id: ObjectId().toString() - op: { i: "foo", p: 3 } - meta: - user_id: ObjectId().toString() - ts: new Date().toString() - }] - } - @newRanges = { - changes: [{ - id: ObjectId().toString() - op: { i: "bar", p: 6 } - meta: - user_id: ObjectId().toString() - ts: new Date().toString() - }] - } - @version = 42 - @doc = { _id: @doc_id, project_id: @project_id, lines: @oldDocLines, rev: @rev = 5, version: @version, ranges: @originalRanges } - - @MongoManager.upsertIntoDocCollection = sinon.stub().callsArg(3) - @MongoManager.setDocVersion = sinon.stub().yields() - @DocManager._getDoc = sinon.stub() - - describe "when only the doc lines have changed", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, @doc) - @DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback - - it "should get the existing doc", -> - @DocManager._getDoc - .calledWith(@project_id, @doc_id, {version: true, rev: true, lines: true, version: true, ranges: true, inS3:true}) - .should.equal true - - it "should upsert the document to the doc collection", -> - @MongoManager.upsertIntoDocCollection - .calledWith(@project_id, @doc_id, {lines: @newDocLines}) - .should.equal true - - it "should not update the version", -> - @MongoManager.setDocVersion.called.should.equal false - - it "should return the callback with the new rev", -> - @callback.calledWith(null, true, @rev + 1).should.equal true - - describe "when the doc ranges have changed", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, @doc) - @RangeManager.shouldUpdateRanges.returns true - @DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version, @newRanges, @callback - - it "should upsert the ranges", -> - @MongoManager.upsertIntoDocCollection - .calledWith(@project_id, @doc_id, {ranges: @newRanges}) - .should.equal true - - it "should not update the version", -> - @MongoManager.setDocVersion.called.should.equal false - - it "should return the callback with the new rev", -> - @callback.calledWith(null, true, @rev + 1).should.equal true - - describe "when only the version has changed", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, @doc) - @DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version + 1, @originalRanges, @callback - - it "should not change the lines or ranges", -> - @MongoManager.upsertIntoDocCollection.called.should.equal false - - it "should update the version", -> - @MongoManager.setDocVersion - .calledWith(@doc_id, @version + 1) - .should.equal true - - it "should return the callback with the old rev", -> - @callback.calledWith(null, true, @rev).should.equal true - - describe "when the doc has not changed at all", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, @doc) - @DocManager.updateDoc @project_id, @doc_id, @oldDocLines, @version, @originalRanges, @callback - - it "should not update the ranges or lines", -> - @MongoManager.upsertIntoDocCollection.called.should.equal false - - it "should not update the version", -> - @MongoManager.setDocVersion.called.should.equal false - - it "should return the callback with the old rev and modified == false", -> - @callback.calledWith(null, false, @rev).should.equal true - - describe "when the version is null", -> - beforeEach -> - @DocManager.updateDoc @project_id, @doc_id, @newDocLines, null, @originalRanges, @callback - - it "should return an error", -> - @callback.calledWith(sinon.match.has('message', "no lines, version or ranges provided")).should.equal true - - describe "when the lines are null", -> - beforeEach -> - @DocManager.updateDoc @project_id, @doc_id, null, @version, @originalRanges, @callback - - it "should return an error", -> - @callback.calledWith(sinon.match.has('message', "no lines, version or ranges provided")).should.equal true - - describe "when the ranges are null", -> - beforeEach -> - @DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, null, @callback - - it "should return an error", -> - @callback.calledWith(sinon.match.has('message', "no lines, version or ranges provided")).should.equal true - - describe "when there is a generic error getting the doc", -> - beforeEach -> - @error = new Error("doc could not be found") - @DocManager._getDoc = sinon.stub().callsArgWith(3, @error, null, null) - @DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback - - it "should not upsert the document to the doc collection", -> - @MongoManager.upsertIntoDocCollection.called.should.equal false - - it "should return the callback with the error", -> - @callback.calledWith(@error).should.equal true - - describe "when the doc lines have not changed", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, @doc) - @DocManager.updateDoc @project_id, @doc_id, @oldDocLines.slice(), @version, @originalRanges, @callback - - it "should not update the doc", -> - @MongoManager.upsertIntoDocCollection.called.should.equal false - - it "should return the callback with the existing rev", -> - @callback.calledWith(null, false, @rev).should.equal true - - describe "when the doc does not exist", -> - beforeEach -> - @DocManager._getDoc = sinon.stub().callsArgWith(3, null, null, null) - @DocManager.updateDoc @project_id, @doc_id, @newDocLines, @version, @originalRanges, @callback - - it "should upsert the document to the doc collection", -> - @MongoManager.upsertIntoDocCollection - .calledWith(@project_id, @doc_id, {lines: @newDocLines, ranges: @originalRanges}) - .should.equal true - - it "should set the version", -> - @MongoManager.setDocVersion - .calledWith(@doc_id, @version) - .should.equal true - - it "should return the callback with the new rev", -> - @callback.calledWith(null, true, 1).should.equal true diff --git a/services/docstore/test/unit/coffee/HttpControllerTests.coffee b/services/docstore/test/unit/coffee/HttpControllerTests.coffee deleted file mode 100644 index 3b7ffbbe8d..0000000000 --- a/services/docstore/test/unit/coffee/HttpControllerTests.coffee +++ /dev/null @@ -1,357 +0,0 @@ -SandboxedModule = require('sandboxed-module') -assert = require("chai").assert -sinon = require('sinon') -chai = require('chai') -chai.should() -expect = chai.expect -modulePath = require('path').join __dirname, '../../../app/js/HttpController' -ObjectId = require("mongojs").ObjectId - -describe "HttpController", -> - beforeEach -> - @HttpController = SandboxedModule.require modulePath, requires: - "./DocManager": @DocManager = {} - "./DocArchiveManager": @DocArchiveManager = {} - "logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() } - "./HealthChecker": {} - @res = { send: sinon.stub(), json: sinon.stub(), setHeader:sinon.stub() } - @res.status = sinon.stub().returns(@res) - @req = { query:{}} - @next = sinon.stub() - @project_id = "mock-project-id" - @doc_id = "mock-doc-id" - @doc = { - _id: @doc_id - lines: ["mock", "lines", " here", "", "", " spaces "] - version: 42 - rev: 5 - } - @deletedDoc = { - deleted:true - _id: @doc_id - lines: ["mock", "lines", " here", "", "", " spaces "] - version: 42 - rev: 5 - } - - describe "getDoc", -> - - describe "without deleted docs", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.getFullDoc = sinon.stub().callsArgWith(2, null, @doc) - @HttpController.getDoc @req, @res, @next - - it "should get the document with the version (including deleted)", -> - @DocManager.getFullDoc - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should return the doc as JSON", -> - @res.json - .calledWith({ - _id: @doc_id - lines: @doc.lines - rev: @doc.rev - version: @doc.version - }) - .should.equal true - - describe "which is deleted", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.getFullDoc = sinon.stub().callsArgWith(2, null, @deletedDoc) - - it "should get the doc from the doc manager", -> - @HttpController.getDoc @req, @res, @next - @DocManager.getFullDoc.calledWith(@project_id, @doc_id).should.equal true - - it "should return 404 if the query string delete is not set ", -> - @HttpController.getDoc @req, @res, @next - @res.send.calledWith(404).should.equal true - - it "should return the doc as JSON if include_deleted is set to true", -> - @req.query.include_deleted = "true" - @HttpController.getDoc @req, @res, @next - @res.json - .calledWith({ - _id: @doc_id - lines: @doc.lines - rev: @doc.rev - deleted: true - version: @doc.version - }) - .should.equal true - - describe "getRawDoc", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.getDocLines = sinon.stub().callsArgWith(2, null, @doc) - @HttpController.getRawDoc @req, @res, @next - - it "should get the document without the version", -> - @DocManager.getDocLines - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should set the content type header", -> - @res.setHeader.calledWith('content-type', 'text/plain').should.equal true - - it "should send the raw version of the doc", -> - assert.deepEqual @res.send.args[0][0], "#{@doc.lines[0]}\n#{@doc.lines[1]}\n#{@doc.lines[2]}\n#{@doc.lines[3]}\n#{@doc.lines[4]}\n#{@doc.lines[5]}" - - describe "getAllDocs", -> - describe "normally", -> - beforeEach -> - @req.params = - project_id: @project_id - @docs = [{ - _id: ObjectId() - lines: ["mock", "lines", "one"] - rev: 2 - }, { - _id: ObjectId() - lines: ["mock", "lines", "two"] - rev: 4 - }] - @DocManager.getAllNonDeletedDocs = sinon.stub().callsArgWith(2, null, @docs) - @HttpController.getAllDocs @req, @res, @next - - it "should get all the (non-deleted) docs", -> - @DocManager.getAllNonDeletedDocs - .calledWith(@project_id, {lines: true, rev: true}) - .should.equal true - - it "should return the doc as JSON", -> - @res.json - .calledWith([{ - _id: @docs[0]._id.toString() - lines: @docs[0].lines - rev: @docs[0].rev - }, { - _id: @docs[1]._id.toString() - lines: @docs[1].lines - rev: @docs[1].rev - }]) - .should.equal true - - describe "with a null doc", -> - beforeEach -> - @req.params = - project_id: @project_id - @docs = [{ - _id: ObjectId() - lines: ["mock", "lines", "one"] - rev: 2 - }, - null, - { - _id: ObjectId() - lines: ["mock", "lines", "two"] - rev: 4 - }] - @DocManager.getAllNonDeletedDocs = sinon.stub().callsArgWith(2, null, @docs) - @HttpController.getAllDocs @req, @res, @next - - it "should return the non null docs as JSON", -> - @res.json - .calledWith([{ - _id: @docs[0]._id.toString() - lines: @docs[0].lines - rev: @docs[0].rev - }, { - _id: @docs[2]._id.toString() - lines: @docs[2].lines - rev: @docs[2].rev - }]) - .should.equal true - - it "should log out an error", -> - @logger.error - .calledWith( - err: sinon.match.has('message', "null doc") - project_id: @project_id - "encountered null doc" - ) - .should.equal true - - describe "getAllRanges", -> - describe "normally", -> - beforeEach -> - @req.params = - project_id: @project_id - @docs = [{ - _id: ObjectId() - ranges: {"mock_ranges": "one"} - }, { - _id: ObjectId() - ranges: {"mock_ranges": "two"} - }] - @DocManager.getAllNonDeletedDocs = sinon.stub().callsArgWith(2, null, @docs) - @HttpController.getAllRanges @req, @res, @next - - it "should get all the (non-deleted) doc ranges", -> - @DocManager.getAllNonDeletedDocs - .calledWith(@project_id, {ranges: true}) - .should.equal true - - it "should return the doc as JSON", -> - @res.json - .calledWith([{ - _id: @docs[0]._id.toString() - ranges: @docs[0].ranges - }, { - _id: @docs[1]._id.toString() - ranges: @docs[1].ranges - }]) - .should.equal true - - describe "updateDoc", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - - describe "when the doc lines exist and were updated", -> - beforeEach -> - @req.body = - lines: @lines = ["hello", "world"] - version: @version = 42 - ranges: @ranges = { changes: "mock" } - @DocManager.updateDoc = sinon.stub().yields(null, true, @rev = 5) - @HttpController.updateDoc @req, @res, @next - - it "should update the document", -> - @DocManager.updateDoc - .calledWith(@project_id, @doc_id, @lines, @version, @ranges) - .should.equal true - - it "should return a modified status", -> - @res.json - .calledWith(modified: true, rev: @rev) - .should.equal true - - describe "when the doc lines exist and were not updated", -> - beforeEach -> - @req.body = - lines: @lines = ["hello", "world"] - version: @version = 42 - ranges: {} - @DocManager.updateDoc = sinon.stub().yields(null, false, @rev = 5) - @HttpController.updateDoc @req, @res, @next - - it "should return a modified status", -> - @res.json - .calledWith(modified: false, rev: @rev) - .should.equal true - - describe "when the doc lines are not provided", -> - beforeEach -> - @req.body = { version: 42, ranges: {} } - @DocManager.updateDoc = sinon.stub().yields(null, false) - @HttpController.updateDoc @req, @res, @next - - it "should not update the document", -> - @DocManager.updateDoc.called.should.equal false - - it "should return a 400 (bad request) response", -> - @res.send - .calledWith(400) - .should.equal true - - describe "when the doc version are not provided", -> - beforeEach -> - @req.body = { version: 42, lines: ["hello world"] } - @DocManager.updateDoc = sinon.stub().yields(null, false) - @HttpController.updateDoc @req, @res, @next - - it "should not update the document", -> - @DocManager.updateDoc.called.should.equal false - - it "should return a 400 (bad request) response", -> - @res.send - .calledWith(400) - .should.equal true - - describe "when the doc ranges is not provided", -> - beforeEach -> - @req.body = { lines : [ "foo" ], version: 42 } - @DocManager.updateDoc = sinon.stub().yields(null, false) - @HttpController.updateDoc @req, @res, @next - - it "should not update the document", -> - @DocManager.updateDoc.called.should.equal false - - it "should return a 400 (bad request) response", -> - @res.send - .calledWith(400) - .should.equal true - - describe "when the doc body is too large", -> - beforeEach -> - @req.body = - lines: @lines = Array(2049).fill('a'.repeat(1024)) - version: @version = 42 - ranges: @ranges = { changes: "mock" } - @HttpController.updateDoc @req, @res, @next - - it "should return a 413 (too large) response", -> - sinon.assert.calledWith(@res.status, 413) - - it "should report that the document body is too large", -> - sinon.assert.calledWith(@res.send, "document body too large") - - describe "deleteDoc", -> - beforeEach -> - @req.params = - project_id: @project_id - doc_id: @doc_id - @DocManager.deleteDoc = sinon.stub().callsArg(2) - @HttpController.deleteDoc @req, @res, @next - - it "should delete the document", -> - @DocManager.deleteDoc - .calledWith(@project_id, @doc_id) - .should.equal true - - it "should return a 204 (No Content)", -> - @res.send - .calledWith(204) - .should.equal true - - describe "archiveAllDocs", -> - beforeEach -> - @req.params = - project_id: @project_id - @DocArchiveManager.archiveAllDocs = sinon.stub().callsArg(1) - @HttpController.archiveAllDocs @req, @res, @next - - it "should archive the project", -> - @DocArchiveManager.archiveAllDocs - .calledWith(@project_id) - .should.equal true - - it "should return a 204 (No Content)", -> - @res.send - .calledWith(204) - .should.equal true - - describe "destroyAllDocs", -> - beforeEach -> - @req.params = - project_id: @project_id - @DocArchiveManager.destroyAllDocs = sinon.stub().callsArg(1) - @HttpController.destroyAllDocs @req, @res, @next - - it "should destroy the docs", -> - sinon.assert.calledWith(@DocArchiveManager.destroyAllDocs, @project_id) - - it "should return 204", -> - sinon.assert.calledWith(@res.send, 204) - diff --git a/services/docstore/test/unit/coffee/MongoManagerTests.coffee b/services/docstore/test/unit/coffee/MongoManagerTests.coffee deleted file mode 100644 index 842a3c8884..0000000000 --- a/services/docstore/test/unit/coffee/MongoManagerTests.coffee +++ /dev/null @@ -1,168 +0,0 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -modulePath = require('path').join __dirname, '../../../app/js/MongoManager' -ObjectId = require("mongojs").ObjectId -assert = require("chai").assert - -describe "MongoManager", -> - beforeEach -> - @MongoManager = SandboxedModule.require modulePath, requires: - "./mongojs": - db: @db = { docs: {}, docOps: {} } - ObjectId: ObjectId - 'metrics-sharelatex': {timeAsyncMethod: sinon.stub()} - 'logger-sharelatex': {log: ()->} - @project_id = ObjectId().toString() - @doc_id = ObjectId().toString() - @callback = sinon.stub() - @stubbedErr = new Error("hello world") - - describe "findDoc", -> - beforeEach -> - @doc = { name: "mock-doc"} - @db.docs.find = sinon.stub().callsArgWith(2, null, [@doc]) - @filter = { lines: true } - @MongoManager.findDoc @project_id, @doc_id, @filter, @callback - - it "should find the doc", -> - @db.docs.find - .calledWith({ - _id: ObjectId(@doc_id) - project_id: ObjectId(@project_id) - }, @filter) - .should.equal true - - it "should call the callback with the doc", -> - @callback.calledWith(null, @doc).should.equal true - - describe "getProjectsDocs", -> - beforeEach -> - @filter = {lines: true} - @doc1 = { name: "mock-doc1" } - @doc2 = { name: "mock-doc2" } - @doc3 = { name: "mock-doc3" } - @doc4 = { name: "mock-doc4" } - @db.docs.find = sinon.stub().callsArgWith(2, null, [@doc, @doc3, @doc4]) - - describe "with included_deleted = false", -> - beforeEach -> - @MongoManager.getProjectsDocs @project_id, include_deleted: false, @filter, @callback - - it "should find the non-deleted docs via the project_id", -> - @db.docs.find - .calledWith({ - project_id: ObjectId(@project_id) - deleted: { $ne: true } - }, @filter) - .should.equal true - - it "should call the callback with the docs", -> - @callback.calledWith(null, [@doc, @doc3, @doc4]).should.equal true - - describe "with included_deleted = true", -> - beforeEach -> - @MongoManager.getProjectsDocs @project_id, include_deleted: true, @filter, @callback - - it "should find all via the project_id", -> - @db.docs.find - .calledWith({ - project_id: ObjectId(@project_id) - }, @filter) - .should.equal true - - it "should call the callback with the docs", -> - @callback.calledWith(null, [@doc, @doc3, @doc4]).should.equal true - - describe "upsertIntoDocCollection", -> - beforeEach -> - @db.docs.update = sinon.stub().callsArgWith(3, @stubbedErr) - @oldRev = 77 - - it "should upsert the document", (done)-> - @MongoManager.upsertIntoDocCollection @project_id, @doc_id, {@lines}, (err)=> - args = @db.docs.update.args[0] - assert.deepEqual args[0], {_id: ObjectId(@doc_id)} - assert.equal args[1]["$set"]["lines"], @lines - assert.equal args[1]["$inc"]["rev"], 1 - assert.deepEqual args[1]["$set"]["project_id"], ObjectId(@project_id) - done() - - it "should return the error", (done)-> - @MongoManager.upsertIntoDocCollection @project_id, @doc_id, {@lines}, (err)=> - err.should.equal @stubbedErr - done() - - describe "markDocAsDeleted", -> - beforeEach -> - @db.docs.update = sinon.stub().callsArgWith(2, @stubbedErr) - @oldRev = 77 - - it "should process the update", (done) -> - @MongoManager.markDocAsDeleted @project_id, @doc_id, (err)=> - args = @db.docs.update.args[0] - assert.deepEqual args[0], {_id: ObjectId(@doc_id), project_id: ObjectId(@project_id)} - assert.equal args[1]["$set"]["deleted"], true - done() - - it "should return the error", (done)-> - @MongoManager.markDocAsDeleted @project_id, @doc_id, (err)=> - err.should.equal @stubbedErr - done() - - describe "destroyDoc", -> - beforeEach (done) -> - @db.docs.remove = sinon.stub().yields() - @db.docOps.remove = sinon.stub().yields() - @MongoManager.destroyDoc '123456789012', done - - it "should destroy the doc", -> - sinon.assert.calledWith(@db.docs.remove, {_id: ObjectId('123456789012')}) - - it "should destroy the docOps", -> - sinon.assert.calledWith(@db.docOps.remove, {doc_id: ObjectId('123456789012')}) - - describe "getDocVersion", -> - describe "when the doc exists", -> - beforeEach -> - @doc = - version: @version = 42 - @db.docOps.find = sinon.stub().callsArgWith(2, null, [@doc]) - @MongoManager.getDocVersion @doc_id, @callback - - it "should look for the doc in the database", -> - @db.docOps.find - .calledWith({ doc_id: ObjectId(@doc_id) }, {version: 1}) - .should.equal true - - it "should call the callback with the version", -> - @callback.calledWith(null, @version).should.equal true - - describe "when the doc doesn't exist", -> - beforeEach -> - @db.docOps.find = sinon.stub().callsArgWith(2, null, []) - @MongoManager.getDocVersion @doc_id, @callback - - it "should call the callback with 0", -> - @callback.calledWith(null, 0).should.equal true - - describe "setDocVersion", -> - beforeEach -> - @version = 42 - @db.docOps.update = sinon.stub().callsArg(3) - @MongoManager.setDocVersion @doc_id, @version, @callback - - it "should update the doc version", -> - @db.docOps.update - .calledWith({ - doc_id: ObjectId(@doc_id) - }, { - $set: - version: @version - }, { - upsert: true - }) - .should.equal true - - it "should call the callback", -> - @callback.called.should.equal true diff --git a/services/docstore/test/unit/coffee/RangeManagerTests.coffee b/services/docstore/test/unit/coffee/RangeManagerTests.coffee deleted file mode 100644 index ea2416ab63..0000000000 --- a/services/docstore/test/unit/coffee/RangeManagerTests.coffee +++ /dev/null @@ -1,155 +0,0 @@ -SandboxedModule = require('sandboxed-module') -sinon = require('sinon') -require('chai').should() -expect = require('chai').expect -modulePath = require('path').join __dirname, '../../../app/js/RangeManager' -ObjectId = require("mongojs").ObjectId -assert = require("chai").assert -_ = require "underscore" - -describe "RangeManager", -> - beforeEach -> - @RangeManager = SandboxedModule.require modulePath, requires: - "./mongojs": - ObjectId: ObjectId - - describe "jsonRangesToMongo", -> - it "should convert ObjectIds and dates to proper objects", -> - change_id = ObjectId().toString() - comment_id = ObjectId().toString() - user_id = ObjectId().toString() - thread_id = ObjectId().toString() - ts = new Date().toJSON() - @RangeManager.jsonRangesToMongo({ - changes: [{ - id: change_id - op: { i: "foo", p: 3 } - metadata: - user_id: user_id - ts: ts - }] - comments: [{ - id: comment_id - op: { c: "foo", p: 3, t: thread_id } - }] - }).should.deep.equal { - changes: [{ - id: ObjectId(change_id) - op: { i: "foo", p: 3 } - metadata: - user_id: ObjectId(user_id) - ts: new Date(ts) - }] - comments: [{ - id: ObjectId(comment_id) - op: { c: "foo", p: 3, t: ObjectId(thread_id) } - }] - } - - it "should leave malformed ObjectIds as they are", -> - change_id = "foo" - comment_id = "bar" - user_id = "baz" - @RangeManager.jsonRangesToMongo({ - changes: [{ - id: change_id - metadata: - user_id: user_id - }] - comments: [{ - id: comment_id - }] - }).should.deep.equal { - changes: [{ - id: change_id - metadata: - user_id: user_id - }] - comments: [{ - id: comment_id - }] - } - - it "should be consistent when transformed through json -> mongo -> json", -> - change_id = ObjectId().toString() - comment_id = ObjectId().toString() - user_id = ObjectId().toString() - thread_id = ObjectId().toString() - ts = new Date().toJSON() - ranges1 = { - changes: [{ - id: change_id - op: { i: "foo", p: 3 } - metadata: - user_id: user_id - ts: ts - }] - comments: [{ - id: comment_id - op: { c: "foo", p: 3, t: thread_id } - }] - } - ranges1_copy = JSON.parse(JSON.stringify(ranges1)) # jsonRangesToMongo modifies in place - ranges2 = JSON.parse(JSON.stringify(@RangeManager.jsonRangesToMongo(ranges1_copy))) - ranges1.should.deep.equal ranges2 - - describe "shouldUpdateRanges", -> - beforeEach () -> - @ranges = { - changes: [{ - id: ObjectId() - op: { i: "foo", p: 3 } - metadata: - user_id: ObjectId() - ts: new Date() - }] - comments: [{ - id: ObjectId() - op: { c: "foo", p: 3, t: ObjectId() } - }] - } - @ranges_copy = @RangeManager.jsonRangesToMongo(JSON.parse(JSON.stringify(@ranges))) - - describe "with a blank new range", -> - it "should throw an error", -> - expect(() => - @RangeManager.shouldUpdateRanges(@ranges, null) - ).to.throw(Error) - - describe "with a blank old range", -> - it "should treat it like {}", -> - @RangeManager.shouldUpdateRanges(null, {}).should.equal false - @RangeManager.shouldUpdateRanges(null, @ranges).should.equal true - - describe "with no changes", -> - it "should return false", -> - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal false - - describe "with changes", -> - it "should return true when the change id changes", -> - @ranges_copy.changes[0].id = ObjectId() - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the change user id changes", -> - @ranges_copy.changes[0].metadata.user_id = ObjectId() - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the change ts changes", -> - @ranges_copy.changes[0].metadata.ts = new Date(Date.now() + 1000) - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the change op changes", -> - @ranges_copy.changes[0].op.i = "bar" - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the comment id changes", -> - @ranges_copy.comments[0].id = ObjectId() - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the comment offset changes", -> - @ranges_copy.comments[0].op.p = 17 - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true - - it "should return true when the comment content changes", -> - @ranges_copy.comments[0].op.c = "bar" - @RangeManager.shouldUpdateRanges(@ranges, @ranges_copy).should.equal true \ No newline at end of file diff --git a/services/docstore/test/unit/js/DocArchiveManagerTests.js b/services/docstore/test/unit/js/DocArchiveManagerTests.js new file mode 100644 index 0000000000..06bd6db8aa --- /dev/null +++ b/services/docstore/test/unit/js/DocArchiveManagerTests.js @@ -0,0 +1,577 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS101: Remove unnecessary use of Array.from + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const { assert } = require('chai') +const sinon = require('sinon') +const chai = require('chai') +const should = chai.should() +const { expect } = chai +const modulePath = '../../../app/js/DocArchiveManager.js' +const SandboxedModule = require('sandboxed-module') +const { ObjectId } = require('mongojs') +const Errors = require('../../../app/js/Errors') +const crypto = require('crypto') + +describe('DocArchiveManager', function() { + beforeEach(function() { + this.settings = { + docstore: { + s3: { + secret: 'secret', + key: 'this_key', + bucket: 'doc-archive-unit-test' + } + } + } + + this.request = { + put: {}, + get: {}, + del: {} + } + + this.archivedDocs = [ + { + _id: ObjectId(), + inS3: true, + rev: 2 + }, + { + _id: ObjectId(), + inS3: true, + rev: 4 + }, + { + _id: ObjectId(), + inS3: true, + rev: 6 + } + ] + + this.mongoDocs = [ + { + _id: ObjectId(), + lines: ['one', 'two', 'three'], + rev: 2 + }, + { + _id: ObjectId(), + lines: ['aaa', 'bbb', 'ccc'], + rev: 4 + }, + { + _id: ObjectId(), + inS3: true, + rev: 6 + }, + { + _id: ObjectId(), + inS3: true, + rev: 6 + }, + { + _id: ObjectId(), + lines: ['111', '222', '333'], + rev: 6 + } + ] + + this.unarchivedDocs = [ + { + _id: ObjectId(), + lines: ['wombat', 'potato', 'banana'], + rev: 2 + }, + { + _id: ObjectId(), + lines: ['llama', 'turnip', 'apple'], + rev: 4 + }, + { + _id: ObjectId(), + lines: ['elephant', 'swede', 'nectarine'], + rev: 6 + } + ] + + this.mixedDocs = this.archivedDocs.concat(this.unarchivedDocs) + + this.MongoManager = { + markDocAsArchived: sinon.stub().callsArgWith(2, null), + upsertIntoDocCollection: sinon.stub().callsArgWith(3, null), + getProjectsDocs: sinon.stub().callsArgWith(3, null, this.mongoDocs), + getArchivedProjectDocs: sinon.stub().callsArgWith(2, null, this.mongoDocs) + } + + this.requires = { + 'settings-sharelatex': this.settings, + './MongoManager': this.MongoManager, + request: this.request, + './RangeManager': (this.RangeManager = {}), + 'logger-sharelatex': { + log() {}, + err() {} + } + } + this.globals = { JSON } + + this.error = 'my errror' + this.project_id = ObjectId().toString() + this.stubbedError = new Errors.NotFoundError('Error in S3 request') + return (this.DocArchiveManager = SandboxedModule.require(modulePath, { + requires: this.requires, + globals: this.globals + })) + }) + + describe('archiveDoc', function() { + it('should use correct options', function(done) { + this.request.put = sinon + .stub() + .callsArgWith(1, null, { statusCode: 200, headers: { etag: '' } }) + return this.DocArchiveManager.archiveDoc( + this.project_id, + this.mongoDocs[0], + err => { + const opts = this.request.put.args[0][0] + assert.deepEqual(opts.aws, { + key: this.settings.docstore.s3.key, + secret: this.settings.docstore.s3.secret, + bucket: this.settings.docstore.s3.bucket + }) + opts.body.should.equal( + JSON.stringify({ + lines: this.mongoDocs[0].lines, + ranges: this.mongoDocs[0].ranges, + schema_v: 1 + }) + ) + opts.timeout.should.equal(30 * 1000) + opts.uri.should.equal( + `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${this.mongoDocs[0]._id}` + ) + return done() + } + ) + }) + + it('should return no md5 error', function(done) { + const data = JSON.stringify({ + lines: this.mongoDocs[0].lines, + ranges: this.mongoDocs[0].ranges, + schema_v: 1 + }) + this.md5 = crypto + .createHash('md5') + .update(data) + .digest('hex') + this.request.put = sinon + .stub() + .callsArgWith(1, null, { statusCode: 200, headers: { etag: this.md5 } }) + return this.DocArchiveManager.archiveDoc( + this.project_id, + this.mongoDocs[0], + err => { + should.not.exist(err) + return done() + } + ) + }) + + return it('should return the error', function(done) { + this.request.put = sinon.stub().callsArgWith(1, this.stubbedError, { + statusCode: 400, + headers: { etag: '' } + }) + return this.DocArchiveManager.archiveDoc( + this.project_id, + this.mongoDocs[0], + err => { + should.exist(err) + return done() + } + ) + }) + }) + + describe('unarchiveDoc', function() { + it('should use correct options', function(done) { + this.request.get = sinon + .stub() + .callsArgWith(1, null, { statusCode: 200 }, this.mongoDocs[0].lines) + this.request.del = sinon + .stub() + .callsArgWith(1, null, { statusCode: 204 }, {}) + return this.DocArchiveManager.unarchiveDoc( + this.project_id, + this.mongoDocs[0]._id, + err => { + const opts = this.request.get.args[0][0] + assert.deepEqual(opts.aws, { + key: this.settings.docstore.s3.key, + secret: this.settings.docstore.s3.secret, + bucket: this.settings.docstore.s3.bucket + }) + opts.json.should.equal(true) + opts.timeout.should.equal(30 * 1000) + opts.uri.should.equal( + `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${this.mongoDocs[0]._id}` + ) + return done() + } + ) + }) + + it('should return the error', function(done) { + this.request.get = sinon.stub().callsArgWith(1, this.stubbedError, {}, {}) + return this.DocArchiveManager.unarchiveDoc( + this.project_id, + this.mongoDocs[0], + err => { + should.exist(err) + return done() + } + ) + }) + + return it('should error if the doc lines are a string not an array', function(done) { + this.request.get = sinon + .stub() + .callsArgWith(1, null, { statusCode: 200 }, 'this is a string') + this.request.del = sinon.stub() + return this.DocArchiveManager.unarchiveDoc( + this.project_id, + this.mongoDocs[0], + err => { + should.exist(err) + this.request.del.called.should.equal(false) + return done() + } + ) + }) + }) + + describe('archiveAllDocs', function() { + it('should archive all project docs which are not in s3', function(done) { + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, this.mongoDocs) + this.DocArchiveManager.archiveDoc = sinon.stub().callsArgWith(2, null) + + return this.DocArchiveManager.archiveAllDocs(this.project_id, err => { + this.DocArchiveManager.archiveDoc + .calledWith(this.project_id, this.mongoDocs[0]) + .should.equal(true) + this.DocArchiveManager.archiveDoc + .calledWith(this.project_id, this.mongoDocs[1]) + .should.equal(true) + this.DocArchiveManager.archiveDoc + .calledWith(this.project_id, this.mongoDocs[4]) + .should.equal(true) + + this.DocArchiveManager.archiveDoc + .calledWith(this.project_id, this.mongoDocs[2]) + .should.equal(false) + this.DocArchiveManager.archiveDoc + .calledWith(this.project_id, this.mongoDocs[3]) + .should.equal(false) + + should.not.exist(err) + return done() + }) + }) + + it('should return error if have no docs', function(done) { + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, null) + + return this.DocArchiveManager.archiveAllDocs(this.project_id, err => { + should.exist(err) + return done() + }) + }) + + it('should return the error', function(done) { + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, this.error, null) + + return this.DocArchiveManager.archiveAllDocs(this.project_id, err => { + err.should.equal(this.error) + return done() + }) + }) + + return describe('when most have been already put in s3', function() { + beforeEach(function() { + let numberOfDocs = 10 * 1000 + this.mongoDocs = [] + while (--numberOfDocs !== 0) { + this.mongoDocs.push({ inS3: true, _id: ObjectId() }) + } + + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, this.mongoDocs) + return (this.DocArchiveManager.archiveDoc = sinon + .stub() + .callsArgWith(2, null)) + }) + + return it('should not throw and error', function(done) { + return this.DocArchiveManager.archiveAllDocs(this.project_id, err => { + should.not.exist(err) + return done() + }) + }) + }) + }) + + describe('unArchiveAllDocs', function() { + it('should unarchive all inS3 docs', function(done) { + this.MongoManager.getArchivedProjectDocs = sinon + .stub() + .callsArgWith(1, null, this.archivedDocs) + this.DocArchiveManager.unarchiveDoc = sinon.stub().callsArgWith(2, null) + return this.DocArchiveManager.unArchiveAllDocs(this.project_id, err => { + for (const doc of Array.from(this.archivedDocs)) { + this.DocArchiveManager.unarchiveDoc + .calledWith(this.project_id, doc._id) + .should.equal(true) + } + should.not.exist(err) + return done() + }) + }) + + it('should return error if have no docs', function(done) { + this.MongoManager.getArchivedProjectDocs = sinon + .stub() + .callsArgWith(1, null, null) + return this.DocArchiveManager.unArchiveAllDocs(this.project_id, err => { + should.exist(err) + return done() + }) + }) + + return it('should return the error', function(done) { + this.MongoManager.getArchivedProjectDocs = sinon + .stub() + .callsArgWith(1, this.error, null) + return this.DocArchiveManager.unArchiveAllDocs(this.project_id, err => { + err.should.equal(this.error) + return done() + }) + }) + }) + + describe('destroyAllDocs', function() { + beforeEach(function() { + this.request.del = sinon + .stub() + .callsArgWith(1, null, { statusCode: 204 }, {}) + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, this.mixedDocs) + this.MongoManager.findDoc = sinon.stub().callsArgWith(3, null, null) + this.MongoManager.destroyDoc = sinon.stub().yields() + return Array.from(this.mixedDocs).map(doc => + this.MongoManager.findDoc + .withArgs(this.project_id, doc._id) + .callsArgWith(3, null, doc) + ) + }) + + it('should destroy all the docs', function(done) { + this.DocArchiveManager.destroyDoc = sinon.stub().callsArgWith(2, null) + return this.DocArchiveManager.destroyAllDocs(this.project_id, err => { + for (const doc of Array.from(this.mixedDocs)) { + this.DocArchiveManager.destroyDoc + .calledWith(this.project_id, doc._id) + .should.equal(true) + } + should.not.exist(err) + return done() + }) + }) + + it('should only the s3 docs from s3', function(done) { + const docOpts = doc => { + return JSON.parse( + JSON.stringify({ + aws: { + key: this.settings.docstore.s3.key, + secret: this.settings.docstore.s3.secret, + bucket: this.settings.docstore.s3.bucket + }, + json: true, + timeout: 30 * 1000, + uri: `https://${this.settings.docstore.s3.bucket}.s3.amazonaws.com/${this.project_id}/${doc._id}` + }) + ) + } + + return this.DocArchiveManager.destroyAllDocs(this.project_id, err => { + let doc + expect(err).not.to.exist + + for (doc of Array.from(this.archivedDocs)) { + sinon.assert.calledWith(this.request.del, docOpts(doc)) + } + for (doc of Array.from(this.unarchivedDocs)) { + expect(this.request.del.calledWith(docOpts(doc))).to.equal(false) + } // no notCalledWith + + return done() + }) + }) + + return it('should remove the docs from mongo', function(done) { + this.DocArchiveManager.destroyAllDocs(this.project_id, err => { + return expect(err).not.to.exist + }) + + for (const doc of Array.from(this.mixedDocs)) { + sinon.assert.calledWith(this.MongoManager.destroyDoc, doc._id) + } + + return done() + }) + }) + + describe('_s3DocToMongoDoc', function() { + describe('with the old schema', function() { + return it('should return the docs lines', function(done) { + return this.DocArchiveManager._s3DocToMongoDoc( + ['doc', 'lines'], + (error, doc) => { + expect(doc).to.deep.equal({ + lines: ['doc', 'lines'] + }) + return done() + } + ) + }) + }) + + describe('with the new schema', function() { + it('should return the doc lines and ranges', function(done) { + this.RangeManager.jsonRangesToMongo = sinon + .stub() + .returns({ mongo: 'ranges' }) + return this.DocArchiveManager._s3DocToMongoDoc( + { + lines: ['doc', 'lines'], + ranges: { json: 'ranges' }, + schema_v: 1 + }, + (error, doc) => { + expect(doc).to.deep.equal({ + lines: ['doc', 'lines'], + ranges: { mongo: 'ranges' } + }) + return done() + } + ) + }) + + return it('should return just the doc lines when there are no ranges', function(done) { + return this.DocArchiveManager._s3DocToMongoDoc( + { + lines: ['doc', 'lines'], + schema_v: 1 + }, + (error, doc) => { + expect(doc).to.deep.equal({ + lines: ['doc', 'lines'] + }) + return done() + } + ) + }) + }) + + return describe('with an unrecognised schema', function() { + return it('should return an error', function(done) { + return this.DocArchiveManager._s3DocToMongoDoc( + { + schema_v: 2 + }, + (error, doc) => { + expect(error).to.exist + return done() + } + ) + }) + }) + }) + + return describe('_mongoDocToS3Doc', function() { + describe('with a valid doc', function() { + return it('should return the json version', function(done) { + let doc + return this.DocArchiveManager._mongoDocToS3Doc( + (doc = { + lines: ['doc', 'lines'], + ranges: { mock: 'ranges' } + }), + (err, s3_doc) => { + expect(s3_doc).to.equal( + JSON.stringify({ + lines: ['doc', 'lines'], + ranges: { mock: 'ranges' }, + schema_v: 1 + }) + ) + return done() + } + ) + }) + }) + + describe('with null bytes in the result', function() { + beforeEach(function() { + this._stringify = JSON.stringify + return (JSON.stringify = sinon.stub().returns('{"bad": "\u0000"}')) + }) + + afterEach(function() { + return (JSON.stringify = this._stringify) + }) + + return it('should return an error', function(done) { + return this.DocArchiveManager._mongoDocToS3Doc( + { + lines: ['doc', 'lines'], + ranges: { mock: 'ranges' } + }, + (err, s3_doc) => { + expect(err).to.exist + return done() + } + ) + }) + }) + + return describe('without doc lines', function() { + return it('should return an error', function(done) { + return this.DocArchiveManager._mongoDocToS3Doc({}, (err, s3_doc) => { + expect(err).to.exist + return done() + }) + }) + }) + }) +}) diff --git a/services/docstore/test/unit/js/DocManagerTests.js b/services/docstore/test/unit/js/DocManagerTests.js new file mode 100644 index 0000000000..9508e74f2e --- /dev/null +++ b/services/docstore/test/unit/js/DocManagerTests.js @@ -0,0 +1,807 @@ +/* eslint-disable + camelcase, + handle-callback-err, + no-dupe-keys, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +const chai = require('chai') +const { assert } = require('chai') +chai.should() +const { expect } = chai +const modulePath = require('path').join(__dirname, '../../../app/js/DocManager') +const { ObjectId } = require('mongojs') +const Errors = require('../../../app/js/Errors') + +describe('DocManager', function() { + beforeEach(function() { + this.DocManager = SandboxedModule.require(modulePath, { + requires: { + './MongoManager': (this.MongoManager = {}), + './DocArchiveManager': (this.DocArchiveManager = {}), + './RangeManager': (this.RangeManager = { + jsonRangesToMongo(r) { + return r + }, + shouldUpdateRanges: sinon.stub().returns(false) + }), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + warn() {}, + err() {} + }) + } + }) + this.doc_id = ObjectId().toString() + this.project_id = ObjectId().toString() + this.another_project_id = ObjectId().toString() + this.callback = sinon.stub() + return (this.stubbedError = new Error('blew up')) + }) + + describe('checkDocExists', function() { + beforeEach(function() { + return (this.DocManager._getDoc = sinon.stub()) + }) + + it('should call get doc with a quick filter', function(done) { + this.DocManager._getDoc.callsArgWith(3, null, { _id: this.doc_id }) + return this.DocManager.checkDocExists( + this.project_id, + this.doc_id, + (err, exist) => { + exist.should.equal(true) + this.DocManager._getDoc + .calledWith(this.project_id, this.doc_id, { _id: 1, inS3: true }) + .should.equal(true) + return done() + } + ) + }) + + it('should return false when doc is not there', function(done) { + this.DocManager._getDoc.callsArgWith(3, null) + return this.DocManager.checkDocExists( + this.project_id, + this.doc_id, + (err, exist) => { + exist.should.equal(false) + return done() + } + ) + }) + + return it('should return error when get doc errors', function(done) { + this.DocManager._getDoc.callsArgWith(3, 'error') + return this.DocManager.checkDocExists( + this.project_id, + this.doc_id, + (err, exist) => { + err.should.equal('error') + return done() + } + ) + }) + }) + + describe('getFullDoc', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub() + return (this.doc = { + _id: this.doc_id, + lines: ['2134'] + }) + }) + + it('should call get doc with a quick filter', function(done) { + this.DocManager._getDoc.callsArgWith(3, null, this.doc) + return this.DocManager.getFullDoc( + this.project_id, + this.doc_id, + (err, doc) => { + doc.should.equal(this.doc) + this.DocManager._getDoc + .calledWith(this.project_id, this.doc_id, { + lines: true, + rev: true, + deleted: true, + version: true, + ranges: true, + inS3: true + }) + .should.equal(true) + return done() + } + ) + }) + + return it('should return error when get doc errors', function(done) { + this.DocManager._getDoc.callsArgWith(3, 'error') + return this.DocManager.getFullDoc( + this.project_id, + this.doc_id, + (err, exist) => { + err.should.equal('error') + return done() + } + ) + }) + }) + + describe('getRawDoc', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub() + return (this.doc = { lines: ['2134'] }) + }) + + it('should call get doc with a quick filter', function(done) { + this.DocManager._getDoc.callsArgWith(3, null, this.doc) + return this.DocManager.getDocLines( + this.project_id, + this.doc_id, + (err, doc) => { + doc.should.equal(this.doc) + this.DocManager._getDoc + .calledWith(this.project_id, this.doc_id, { + lines: true, + inS3: true + }) + .should.equal(true) + return done() + } + ) + }) + + return it('should return error when get doc errors', function(done) { + this.DocManager._getDoc.callsArgWith(3, 'error') + return this.DocManager.getDocLines( + this.project_id, + this.doc_id, + (err, exist) => { + err.should.equal('error') + return done() + } + ) + }) + }) + + describe('getDoc', function() { + beforeEach(function() { + this.project = { name: 'mock-project' } + this.doc = { + _id: this.doc_id, + project_id: this.project_id, + lines: ['mock-lines'] + } + this.version = 42 + this.MongoManager.findDoc = sinon.stub() + return (this.MongoManager.getDocVersion = sinon + .stub() + .yields(null, this.version)) + }) + + describe('when using a filter', function() { + beforeEach(function() { + return this.MongoManager.findDoc.yields(null, this.doc) + }) + + it('should error if inS3 is not set to true', function(done) { + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { inS3: false }, + err => { + expect(err).to.exist + return done() + } + ) + }) + + it('should always get inS3 even when no filter is passed', function(done) { + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + undefined, + err => { + this.MongoManager.findDoc.called.should.equal(false) + expect(err).to.exist + return done() + } + ) + }) + + return it('should not error if inS3 is set to true', function(done) { + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { inS3: true }, + err => { + expect(err).to.not.exist + return done() + } + ) + }) + }) + + describe('when the doc is in the doc collection', function() { + beforeEach(function() { + this.MongoManager.findDoc.yields(null, this.doc) + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { version: true, inS3: true }, + this.callback + ) + }) + + it('should get the doc from the doc collection', function() { + return this.MongoManager.findDoc + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should get the doc version from the docOps collection', function() { + return this.MongoManager.getDocVersion + .calledWith(this.doc_id) + .should.equal(true) + }) + + return it('should return the callback with the doc with the version', function() { + this.callback.called.should.equal(true) + const doc = this.callback.args[0][1] + doc.lines.should.equal(this.doc.lines) + return doc.version.should.equal(this.version) + }) + }) + + describe('without the version filter', function() { + beforeEach(function() { + this.MongoManager.findDoc.yields(null, this.doc) + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { version: false, inS3: true }, + this.callback + ) + }) + + return it('should not get the doc version from the docOps collection', function() { + return this.MongoManager.getDocVersion.called.should.equal(false) + }) + }) + + describe('when MongoManager.findDoc errors', function() { + beforeEach(function() { + this.MongoManager.findDoc.yields(this.stubbedError) + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { version: true, inS3: true }, + this.callback + ) + }) + + return it('should return the error', function() { + return this.callback.calledWith(this.stubbedError).should.equal(true) + }) + }) + + describe('when the doc is archived', function() { + beforeEach(function() { + this.doc = { + _id: this.doc_id, + project_id: this.project_id, + lines: ['mock-lines'], + inS3: true + } + this.MongoManager.findDoc.yields(null, this.doc) + this.DocArchiveManager.unarchiveDoc = ( + project_id, + doc_id, + callback + ) => { + this.doc.inS3 = false + return callback() + } + sinon.spy(this.DocArchiveManager, 'unarchiveDoc') + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { version: true, inS3: true }, + this.callback + ) + }) + + it('should call the DocArchive to unarchive the doc', function() { + return this.DocArchiveManager.unarchiveDoc + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should look up the doc twice', function() { + return this.MongoManager.findDoc.calledTwice.should.equal(true) + }) + + return it('should return the doc', function() { + return this.callback.calledWith(null, this.doc).should.equal(true) + }) + }) + + return describe('when the doc does not exist in the docs collection', function() { + beforeEach(function() { + this.MongoManager.findDoc = sinon.stub().yields(null, null) + return this.DocManager._getDoc( + this.project_id, + this.doc_id, + { version: true, inS3: true }, + this.callback + ) + }) + + return it('should return a NotFoundError', function() { + return this.callback + .calledWith( + sinon.match.has( + 'message', + `No such doc: ${this.doc_id} in project ${this.project_id}` + ) + ) + .should.equal(true) + }) + }) + }) + + describe('getAllNonDeletedDocs', function() { + describe('when the project exists', function() { + beforeEach(function() { + this.docs = [ + { + _id: this.doc_id, + project_id: this.project_id, + lines: ['mock-lines'] + } + ] + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, this.docs) + this.DocArchiveManager.unArchiveAllDocs = sinon + .stub() + .callsArgWith(1, null, this.docs) + this.filter = { lines: true } + return this.DocManager.getAllNonDeletedDocs( + this.project_id, + this.filter, + this.callback + ) + }) + + it('should get the project from the database', function() { + return this.MongoManager.getProjectsDocs + .calledWith(this.project_id, { include_deleted: false }, this.filter) + .should.equal(true) + }) + + return it('should return the docs', function() { + return this.callback.calledWith(null, this.docs).should.equal(true) + }) + }) + + return describe('when there are no docs for the project', function() { + beforeEach(function() { + this.MongoManager.getProjectsDocs = sinon + .stub() + .callsArgWith(3, null, null) + this.DocArchiveManager.unArchiveAllDocs = sinon + .stub() + .callsArgWith(1, null) + return this.DocManager.getAllNonDeletedDocs( + this.project_id, + this.filter, + this.callback + ) + }) + + return it('should return a NotFoundError', function() { + return this.callback + .calledWith( + sinon.match.has('message', `No docs for project ${this.project_id}`) + ) + .should.equal(true) + }) + }) + }) + + describe('deleteDoc', function() { + describe('when the doc exists', function() { + beforeEach(function() { + this.lines = ['mock', 'doc', 'lines'] + this.rev = 77 + this.DocManager.checkDocExists = sinon + .stub() + .callsArgWith(2, null, true) + this.MongoManager.markDocAsDeleted = sinon.stub().callsArg(2) + return this.DocManager.deleteDoc( + this.project_id, + this.doc_id, + this.callback + ) + }) + + it('should get the doc', function() { + return this.DocManager.checkDocExists + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should mark doc as deleted', function() { + return this.MongoManager.markDocAsDeleted + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + return it('should return the callback', function() { + return this.callback.called.should.equal(true) + }) + }) + + return describe('when the doc does not exist', function() { + beforeEach(function() { + this.DocManager.checkDocExists = sinon + .stub() + .callsArgWith(2, null, false) + return this.DocManager.deleteDoc( + this.project_id, + this.doc_id, + this.callback + ) + }) + + return it('should return a NotFoundError', function() { + return this.callback + .calledWith( + sinon.match.has( + 'message', + `No such project/doc to delete: ${this.project_id}/${this.doc_id}` + ) + ) + .should.equal(true) + }) + }) + }) + + return describe('updateDoc', function() { + beforeEach(function() { + this.oldDocLines = ['old', 'doc', 'lines'] + this.newDocLines = ['new', 'doc', 'lines'] + this.originalRanges = { + changes: [ + { + id: ObjectId().toString(), + op: { i: 'foo', p: 3 }, + meta: { + user_id: ObjectId().toString(), + ts: new Date().toString() + } + } + ] + } + this.newRanges = { + changes: [ + { + id: ObjectId().toString(), + op: { i: 'bar', p: 6 }, + meta: { + user_id: ObjectId().toString(), + ts: new Date().toString() + } + } + ] + } + this.version = 42 + this.doc = { + _id: this.doc_id, + project_id: this.project_id, + lines: this.oldDocLines, + rev: (this.rev = 5), + version: this.version, + ranges: this.originalRanges + } + + this.MongoManager.upsertIntoDocCollection = sinon.stub().callsArg(3) + this.MongoManager.setDocVersion = sinon.stub().yields() + return (this.DocManager._getDoc = sinon.stub()) + }) + + describe('when only the doc lines have changed', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.newDocLines, + this.version, + this.originalRanges, + this.callback + ) + }) + + it('should get the existing doc', function() { + return this.DocManager._getDoc + .calledWith(this.project_id, this.doc_id, { + version: true, + rev: true, + lines: true, + version: true, + ranges: true, + inS3: true + }) + .should.equal(true) + }) + + it('should upsert the document to the doc collection', function() { + return this.MongoManager.upsertIntoDocCollection + .calledWith(this.project_id, this.doc_id, { lines: this.newDocLines }) + .should.equal(true) + }) + + it('should not update the version', function() { + return this.MongoManager.setDocVersion.called.should.equal(false) + }) + + return it('should return the callback with the new rev', function() { + return this.callback + .calledWith(null, true, this.rev + 1) + .should.equal(true) + }) + }) + + describe('when the doc ranges have changed', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc) + this.RangeManager.shouldUpdateRanges.returns(true) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.oldDocLines, + this.version, + this.newRanges, + this.callback + ) + }) + + it('should upsert the ranges', function() { + return this.MongoManager.upsertIntoDocCollection + .calledWith(this.project_id, this.doc_id, { ranges: this.newRanges }) + .should.equal(true) + }) + + it('should not update the version', function() { + return this.MongoManager.setDocVersion.called.should.equal(false) + }) + + return it('should return the callback with the new rev', function() { + return this.callback + .calledWith(null, true, this.rev + 1) + .should.equal(true) + }) + }) + + describe('when only the version has changed', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.oldDocLines, + this.version + 1, + this.originalRanges, + this.callback + ) + }) + + it('should not change the lines or ranges', function() { + return this.MongoManager.upsertIntoDocCollection.called.should.equal( + false + ) + }) + + it('should update the version', function() { + return this.MongoManager.setDocVersion + .calledWith(this.doc_id, this.version + 1) + .should.equal(true) + }) + + return it('should return the callback with the old rev', function() { + return this.callback.calledWith(null, true, this.rev).should.equal(true) + }) + }) + + describe('when the doc has not changed at all', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.oldDocLines, + this.version, + this.originalRanges, + this.callback + ) + }) + + it('should not update the ranges or lines', function() { + return this.MongoManager.upsertIntoDocCollection.called.should.equal( + false + ) + }) + + it('should not update the version', function() { + return this.MongoManager.setDocVersion.called.should.equal(false) + }) + + return it('should return the callback with the old rev and modified == false', function() { + return this.callback + .calledWith(null, false, this.rev) + .should.equal(true) + }) + }) + + describe('when the version is null', function() { + beforeEach(function() { + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.newDocLines, + null, + this.originalRanges, + this.callback + ) + }) + + return it('should return an error', function() { + return this.callback + .calledWith( + sinon.match.has('message', 'no lines, version or ranges provided') + ) + .should.equal(true) + }) + }) + + describe('when the lines are null', function() { + beforeEach(function() { + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + null, + this.version, + this.originalRanges, + this.callback + ) + }) + + return it('should return an error', function() { + return this.callback + .calledWith( + sinon.match.has('message', 'no lines, version or ranges provided') + ) + .should.equal(true) + }) + }) + + describe('when the ranges are null', function() { + beforeEach(function() { + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.newDocLines, + this.version, + null, + this.callback + ) + }) + + return it('should return an error', function() { + return this.callback + .calledWith( + sinon.match.has('message', 'no lines, version or ranges provided') + ) + .should.equal(true) + }) + }) + + describe('when there is a generic error getting the doc', function() { + beforeEach(function() { + this.error = new Error('doc could not be found') + this.DocManager._getDoc = sinon + .stub() + .callsArgWith(3, this.error, null, null) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.newDocLines, + this.version, + this.originalRanges, + this.callback + ) + }) + + it('should not upsert the document to the doc collection', function() { + return this.MongoManager.upsertIntoDocCollection.called.should.equal( + false + ) + }) + + return it('should return the callback with the error', function() { + return this.callback.calledWith(this.error).should.equal(true) + }) + }) + + describe('when the doc lines have not changed', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, this.doc) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.oldDocLines.slice(), + this.version, + this.originalRanges, + this.callback + ) + }) + + it('should not update the doc', function() { + return this.MongoManager.upsertIntoDocCollection.called.should.equal( + false + ) + }) + + return it('should return the callback with the existing rev', function() { + return this.callback + .calledWith(null, false, this.rev) + .should.equal(true) + }) + }) + + return describe('when the doc does not exist', function() { + beforeEach(function() { + this.DocManager._getDoc = sinon.stub().callsArgWith(3, null, null, null) + return this.DocManager.updateDoc( + this.project_id, + this.doc_id, + this.newDocLines, + this.version, + this.originalRanges, + this.callback + ) + }) + + it('should upsert the document to the doc collection', function() { + return this.MongoManager.upsertIntoDocCollection + .calledWith(this.project_id, this.doc_id, { + lines: this.newDocLines, + ranges: this.originalRanges + }) + .should.equal(true) + }) + + it('should set the version', function() { + return this.MongoManager.setDocVersion + .calledWith(this.doc_id, this.version) + .should.equal(true) + }) + + return it('should return the callback with the new rev', function() { + return this.callback.calledWith(null, true, 1).should.equal(true) + }) + }) + }) +}) diff --git a/services/docstore/test/unit/js/HttpControllerTests.js b/services/docstore/test/unit/js/HttpControllerTests.js new file mode 100644 index 0000000000..e282c247ff --- /dev/null +++ b/services/docstore/test/unit/js/HttpControllerTests.js @@ -0,0 +1,489 @@ +/* eslint-disable + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const { assert } = require('chai') +const sinon = require('sinon') +const chai = require('chai') +chai.should() +const { expect } = chai +const modulePath = require('path').join( + __dirname, + '../../../app/js/HttpController' +) +const { ObjectId } = require('mongojs') + +describe('HttpController', function() { + beforeEach(function() { + this.HttpController = SandboxedModule.require(modulePath, { + requires: { + './DocManager': (this.DocManager = {}), + './DocArchiveManager': (this.DocArchiveManager = {}), + 'logger-sharelatex': (this.logger = { + log: sinon.stub(), + error: sinon.stub() + }), + './HealthChecker': {} + } + }) + this.res = { + send: sinon.stub(), + json: sinon.stub(), + setHeader: sinon.stub() + } + this.res.status = sinon.stub().returns(this.res) + this.req = { query: {} } + this.next = sinon.stub() + this.project_id = 'mock-project-id' + this.doc_id = 'mock-doc-id' + this.doc = { + _id: this.doc_id, + lines: ['mock', 'lines', ' here', '', '', ' spaces '], + version: 42, + rev: 5 + } + return (this.deletedDoc = { + deleted: true, + _id: this.doc_id, + lines: ['mock', 'lines', ' here', '', '', ' spaces '], + version: 42, + rev: 5 + }) + }) + + describe('getDoc', function() { + describe('without deleted docs', function() { + beforeEach(function() { + this.req.params = { + project_id: this.project_id, + doc_id: this.doc_id + } + this.DocManager.getFullDoc = sinon + .stub() + .callsArgWith(2, null, this.doc) + return this.HttpController.getDoc(this.req, this.res, this.next) + }) + + it('should get the document with the version (including deleted)', function() { + return this.DocManager.getFullDoc + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + return it('should return the doc as JSON', function() { + return this.res.json + .calledWith({ + _id: this.doc_id, + lines: this.doc.lines, + rev: this.doc.rev, + version: this.doc.version + }) + .should.equal(true) + }) + }) + + return describe('which is deleted', function() { + beforeEach(function() { + this.req.params = { + project_id: this.project_id, + doc_id: this.doc_id + } + return (this.DocManager.getFullDoc = sinon + .stub() + .callsArgWith(2, null, this.deletedDoc)) + }) + + it('should get the doc from the doc manager', function() { + this.HttpController.getDoc(this.req, this.res, this.next) + return this.DocManager.getFullDoc + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should return 404 if the query string delete is not set ', function() { + this.HttpController.getDoc(this.req, this.res, this.next) + return this.res.send.calledWith(404).should.equal(true) + }) + + return it('should return the doc as JSON if include_deleted is set to true', function() { + this.req.query.include_deleted = 'true' + this.HttpController.getDoc(this.req, this.res, this.next) + return this.res.json + .calledWith({ + _id: this.doc_id, + lines: this.doc.lines, + rev: this.doc.rev, + deleted: true, + version: this.doc.version + }) + .should.equal(true) + }) + }) + }) + + describe('getRawDoc', function() { + beforeEach(function() { + this.req.params = { + project_id: this.project_id, + doc_id: this.doc_id + } + this.DocManager.getDocLines = sinon.stub().callsArgWith(2, null, this.doc) + return this.HttpController.getRawDoc(this.req, this.res, this.next) + }) + + it('should get the document without the version', function() { + return this.DocManager.getDocLines + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should set the content type header', function() { + return this.res.setHeader + .calledWith('content-type', 'text/plain') + .should.equal(true) + }) + + return it('should send the raw version of the doc', function() { + return assert.deepEqual( + this.res.send.args[0][0], + `${this.doc.lines[0]}\n${this.doc.lines[1]}\n${this.doc.lines[2]}\n${this.doc.lines[3]}\n${this.doc.lines[4]}\n${this.doc.lines[5]}` + ) + }) + }) + + describe('getAllDocs', function() { + describe('normally', function() { + beforeEach(function() { + this.req.params = { project_id: this.project_id } + this.docs = [ + { + _id: ObjectId(), + lines: ['mock', 'lines', 'one'], + rev: 2 + }, + { + _id: ObjectId(), + lines: ['mock', 'lines', 'two'], + rev: 4 + } + ] + this.DocManager.getAllNonDeletedDocs = sinon + .stub() + .callsArgWith(2, null, this.docs) + return this.HttpController.getAllDocs(this.req, this.res, this.next) + }) + + it('should get all the (non-deleted) docs', function() { + return this.DocManager.getAllNonDeletedDocs + .calledWith(this.project_id, { lines: true, rev: true }) + .should.equal(true) + }) + + return it('should return the doc as JSON', function() { + return this.res.json + .calledWith([ + { + _id: this.docs[0]._id.toString(), + lines: this.docs[0].lines, + rev: this.docs[0].rev + }, + { + _id: this.docs[1]._id.toString(), + lines: this.docs[1].lines, + rev: this.docs[1].rev + } + ]) + .should.equal(true) + }) + }) + + return describe('with a null doc', function() { + beforeEach(function() { + this.req.params = { project_id: this.project_id } + this.docs = [ + { + _id: ObjectId(), + lines: ['mock', 'lines', 'one'], + rev: 2 + }, + null, + { + _id: ObjectId(), + lines: ['mock', 'lines', 'two'], + rev: 4 + } + ] + this.DocManager.getAllNonDeletedDocs = sinon + .stub() + .callsArgWith(2, null, this.docs) + return this.HttpController.getAllDocs(this.req, this.res, this.next) + }) + + it('should return the non null docs as JSON', function() { + return this.res.json + .calledWith([ + { + _id: this.docs[0]._id.toString(), + lines: this.docs[0].lines, + rev: this.docs[0].rev + }, + { + _id: this.docs[2]._id.toString(), + lines: this.docs[2].lines, + rev: this.docs[2].rev + } + ]) + .should.equal(true) + }) + + return it('should log out an error', function() { + return this.logger.error + .calledWith( + { + err: sinon.match.has('message', 'null doc'), + project_id: this.project_id + }, + 'encountered null doc' + ) + .should.equal(true) + }) + }) + }) + + describe('getAllRanges', function() { + return describe('normally', function() { + beforeEach(function() { + this.req.params = { project_id: this.project_id } + this.docs = [ + { + _id: ObjectId(), + ranges: { mock_ranges: 'one' } + }, + { + _id: ObjectId(), + ranges: { mock_ranges: 'two' } + } + ] + this.DocManager.getAllNonDeletedDocs = sinon + .stub() + .callsArgWith(2, null, this.docs) + return this.HttpController.getAllRanges(this.req, this.res, this.next) + }) + + it('should get all the (non-deleted) doc ranges', function() { + return this.DocManager.getAllNonDeletedDocs + .calledWith(this.project_id, { ranges: true }) + .should.equal(true) + }) + + return it('should return the doc as JSON', function() { + return this.res.json + .calledWith([ + { + _id: this.docs[0]._id.toString(), + ranges: this.docs[0].ranges + }, + { + _id: this.docs[1]._id.toString(), + ranges: this.docs[1].ranges + } + ]) + .should.equal(true) + }) + }) + }) + + describe('updateDoc', function() { + beforeEach(function() { + return (this.req.params = { + project_id: this.project_id, + doc_id: this.doc_id + }) + }) + + describe('when the doc lines exist and were updated', function() { + beforeEach(function() { + this.req.body = { + lines: (this.lines = ['hello', 'world']), + version: (this.version = 42), + ranges: (this.ranges = { changes: 'mock' }) + } + this.DocManager.updateDoc = sinon + .stub() + .yields(null, true, (this.rev = 5)) + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + it('should update the document', function() { + return this.DocManager.updateDoc + .calledWith( + this.project_id, + this.doc_id, + this.lines, + this.version, + this.ranges + ) + .should.equal(true) + }) + + return it('should return a modified status', function() { + return this.res.json + .calledWith({ modified: true, rev: this.rev }) + .should.equal(true) + }) + }) + + describe('when the doc lines exist and were not updated', function() { + beforeEach(function() { + this.req.body = { + lines: (this.lines = ['hello', 'world']), + version: (this.version = 42), + ranges: {} + } + this.DocManager.updateDoc = sinon + .stub() + .yields(null, false, (this.rev = 5)) + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + return it('should return a modified status', function() { + return this.res.json + .calledWith({ modified: false, rev: this.rev }) + .should.equal(true) + }) + }) + + describe('when the doc lines are not provided', function() { + beforeEach(function() { + this.req.body = { version: 42, ranges: {} } + this.DocManager.updateDoc = sinon.stub().yields(null, false) + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + it('should not update the document', function() { + return this.DocManager.updateDoc.called.should.equal(false) + }) + + return it('should return a 400 (bad request) response', function() { + return this.res.send.calledWith(400).should.equal(true) + }) + }) + + describe('when the doc version are not provided', function() { + beforeEach(function() { + this.req.body = { version: 42, lines: ['hello world'] } + this.DocManager.updateDoc = sinon.stub().yields(null, false) + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + it('should not update the document', function() { + return this.DocManager.updateDoc.called.should.equal(false) + }) + + return it('should return a 400 (bad request) response', function() { + return this.res.send.calledWith(400).should.equal(true) + }) + }) + + describe('when the doc ranges is not provided', function() { + beforeEach(function() { + this.req.body = { lines: ['foo'], version: 42 } + this.DocManager.updateDoc = sinon.stub().yields(null, false) + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + it('should not update the document', function() { + return this.DocManager.updateDoc.called.should.equal(false) + }) + + return it('should return a 400 (bad request) response', function() { + return this.res.send.calledWith(400).should.equal(true) + }) + }) + + return describe('when the doc body is too large', function() { + beforeEach(function() { + this.req.body = { + lines: (this.lines = Array(2049).fill('a'.repeat(1024))), + version: (this.version = 42), + ranges: (this.ranges = { changes: 'mock' }) + } + return this.HttpController.updateDoc(this.req, this.res, this.next) + }) + + it('should return a 413 (too large) response', function() { + return sinon.assert.calledWith(this.res.status, 413) + }) + + return it('should report that the document body is too large', function() { + return sinon.assert.calledWith(this.res.send, 'document body too large') + }) + }) + }) + + describe('deleteDoc', function() { + beforeEach(function() { + this.req.params = { + project_id: this.project_id, + doc_id: this.doc_id + } + this.DocManager.deleteDoc = sinon.stub().callsArg(2) + return this.HttpController.deleteDoc(this.req, this.res, this.next) + }) + + it('should delete the document', function() { + return this.DocManager.deleteDoc + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + return it('should return a 204 (No Content)', function() { + return this.res.send.calledWith(204).should.equal(true) + }) + }) + + describe('archiveAllDocs', function() { + beforeEach(function() { + this.req.params = { project_id: this.project_id } + this.DocArchiveManager.archiveAllDocs = sinon.stub().callsArg(1) + return this.HttpController.archiveAllDocs(this.req, this.res, this.next) + }) + + it('should archive the project', function() { + return this.DocArchiveManager.archiveAllDocs + .calledWith(this.project_id) + .should.equal(true) + }) + + return it('should return a 204 (No Content)', function() { + return this.res.send.calledWith(204).should.equal(true) + }) + }) + + return describe('destroyAllDocs', function() { + beforeEach(function() { + this.req.params = { project_id: this.project_id } + this.DocArchiveManager.destroyAllDocs = sinon.stub().callsArg(1) + return this.HttpController.destroyAllDocs(this.req, this.res, this.next) + }) + + it('should destroy the docs', function() { + return sinon.assert.calledWith( + this.DocArchiveManager.destroyAllDocs, + this.project_id + ) + }) + + return it('should return 204', function() { + return sinon.assert.calledWith(this.res.send, 204) + }) + }) +}) diff --git a/services/docstore/test/unit/js/MongoManagerTests.js b/services/docstore/test/unit/js/MongoManagerTests.js new file mode 100644 index 0000000000..0af25c5ce7 --- /dev/null +++ b/services/docstore/test/unit/js/MongoManagerTests.js @@ -0,0 +1,293 @@ +/* eslint-disable + handle-callback-err, + no-return-assign, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const modulePath = require('path').join( + __dirname, + '../../../app/js/MongoManager' +) +const { ObjectId } = require('mongojs') +const { assert } = require('chai') + +describe('MongoManager', function() { + beforeEach(function() { + this.MongoManager = SandboxedModule.require(modulePath, { + requires: { + './mongojs': { + db: (this.db = { docs: {}, docOps: {} }), + ObjectId + }, + 'metrics-sharelatex': { timeAsyncMethod: sinon.stub() }, + 'logger-sharelatex': { log() {} } + } + }) + this.project_id = ObjectId().toString() + this.doc_id = ObjectId().toString() + this.callback = sinon.stub() + return (this.stubbedErr = new Error('hello world')) + }) + + describe('findDoc', function() { + beforeEach(function() { + this.doc = { name: 'mock-doc' } + this.db.docs.find = sinon.stub().callsArgWith(2, null, [this.doc]) + this.filter = { lines: true } + return this.MongoManager.findDoc( + this.project_id, + this.doc_id, + this.filter, + this.callback + ) + }) + + it('should find the doc', function() { + return this.db.docs.find + .calledWith( + { + _id: ObjectId(this.doc_id), + project_id: ObjectId(this.project_id) + }, + this.filter + ) + .should.equal(true) + }) + + return it('should call the callback with the doc', function() { + return this.callback.calledWith(null, this.doc).should.equal(true) + }) + }) + + describe('getProjectsDocs', function() { + beforeEach(function() { + this.filter = { lines: true } + this.doc1 = { name: 'mock-doc1' } + this.doc2 = { name: 'mock-doc2' } + this.doc3 = { name: 'mock-doc3' } + this.doc4 = { name: 'mock-doc4' } + return (this.db.docs.find = sinon + .stub() + .callsArgWith(2, null, [this.doc, this.doc3, this.doc4])) + }) + + describe('with included_deleted = false', function() { + beforeEach(function() { + return this.MongoManager.getProjectsDocs( + this.project_id, + { include_deleted: false }, + this.filter, + this.callback + ) + }) + + it('should find the non-deleted docs via the project_id', function() { + return this.db.docs.find + .calledWith( + { + project_id: ObjectId(this.project_id), + deleted: { $ne: true } + }, + this.filter + ) + .should.equal(true) + }) + + return it('should call the callback with the docs', function() { + return this.callback + .calledWith(null, [this.doc, this.doc3, this.doc4]) + .should.equal(true) + }) + }) + + return describe('with included_deleted = true', function() { + beforeEach(function() { + return this.MongoManager.getProjectsDocs( + this.project_id, + { include_deleted: true }, + this.filter, + this.callback + ) + }) + + it('should find all via the project_id', function() { + return this.db.docs.find + .calledWith( + { + project_id: ObjectId(this.project_id) + }, + this.filter + ) + .should.equal(true) + }) + + return it('should call the callback with the docs', function() { + return this.callback + .calledWith(null, [this.doc, this.doc3, this.doc4]) + .should.equal(true) + }) + }) + }) + + describe('upsertIntoDocCollection', function() { + beforeEach(function() { + this.db.docs.update = sinon.stub().callsArgWith(3, this.stubbedErr) + return (this.oldRev = 77) + }) + + it('should upsert the document', function(done) { + return this.MongoManager.upsertIntoDocCollection( + this.project_id, + this.doc_id, + { lines: this.lines }, + err => { + const args = this.db.docs.update.args[0] + assert.deepEqual(args[0], { _id: ObjectId(this.doc_id) }) + assert.equal(args[1].$set.lines, this.lines) + assert.equal(args[1].$inc.rev, 1) + assert.deepEqual(args[1].$set.project_id, ObjectId(this.project_id)) + return done() + } + ) + }) + + return it('should return the error', function(done) { + return this.MongoManager.upsertIntoDocCollection( + this.project_id, + this.doc_id, + { lines: this.lines }, + err => { + err.should.equal(this.stubbedErr) + return done() + } + ) + }) + }) + + describe('markDocAsDeleted', function() { + beforeEach(function() { + this.db.docs.update = sinon.stub().callsArgWith(2, this.stubbedErr) + return (this.oldRev = 77) + }) + + it('should process the update', function(done) { + return this.MongoManager.markDocAsDeleted( + this.project_id, + this.doc_id, + err => { + const args = this.db.docs.update.args[0] + assert.deepEqual(args[0], { + _id: ObjectId(this.doc_id), + project_id: ObjectId(this.project_id) + }) + assert.equal(args[1].$set.deleted, true) + return done() + } + ) + }) + + return it('should return the error', function(done) { + return this.MongoManager.markDocAsDeleted( + this.project_id, + this.doc_id, + err => { + err.should.equal(this.stubbedErr) + return done() + } + ) + }) + }) + + describe('destroyDoc', function() { + beforeEach(function(done) { + this.db.docs.remove = sinon.stub().yields() + this.db.docOps.remove = sinon.stub().yields() + return this.MongoManager.destroyDoc('123456789012', done) + }) + + it('should destroy the doc', function() { + return sinon.assert.calledWith(this.db.docs.remove, { + _id: ObjectId('123456789012') + }) + }) + + return it('should destroy the docOps', function() { + return sinon.assert.calledWith(this.db.docOps.remove, { + doc_id: ObjectId('123456789012') + }) + }) + }) + + describe('getDocVersion', function() { + describe('when the doc exists', function() { + beforeEach(function() { + this.doc = { version: (this.version = 42) } + this.db.docOps.find = sinon.stub().callsArgWith(2, null, [this.doc]) + return this.MongoManager.getDocVersion(this.doc_id, this.callback) + }) + + it('should look for the doc in the database', function() { + return this.db.docOps.find + .calledWith({ doc_id: ObjectId(this.doc_id) }, { version: 1 }) + .should.equal(true) + }) + + return it('should call the callback with the version', function() { + return this.callback.calledWith(null, this.version).should.equal(true) + }) + }) + + return describe("when the doc doesn't exist", function() { + beforeEach(function() { + this.db.docOps.find = sinon.stub().callsArgWith(2, null, []) + return this.MongoManager.getDocVersion(this.doc_id, this.callback) + }) + + return it('should call the callback with 0', function() { + return this.callback.calledWith(null, 0).should.equal(true) + }) + }) + }) + + return describe('setDocVersion', function() { + beforeEach(function() { + this.version = 42 + this.db.docOps.update = sinon.stub().callsArg(3) + return this.MongoManager.setDocVersion( + this.doc_id, + this.version, + this.callback + ) + }) + + it('should update the doc version', function() { + return this.db.docOps.update + .calledWith( + { + doc_id: ObjectId(this.doc_id) + }, + { + $set: { + version: this.version + } + }, + { + upsert: true + } + ) + .should.equal(true) + }) + + return it('should call the callback', function() { + return this.callback.called.should.equal(true) + }) + }) +}) diff --git a/services/docstore/test/unit/js/RangeManagerTests.js b/services/docstore/test/unit/js/RangeManagerTests.js new file mode 100644 index 0000000000..ff6d96a0d1 --- /dev/null +++ b/services/docstore/test/unit/js/RangeManagerTests.js @@ -0,0 +1,257 @@ +/* eslint-disable + camelcase, + no-return-assign, + no-unused-vars, +*/ +// TODO: This file was created by bulk-decaffeinate. +// Fix any style issues and re-enable lint. +/* + * decaffeinate suggestions: + * DS102: Remove unnecessary code created because of implicit returns + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md + */ +const SandboxedModule = require('sandboxed-module') +const sinon = require('sinon') +require('chai').should() +const { expect } = require('chai') +const modulePath = require('path').join( + __dirname, + '../../../app/js/RangeManager' +) +const { ObjectId } = require('mongojs') +const { assert } = require('chai') +const _ = require('underscore') + +describe('RangeManager', function() { + beforeEach(function() { + return (this.RangeManager = SandboxedModule.require(modulePath, { + requires: { + './mongojs': { + ObjectId + } + } + })) + }) + + describe('jsonRangesToMongo', function() { + it('should convert ObjectIds and dates to proper objects', function() { + const change_id = ObjectId().toString() + const comment_id = ObjectId().toString() + const user_id = ObjectId().toString() + const thread_id = ObjectId().toString() + const ts = new Date().toJSON() + return this.RangeManager.jsonRangesToMongo({ + changes: [ + { + id: change_id, + op: { i: 'foo', p: 3 }, + metadata: { + user_id, + ts + } + } + ], + comments: [ + { + id: comment_id, + op: { c: 'foo', p: 3, t: thread_id } + } + ] + }).should.deep.equal({ + changes: [ + { + id: ObjectId(change_id), + op: { i: 'foo', p: 3 }, + metadata: { + user_id: ObjectId(user_id), + ts: new Date(ts) + } + } + ], + comments: [ + { + id: ObjectId(comment_id), + op: { c: 'foo', p: 3, t: ObjectId(thread_id) } + } + ] + }) + }) + + it('should leave malformed ObjectIds as they are', function() { + const change_id = 'foo' + const comment_id = 'bar' + const user_id = 'baz' + return this.RangeManager.jsonRangesToMongo({ + changes: [ + { + id: change_id, + metadata: { + user_id + } + } + ], + comments: [ + { + id: comment_id + } + ] + }).should.deep.equal({ + changes: [ + { + id: change_id, + metadata: { + user_id + } + } + ], + comments: [ + { + id: comment_id + } + ] + }) + }) + + return it('should be consistent when transformed through json -> mongo -> json', function() { + const change_id = ObjectId().toString() + const comment_id = ObjectId().toString() + const user_id = ObjectId().toString() + const thread_id = ObjectId().toString() + const ts = new Date().toJSON() + const ranges1 = { + changes: [ + { + id: change_id, + op: { i: 'foo', p: 3 }, + metadata: { + user_id, + ts + } + } + ], + comments: [ + { + id: comment_id, + op: { c: 'foo', p: 3, t: thread_id } + } + ] + } + const ranges1_copy = JSON.parse(JSON.stringify(ranges1)) // jsonRangesToMongo modifies in place + const ranges2 = JSON.parse( + JSON.stringify(this.RangeManager.jsonRangesToMongo(ranges1_copy)) + ) + return ranges1.should.deep.equal(ranges2) + }) + }) + + return describe('shouldUpdateRanges', function() { + beforeEach(function() { + this.ranges = { + changes: [ + { + id: ObjectId(), + op: { i: 'foo', p: 3 }, + metadata: { + user_id: ObjectId(), + ts: new Date() + } + } + ], + comments: [ + { + id: ObjectId(), + op: { c: 'foo', p: 3, t: ObjectId() } + } + ] + } + return (this.ranges_copy = this.RangeManager.jsonRangesToMongo( + JSON.parse(JSON.stringify(this.ranges)) + )) + }) + + describe('with a blank new range', function() { + return it('should throw an error', function() { + return expect(() => { + return this.RangeManager.shouldUpdateRanges(this.ranges, null) + }).to.throw(Error) + }) + }) + + describe('with a blank old range', function() { + return it('should treat it like {}', function() { + this.RangeManager.shouldUpdateRanges(null, {}).should.equal(false) + return this.RangeManager.shouldUpdateRanges( + null, + this.ranges + ).should.equal(true) + }) + }) + + describe('with no changes', function() { + return it('should return false', function() { + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(false) + }) + }) + + return describe('with changes', function() { + it('should return true when the change id changes', function() { + this.ranges_copy.changes[0].id = ObjectId() + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + it('should return true when the change user id changes', function() { + this.ranges_copy.changes[0].metadata.user_id = ObjectId() + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + it('should return true when the change ts changes', function() { + this.ranges_copy.changes[0].metadata.ts = new Date(Date.now() + 1000) + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + it('should return true when the change op changes', function() { + this.ranges_copy.changes[0].op.i = 'bar' + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + it('should return true when the comment id changes', function() { + this.ranges_copy.comments[0].id = ObjectId() + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + it('should return true when the comment offset changes', function() { + this.ranges_copy.comments[0].op.p = 17 + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + + return it('should return true when the comment content changes', function() { + this.ranges_copy.comments[0].op.c = 'bar' + return this.RangeManager.shouldUpdateRanges( + this.ranges, + this.ranges_copy + ).should.equal(true) + }) + }) + }) +})