diff --git a/services/docstore/.github/dependabot.yml b/services/docstore/.github/dependabot.yml new file mode 100644 index 0000000000..c6f98d843d --- /dev/null +++ b/services/docstore/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + + pull-request-branch-name: + # Separate sections of the branch name with a hyphen + # Docker images use the branch name and do not support slashes in tags + # https://github.com/overleaf/google-ops/issues/822 + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#pull-request-branch-nameseparator + separator: "-" + + # Block informal upgrades -- security upgrades use a separate queue. + # https://docs.github.com/en/github/administering-a-repository/configuration-options-for-dependency-updates#open-pull-requests-limit + open-pull-requests-limit: 0 diff --git a/services/docstore/Dockerfile b/services/docstore/Dockerfile index b07f7117bc..78a715757d 100644 --- a/services/docstore/Dockerfile +++ b/services/docstore/Dockerfile @@ -15,8 +15,6 @@ RUN npm ci --quiet COPY . /app - - FROM base COPY --from=app /app /app diff --git a/services/docstore/Jenkinsfile b/services/docstore/Jenkinsfile deleted file mode 100644 index 15471816e2..0000000000 --- a/services/docstore/Jenkinsfile +++ /dev/null @@ -1,131 +0,0 @@ -String cron_string = BRANCH_NAME == "master" ? "@daily" : "" - -pipeline { - agent any - - environment { - GIT_PROJECT = "docstore" - JENKINS_WORKFLOW = "docstore-sharelatex" - TARGET_URL = "${env.JENKINS_URL}blue/organizations/jenkins/${JENKINS_WORKFLOW}/detail/$BRANCH_NAME/$BUILD_NUMBER/pipeline" - GIT_API_URL = "https://api.github.com/repos/overleaf/${GIT_PROJECT}/statuses/$GIT_COMMIT" - } - - triggers { - pollSCM('* * * * *') - cron(cron_string) - } - - stages { - - stage('Install') { - steps { - withCredentials([usernamePassword(credentialsId: 'GITHUB_INTEGRATION', usernameVariable: 'GH_AUTH_USERNAME', passwordVariable: 'GH_AUTH_PASSWORD')]) { - sh "curl $GIT_API_URL \ - --data '{ \ - \"state\" : \"pending\", \ - \"target_url\": \"$TARGET_URL\", \ - \"description\": \"Your build is underway\", \ - \"context\": \"ci/jenkins\" }' \ - -u $GH_AUTH_USERNAME:$GH_AUTH_PASSWORD" - } - } - } - - stage('Build') { - steps { - sh 'make build' - } - } - - 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' - } - } - - stage('Acceptance Tests') { - steps { - sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_acceptance' - } - } - - stage('Package and docker push') { - steps { - sh 'echo ${BUILD_NUMBER} > build_number.txt' - sh 'touch build.tar.gz' // Avoid tar warning about files changing during read - sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make tar' - - withCredentials([file(credentialsId: 'gcr.io_overleaf-ops', variable: 'DOCKER_REPO_KEY_PATH')]) { - sh 'docker login -u _json_key --password-stdin https://gcr.io/overleaf-ops < ${DOCKER_REPO_KEY_PATH}' - } - sh 'DOCKER_REPO=gcr.io/overleaf-ops make publish' - sh 'docker logout https://gcr.io/overleaf-ops' - - } - } - - stage('Publish to s3') { - steps { - sh 'echo ${BRANCH_NAME}-${BUILD_NUMBER} > build_number.txt' - withAWS(credentials:'S3_CI_BUILDS_AWS_KEYS', region:"${S3_REGION_BUILD_ARTEFACTS}") { - s3Upload(file:'build.tar.gz', bucket:"${S3_BUCKET_BUILD_ARTEFACTS}", path:"${JOB_NAME}/${BUILD_NUMBER}.tar.gz") - } - withAWS(credentials:'S3_CI_BUILDS_AWS_KEYS', region:"${S3_REGION_BUILD_ARTEFACTS}") { - // The deployment process uses this file to figure out the latest build - s3Upload(file:'build_number.txt', bucket:"${S3_BUCKET_BUILD_ARTEFACTS}", path:"${JOB_NAME}/latest") - } - } - } - } - - post { - always { - sh 'DOCKER_COMPOSE_FLAGS="-f docker-compose.ci.yml" make test_clean' - sh 'make clean' - } - - success { - withCredentials([usernamePassword(credentialsId: 'GITHUB_INTEGRATION', usernameVariable: 'GH_AUTH_USERNAME', passwordVariable: 'GH_AUTH_PASSWORD')]) { - sh "curl $GIT_API_URL \ - --data '{ \ - \"state\" : \"success\", \ - \"target_url\": \"$TARGET_URL\", \ - \"description\": \"Your build succeeded!\", \ - \"context\": \"ci/jenkins\" }' \ - -u $GH_AUTH_USERNAME:$GH_AUTH_PASSWORD" - } - } - - failure { - mail(from: "${EMAIL_ALERT_FROM}", - to: "${EMAIL_ALERT_TO}", - subject: "Jenkins build failed: ${JOB_NAME}:${BUILD_NUMBER}", - body: "Build: ${BUILD_URL}") - withCredentials([usernamePassword(credentialsId: 'GITHUB_INTEGRATION', usernameVariable: 'GH_AUTH_USERNAME', passwordVariable: 'GH_AUTH_PASSWORD')]) { - sh "curl $GIT_API_URL \ - --data '{ \ - \"state\" : \"failure\", \ - \"target_url\": \"$TARGET_URL\", \ - \"description\": \"Your build failed\", \ - \"context\": \"ci/jenkins\" }' \ - -u $GH_AUTH_USERNAME:$GH_AUTH_PASSWORD" - } - } - } - - // The options directive is for configuration that applies to the whole job. - options { - // we'd like to make sure remove old builds, so we don't fill up our storage! - buildDiscarder(logRotator(numToKeepStr:'50')) - - // And we'd really like to be sure that this build doesn't hang forever, so let's time it out after: - timeout(time: 30, unit: 'MINUTES') - } -} diff --git a/services/docstore/Makefile b/services/docstore/Makefile index 7bd0895b81..77f11edeea 100644 --- a/services/docstore/Makefile +++ b/services/docstore/Makefile @@ -25,13 +25,13 @@ clean: docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) format: - $(DOCKER_COMPOSE) run --rm test_unit npm run format + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent format format_fix: - $(DOCKER_COMPOSE) run --rm test_unit npm run format:fix + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent format:fix lint: - $(DOCKER_COMPOSE) run --rm test_unit npm run lint + $(DOCKER_COMPOSE) run --rm test_unit npm run --silent lint test: format lint test_unit test_acceptance diff --git a/services/docstore/app/js/DocArchiveManager.js b/services/docstore/app/js/DocArchiveManager.js index f763d081ee..d564113c9b 100644 --- a/services/docstore/app/js/DocArchiveManager.js +++ b/services/docstore/app/js/DocArchiveManager.js @@ -1,295 +1,180 @@ -/* 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 { callbackify } = require('util') +const MongoManager = require('./MongoManager').promises 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 Streamifier = require('streamifier') const RangeManager = require('./RangeManager') -const thirtySeconds = 30 * 1000 +const PersistorManager = require('./PersistorManager') +const asyncPool = require('tiny-async-pool') -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) - } - ) - }, +const PARALLEL_JOBS = 5 - 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}` - } +module.exports = { + archiveAllDocs: callbackify(archiveAllDocs), + archiveDoc: callbackify(archiveDoc), + unArchiveAllDocs: callbackify(unArchiveAllDocs), + unarchiveDoc: callbackify(unarchiveDoc), + destroyAllDocs: callbackify(destroyAllDocs), + destroyDoc: callbackify(destroyDoc), + promises: { + archiveAllDocs, + archiveDoc, + unArchiveAllDocs, + unarchiveDoc, + destroyAllDocs, + destroyDoc } } + +async function archiveAllDocs(projectId) { + const docs = await MongoManager.getProjectsDocs( + projectId, + { include_deleted: true }, + { lines: true, ranges: true, rev: true, inS3: true } + ) + + if (!docs) { + throw new Errors.NotFoundError(`No docs for project ${projectId}`) + } + + const docsToArchive = docs.filter((doc) => !doc.inS3) + if (docsToArchive.length) { + await asyncPool(PARALLEL_JOBS, docsToArchive, (doc) => + archiveDoc(projectId, doc) + ) + } +} + +async function archiveDoc(projectId, doc) { + logger.log( + { project_id: projectId, doc_id: doc._id }, + 'sending doc to persistor' + ) + const key = `${projectId}/${doc._id}` + + if (doc.lines == null) { + throw new Error('doc has no lines') + } + + const json = JSON.stringify({ + lines: doc.lines, + ranges: doc.ranges, + schema_v: 1 + }) + + // this should never happen, but protects against memory-corruption errors that + // have happened in the past + if (json.indexOf('\u0000') > -1) { + const error = new Error('null bytes detected') + logger.err({ err: error, doc }, error.message) + throw error + } + + const md5 = crypto.createHash('md5').update(json).digest('hex') + const stream = Streamifier.createReadStream(json) + await PersistorManager.sendStream(settings.docstore.bucket, key, stream, { + sourceMd5: md5 + }) + await MongoManager.markDocAsArchived(doc._id, doc.rev) +} + +async function unArchiveAllDocs(projectId) { + const docs = await MongoManager.getArchivedProjectDocs(projectId) + if (!docs) { + throw new Errors.NotFoundError(`No docs for project ${projectId}`) + } + if (!docs.length) { + // asyncPool will throw an error with an empty array + return + } + await asyncPool(PARALLEL_JOBS, docs, (doc) => + unarchiveDoc(projectId, doc._id) + ) +} + +async function unarchiveDoc(projectId, docId) { + logger.log( + { project_id: projectId, doc_id: docId }, + 'getting doc from persistor' + ) + const key = `${projectId}/${docId}` + const sourceMd5 = await PersistorManager.getObjectMd5Hash( + settings.docstore.bucket, + key + ) + const stream = await PersistorManager.getObjectStream( + settings.docstore.bucket, + key + ) + stream.resume() + const json = await _streamToString(stream) + const md5 = crypto.createHash('md5').update(json).digest('hex') + if (sourceMd5 !== md5) { + throw new Errors.Md5MismatchError('md5 mismatch when downloading doc', { + key, + sourceMd5, + md5 + }) + } + + const doc = JSON.parse(json) + + const mongoDoc = {} + if (doc.schema_v === 1 && doc.lines != null) { + mongoDoc.lines = doc.lines + if (doc.ranges != null) { + mongoDoc.ranges = RangeManager.jsonRangesToMongo(doc.ranges) + } + } else if (Array.isArray(doc)) { + mongoDoc.lines = doc + } else { + throw new Error("I don't understand the doc format in s3") + } + await MongoManager.upsertIntoDocCollection(projectId, docId, mongoDoc) + await PersistorManager.deleteObject(settings.docstore.bucket, key) +} + +async function destroyAllDocs(projectId) { + const docs = await MongoManager.getProjectsDocs( + projectId, + { include_deleted: true }, + { _id: 1 } + ) + if (docs && docs.length) { + await asyncPool(PARALLEL_JOBS, docs, (doc) => + destroyDoc(projectId, doc._id) + ) + } +} + +async function destroyDoc(projectId, docId) { + logger.log( + { project_id: projectId, doc_id: docId }, + 'removing doc from mongo and persistor' + ) + const doc = await MongoManager.findDoc(projectId, docId, { + inS3: 1 + }) + if (!doc) { + throw new Errors.NotFoundError('Doc not found in Mongo') + } + + if (doc.inS3) { + await PersistorManager.deleteObject( + settings.docstore.bucket, + `${projectId}/${docId}` + ) + } + await MongoManager.destroyDoc(docId) +} + +async function _streamToString(stream) { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', (chunk) => chunks.push(chunk)) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) +} diff --git a/services/docstore/app/js/Errors.js b/services/docstore/app/js/Errors.js index a950b6de20..6a74485494 100644 --- a/services/docstore/app/js/Errors.js +++ b/services/docstore/app/js/Errors.js @@ -1,16 +1,10 @@ -/* 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 +// import Errors from object-persistor to pass instanceof checks +const OError = require('@overleaf/o-error') +const { Errors } = require('@overleaf/object-persistor') -module.exports = Errors = { NotFoundError } +class Md5MismatchError extends OError {} + +module.exports = { + Md5MismatchError, + ...Errors +} diff --git a/services/docstore/app/js/MongoManager.js b/services/docstore/app/js/MongoManager.js index b3e90c63c9..b6e90a7ad3 100644 --- a/services/docstore/app/js/MongoManager.js +++ b/services/docstore/app/js/MongoManager.js @@ -14,6 +14,7 @@ let MongoManager const { db, ObjectId } = require('./mongojs') const logger = require('logger-sharelatex') const metrics = require('metrics-sharelatex') +const { promisify } = require('util') module.exports = MongoManager = { findDoc(project_id, doc_id, filter, callback) { @@ -162,14 +163,11 @@ module.exports = MongoManager = { ) } } -;[ - 'findDoc', - 'getProjectsDocs', - 'getArchivedProjectDocs', - 'upsertIntoDocCollection', - 'markDocAsArchived', - 'getDocVersion', - 'setDocVersion' -].map((method) => + +const methods = Object.getOwnPropertyNames(MongoManager) + +module.exports.promises = {} +for (const method of methods) { metrics.timeAsyncMethod(MongoManager, method, 'mongo.MongoManager', logger) -) + module.exports.promises[method] = promisify(module.exports[method]) +} diff --git a/services/docstore/app/js/PersistorManager.js b/services/docstore/app/js/PersistorManager.js new file mode 100644 index 0000000000..b3d194a210 --- /dev/null +++ b/services/docstore/app/js/PersistorManager.js @@ -0,0 +1,9 @@ +const settings = require('settings-sharelatex') + +const persistorSettings = settings.docstore +persistorSettings.Metrics = require('metrics-sharelatex') + +const ObjectPersistor = require('@overleaf/object-persistor') +const persistor = ObjectPersistor(persistorSettings) + +module.exports = persistor diff --git a/services/docstore/buildscript.txt b/services/docstore/buildscript.txt index 48c7852d74..d4e653d97b 100644 --- a/services/docstore/buildscript.txt +++ b/services/docstore/buildscript.txt @@ -1,10 +1,8 @@ docstore ---acceptance-creds=None ---dependencies=mongo +--dependencies=mongo,gcs --docker-repos=gcr.io/overleaf-ops --env-add= --env-pass-through= ---language=es --node-version=10.21.0 --public-repo=True ---script-version=2.3.0 +--script-version=3.2.0 diff --git a/services/docstore/config/settings.defaults.js b/services/docstore/config/settings.defaults.js index d22ff8fee1..c538b79045 100644 --- a/services/docstore/config/settings.defaults.js +++ b/services/docstore/config/settings.defaults.js @@ -17,8 +17,15 @@ const Settings = { mongo: {}, docstore: { + backend: process.env.BACKEND || 's3', healthCheck: { project_id: process.env.HEALTH_CHECK_PROJECT_ID + }, + bucket: process.env.BUCKET_NAME || process.env.AWS_BUCKET || 'bucket', + gcs: { + unlockBeforeDelete: process.env.GCS_UNLOCK_BEFORE_DELETE === 'true', + deletedBucketSuffix: process.env.GCS_DELETED_BUCKET_SUFFIX, + deleteConcurrency: parseInt(process.env.GCS_DELETE_CONCURRENCY) || 50 } }, @@ -41,7 +48,28 @@ if ( Settings.docstore.s3 = { key: process.env.AWS_ACCESS_KEY_ID, secret: process.env.AWS_SECRET_ACCESS_KEY, - bucket: process.env.AWS_BUCKET + bucket: process.env.AWS_BUCKET, + endpoint: process.env.AWS_S3_ENDPOINT, + pathStyle: process.env.AWS_S3_PATH_STYLE, + partSize: parseInt(process.env.AWS_S3_PARTSIZE) || 100 * 1024 * 1024 + } +} + +if (process.env.GCS_API_ENDPOINT) { + Settings.docstore.gcs.endpoint = { + apiEndpoint: process.env.GCS_API_ENDPOINT, + apiScheme: process.env.GCS_API_SCHEME, + projectId: process.env.GCS_PROJECT_ID + } +} + +if (process.env.FALLBACK_BACKEND) { + Settings.docstore.fallback = { + backend: process.env.FALLBACK_BACKEND, + // mapping of bucket names on the fallback, to bucket names on the primary. + // e.g. { myS3UserFilesBucketName: 'myGoogleUserFilesBucketName' } + buckets: JSON.parse(process.env.FALLBACK_BUCKET_MAPPING || '{}'), + copyOnMiss: process.env.COPY_ON_MISS === 'true' } } diff --git a/services/docstore/docker-compose.ci.yml b/services/docstore/docker-compose.ci.yml index 53da6509c7..d2ad377bcd 100644 --- a/services/docstore/docker-compose.ci.yml +++ b/services/docstore/docker-compose.ci.yml @@ -11,6 +11,7 @@ services: command: npm run test:unit:_run environment: NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" test_acceptance: @@ -24,11 +25,18 @@ services: REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres + GCS_API_ENDPOINT: gcs:9090 + GCS_API_SCHEME: http + GCS_PROJECT_ID: fake + STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1 MOCHA_GREP: ${MOCHA_GREP} NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" depends_on: mongo: condition: service_healthy + gcs: + condition: service_healthy user: node command: npm run test:acceptance:_run @@ -42,3 +50,7 @@ services: user: root mongo: image: mongo:3.6 + gcs: + build: + context: test/acceptance/deps + dockerfile: Dockerfile.fake-gcs diff --git a/services/docstore/docker-compose.yml b/services/docstore/docker-compose.yml index 87ea601dd0..40e0d9af62 100644 --- a/services/docstore/docker-compose.yml +++ b/services/docstore/docker-compose.yml @@ -13,7 +13,8 @@ services: environment: MOCHA_GREP: ${MOCHA_GREP} NODE_ENV: test - command: npm run test:unit + NODE_OPTIONS: "--unhandled-rejections=strict" + command: npm run --silent test:unit user: node test_acceptance: @@ -29,15 +30,26 @@ services: REDIS_HOST: redis MONGO_HOST: mongo POSTGRES_HOST: postgres + GCS_API_ENDPOINT: gcs:9090 + GCS_API_SCHEME: http + GCS_PROJECT_ID: fake + STORAGE_EMULATOR_HOST: http://gcs:9090/storage/v1 MOCHA_GREP: ${MOCHA_GREP} LOG_LEVEL: ERROR NODE_ENV: test + NODE_OPTIONS: "--unhandled-rejections=strict" user: node depends_on: mongo: condition: service_healthy - command: npm run test:acceptance + gcs: + condition: service_healthy + command: npm run --silent test:acceptance mongo: image: mongo:3.6 + gcs: + build: + context: test/acceptance/deps + dockerfile: Dockerfile.fake-gcs diff --git a/services/docstore/nodemon.json b/services/docstore/nodemon.json index 5826281b84..e3e8817d90 100644 --- a/services/docstore/nodemon.json +++ b/services/docstore/nodemon.json @@ -8,7 +8,6 @@ "execMap": { "js": "npm run start" }, - "watch": [ "app/js/", "app.js", diff --git a/services/docstore/package-lock.json b/services/docstore/package-lock.json index b69cba7ca3..9afa158ecb 100644 --- a/services/docstore/package-lock.json +++ b/services/docstore/package-lock.json @@ -77,30 +77,22 @@ "integrity": "sha512-E6SpIDJZ0cZAKoCNk+qSDd0ChfTnpiJN9FfNf3RZ20dzwA2vL2oq5IX1XTVT+4vDmRlta2nGk5HGMMskJAR+4A==" }, "@babel/runtime": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.8.4.tgz", - "integrity": "sha512-neAp3zt80trRVBI1x0azq6c57aNBqYZH8KhMm3TaB7wEI5Q4A2SHfBHE8w9gOhI/lrqxtEbXZgQIrHP+wvSGwQ==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.5.tgz", + "integrity": "sha512-otddXKhdNn7d0ptoFRHtMLa8LqDxLYwTjB4nYgM1yy5N6gU/MUf8zqyyLltCH3yAVitBzmwK4us+DD0l/MauAg==", "dev": true, "requires": { - "regenerator-runtime": "^0.13.2" + "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.9.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.9.2.tgz", - "integrity": "sha512-HHxmgxbIzOfFlZ+tdeRKtaxWOMUoCG5Mu3wKeUmOxjYrwb3AAHgnmtCUbPPK11/raIWLIBK250t8E2BPO0p7jA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.5.tgz", + "integrity": "sha512-RMafpmrNB5E/bwdSphLr8a8++9TosnyJp98RZzI6VOx2R2CCMpsXXXRvmI700O9oEKpXdZat6oEK68/F0zjd4A==", "dev": true, "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" - }, - "dependencies": { - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - } } }, "@babel/template": { @@ -168,6 +160,24 @@ "google-auth-library": "^5.5.0", "retry-request": "^4.0.0", "teeny-request": "^6.0.0" + }, + "dependencies": { + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + } } }, "@google-cloud/debug-agent": { @@ -385,6 +395,22 @@ "type-fest": "^0.12.0" }, "dependencies": { + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + }, "type-fest": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.12.0.tgz", @@ -393,12 +419,12 @@ } }, "@google-cloud/logging-bunyan": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-2.0.3.tgz", - "integrity": "sha512-8n9MwsCRd4v8WZg17+d3m7qInud7lYTm5rpwXHY0/lzWEJYjeiztT09BiCYh56EEhHr+ynymJnzUDZKazkywlg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-bunyan/-/logging-bunyan-3.0.0.tgz", + "integrity": "sha512-ZLVXEejNQ27ktGcA3S/sd7GPefp7kywbn+/KoBajdb1Syqcmtc98jhXpYQBXVtNP2065iyu77s4SBaiYFbTC5A==", "requires": { "@google-cloud/logging": "^7.0.0", - "google-auth-library": "^5.0.0" + "google-auth-library": "^6.0.0" } }, "@google-cloud/paginator": { @@ -461,6 +487,11 @@ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-0.3.1.tgz", "integrity": "sha512-QzB0/IMvB0eFxFK7Eqh+bfC8NLv3E9ScjWQrPOk6GgfNroxcVITdTlT8NRsRrcp5+QQJVPLkRqKG0PUdaWXmHw==" }, + "@types/node": { + "version": "10.17.26", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.26.tgz", + "integrity": "sha512-myMwkO2Cr82kirHY8uknNRHEVtn0wV3DTQfkrjx17jmkstDRZ24gNUdl8AHXVyVclTYI/bNjgTPTAWvWLqXqkw==" + }, "agent-base": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", @@ -587,6 +618,26 @@ "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.5.tgz", "integrity": "sha512-vFMQIWt+J/7FLNyKouZ9TazT74PRV3wgv9UT4cRjC8BffxFbKXkgIWR42URCPSnHm/QDz6BOlb2Q0U4+VQT67Q==" }, + "protobufjs": { + "version": "6.8.9", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.9.tgz", + "integrity": "sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.0", + "@types/node": "^10.1.0", + "long": "^4.0.0" + } + }, "teeny-request": { "version": "3.11.3", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz", @@ -609,6 +660,140 @@ "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-1.0.4.tgz", "integrity": "sha512-VccZDcOql77obTnFh0TbNED/6ZbbmHDf8UMNnzO1d5g9V0Htfm4k5cllY8P1tJsRKC3zWYGRLaViiupcgVjBoQ==" }, + "@google-cloud/storage": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.1.2.tgz", + "integrity": "sha512-j2blsBVv6Tt5Z7ff6kOSIg5zVQPdlcTQh/4zMb9h7xMj4ekwndQA60le8c1KEa+Y6SR3EM6ER2AvKYK53P7vdQ==", + "requires": { + "@google-cloud/common": "^3.0.0", + "@google-cloud/paginator": "^3.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.0", + "compressible": "^2.0.12", + "concat-stream": "^2.0.0", + "date-and-time": "^0.13.0", + "duplexify": "^3.5.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "gcs-resumable-upload": "^3.0.0", + "hash-stream-validation": "^0.2.2", + "mime": "^2.2.0", + "mime-types": "^2.0.8", + "onetime": "^5.1.0", + "p-limit": "^3.0.1", + "pumpify": "^2.0.0", + "readable-stream": "^3.4.0", + "snakeize": "^0.1.0", + "stream-events": "^1.0.1", + "through2": "^4.0.0", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "@google-cloud/common": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-3.3.2.tgz", + "integrity": "sha512-W7JRLBEJWYtZQQuGQX06U6GBOSLrSrlvZxv6kGNwJtFrusu6AVgZltQ9Pajuz9Dh9aSXy9aTnBcyxn2/O0EGUw==", + "requires": { + "@google-cloud/projectify": "^2.0.0", + "@google-cloud/promisify": "^2.0.0", + "arrify": "^2.0.1", + "duplexify": "^4.1.1", + "ent": "^2.2.0", + "extend": "^3.0.2", + "google-auth-library": "^6.0.0", + "retry-request": "^4.1.1", + "teeny-request": "^7.0.0" + }, + "dependencies": { + "duplexify": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.1.tgz", + "integrity": "sha512-DY3xVEmVHTv1wSzKNbwoU6nVjzI369Y6sPoqfYr0/xlx3IdX2n94xIszTcjPO8W8ZIv0Wb0PXNcjuZyT4wiICA==", + "requires": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.0" + } + } + } + }, + "@google-cloud/paginator": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-3.0.2.tgz", + "integrity": "sha512-kXK+Dbz4pNvv8bKU80Aw5HsIdgOe0WuMTd8/fI6tkANUxzvJOVJQQRsWVqcHSWK2RXHPTA9WBniUCwY6gAJDXw==", + "requires": { + "arrify": "^2.0.0", + "extend": "^3.0.2" + } + }, + "@google-cloud/projectify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.0.1.tgz", + "integrity": "sha512-ZDG38U/Yy6Zr21LaR3BTiiLtpJl6RkPS/JwoRT453G+6Q1DhlV0waNf8Lfu+YVYGIIxgKnLayJRfYlFJfiI8iQ==" + }, + "@google-cloud/promisify": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-2.0.2.tgz", + "integrity": "sha512-EvuabjzzZ9E2+OaYf+7P9OAiiwbTxKYL0oGLnREQd+Su2NTQBpomkdlkBowFvyWsaV0d1sSGxrKpSNcrhPqbxg==" + }, + "gaxios": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.4.tgz", + "integrity": "sha512-97NmFuMETFQh6gqPUxkqjxRMjmY8aRKRMphIkgO/b90AbCt5wAVuXsp8oWjIXlLN2pIK/fsXD8edcM7ULkFMLg==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "p-limit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", + "requires": { + "p-try": "^2.0.0" + } + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "teeny-request": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-7.0.0.tgz", + "integrity": "sha512-kWD3sdGmIix6w7c8ZdVKxWq+3YwVPGWz+Mq0wRZXayEKY/YHb63b8uphfBzcFDmyq8frD9+UTc3wLyOhltRbtg==", + "requires": { + "http-proxy-agent": "^4.0.0", + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.2.0", + "stream-events": "^1.0.5", + "uuid": "^8.0.0" + } + }, + "through2": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-4.0.2.tgz", + "integrity": "sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==", + "requires": { + "readable-stream": "3" + } + }, + "uuid": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.2.0.tgz", + "integrity": "sha512-CYpGiFTUrmI6OBMkAdjSDM0k5h8SkkiTP4WAjQgDgNB1S3Ou9VBEvr6q0Kv2H1mMk7IWfxYGpMH5sd5AvcIV2Q==" + } + } + }, "@google-cloud/trace-agent": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@google-cloud/trace-agent/-/trace-agent-3.6.1.tgz", @@ -850,6 +1035,41 @@ "resolved": "https://registry.npmjs.org/@overleaf/o-error/-/o-error-3.0.0.tgz", "integrity": "sha512-LsM2s6Iy9G97ktPo0ys4VxtI/m3ahc1ZHwjo5XnhXtjeIkkkVAehsrcRRoV/yWepPjymB0oZonhcfojpjYR/tg==" }, + "@overleaf/object-persistor": { + "version": "git+https://github.com/overleaf/object-persistor.git#4ca62157a2beb747e9a56da3ce1569124b90378a", + "from": "git+https://github.com/overleaf/object-persistor.git", + "requires": { + "@google-cloud/storage": "^5.1.2", + "@overleaf/o-error": "^3.0.0", + "aws-sdk": "^2.718.0", + "fast-crc32c": "^2.0.0", + "glob": "^7.1.6", + "logger-sharelatex": "^2.1.1", + "node-uuid": "^1.4.8", + "range-parser": "^1.2.1", + "tiny-async-pool": "^1.1.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "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" + } + }, + "node-uuid": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz", + "integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=" + } + } + }, "@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -910,18 +1130,18 @@ "integrity": "sha512-lu8BpxjAtRCAo5ifytTpCPCj99LF7o/2Myn+NXyNCBqvPYn7Pjd76AMmUB5l7XF1U6t0hcWrlEM5ESufW7wAeA==" }, "@sinonjs/commons": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.7.1.tgz", - "integrity": "sha512-Debi3Baff1Qu1Unc3mjJ96MgpbwTn43S1+9yJ0llWygPwDNu2aaWBD6yc9y/Z8XDRNhx7U+u2UDg2OGQXkclUQ==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.0.tgz", - "integrity": "sha512-atR1J/jRXvQAb47gfzSK8zavXy7BcpnYq21ALon0U99etu99vsir0trzIO3wpeLtW+LLVY6X7EkfVTbjGSH8Ww==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", + "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0" @@ -1003,20 +1223,26 @@ "integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA==", "dev": true }, + "@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, "@types/long": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, "@types/node": { - "version": "10.17.17", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.17.tgz", - "integrity": "sha512-gpNnRnZP3VWzzj5k3qrpRC6Rk3H/uclhAVo1aIvwzK5p5cOrs9yEyQ8H/HBsBY0u5rrWxXEiVPQ0dEB6pkjE8Q==" + "version": "13.13.13", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.13.tgz", + "integrity": "sha512-UfvBE9oRCAJVzfR+3eWm/sdLFe/qroAPEXP3GPJ1SehQiEVgZT6NQZWYbPMiJ3UdcKM06v4j+S1lTcdWCmw+3g==" }, "@types/request": { - "version": "2.48.4", - "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", - "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", "requires": { "@types/caseless": "*", "@types/node": "*", @@ -1042,9 +1268,9 @@ "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==" }, "@types/tough-cookie": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.6.tgz", - "integrity": "sha512-wHNBMnkoEBiRAd3s8KTKwIuO9biFtTf0LehITzBhSco+HQI0xkXZbLOD55SW3Aqw3oUkHstkm5SPv58yaAdFPQ==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" }, "@typescript-eslint/experimental-utils": { "version": "1.13.0", @@ -1223,13 +1449,13 @@ } }, "aria-query": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-3.0.0.tgz", - "integrity": "sha512-majUxHgLehQTeSA+hClx+DY09OVUqG3GtezWkF1krgLGNdlDu9l9V8DaqNMWbq4Eddc8wsyDA0hpDUtnYxQEXw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-4.2.2.tgz", + "integrity": "sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA==", "dev": true, "requires": { - "ast-types-flow": "0.0.7", - "commander": "^2.11.0" + "@babel/runtime": "^7.10.2", + "@babel/runtime-corejs3": "^7.10.2" } }, "array-flatten": { @@ -1258,6 +1484,17 @@ "es-abstract": "^1.17.0-next.1" } }, + "array.prototype.flatmap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz", + "integrity": "sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1", + "function-bind": "^1.1.1" + } + }, "arrify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", @@ -1309,6 +1546,29 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "aws-sdk": { + "version": "2.729.0", + "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.729.0.tgz", + "integrity": "sha512-x5q/+9wM37tBYCmYNGMj+aU9qeXqhbUH0Dz3wlSpAW55+N4NwlBbpqhyCV3oF9bePEJtEnxLGWNXpb9BNWNhew==", + "requires": { + "buffer": "4.9.2", + "events": "1.1.1", + "ieee754": "1.1.13", + "jmespath": "0.15.0", + "querystring": "0.2.0", + "sax": "1.2.1", + "url": "0.10.3", + "uuid": "3.3.2", + "xml2js": "0.4.19" + }, + "dependencies": { + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -1321,6 +1581,12 @@ "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", "dev": true }, + "axe-core": { + "version": "3.5.5", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.5.5.tgz", + "integrity": "sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q==", + "dev": true + }, "axios": { "version": "0.18.1", "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", @@ -1331,9 +1597,9 @@ } }, "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", + "integrity": "sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA==", "dev": true }, "babel-eslint": { @@ -1386,9 +1652,9 @@ "integrity": "sha512-S4XzBk5sMB+Rcb/LNcpzXr57VRTxgAvaAEDAl1AwRx27j00hT84O6OkteE7u8UB3NuaaygCRrEpqox4uDOrbdQ==" }, "binary-extensions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", - "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", + "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", "dev": true }, "bindings": { @@ -1465,23 +1731,39 @@ "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.3.tgz", "integrity": "sha512-TdiJxMVnodVS7r0BdL42y/pqC9cL2iKynVwA0Ho3qbsQYr428veL3l7BQyuqiw+Q5SqqoT0m4srSY/BlZ9AxXg==" }, + "buffer": { + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4", + "isarray": "^1.0.0" + } + }, "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": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + }, "builtin-modules": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.1.0.tgz", "integrity": "sha512-k0KL0aWZuBt2lrxrcASWDfwOLMnodeQjodT/1SxEQAXsHANgo6ZC/VEaSEHCXt7aSTZ4/4H5LKa+tBXmW7Vtvw==" }, "bunyan": { - "version": "1.8.12", - "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz", - "integrity": "sha512-dmDUbGHeGcvCDLRFOscZkwx1ZO/aFz3bJOCi5nCgzdhFGPxwK+y5AcDBnqagNGlJZ7lje/l6JUEz9mQcutttdg==", + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz", + "integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==", + "dev": true, "requires": { "dtrace-provider": "~0.8", - "moment": "^2.10.6", + "moment": "^2.19.3", "mv": "~2", "safe-json-stringify": "~1" } @@ -1544,6 +1826,15 @@ "type-detect": "^4.0.5" } }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -1575,6 +1866,11 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "dev": true }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==" + }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -1670,12 +1966,6 @@ "delayed-stream": "~1.0.0" } }, - "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", @@ -1687,11 +1977,88 @@ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==" }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + }, + "dependencies": { + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + } + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "concat-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz", + "integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==", + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.0.2", + "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + } + } + }, + "configstore": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-5.0.1.tgz", + "integrity": "sha512-aMKprgk5YhBNyH25hj8wGt2+D52Sw1DRRIzqBwLp2Ya9mFmY8KPvvtvmna8SxVR9JMZ4kzMD68N22vlaRpkeFA==", + "requires": { + "dot-prop": "^5.2.0", + "graceful-fs": "^4.1.2", + "make-dir": "^3.0.0", + "unique-string": "^2.0.0", + "write-file-atomic": "^3.0.0", + "xdg-basedir": "^4.0.0" + }, + "dependencies": { + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "requires": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + } + } + }, "console-log-level": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/console-log-level/-/console-log-level-1.4.1.tgz", @@ -1750,9 +2117,9 @@ "dev": true }, "core-js-pure": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.4.tgz", - "integrity": "sha512-epIhRLkXdgv32xIUFaaAry2wdxZYBi6bgM7cB136dzzXXa+dFyRLTZeLUJxnd8ShrmyVXBub63n2NHo2JAt8Cw==", + "version": "3.6.5", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", + "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", "dev": true }, "core-util-is": { @@ -1785,10 +2152,20 @@ "which": "^1.2.9" } }, + "crypt": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", + "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==" + }, + "crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==" + }, "d64": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/d64/-/d64-1.0.0.tgz", - "integrity": "sha1-QAKofoUMv8n52XBrYPymE6MzbpA=" + "integrity": "sha512-5eNy3WZziVYnrogqgXhcdEmqcDB2IHurTqLcrgssJsfkMVCUoUaZpK6cJjxxvLV2dUm5SuJMNcYfVGoin9UIRw==" }, "damerau-levenshtein": { "version": "1.0.6", @@ -1813,6 +2190,11 @@ } } }, + "date-and-time": { + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-0.13.1.tgz", + "integrity": "sha512-/Uge9DJAT+s+oAcDxtBhyR8+sKjUnZbYmyhbmWjTHNtX7B7oWD8YyYdeXcBRbwSj6hVvj+IQegJam7m7czhbFw==" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2001,22 +2383,22 @@ } }, "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==", + "version": "1.17.6", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", + "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", "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", + "is-callable": "^1.2.0", + "is-regex": "^1.1.0", "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" + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" } }, "es-to-primitive": { @@ -2136,9 +2518,9 @@ } }, "eslint-config-prettier": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.10.1.tgz", - "integrity": "sha512-svTy6zh1ecQojvpbJSgH3aei/Rt7C6i090l5f2WQ4aB05lYHeZIR1qL4wZyyILTbtmnbHP5Yn8MrsOJMGa8RkQ==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.11.0.tgz", + "integrity": "sha512-oB8cpLWSAjOVFEJhhyMZh6NOEOtBVziaqdDQ86+qhDHFbZXoRTM7pNSvFRfW/W/L/LrQ38C99J5CGuRBBzBsdA==", "dev": true, "requires": { "get-stdin": "^6.0.0" @@ -2166,28 +2548,19 @@ } }, "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==", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz", + "integrity": "sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA==", "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==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -2196,42 +2569,31 @@ } }, "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==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", "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==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-expect/-/eslint-plugin-chai-expect-2.2.0.tgz", + "integrity": "sha512-ExTJKhgeYMfY8wDj3UiZmgpMKJOUHGNHmWMlxT49JUDB1vTnw0sSNfXJSxnX+LcebyBD/gudXzjzD136WqPJrQ==", "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==", + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-chai-friendly/-/eslint-plugin-chai-friendly-0.6.0.tgz", + "integrity": "sha512-Uvvv1gkbRGp/qfN15B0kQyQWg+oFA8buDSqrwmW3egNSk/FpqH2MjQqKOuKwmEL6w4QIQrIjDp+gg6kGGmD3oQ==", "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==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "requires": { "eslint-utils": "^2.0.0", @@ -2239,51 +2601,43 @@ }, "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==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "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==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", + "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", "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==", + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz", + "integrity": "sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg==", "dev": true, "requires": { - "array-includes": "^3.0.3", - "array.prototype.flat": "^1.2.1", + "array-includes": "^3.1.1", + "array.prototype.flat": "^1.2.3", "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", + "eslint-import-resolver-node": "^0.3.3", + "eslint-module-utils": "^2.6.0", "has": "^1.0.3", "minimatch": "^3.0.4", - "object.values": "^1.1.0", + "object.values": "^1.1.1", "read-pkg-up": "^2.0.0", - "resolve": "^1.12.0" + "resolve": "^1.17.0", + "tsconfig-paths": "^3.9.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", @@ -2295,9 +2649,9 @@ } }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" @@ -2306,26 +2660,28 @@ } }, "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==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz", + "integrity": "sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g==", "dev": true, "requires": { - "@babel/runtime": "^7.4.5", - "aria-query": "^3.0.0", - "array-includes": "^3.0.3", + "@babel/runtime": "^7.10.2", + "aria-query": "^4.2.2", + "array-includes": "^3.1.1", "ast-types-flow": "^0.0.7", - "axobject-query": "^2.0.2", - "damerau-levenshtein": "^1.0.4", - "emoji-regex": "^7.0.2", + "axe-core": "^3.5.4", + "axobject-query": "^2.1.2", + "damerau-levenshtein": "^1.0.6", + "emoji-regex": "^9.0.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.1" + "jsx-ast-utils": "^2.4.1", + "language-tags": "^1.0.5" }, "dependencies": { "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.0.0.tgz", + "integrity": "sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w==", "dev": true } } @@ -2352,9 +2708,9 @@ } }, "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==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "requires": { "eslint-plugin-es": "^3.0.0", @@ -2366,18 +2722,18 @@ }, "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==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "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==", + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", "dev": true }, "semver": { @@ -2389,9 +2745,9 @@ } }, "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==", + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", + "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", "dev": true, "requires": { "prettier-linter-helpers": "^1.0.0" @@ -2404,23 +2760,22 @@ "dev": true }, "eslint-plugin-react": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz", - "integrity": "sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ==", + "version": "7.20.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.20.3.tgz", + "integrity": "sha512-txbo090buDeyV0ugF3YMWrzLIUqpYTsWSDZV9xLSmExE1P/Kmgg9++PD931r+KEWS66O1c9R4srLVVHmeHpoAg==", "dev": true, "requires": { "array-includes": "^3.1.1", + "array.prototype.flatmap": "^1.2.3", "doctrine": "^2.1.0", "has": "^1.0.3", - "jsx-ast-utils": "^2.2.3", - "object.entries": "^1.1.1", + "jsx-ast-utils": "^2.4.1", + "object.entries": "^1.1.2", "object.fromentries": "^2.0.2", "object.values": "^1.1.1", "prop-types": "^15.7.2", - "resolve": "^1.15.1", - "semver": "^6.3.0", - "string.prototype.matchall": "^4.0.2", - "xregexp": "^4.3.0" + "resolve": "^1.17.0", + "string.prototype.matchall": "^4.0.2" }, "dependencies": { "doctrine": { @@ -2433,19 +2788,13 @@ } }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "dev": true, "requires": { "path-parse": "^1.0.6" } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true } } }, @@ -2558,6 +2907,11 @@ "uuid": "^3.0.1" } }, + "events": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", + "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=" + }, "express": { "version": "4.17.1", "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", @@ -2617,6 +2971,14 @@ "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", "dev": true }, + "fast-crc32c": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-crc32c/-/fast-crc32c-2.0.0.tgz", + "integrity": "sha512-LIREwygxtxzHF11oLJ4xIVKu/ZWNgrj/QaGvaSD8ZggIsgCyCtSYevlrpWVqNau57ZwezV8K1HFBSjQ7FcRbTQ==", + "requires": { + "sse4_crc32": "^6.0.1" + } + }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -2887,9 +3249,9 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "fsevents": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", - "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", "dev": true, "optional": true }, @@ -2926,6 +3288,34 @@ "json-bigint": "^0.3.0" } }, + "gcs-resumable-upload": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/gcs-resumable-upload/-/gcs-resumable-upload-3.1.0.tgz", + "integrity": "sha512-gB8xH6EjYCv9lfBEL4FK5+AMgTY0feYoNHAYOV5nCuOrDPhy5MOiyJE8WosgxhbKBPS361H7fkwv6CTufEh9bg==", + "requires": { + "abort-controller": "^3.0.0", + "configstore": "^5.0.0", + "extend": "^3.0.2", + "gaxios": "^3.0.0", + "google-auth-library": "^6.0.0", + "pumpify": "^2.0.0", + "stream-events": "^1.0.4" + }, + "dependencies": { + "gaxios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", + "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + } + } + }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -2988,19 +3378,61 @@ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==" }, "google-auth-library": { - "version": "5.10.1", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", - "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-6.0.3.tgz", + "integrity": "sha512-2Np6ojPmaJGXHSMsBhtTQEKfSMdLc8hefoihv7N2cwEr8E5bq39fhoat6TcXHwa0XoGO5Guh9sp3nxHFPmibMw==", "requires": { "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", "fast-text-encoding": "^1.0.0", - "gaxios": "^2.1.0", - "gcp-metadata": "^3.4.0", - "gtoken": "^4.1.0", + "gaxios": "^3.0.0", + "gcp-metadata": "^4.1.0", + "gtoken": "^5.0.0", "jws": "^4.0.0", "lru-cache": "^5.0.0" + }, + "dependencies": { + "gaxios": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-3.0.3.tgz", + "integrity": "sha512-PkzQludeIFhd535/yucALT/Wxyj/y2zLyrMwPcJmnLHDugmV49NvAi/vb+VUq/eWztATZCNcb8ue+ywPG+oLuw==", + "requires": { + "abort-controller": "^3.0.0", + "extend": "^3.0.2", + "https-proxy-agent": "^5.0.0", + "is-stream": "^2.0.0", + "node-fetch": "^2.3.0" + } + }, + "gcp-metadata": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.1.0.tgz", + "integrity": "sha512-r57SV28+olVsflPlKyVig3Muo/VDlcsObMtvDGOEtEJXj+DDE8bEl0coIkXh//hbkSDTvo+f5lbihZOndYXQQQ==", + "requires": { + "gaxios": "^3.0.0", + "json-bigint": "^0.3.0" + } + }, + "google-p12-pem": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.0.1.tgz", + "integrity": "sha512-VlQgtozgNVVVcYTXS36eQz4PXPt9gIPqLOhHN0QiV6W6h4qSCNVKPtKC5INtJsaHHF2r7+nOIa26MJeJMTaZEQ==", + "requires": { + "node-forge": "^0.9.0" + } + }, + "gtoken": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-5.0.1.tgz", + "integrity": "sha512-33w4FNDkUcyIOq/TqyC+drnKdI4PdXmWp9lZzssyEQKuvu9ZFN3KttaSnDKo52U3E51oujVGop93mKxmqO8HHg==", + "requires": { + "gaxios": "^3.0.0", + "google-p12-pem": "^3.0.0", + "jws": "^4.0.0", + "mime": "^2.2.0" + } + } } }, "google-gax": { @@ -3025,6 +3457,22 @@ "walkdir": "^0.4.0" }, "dependencies": { + "google-auth-library": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-5.10.1.tgz", + "integrity": "sha512-rOlaok5vlpV9rSiUu5EpR0vVpc+PhN62oF4RyX/6++DG1VsaulAFEMlDYBLjJDDPI6OcNOCGAKy9UVB/3NIDXg==", + "requires": { + "arrify": "^2.0.0", + "base64-js": "^1.3.0", + "ecdsa-sig-formatter": "^1.0.11", + "fast-text-encoding": "^1.0.0", + "gaxios": "^2.1.0", + "gcp-metadata": "^3.4.0", + "gtoken": "^4.1.0", + "jws": "^4.0.0", + "lru-cache": "^5.0.0" + } + }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -3115,6 +3563,25 @@ "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, + "hash-stream-validation": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/hash-stream-validation/-/hash-stream-validation-0.2.3.tgz", + "integrity": "sha512-OEohGLoUOh+bwsIpHpdvhIXFyRGjeLqJbT8Yc5QTZPbRM7LKywagTQxnX/6mghLDOrD9YGz88hy5mLN2eKflYQ==", + "requires": { + "through2": "^2.0.0" + }, + "dependencies": { + "through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "requires": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" + } + } + } + }, "hasha": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", @@ -3231,6 +3698,11 @@ "safer-buffer": ">= 2.1.2 < 3" } }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, "ignore": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", @@ -3412,9 +3884,9 @@ "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" }, "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==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", + "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", "dev": true }, "is-date-object": { @@ -3462,12 +3934,12 @@ "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", + "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", "dev": true, "requires": { - "has": "^1.0.3" + "has-symbols": "^1.0.1" } }, "is-stream": { @@ -3498,8 +3970,7 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" }, "isarray": { "version": "1.0.0", @@ -3627,6 +4098,11 @@ "html-escaper": "^2.0.0" } }, + "jmespath": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", + "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" + }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3686,7 +4162,17 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "dev": true + }, + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } }, "jsprim": { "version": "1.4.1", @@ -3709,12 +4195,12 @@ } }, "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==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz", + "integrity": "sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w==", "dev": true, "requires": { - "array-includes": "^3.0.3", + "array-includes": "^3.1.1", "object.assign": "^4.1.0" } }, @@ -3743,6 +4229,21 @@ "safe-buffer": "^5.0.1" } }, + "language-subtag-registry": { + "version": "0.3.20", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz", + "integrity": "sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg==", + "dev": true + }, + "language-tags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.5.tgz", + "integrity": "sha512-qJhlO9cGXi6hBGKoxEG/sKZDAHD5Hnu9Hs4WbOY3pCWXDhw0N8x1NenNzm2EnNLkLkk7J2SdxAkDSbb6ftT+UQ==", + "dev": true, + "requires": { + "language-subtag-registry": "~0.3.2" + } + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -3784,19 +4285,19 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash.at": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.at/-/lodash.at-4.6.0.tgz", - "integrity": "sha1-k83OZk8KGZTqM9181A4jr9EbD/g=" + "integrity": "sha512-GOTh0SEp+Yosnlpjic+8cl2WM9MykorogkGA9xyIFkkObQ3H3kNZqZ+ohuq4K3FrSVo7hMcZBMataJemrxC3BA==" }, "lodash.camelcase": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" }, "lodash.flattendeep": { "version": "4.4.0", @@ -3812,7 +4313,7 @@ "lodash.has": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/lodash.has/-/lodash.has-4.5.2.tgz", - "integrity": "sha1-0Z9NwQlQWMzL4rDN9O4P5Ko3yGI=" + "integrity": "sha512-rnYUdIo6xRCJnQmbVFEwcxF144erlD+M3YcJUVesflU9paQaE8p+fJDcIQrlMYbxoANFL+AB9hZrzSBBk5PL+g==" }, "lodash.memoize": { "version": "4.1.2", @@ -3852,15 +4353,28 @@ } }, "logger-sharelatex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-2.1.0.tgz", - "integrity": "sha512-WgAABqnBMOv0VAwQJyw+fY4rpqKX+nPJNRydGEYZTo+6lKtJI2TttmZ+Coryg1LEzAjNagxoU78XOHKQvhg7qg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/logger-sharelatex/-/logger-sharelatex-2.1.1.tgz", + "integrity": "sha512-qqSrBqUgHWnStxtTZ/fSsqPxj9Ju9onok7Vfm3bv5MS702jH+hRsCSA9oXOMvOLcWJrZFnhCZaLGeOvXToUaxw==", "requires": { - "@google-cloud/logging-bunyan": "^2.0.0", + "@google-cloud/logging-bunyan": "^3.0.0", "@overleaf/o-error": "^3.0.0", - "bunyan": "1.8.12", - "raven": "1.1.3", - "yn": "^3.1.1" + "bunyan": "^1.8.14", + "raven": "^2.6.4", + "yn": "^4.0.0" + }, + "dependencies": { + "bunyan": { + "version": "1.8.14", + "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.14.tgz", + "integrity": "sha512-LlahJUxXzZLuw/hetUQJmRgZ1LF6+cr5TPpRj6jf327AsiIq2jhYEH4oqUUkVKTor+9w2BT3oxVwhzE5lw9tcg==", + "requires": { + "dtrace-provider": "~0.8", + "moment": "^2.19.3", + "mv": "~2", + "safe-json-stringify": "~1" + } + } } }, "loglevel": { @@ -3943,11 +4457,6 @@ "yallist": "^3.0.2" } }, - "lsmod": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz", - "integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks=" - }, "lynx": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz", @@ -3996,6 +4505,23 @@ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.1.0.tgz", "integrity": "sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g==" }, + "md5": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz", + "integrity": "sha512-PlGG4z5mBANDGCKsYQe0CaUYHdZYZt8ZPZLmEt+Urf0W4GlpTX4HescwHU+dc9+Z/G/vZKYZYFrwgm9VxK6QOQ==", + "requires": { + "charenc": "~0.0.1", + "crypt": "~0.0.1", + "is-buffer": "~1.1.1" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + } + } + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -4054,9 +4580,9 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "metrics-sharelatex": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.6.2.tgz", - "integrity": "sha512-bOLfkSCexiPgB96hdXhoOWyvvrwscgjeZPEqdcJ7BTGxY59anzvymNf5hTGJ1RtS4sblDKxITw3L5a+gYKhRYQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/metrics-sharelatex/-/metrics-sharelatex-2.7.0.tgz", + "integrity": "sha512-fv7TsM2pVhY/VuHqP5p9Ip2BhHfGPrP0e2TuZGCMK9/J83ZuGCrxkpaU3OOz31CWcmcQ5am4Du70EZAaIdA/2A==", "requires": { "@google-cloud/debug-agent": "^3.0.0", "@google-cloud/profiler": "^0.2.3", @@ -4072,6 +4598,11 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", "integrity": "sha512-z4o1fvKUojIWh9XuaVLUDdf86RQiq13AC1dmHbTpoyuu+bquHms76v16CjycCbec87J7z0k//SiQVk0sMdFmpQ==" + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" } } }, @@ -4096,8 +4627,7 @@ "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 + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "minimatch": { "version": "3.0.4", @@ -4107,6 +4637,12 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, "mkdirp": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", @@ -4123,9 +4659,9 @@ } }, "mocha": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", - "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.2.0.tgz", + "integrity": "sha512-O9CIypScywTVpNaRrCAgoUnJgozpIofjKUYmJhiCIJMiuYnLI6otcb1/kpW9/n/tJODHGZ7i8aLQoDVsMtOKQQ==", "dev": true, "requires": { "ansi-colors": "3.2.3", @@ -4141,7 +4677,7 @@ "js-yaml": "3.13.1", "log-symbols": "3.0.0", "minimatch": "3.0.4", - "mkdirp": "0.5.3", + "mkdirp": "0.5.5", "ms": "2.1.1", "node-environment-flags": "1.0.6", "object.assign": "4.1.0", @@ -4208,6 +4744,15 @@ "path-exists": "^3.0.0" } }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, "ms": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", @@ -4257,16 +4802,6 @@ "y18n": "^4.0.0", "yargs-parser": "^13.1.2" } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, @@ -4377,9 +4912,9 @@ "dev": true }, "nise": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.3.tgz", - "integrity": "sha512-EGlhjm7/4KvmmE6B/UFsKh7eHykRl9VH+au8dduHLCyWUO/hr7+N+WtTvDUwc9zHuM1IaIJs/0lQ6Ag1jDkQSg==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", + "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0", @@ -4406,6 +4941,12 @@ } } }, + "node-addon-api": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-1.7.2.tgz", + "integrity": "sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==", + "optional": true + }, "node-environment-flags": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", @@ -4549,9 +5090,9 @@ "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==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", "dev": true }, "object-keys": { @@ -4573,14 +5114,13 @@ } }, "object.entries": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.1.tgz", - "integrity": "sha512-ilqR7BgdyZetJutmDPfXCDffGa0/Yzl2ivVNpbx/g4UeWrCdRnFDUBrKJGLhGieRHDATnyZXWBeCb29k9CJysQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.2.tgz", + "integrity": "sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA==", "dev": true, "requires": { "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1", - "function-bind": "^1.1.1", + "es-abstract": "^1.17.5", "has": "^1.0.3" } }, @@ -4638,7 +5178,6 @@ "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" } @@ -4728,9 +5267,9 @@ } }, "parse-duration": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.2.tgz", - "integrity": "sha512-0qfMZyjOUFBeEIvJ5EayfXJqaEXxQ+Oj2b7tWJM3hvEXvXsYCk05EDVI23oYnEw2NaFYUWdABEVPBvBMh8L/pA==" + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-0.1.3.tgz", + "integrity": "sha512-hMOZHfUmjxO5hMKn7Eft+ckP2M4nV4yzauLXiw3PndpkASnx5r8pDAMcOAiqxoemqWjMWmz4fOHQM6n6WwETXw==" }, "parse-json": { "version": "2.2.0", @@ -4844,9 +5383,9 @@ "dev": true }, "prettier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.1.tgz", - "integrity": "sha512-piXGBcY1zoFOG0MvHpNE5reAGseLmaCRifQ/fmfF49BcYkInEs/naD/unxGNAeOKFA5+JxVrPyMvMlpzcd20UA==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", "dev": true }, "prettier-eslint": { @@ -5480,9 +6019,9 @@ } }, "protobufjs": { - "version": "6.8.9", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.8.9.tgz", - "integrity": "sha512-j2JlRdUeL/f4Z6x4aU4gj9I2LECglC+5qR2TrWb193Tla1qfdaNQTZ8I27Pt7K0Ajmvjjpft7O3KWTGciz4gpw==", + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.9.0.tgz", + "integrity": "sha512-LlGVfEWDXoI/STstRDdZZKb/qusoAWUnmLg9R8OLSO473mBLWHowx8clbX5/+mKDEI+v7GzjoK9tRPZMMcoTrg==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -5494,8 +6033,8 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.0", - "@types/node": "^10.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", "long": "^4.0.0" } }, @@ -5561,11 +6100,21 @@ } } }, + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, "qs": { "version": "6.7.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, "quick-lru": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz", @@ -5584,26 +6133,31 @@ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raven": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/raven/-/raven-1.1.3.tgz", - "integrity": "sha1-QnPBrm005CMPUbLAEEGjK5Iygio=", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/raven/-/raven-2.6.4.tgz", + "integrity": "sha512-6PQdfC4+DQSFncowthLf+B6Hr0JpPsFBgTVYTAOq7tCmx/kR4SXbeawtPch20+3QfUcQDoJBLjWW1ybvZ4kXTw==", "requires": { "cookie": "0.3.1", - "json-stringify-safe": "5.0.1", - "lsmod": "1.0.0", - "stack-trace": "0.0.9", - "uuid": "3.0.0" + "md5": "^2.2.1", + "stack-trace": "0.0.10", + "timed-out": "4.0.1", + "uuid": "3.3.2" }, "dependencies": { "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==" + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" }, "uuid": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.0.tgz", - "integrity": "sha1-Zyj8BFnEUNeWqZwxg3VpvfZy1yg=" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" } } }, @@ -5684,9 +6238,9 @@ } }, "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==", + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", "dev": true }, "regexp.prototype.flags": { @@ -5778,9 +6332,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "requires": { "path-parse": "^1.0.6" } @@ -5909,9 +6463,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "sandboxed-module": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.3.tgz", - "integrity": "sha512-wXiA6ULoGjCDwjn6evQF/Qi+oe77P+aCxizUktLBBKdqNbTxwec4GySJcS+O7iZFhme2ex04m+14KgknKKqFsw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/sandboxed-module/-/sandboxed-module-2.0.4.tgz", + "integrity": "sha512-AwEPOdO8mg/wJjr876yCHP2DHqVN0MaggEXhp6IIf3bcI5cYoQl9QrrCHSrvToHjvdEiS5x4TVZRgjD2bEmNTA==", "dev": true, "requires": { "require-like": "0.1.2", @@ -5927,6 +6481,11 @@ "sparse-bitfield": "^3.0.3" } }, + "sax": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz", + "integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o=" + }, "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", @@ -6036,13 +6595,13 @@ "integrity": "sha512-meQNNykwecVxdu1RlYMKpQx4+wefIYpmxi6gexo/KAbwquJrBUrBmKYJrE8KFkVQAAVWEnwNdu21PgrD77J3xA==" }, "sinon": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.1.tgz", - "integrity": "sha512-iTTyiQo5T94jrOx7X7QLBZyucUJ2WvL9J13+96HMfm2CGoJYbIPqRfl6wgNcqmzk0DI28jeGx5bUTXizkrqBmg==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.0.2.tgz", + "integrity": "sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==", "dev": true, "requires": { - "@sinonjs/commons": "^1.7.0", - "@sinonjs/fake-timers": "^6.0.0", + "@sinonjs/commons": "^1.7.2", + "@sinonjs/fake-timers": "^6.0.1", "@sinonjs/formatio": "^5.0.1", "@sinonjs/samsam": "^5.0.3", "diff": "^4.0.2", @@ -6073,6 +6632,12 @@ } } }, + "sinon-chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", + "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", + "dev": true + }, "slice-ansi": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", @@ -6101,6 +6666,11 @@ "to-snake-case": "^1.0.0" } }, + "snakeize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/snakeize/-/snakeize-0.1.0.tgz", + "integrity": "sha512-ot3bb6pQt6IVq5G/JQ640ceSYTPtriVrwNyfoUw1LmQQGzPMAGxE5F+ded2UwSUCyf2PW1fFAYUnVEX21PWbpQ==" + }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -6192,6 +6762,16 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" }, + "sse4_crc32": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/sse4_crc32/-/sse4_crc32-6.0.1.tgz", + "integrity": "sha512-FUTYXpLroqytNKWIfHzlDWoy9E4tmBB/RklNMy6w3VJs+/XEYAHgbiylg4SS43iOk/9bM0BlJ2EDpFAGT66IoQ==", + "optional": true, + "requires": { + "bindings": "^1.3.0", + "node-addon-api": "^1.3.0" + } + }, "sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", @@ -6229,7 +6809,8 @@ "stack-trace": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.9.tgz", - "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==" + "integrity": "sha512-vjUc6sfgtgY0dxCdnc40mK6Oftjo9+2K8H/NG81TMhgL392FtiPA9tn9RLyTxXmTLPJPjF3VyzFp6bsWFLisMQ==", + "dev": true }, "statsd-parser": { "version": "0.0.4", @@ -6254,6 +6835,11 @@ "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==" }, + "streamifier": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/streamifier/-/streamifier-0.1.1.tgz", + "integrity": "sha512-zDgl+muIlWzXNsXeyUfOk9dChMjlpkq0DRsxujtYPgyJ676yQ8jEm6zzaaWHFDg5BNcLuif0eD2MTyJdZqXpdg==" + }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", @@ -6290,24 +6876,24 @@ "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==", + "string.prototype.trimend": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", + "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, - "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==", + "string.prototype.trimstart": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", + "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", "dev": true, "requires": { "define-properties": "^1.1.3", - "function-bind": "^1.1.1" + "es-abstract": "^1.17.5" } }, "string_decoder": { @@ -6354,7 +6940,7 @@ "stubs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/stubs/-/stubs-3.0.0.tgz", - "integrity": "sha1-6NK6H6nJBXAwPAMLaQD31fiavls=" + "integrity": "sha512-PdHt7hHUJKxvTCgbKX9C1V/ftOcjJQgz8BZwNfV5c4B6dcGqlpelTbJ999jBGZ2jYiPAwcX5dP6oBwVlBlUbxw==" }, "supports-color": { "version": "6.0.0", @@ -6574,6 +7160,20 @@ "resolved": "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz", "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==" }, + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==" + }, + "tiny-async-pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/tiny-async-pool/-/tiny-async-pool-1.1.0.tgz", + "integrity": "sha512-jIglyHF/9QdCC3662m/UMVADE6SlocBDpXdFLMZyiAfrw8MSG1pml7lwRtBMT6L/z4dddAxfzw2lpW2Vm42fyQ==", + "requires": { + "semver": "^5.5.0", + "yaassertion": "^1.0.0" + } + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -6596,7 +7196,7 @@ "to-no-case": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/to-no-case/-/to-no-case-1.0.2.tgz", - "integrity": "sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo=" + "integrity": "sha512-Z3g735FxuZY8rodxV4gH7LxClE4H0hTIyHNIHdk+vpQxjLm0cwnKXq/OFVZ76SOQmto7txVcwSCwkU5kqp+FKg==" }, "to-regex-range": { "version": "5.0.1", @@ -6610,7 +7210,7 @@ "to-snake-case": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-snake-case/-/to-snake-case-1.0.0.tgz", - "integrity": "sha1-znRpE4l5RgGah+Yu366upMYIq4w=", + "integrity": "sha512-joRpzBAk1Bhi2eGEYBjukEWHOe/IvclOkiJl3DtA91jV6NwQ3MwXA4FHYeqk8BNp/D8bmi9tcNbRu/SozP0jbQ==", "requires": { "to-space-case": "^1.0.0" } @@ -6618,7 +7218,7 @@ "to-space-case": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/to-space-case/-/to-space-case-1.0.0.tgz", - "integrity": "sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc=", + "integrity": "sha512-rLdvwXZ39VOn1IxGL3V6ZstoTbwLRckQmn/U8ZDLuWwIXNpuZDhQ3AiRUlhTbOXFVE9C+dR51wM0CBDhk31VcA==", "requires": { "to-no-case": "^1.0.0" } @@ -6646,6 +7246,18 @@ } } }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + } + }, "tslib": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz", @@ -6697,6 +7309,19 @@ "mime-types": "~2.1.24" } }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, + "typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "requires": { + "is-typedarray": "^1.0.0" + } + }, "typescript": { "version": "3.7.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", @@ -6704,9 +7329,17 @@ "dev": true }, "underscore": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.2.tgz", - "integrity": "sha512-D39qtimx0c1fI3ya1Lnhk3E9nONswSKhnffBI0gME9C99fYOkNi04xs8K6pePLhvl1frbDemkaBQ5ikWllR2HQ==" + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.10.2.tgz", + "integrity": "sha512-N4P+Q/BuyuEKFJ43B9gYuOj4TQUHXX+j2FqguVOpjkssLUUrnJofCcBccJSCoeturDoZU6GorDTHSvUDlSQbTg==" + }, + "unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "requires": { + "crypto-random-string": "^2.0.0" + } }, "unpipe": { "version": "1.0.0", @@ -6730,6 +7363,15 @@ } } }, + "url": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz", + "integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -6799,9 +7441,9 @@ }, "dependencies": { "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "acorn-jsx": { @@ -6980,20 +7622,40 @@ "signal-exit": "^3.0.2" } }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "dev": true, + "xdg-basedir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xdg-basedir/-/xdg-basedir-4.0.0.tgz", + "integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==" + }, + "xml2js": { + "version": "0.4.19", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz", + "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "requires": { - "@babel/runtime-corejs3": "^7.8.3" + "sax": ">=0.6.0", + "xmlbuilder": "~9.0.1" } }, + "xmlbuilder": { + "version": "9.0.7", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz", + "integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, "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==" }, + "yaassertion": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/yaassertion/-/yaassertion-1.0.2.tgz", + "integrity": "sha512-sBoJBg5vTr3lOpRX0yFD+tz7wv/l2UPMFthag4HGTMPrypBRKerjjS8jiEnNMjcAEtPXjbHiKE0UwRR1W1GXBg==" + }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -7064,9 +7726,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -7084,9 +7746,9 @@ } }, "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-4.0.0.tgz", + "integrity": "sha512-huWiiCS4TxKc4SfgmTwW1K7JmXPPAmuXWYy4j9qjQo4+27Kni8mGhAAi1cloRWmBe2EqcLgt3IGqQoRL/MtPgg==" } } } diff --git a/services/docstore/package.json b/services/docstore/package.json index 455dc14ed7..5dc0415d9b 100644 --- a/services/docstore/package.json +++ b/services/docstore/package.json @@ -14,45 +14,51 @@ "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", - "lint": "node_modules/.bin/eslint .", + "lint": "node_modules/.bin/eslint --max-warnings 0 .", "format": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --list-different", "format:fix": "node_modules/.bin/prettier-eslint $PWD'/**/*.js' --write" }, "dependencies": { + "@overleaf/object-persistor": "git+https://github.com/overleaf/object-persistor.git", "async": "^2.6.3", "body-parser": "^1.19.0", "express": "^4.17.1", - "logger-sharelatex": "^2.1.0", - "metrics-sharelatex": "^2.6.2", + "logger-sharelatex": "^2.1.1", + "metrics-sharelatex": "^2.7.0", "mongojs": "3.1.0", "settings-sharelatex": "^1.1.0", - "underscore": "~1.9.2" + "streamifier": "^0.1.1", + "tiny-async-pool": "^1.1.0", + "underscore": "~1.10.2" }, "devDependencies": { + "@google-cloud/storage": "^5.1.2", "babel-eslint": "^10.1.0", - "bunyan": "~1.8.12", + "bunyan": "~1.8.14", "chai": "~4.2.0", + "chai-as-promised": "^7.1.1", "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0", - "eslint-config-standard": "^14.1.0", + "eslint-config-prettier": "^6.11.0", + "eslint-config-standard": "^14.1.1", "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-chai-expect": "^2.2.0", + "eslint-plugin-chai-friendly": "^0.6.0", + "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jsx-a11y": "^6.3.1", "eslint-plugin-mocha": "^6.3.0", - "eslint-plugin-node": "^11.0.0", - "eslint-plugin-prettier": "^3.1.2", + "eslint-plugin-node": "^11.1.0", + "eslint-plugin-prettier": "^3.1.4", "eslint-plugin-promise": "^4.2.1", - "eslint-plugin-react": "^7.19.0", + "eslint-plugin-react": "^7.20.3", "eslint-plugin-standard": "^4.0.1", - "mocha": "^7.1.1", - "prettier": "^2.0.0", + "mocha": "^7.2.0", + "prettier": "^2.0.5", "prettier-eslint-cli": "^5.0.0", "request": "~2.88.2", - "sandboxed-module": "~2.0.3", - "sinon": "~9.0.1" + "sandboxed-module": "~2.0.4", + "sinon": "~9.0.2", + "sinon-chai": "^3.5.0" }, "engines": { "node": "~6.14.1" diff --git a/services/docstore/test/acceptance/deps/Dockerfile.fake-gcs b/services/docstore/test/acceptance/deps/Dockerfile.fake-gcs new file mode 100644 index 0000000000..0e6de7e735 --- /dev/null +++ b/services/docstore/test/acceptance/deps/Dockerfile.fake-gcs @@ -0,0 +1,5 @@ +FROM fsouza/fake-gcs-server:latest +RUN apk add --update --no-cache curl +COPY healthcheck.sh /healthcheck.sh +HEALTHCHECK --interval=1s --timeout=1s --retries=30 CMD /healthcheck.sh http://localhost:9090 +CMD ["--port=9090", "--scheme=http"] diff --git a/services/docstore/test/acceptance/deps/healthcheck.sh b/services/docstore/test/acceptance/deps/healthcheck.sh new file mode 100644 index 0000000000..cd19cea637 --- /dev/null +++ b/services/docstore/test/acceptance/deps/healthcheck.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +# health check to allow 404 status code as valid +STATUSCODE=$(curl --silent --output /dev/null --write-out "%{http_code}" $1) +# will be 000 on non-http error (e.g. connection failure) +if test $STATUSCODE -ge 500 || test $STATUSCODE -lt 200; then + exit 1 +fi +exit 0 diff --git a/services/docstore/test/acceptance/js/ArchiveDocsTests.js b/services/docstore/test/acceptance/js/ArchiveDocsTests.js index 3f559f566e..959af6715b 100644 --- a/services/docstore/test/acceptance/js/ArchiveDocsTests.js +++ b/services/docstore/test/acceptance/js/ArchiveDocsTests.js @@ -12,22 +12,37 @@ * 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') +process.env.BACKEND = 'gcs' const Settings = require('settings-sharelatex') -const DocArchiveManager = require('../../../app/js/DocArchiveManager.js') -const request = require('request') +const chai = require('chai') +const { expect } = chai +const should = chai.should() +const { db, ObjectId } = require('../../../app/js/mongojs') +const async = require('async') const DocstoreApp = require('./helpers/DocstoreApp') const DocstoreClient = require('./helpers/DocstoreClient') +const { Storage } = require('@google-cloud/storage') +const Persistor = require('../../../app/js/PersistorManager') +const Streamifier = require('streamifier') + +function uploadContent(path, json, callback) { + const stream = Streamifier.createReadStream(JSON.stringify(json)) + Persistor.sendStream(Settings.docstore.bucket, path, stream) + .then(() => callback()) + .catch(callback) +} describe('Archiving', function () { before(function (done) { return DocstoreApp.ensureRunning(done) }) + before(async function () { + const storage = new Storage(Settings.docstore.gcs.endpoint) + await storage.createBucket(Settings.docstore.bucket) + await storage.createBucket(`${Settings.docstore.bucket}-deleted`) + }) + describe('multiple docs in a project', function () { before(function (done) { this.project_id = ObjectId() @@ -99,10 +114,10 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { s3_doc.lines.should.deep.equal(doc.lines) s3_doc.ranges.should.deep.equal(doc.ranges) - return callback() + callback() } ) } @@ -215,7 +230,7 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, this.doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { if (error != null) { throw error } @@ -316,7 +331,7 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, this.doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { if (error != null) { throw error } @@ -790,7 +805,7 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, this.doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { if (error != null) { throw error } @@ -909,7 +924,7 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, this.doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { if (error != null) { throw error } @@ -1006,7 +1021,7 @@ describe('Archiving', function () { return DocstoreClient.getS3Doc( this.project_id, this.doc._id, - (error, res, s3_doc) => { + (error, s3_doc) => { if (error != null) { throw error } @@ -1054,39 +1069,36 @@ describe('Archiving', function () { 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() + uploadContent( + `${this.project_id}/${this.doc._id}`, + this.doc.lines, + (error) => { + expect(error).not.to.exist + db.docs.insert( + { + project_id: this.project_id, + _id: this.doc._id, + rev: this.doc.version, + inS3: true + }, + (error) => { + if (error != null) { + throw error } - ) - } - ) - }) + 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) { diff --git a/services/docstore/test/acceptance/js/DeletingDocsTests.js b/services/docstore/test/acceptance/js/DeletingDocsTests.js index fd534da010..41a62fbebb 100644 --- a/services/docstore/test/acceptance/js/DeletingDocsTests.js +++ b/services/docstore/test/acceptance/js/DeletingDocsTests.js @@ -11,12 +11,12 @@ * 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 Errors = require('../../../app/js/Errors') const DocstoreClient = require('./helpers/DocstoreClient') @@ -143,17 +143,10 @@ describe("Destroying a project's documents", function () { }) 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() - } - ) + return DocstoreClient.getS3Doc(this.project_id, this.doc_id, (error) => { + expect(error).to.be.instanceOf(Errors.NotFoundError) + done() + }) }) }) }) diff --git a/services/docstore/test/acceptance/js/helpers/DocstoreClient.js b/services/docstore/test/acceptance/js/helpers/DocstoreClient.js index bec63a261a..237ef42134 100644 --- a/services/docstore/test/acceptance/js/helpers/DocstoreClient.js +++ b/services/docstore/test/acceptance/js/helpers/DocstoreClient.js @@ -13,9 +13,23 @@ */ 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') +const Persistor = require('../../../../app/js/PersistorManager') + +async function streamToString(stream) { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', (chunk) => chunks.push(chunk)) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) +} + +async function getStringFromPersistor(persistor, bucket, key) { + const stream = await persistor.getObjectStream(bucket, key, {}) + stream.resume() + return streamToString(stream) +} module.exports = DocstoreClient = { createDoc(project_id, doc_id, lines, version, ranges, callback) { @@ -55,7 +69,9 @@ module.exports = DocstoreClient = { url: `http://localhost:${settings.internal.docstore.port}/project/${project_id}/doc`, json: true }, - callback + (req, res, body) => { + callback(req, res, body) + } ) }, @@ -126,11 +142,14 @@ module.exports = DocstoreClient = { }, 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) + getStringFromPersistor( + Persistor, + settings.docstore.bucket, + `${project_id}/${doc_id}` + ) + .then((data) => { + callback(null, JSON.parse(data)) + }) + .catch(callback) } } diff --git a/services/docstore/test/unit/js/DocArchiveManagerTests.js b/services/docstore/test/unit/js/DocArchiveManagerTests.js index f950fe4dc8..026c7654b4 100644 --- a/services/docstore/test/unit/js/DocArchiveManagerTests.js +++ b/services/docstore/test/unit/js/DocArchiveManagerTests.js @@ -1,47 +1,59 @@ -/* 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') + +chai.use(require('chai-as-promised')) +chai.use(require('sinon-chai')) describe('DocArchiveManager', function () { + let DocArchiveManager, + PersistorManager, + MongoManager, + RangeManager, + Settings, + Logger, + Crypto, + Streamifier, + HashDigest, + HashUpdate, + archivedDocs, + mongoDocs, + docJson, + md5Sum, + projectId, + readStream, + stream + beforeEach(function () { - this.settings = { + md5Sum = 'decafbad' + + RangeManager = { + jsonRangesToMongo: sinon.stub().returns({ mongo: 'ranges' }) + } + Settings = { docstore: { - s3: { - secret: 'secret', - key: 'this_key', - bucket: 'doc-archive-unit-test' - } + bucket: 'wombat' } } - - this.request = { - put: {}, - get: {}, - del: {} + Logger = { + log: sinon.stub(), + err: sinon.stub() + } + HashDigest = sinon.stub().returns(md5Sum) + HashUpdate = sinon.stub().returns({ digest: HashDigest }) + Crypto = { + createHash: sinon.stub().returns({ update: HashUpdate }) + } + Streamifier = { + createReadStream: sinon.stub().returns({ stream: 'readStream' }) } - this.archivedDocs = [ + projectId = ObjectId() + archivedDocs = [ { _id: ObjectId(), inS3: true, @@ -58,8 +70,7 @@ describe('DocArchiveManager', function () { rev: 6 } ] - - this.mongoDocs = [ + mongoDocs = [ { _id: ObjectId(), lines: ['one', 'two', 'three'], @@ -87,488 +98,427 @@ describe('DocArchiveManager', function () { } ] - 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 - } - ] + docJson = JSON.stringify({ + lines: mongoDocs[0].lines, + ranges: mongoDocs[0].ranges, + schema_v: 1 + }) - this.mixedDocs = this.archivedDocs.concat(this.unarchivedDocs) + stream = { + on: sinon.stub(), + resume: sinon.stub() + } + stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8')) + stream.on.withArgs('end').yields() - 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) + readStream = { + stream: 'readStream' } - this.requires = { - 'settings-sharelatex': this.settings, - './MongoManager': this.MongoManager, - request: this.request, - './RangeManager': (this.RangeManager = {}), - 'logger-sharelatex': { - log() {}, - err() {} + PersistorManager = { + getObjectStream: sinon.stub().resolves(stream), + sendStream: sinon.stub().resolves(), + getObjectMd5Hash: sinon.stub().resolves(md5Sum), + deleteObject: sinon.stub().resolves() + } + + MongoManager = { + promises: { + markDocAsArchived: sinon.stub().resolves(), + upsertIntoDocCollection: sinon.stub().resolves(), + getProjectsDocs: sinon.stub().resolves(mongoDocs), + getArchivedProjectDocs: sinon.stub().resolves(archivedDocs), + findDoc: sinon.stub().resolves(), + destroyDoc: sinon.stub().resolves() } } - this.globals = { JSON } + for (const mongoDoc of mongoDocs) { + MongoManager.promises.findDoc + .withArgs(projectId, mongoDoc._id) + .resolves(mongoDoc) + } - 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 - })) + DocArchiveManager = SandboxedModule.require(modulePath, { + requires: { + 'settings-sharelatex': Settings, + 'logger-sharelatex': Logger, + crypto: Crypto, + streamifier: Streamifier, + './MongoManager': MongoManager, + './RangeManager': RangeManager, + './PersistorManager': PersistorManager, + './Errors': Errors + }, + globals: { + console, + JSON + } + }) }) 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 resolve when passed a valid document', async function () { + await expect( + DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + ).to.eventually.be.fulfilled + }) + + it('should throw an error if the doc has no lines', async function () { + const doc = mongoDocs[0] + doc.lines = null + + await expect( + DocArchiveManager.promises.archiveDoc(projectId, doc) + ).to.eventually.be.rejectedWith('doc has no lines') + }) + + it('should add the schema version', async function () { + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[1]) + expect(Streamifier.createReadStream).to.have.been.calledWith( + sinon.match(/"schema_v":1/) ) }) - it('should return no md5 error', function (done) { - const data = JSON.stringify({ - lines: this.mongoDocs[0].lines, - ranges: this.mongoDocs[0].ranges, + it('should calculate the hex md5 sum of the content', async function () { + const json = JSON.stringify({ + lines: mongoDocs[0].lines, + ranges: 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() - } + + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + + expect(Crypto.createHash).to.have.been.calledWith('md5') + expect(HashUpdate).to.have.been.calledWith(json) + expect(HashDigest).to.have.been.calledWith('hex') + }) + + it('should pass the md5 hash to the object persistor for verification', async function () { + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + + expect(PersistorManager.sendStream).to.have.been.calledWith( + sinon.match.any, + sinon.match.any, + sinon.match.any, + { sourceMd5: md5Sum } ) }) - 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() - } + it('should pass the correct bucket and key to the persistor', async function () { + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + + expect(PersistorManager.sendStream).to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${mongoDocs[0]._id}` ) }) + + it('should create a stream from the encoded json and send it', async function () { + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + expect(Streamifier.createReadStream).to.have.been.calledWith(docJson) + expect(PersistorManager.sendStream).to.have.been.calledWith( + sinon.match.any, + sinon.match.any, + readStream + ) + }) + + it('should mark the doc as archived', async function () { + await DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith( + mongoDocs[0]._id, + mongoDocs[0].rev + ) + }) + + describe('with null bytes in the result', function () { + const _stringify = JSON.stringify + + beforeEach(function () { + JSON.stringify = sinon.stub().returns('{"bad": "\u0000"}') + }) + + afterEach(function () { + JSON.stringify = _stringify + }) + + it('should return an error', async function () { + await expect( + DocArchiveManager.promises.archiveDoc(projectId, mongoDocs[0]) + ).to.eventually.be.rejectedWith('null bytes detected') + }) + }) }) 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}` + let docId + + beforeEach(function () { + docId = mongoDocs[0]._id + }) + + it('should resolve when passed a valid document', async function () { + await expect(DocArchiveManager.promises.unarchiveDoc(projectId, docId)).to + .eventually.be.fulfilled + }) + + it('should throw an error if the md5 does not match', async function () { + PersistorManager.getObjectMd5Hash.resolves('badf00d') + await expect( + DocArchiveManager.promises.unarchiveDoc(projectId, docId) + ).to.eventually.be.rejected.and.be.instanceof(Errors.Md5MismatchError) + }) + + it('should update the doc lines in mongo', async function () { + await DocArchiveManager.promises.unarchiveDoc(projectId, docId) + expect( + MongoManager.promises.upsertIntoDocCollection + ).to.have.been.calledWith(projectId, docId, { lines: mongoDocs[0].lines }) + }) + + it('should delete the doc in s3', async function () { + await DocArchiveManager.promises.unarchiveDoc(projectId, docId) + expect(PersistorManager.deleteObject).to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${docId}` + ) + }) + + describe('doc contents', function () { + let mongoDoc, s3Doc + + describe('when the doc has the old schema', function () { + beforeEach(function () { + mongoDoc = { + lines: ['doc', 'lines'] + } + s3Doc = ['doc', 'lines'] + docJson = JSON.stringify(s3Doc) + stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8')) + }) + + it('should return the docs lines', async function () { + await DocArchiveManager.promises.unarchiveDoc(projectId, docId) + expect( + MongoManager.promises.upsertIntoDocCollection + ).to.have.been.calledWith(projectId, docId, mongoDoc) + }) + }) + + describe('with the new schema and ranges', function () { + beforeEach(function () { + s3Doc = { + lines: ['doc', 'lines'], + ranges: { json: 'ranges' }, + schema_v: 1 + } + mongoDoc = { + lines: ['doc', 'lines'], + ranges: { mongo: 'ranges' } + } + docJson = JSON.stringify(s3Doc) + stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8')) + }) + + it('should return the doc lines and ranges', async function () { + await DocArchiveManager.promises.unarchiveDoc(projectId, docId) + expect( + MongoManager.promises.upsertIntoDocCollection + ).to.have.been.calledWith(projectId, docId, mongoDoc) + }) + }) + + describe('with the new schema and no ranges', function () { + beforeEach(function () { + s3Doc = { + lines: ['doc', 'lines'], + schema_v: 1 + } + mongoDoc = { + lines: ['doc', 'lines'] + } + docJson = JSON.stringify(s3Doc) + stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8')) + }) + + it('should return only the doc lines', async function () { + await DocArchiveManager.promises.unarchiveDoc(projectId, docId) + expect( + MongoManager.promises.upsertIntoDocCollection + ).to.have.been.calledWith(projectId, docId, mongoDoc) + }) + }) + + describe('with an unrecognised schema', function () { + beforeEach(function () { + s3Doc = { + lines: ['doc', 'lines'], + schema_v: 2 + } + docJson = JSON.stringify(s3Doc) + stream.on.withArgs('data').yields(Buffer.from(docJson, 'utf8')) + }) + + it('should throw an error', async function () { + await expect( + DocArchiveManager.promises.unarchiveDoc(projectId, docId) + ).to.eventually.be.rejectedWith( + "I don't understand the doc format in s3" ) - return done() - } - ) + }) + }) + }) + }) + + describe('destroyDoc', function () { + let docId + + beforeEach(function () { + docId = mongoDocs[0]._id }) - 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() - } - ) + it('should resolve when passed a valid document', async function () { + await expect(DocArchiveManager.promises.destroyDoc(projectId, docId)).to + .eventually.be.fulfilled }) - 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() - } - ) + it('should throw a not found error when there is no document', async function () { + await expect( + DocArchiveManager.promises.destroyDoc(projectId, 'wombat') + ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError) + }) + + describe('when the doc is in s3', function () { + beforeEach(function () { + mongoDocs[0].inS3 = true + }) + + it('should delete the document from s3, if it is in s3', async function () { + await DocArchiveManager.promises.destroyDoc(projectId, docId) + expect(PersistorManager.deleteObject).to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${docId}` + ) + }) + + it('should delete the doc in mongo', async function () { + await DocArchiveManager.promises.destroyDoc(projectId, docId) + }) + }) + + describe('when the doc is not in s3', function () { + beforeEach(function () { + mongoDocs[0].inS3 = false + }) + + it('should not delete the document from s3, if it is not in s3', async function () { + await DocArchiveManager.promises.destroyDoc(projectId, docId) + expect(PersistorManager.deleteObject).not.to.have.been.called + }) + + it('should delete the doc in mongo', async function () { + await DocArchiveManager.promises.destroyDoc(projectId, docId) + }) }) }) 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 resolve with valid arguments', async function () { + await expect(DocArchiveManager.promises.archiveAllDocs(projectId)).to + .eventually.be.fulfilled }) - it('should return error if have no docs', function (done) { - this.MongoManager.getProjectsDocs = sinon - .stub() - .callsArgWith(3, null, null) + it('should archive all project docs which are not in s3', async function () { + await DocArchiveManager.promises.archiveAllDocs(projectId) + // not inS3 + expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith( + mongoDocs[0]._id + ) + expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith( + mongoDocs[1]._id + ) + expect(MongoManager.promises.markDocAsArchived).to.have.been.calledWith( + mongoDocs[4]._id + ) - return this.DocArchiveManager.archiveAllDocs(this.project_id, (err) => { - should.exist(err) - return done() - }) + // inS3 + expect( + MongoManager.promises.markDocAsArchived + ).not.to.have.been.calledWith(mongoDocs[2]._id) + expect( + MongoManager.promises.markDocAsArchived + ).not.to.have.been.calledWith(mongoDocs[3]._id) }) - it('should return the error', function (done) { - this.MongoManager.getProjectsDocs = sinon - .stub() - .callsArgWith(3, this.error, null) + it('should return error if the project has no docs', async function () { + MongoManager.promises.getProjectsDocs.resolves(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() - }) - }) + await expect( + DocArchiveManager.promises.archiveAllDocs(projectId) + ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError) }) }) 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 resolve with valid arguments', async function () { + await expect(DocArchiveManager.promises.unArchiveAllDocs(projectId)).to + .eventually.be.fulfilled }) - 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() - }) + it('should unarchive all inS3 docs', async function () { + await DocArchiveManager.promises.unArchiveAllDocs(projectId) + + for (const doc of archivedDocs) { + expect(PersistorManager.getObjectStream).to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${doc._id}` + ) + } }) - 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() - }) + it('should return error if the project has no docs', async function () { + MongoManager.promises.getArchivedProjectDocs.resolves(null) + + await expect( + DocArchiveManager.promises.unArchiveAllDocs(projectId) + ).to.eventually.be.rejected.and.be.instanceof(Errors.NotFoundError) }) }) 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 resolve with valid arguments', async function () { + await expect(DocArchiveManager.promises.destroyAllDocs(projectId)).to + .eventually.be.fulfilled }) - 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 delete all docs that are in s3 from s3', async function () { + await DocArchiveManager.promises.destroyAllDocs(projectId) - 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}` - }) + // not inS3 + for (const index of [0, 1, 4]) { + expect(PersistorManager.deleteObject).not.to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${mongoDocs[index]._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) + // inS3 + for (const index of [2, 3]) { + expect(PersistorManager.deleteObject).to.have.been.calledWith( + Settings.docstore.bucket, + `${projectId}/${mongoDocs[index]._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() - } + it('should destroy all docs in mongo', async function () { + await DocArchiveManager.promises.destroyAllDocs(projectId) + + for (const mongoDoc of mongoDocs) { + expect(MongoManager.promises.destroyDoc).to.have.been.calledWith( + mongoDoc._id ) - }) - - 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 index 63b70493a5..e7f5baa567 100644 --- a/services/docstore/test/unit/js/DocManagerTests.js +++ b/services/docstore/test/unit/js/DocManagerTests.js @@ -38,7 +38,8 @@ describe('DocManager', function () { log: sinon.stub(), warn() {}, err() {} - }) + }), + './Errors': Errors } }) this.doc_id = ObjectId().toString() diff --git a/services/docstore/test/unit/js/MongoManagerTests.js b/services/docstore/test/unit/js/MongoManagerTests.js index f26e6539f1..fec3ec0682 100644 --- a/services/docstore/test/unit/js/MongoManagerTests.js +++ b/services/docstore/test/unit/js/MongoManagerTests.js @@ -29,6 +29,9 @@ describe('MongoManager', function () { }, 'metrics-sharelatex': { timeAsyncMethod: sinon.stub() }, 'logger-sharelatex': { log() {} } + }, + globals: { + console } }) this.project_id = ObjectId().toString()